구조체 값을 초기화 하는 방법 : ft_memset
printf함수가 write보다 늦게 나오는 이유
printf함수는 라인 버퍼링을 사용하기 때문이다.
line buffering 이란?
버퍼에 개행 문자가 입력될 때 마다 출력한다.
즉 개행문자가 들어오지 않는다면 버퍼에 계속 쌓아두다가 더이상 실행 할 명령이 없을 때 해당 버퍼의 내용을 출력한다.(과제에서는 실제 printf처럼 버퍼관리를 수행해서는 안된다고 한다.)
가변인자 목록(코딩도장)
42서울 ft_printf 시작 전 공부 정(printf직접 구현하기)
구조체 값을 초기화 하는 방법 : ft_memset
printf함수가 write보다 늦게 나오는 이유
printf함수는 라인 버퍼링을 사용하기 때문이다.
line buffering 이란?
버퍼에 개행 문자가 입력될 때 마다 출력한다.
즉 개행문자가 들어오지 않는다면 버퍼에 계속 쌓아두다가 더이상 실행 할 명령이 없을 때 해당 버퍼의 내용을 출력한다.(과제에서는 실제 printf처럼 버퍼관리를 수행해서는 안된다고 한다.)
가변인자 목록(코딩도장)
printf 또는 scanf같은 함수에서는 매개변수의 갯수가 정해지지 않은 함수이다.
함수에 들어가는 인자의 갯수가 변하는 것을 가변인자(가변인수)라고 한다.
- 함수에서 가변인자 정의! 참조 코드
#include <stdio.h> // args는 고정 매개변수 void printNumbers(int args, ...) { printf("%d ", args); } int main() { printNumbers(1, 10); printNumbers(2, 10, 20); printNumbers(3, 10, 20, 30); printNumbers(4, 10, 20, 30, 40); return 0; }//출력값 1 2 3 4 args에 첫번째 인자가 들어갑니다.
#include <stdarg.h>#include <stdio.h> #include <stdarg.h> // va_list, va_start, va_arg, va_end가 정의된 헤더 파일 void printNumbers(int args, ...) // 가변 인자의 개수를 받음, ...로 가변 인자 설정 { va_list ap; // 가변 인자 목록 포인터 va_start(ap, args); // 가변 인자 목록 포인터 설정 for (int i = 0; i < args; i++) // 가변 인자 개수만큼 반복 { int num = va_arg(ap, int); // int 크기만큼 가변 인자 목록 포인터에서 값을 가져옴 // ap를 int 크기만큼 순방향으로 이동 printf("%d ", num); // 가변 인자 값 출력 } va_end(ap); // 가변 인자 목록 포인터를 NULL로 초기화 printf("\\n"); // 줄바꿈 } int main() { printNumbers(1, 10); // 인수 개수 1개 printNumbers(2, 10, 20); // 인수 개수 2개 printNumbers(3, 10, 20, 30); // 인수 개수 3개 printNumbers(4, 10, 20, 30, 40); // 인수 개수 4개 return 0; }
- 반환값자료형 함수이름(자료형 고정매개변수, ...) { }
- C의 표준 라이브러리 헤더로 인자 수를 제한 없이 할 수 있도록 하는 함수를 허용하도록 한다.
- 즉 가변인자함수를 가지고 자료형으로 va_list를 가지며 ,함수로 va_start, va_arg, va_end 등의 함수를 포함하고 있다.
- 함수를 호출 할 때 맨처음에 인자 개수를 넣어자구 인자 개수에 맞게 인수를 콤마로 구분해 넣어준다.
- 각각 자료형이 다른 가변인자 처리
- void 함수명 (char *types, …) // 가변인자의 자료형을 받고 …으로 가변 인자를 설정한다.
- va_start(가변인자 포인터, types); 가변인자 포인터 열에서 문자 갯수를 구해서 가변인자 포인터 설정.
- switch와 가변인자를 함께 사용하면된다.(코드)
#include <stdio.h>
#include <stdarg.h> // va_list, va_start, va_arg, va_end가 정의된 헤더 파일
void printValues(char *types, ...) // 가변 인자의 자료형을 받음, ...로 가변 인자 설정
{
va_list ap; // 가변 인자 목록
int i = 0;
va_start(ap, types); // types 문자열에서 문자 개수를 구해서 가변 인자 포인터 설정
while (types[i] != '\\0') // 가변 인자 자료형이 없을 때까지 반복
{
switch (types[i]) // 가변 인자 자료형으로 분기
{
case 'i': // int형일 때
printf("%d ", va_arg(ap, int)); // int 크기만큼 값을 가져옴
// ap를 int 크기만큼 순방향으로 이동
break;
case 'd': // double형일 때
printf("%f ", va_arg(ap, double)); // double 크기만큼 값을 가져옴
// ap를 double 크기만큼 순방향으로 이동
break;
case 'c': // char형 문자일 때
printf("%c ", va_arg(ap, char)); // char 크기만큼 값을 가져옴
// ap를 char 크기만큼 순방향으로 이동
break;
case 's': // char *형 문자열일 때
printf("%s ", va_arg(ap, char *)); // char * 크기만큼 값을 가져옴
// ap를 char * 크기만큼 순방향으로 이동
break;
default:
break;
}
i++;
}
va_end(ap); // 가변 인자 포인터를 NULL로 초기화
printf("\\n"); // 줄바꿈
}
int main()
{
printValues("i", 10); // 정수
printValues("ci", 'a', 10); // 문자, 정수
printValues("dci", 1.234567, 'a', 10); // 실수, 문자, 정수
printValues("sicd", "Hello, world!", 10, 'a', 1.234567);
// 문자열, 정수, 문자, 실수
return 0;
}
매크로 함수란
#deifne 선행 처리 시지문에 인수로 함수의 정의를 전달 함으로써 함수처럼 동작하는 메크로이다.
#define SUB(X,Y) X-Y
#define PRT(X) printf("계산 결과는 %d입니다.\n", X)
이런식이다.
- 매크로 함수의 전체를 괄호(())로 감싸야 합니다.
- 매크로 함수의 인수들도 각각 괄호로 감싸야 합니다.
- 매크로 함수를 호출할 때에는 증감 연산자(++, --)나 복합 대입 연산자 등은 사용하지 않는 것이 좋습니다.
매크로 함수의 장점은 다음과 같습니다.
- 매크로 함수는 단순 치환만을 해주므로, 인수의 타입을 신경 쓰지 않습니다.
- 매크로 함수를 사용하면 여러 개의 명령문을 동시에 포함할 수 있습니다.
- 함수 호출에 의한 성능 저하가 일어나지 않으므로, 프로그램의 실행속도가 향상됩니다.
매크로 함수의 단점은 다음과 같습니다.
- 원하는 결과를 얻는 정확한 매크로 함수의 구현은 어려우며, 따라서 디버깅 또한 매우 어렵습니다.
- 매크로 함수의 크기가 증가하면 증가할수록 사용되는 괄호 또한 매우 많아져서 가독성이 떨어집니다.
Makefile 공부
- make - C dir
- Makefile을 계속 읽지 말고 우선은 dir로 이동하라는 것
- 순환 make에 사용된다.
printf함수
1. format
printf는 첫번째 인수로 format을 받아서 뒤의 인수를 출력한다.
이런 format은 3가지 종류가 있다.
- 표준 출력에 출력되는 일반 문자
- 표준 출력에 변환되어 복사되는 문자 이스케이프 시퀀스(\b, \t 이런 애들…)
- 다음 연속 인수의 인쇄를 일으키는 형식 지정.(%d, %s, %c 등으로 받는 인자, 가변인자를 받아야함.)
2. 인수의 규칙
첫 번째 이후의 인수는 대응하는 형식이 c,b s인 경우 문자열로 처리되며, 그렇지 않은 경우 아래 확장자를 가진 C의 상수로 간주된다.
- 앞에 선행 플러스 기호 또는 마이너스 기호를 사용 할 수 있다. -숫자를 인자로 전달해야 할 때, 문자열로 전달한다. (itoa)
- 선두 문자가 단일 따옴표 또는 이중 따옴표 인 경우 값은 다음 문자의 문자코드이다. 값은 다음 문자의 문자코드이다. “asdf”가 인자일 경우 a가 가리키는 포인터이고, c이면 c의 포인터를 전달한다.
3. 리턴 값
출력한 문자열의 총 길이를 리턴
4. 변환자.
printf에서 구현해야 하는 플래그는 cspdiuxX%이다.
- %c : 인자의 맨 앞 1바이트가 출력된다.
- %s : 문자열을 출력한다.
- %p : 포인터의 주소를 출력한다.
- %d : 10진수의 정수를 출력한다.
- %i : 정수를 10진수로 출력한다.(%d와 같음)
- %u : 부호없는 10진수 정수를 출력한다.
- %x : 16진수로 변환해 출력한다.(10이상 숫자는 소문자)
- %X :16진수로 변환해 출력한다. (10이상 숫자는 대문자.)
- %% : %를 출력한다.
5. 허용함수
stdlib : malloc free wirte
stdarg : va_start, va_arg, va_copy, va_end
6. stdarg헤더에 대해
- 가변인자 처리 메크로 모음
- va_list : 가변 인자 목록. 가변 인자의 메모리 주소를 저장하는 포인터이다.
- va_start : 가변 인자를 가져올 수 있도록 포인터를 설정한다.
- va_arg :가변 인자 포인터에서 특정 자료형 크기만큼 값을 초기화 한다.
- va_end : 가변 인자 처리가 끝났을 때 포인터를 NULL로 초기화 한다.
- va_list 변수명; : 가변인자 목록 포인터를 선언.
- va_start(가변인자 포인터, 가변인자 갯수) : 가변인자 목록 포인터를 설장하는 것.
- va_arg(가변인자 포인터 ,자료형) : 가변인자 포인터에서 자료형의 크기만큼 역참조 해서 값을 가져오고 포인터를 자료형 만큼 순방향 이동시킨다.
- va_end(가변인자 포인터) : 가변인자 포인터를 널로 초기화 해준다.
va_start
void va_start(va_list ap, last);
#define va_start(ap, pN) \\
((ap) = ((va_list) (&pN) + __va_argsiz(pN)))
va_arg ,va_end를 나중에 사용하기 위해 가장 먼저 호출해야만 한다.
ap인자를 초기 설정해주는 역활이다.(Argument pointer)
last 인자는 가변인자 목록 앞의 마지막인자의 이름이다.(—왜 마지막인지 모르겠지만, 목록앞의 마지막 인자의 이름이라고 한다. 즉 호출된 함수가 타임을 알고있는 마지막 인자라는데 printf의 첫번째 인자로 생각하면 될듯.)
이 인자의 주소는 va_start메크로에서 사용되므로 함수,배열 등으로 선언하면 안된다.
last의 역활 : 첫번째 가변인자의 주소를 알기위해 고정인수가 필요한데, va_list 주소값에다가 고정인수의 크기를 더한 위치로 ap를 초기화한다.
마지막 고정인수가 char형 포인텅인 경우 fmt의 시작포인터로부터 +8byte한 곳에 ap가 위치한다.
왜 이게 last라고 불리는지 궁금하다면 여러가지 고정인자가 있고 가변인자가 있는 경우를 생각해보면 된다.
만약 함수가 (int a, int b ,int c, …) 이렇게 정의가 됐다면 우리는 이 고정인자의 어디부터 가변인자 리스트에 넣을 것인지를 찾아보는 것이다.
int test(int *ar, int a, ...)
{
va_list ap;
va_start(ap, ar);
}
위의 코드를 돌려보려면 컴파일러가 오류를 뿜는다 가변인자 전의 마지막 인자를 꼭 해달라고 한다.
va_arg
type va_arg(va_list ap, type);
#define va_arg(ap, t) \\
(((ap) = (ap) + __va_argsiz(t)), \\
*((t*) (void*) ((ap) - __va_argsiz(t))))
인수 ap 는 va_start로 초기화가 된 변수이다.
va_arg를 호출할때마다 다음 인수가를 반환하도록 한다. 즉 read함수와 비슷하게 뒤로 밀어주는 것이다.
인수 type는 type의 크기만큼 포인터를 전진시킨다. 그리고 받은 인자 타입의 포인터를 리턴해준다.
!!!! 중요 !!!
- 다음 인수가 없거나 타입이 다음인수의 타입과 호환되지 않으면 랜덤한 오류가 발생한다.
- va_arg를 사용하는 함수에 ap가 전달되면 해당 함수가 반환된 후의 ap값은 정의되지 않는다.
va_arg 함수 주의 할 특징
visual stdio의 경우 va_arg(ap ,char)처럼 사용이 가능하다.
단, gcc에서는 char 형 문자일 때 va_arg메크로에 char형 대신에 int를 사용해야만 한다.
char, bool,short → int로
float →double로 바뀐다.
- 주의할 점 : va로 float을 넘기게 되면 넘어갈 때 double형태로 넘어가게 된다. (short, char 등도 int로 넘어간다.)
- 따라서 va_arg(va_list list, type_t) 에서 type는 float이 될 수 없다.
- 다만 function_call 시 float을 인자로 넘긴다면, va_arg(list,double)double로 지정하더라도 할당 받는 변수의 형태에 따라서 자동으로 float으로 변환이 된다. 할당 받는 변수가 float이면 float으로 double이면 double로 처리된다.
- int 미만의 데이터 형은 int로 승격된다
- va_end
- void va_end(va_list ap);
#undef va_end
void va_end (__gnuc_va_list); /* Defined in libgcc.a */
# define va_end(AP) ((void)0)
va_list 가 무엇인가?
일단 va_list의 정의를 찾아보면
#ifndef _VA_LIST
typedef __builtin_va_list va_list;
#define _VA_LIST
이렇게 되어있다.
그래서 __builtin_va_list를 검색해 봤는데 일단 코드에 추가된 파일이 아니라 gcc내부에서 알아서 정의해주고 컴파일러의 종류에 따라서 다르게 구현이 되어있다고 한다.
va_list형 변수가 va_arg를 사용해서 이동을 하면 정말 주소값이 이동을 하는건지 궁금했지만 그 구현이 어떻게 되어있는지 알 수가 없었다.
그래서 va_list형 변수를 찍어보면서 va_arg작동 방식이 주소값을 이동시키면서 보는건지 봤는데 그 테스트 결과
va_list ap;
va_start(ap, ar);
for (int i =0; i < 12; i++)
{
printf("%p\\\\n",ap);
//printf("ar:::::: %d,%p\\\\n",*ar ,ar);
printf("%d\\\\n", va_arg(ap,int));
//ap+= sizeof(int);
}
va_end(ap);
이렇게 코드를 짜보면서 테스트를 해봤는데 ap주소값의 변화는 없었다.
그래서 따로 주소값이 이동하는게 아니라 피벗값을 줘서 그 피벗값을 이동시키는 것이라는걸 유추해 볼 수 있다.
나는 그래서 ap의 주소값을 이동시키는 방법을 사용해서 과연 이동 후 에는 va_arg를 사용했을 때 만약 한칸 건너 뛴 값이 나온다면 나는 테스트가 나의 유추와 맞다고 생각할 수 있었다.
하지만 va_list자체적으로 이 주소값은 const형으로 선언됐는지 변경이 불가능했고 그래서 뒤로 밀어볼 수가 없었다. 하지만 제법 많은걸 알게됐다. 일단 여기까지가 내가 찾아본 부분이다.
- 함수 포인터 공부 https://rnathsus.tistory.com/184
- write함수가 실패하는 경우에 대하여 https://linux.die.net/man/2/write 와 https://linux.die.net/man/3/explain_write
이상한 테스트 :
int max값보다 큰 길이의 문자열이 들어올때
참고 링크
'42Seoul > NetPractice' 카테고리의 다른 글
Netpractice를 3일만에 끝내보자. (6~10번 문제풀이)(3/3) (0) | 2023.01.11 |
---|---|
Netpractice를 3일만에 끝내보자.(문제풀이 및 개념공부 1~5번)(2/3) (0) | 2023.01.11 |
Netpractice를 3일만에 끝내보자. (시작 전 개념공부)(1/3) (2) | 2023.01.11 |