2021. 6. 18. 23:20ㆍ42 SEOUL/42 cursus
가변 인자란 무엇인가 ?
printf의 프로토 타입을 보면, int printf(const char *format, ...);로 두 번째 인자가 생략 기호(ellipsis)인...라는 것을 알 수 있다.
앞의 문자열에 있는 형식의 갯수에 따라 인자의 갯수를 달리 받아야하기 때문에 가변 인자를 쓰며, 가변인자란 다음과 같다.
상황에 따라 원하는 만큼의 인자를 받아서 사용, 처리할 수 있게해주는 것이 가변 인자이다.
가변 인자(variable argument)는 <stdarg.h> 헤더에 선언되어있으며, 필수 인자와 선택적 인자, 두 개를 요구한다.
선택적 인자인 가변 인자는 변수의 갯수가 정해져있지 않기 때문에 첫째로 필수 인자를 받은 후, 선택적 인자를 받게 되며, 선택적 인자의 뒤에는 다른 인자를 받을 수 없다.
printf로 예를 들면
printf(const char *format, ...);
인데 이 중 format이 필수 인자가 되는 것이고, 뒤의 ...이 가변인자가 된다.
보통 필수 인자로 주어지는 인자들 중 하나는 선택적 인자의 수를 명시하여 정지 신호를 찾을 수 있게 해준다.
매크로 함수
printf처럼 매개변수의 개수가 정해지지 않은 함수에서 매개변수를 이용하기 위해서는 stdarg.h에 정의된 매크로를 이용해야한다.
va_list
함수로 전달되는 인수들은 스택(Stack)에 저장되어 함수에서 인수를 꺼내쓰게 된다. 스택의 인수를 읽을 때에는 포인터 연산을 해야하는데, 읽고있는 위치를 가리키는 포인터 변수가 va_list이다.
va_list 타입은 char *형으로 저장되어있으며, 관습적으로 변수의 이름은 ap를 사용한다.
해당 매크로 함수의 원형은 다음과 같다.
typedef char* va_list;
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
#define va_end(ap) (ap=(va_list)0)
_INTSIZEOF(n) 매크로는 n의 길이를 계산하는데, n의 값에 따라 값을 4의 배수로 출력한다.
int형 정수의 크기는 당연하게도 4가 나오고, 재미있게도 char 역시 4가 나오고 double의 경우 8이 나온다.
이러한 인수들이 말한 것처럼 스택!!에 들어간다면 마지막에 들어간 값이 맨 처음으로 나와야하기 때문에, va_start를 이용해 역순으로 고정 인자 뒤에 넣어준다고 보면 될 것이다.
va_start(ap, 마지막고정 인자)
함수로 들어온 가변 인수들은 해당 명령을 통해 읽힐 수 있는 상태가 되는데, ap 포인터가 첫 번째 가변 인수를 가리키도록 초기화해주며, 가변 인수의 시작 포인터를 지정하기 위해, 마지막 고정 인수를 전달해주어야한다. va_start에서는 마지막 고정 인수 다음의 포인터 위치에 가변 인자(ap)를 위치시켜주므로 고정 인수 이후부터 ap를 읽으면 순서대로 가변 인자를 읽을 수 있다.
#define va_start(ap, v) (ap=(va_list)&v + _INTSIZEOF(v))
//v는 고정 인자, va_listFH 형변환 된 고정 인자의 포인터 위치에 고정 인자의 크기만큼(정확히는 4의 배수) 이동
va_start는 ap를 va_list로 캐스팅한 v의 포인터의 시작점에 v의 _INTSIZEOF 크기만큼 이동시켜 그 자리에 ap 포인터를 위치시킴
va_arg(ap, 인자 타입)
가변 인수를 읽어오는 명령이다. va_start에서 고정 인자의 뒤에 가변 인자를 세팅해두었으므로, ap 위치에 있는 값을 (인자 타입 만큼)순차적으로 읽어오는 함수이다. 예를 들어 char형만큼 읽어오고 싶다면 va_arg(ap, char), 정수형만큼 읽고 싶다면 va_arg(ap, int)명령어를 통해 포인터를 이동시킬 수 있다.
이 부분에서 일반 함수와 매크로 함수의 차이가 생기게 되는데, 임시 변수명이 아닌 타입 명 자체가 인자로 전달될 수 있는 것은 va_arg가 매크로 함수로 해당 함수 내에서 sizeof 연산자와 캐스트 연산자로 전달되기 때문이다.
#define va_arg(ap, t) (*(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
해당 명령은 ap 번지에 있는 가변 인수를 t 타입으로 캐스팅하여 읽고 다시 원래에 위치에 이동시킨 후 해당 형만큼 읽는다.(복잡;)
때문에 해당 값을 제대로 읽기 위해서는 가변 인자의 타입을 정확히 명시해야한다.
va_end(ap)
이 명령은 가변 인수를 다 읽은 후 뒷 정리를 하는데, 인텔 계열의 CPU에서는 해당 명령이 아무런 행동을 하지 않는다. 다만 미래의 환경 혹은 다른 플랫폼에서는 해당 명령이 필요할 수 있기에 관례적으로 넣는 편이다.
이제 짜보자구 ,,,
피씬 전에 볼때는 이게 뭐지 으흑흑?했는데 이제 보니까 코딩 도장꺼라 쉽게 느껴졌음 흑흑,, 너무 잘 설명해주세요
'42 SEOUL > 42 cursus' 카테고리의 다른 글
42 CURSUS / ft_printf_03 (Flow chart) (0) | 2021.06.21 |
---|---|
42 CURSUS / ft_printf_02 (0) | 2021.06.19 |
42 CURSUS / GET_NEXT_LINE (0) | 2021.06.16 |
42 cursus / ft_printf _00 (0) | 2021.06.16 |
42 CURSUS / NETWHAT (0) | 2021.05.23 |