가만보면 printf() 함수는
printf(”%d %d %d”, 3, 4, 5)
처럼 인자가 여러개여도 된다. 이것이 왜 가능한지 알아보기 위해 가변인자라는 것을 먼저 알아보자
printf() 함수의 매개변수는 0 ~ n 개의 값을 가질 수 있으며, format의 형식 지정자 값에 따라 매개변수의 수가 다르다.
만약 printf(”%d %d %d”, 3, 4, 5, 6) 인 경우 형식지정자가 3개 이므로 매개변수는 최소 3개 이상이 와야한다. 형식지정자가 매개변수보다 많을 경우 그 이후는 무시된다
형식지정자 ≤ 매개변수 조건이 맞춰줘야함
위의 헤더에 va_list, va_start, va_arg, va_end, va_copy 등이 정의되어 있다.
가변인자는 몇개가 될지 모르기 때문에 ‘...’ 으로 표시한다. 주의해야할 점은 최소 1개 이상의 고정 인수가 있어야 하며, ‘...’은 파라미터 순서 상 마지막에 위치해야한다.
int ft_printf(const char *, ...);
‘...’ 부분에 들어오는 변수명이 없는데, 이를 표현하기 위해 매크로 정의들을 이용한다.
반환값이 int인 이유는 출력된 문자열의 수가 반환되기 때문
va_list
va_list 라는 타입으로 길이가 변할 수 있는 인수들을 저장하기 위한 가변의 저장공간이다.
가변인수들에 대한 정보를 홀드하기 위한 타입이다.
void va_start(va_list ap, 첫 번째 매개변수)
두 번째 인자로 첫 번째 매개변수를 넣어, ap가 이후 들어올 가변인자를 가르키게 한다.
두개의 인자 va_list의 인스턴스, 그리고 고정인수를 받아 va_list를 초기화함
첫번째 가변인자 주소를 알려면 고정인수가 필요하기 때문
#define va_start(ap, v) ( (ap) = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)
위와 같이 정의하는데, 이는 ap는 가변인수 함수에 들어오는 고정인수 다음부터 위치되어야하기 때문이다.
type va_arg(va_list ap, type)
#define va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
ap(가변인자 포인터 변수)가 가르키는 곳에서 type 자료형만큼 데이터를 읽어와 type 자료형으로 반환
ap 포인터가 위치한 부분의 데이터를 읽어 반환한다
그 다음 ap 포인터를 타입길이만큼 뒤로 옮기기 때문에 그 다음 값이 이어서 출력될 수 있다.
ap 주소값은 t 사이즈만큼 증가
하지만 그 다음 값을 빼고 앞에 덧셈할 때처럼 ap에 대입하지는 않음
즉 ap 사이즈는 증가한 위치에 고대로 있지만 실제 우리가 출력하는 부분은 기존 부분이 되는 것
위의 그림처럼 ap 는 104번지에 가있지만 실제 우리가 데이터를 출력하는건 100번지에 있는 값
그래서 다음에 매크로를 다시 호출했을 때 104번지 값을 호출하고 ap는 type만큼 뒤로 밀림
이렇게 하는 이유는 타입마다 사이즈가 다른데 이 타입별로 ap 포인터를 뒤로 옮기기 위해서이다.
void va_end(va_list)
ap 가 가르키는 곳을 NULL로 초기화
- GCC(C표준)에서는 가변인자로 받은 값이 int보다 작다면 int를 지정해야한다.
- float는 double로 지정해야 한다.
(d 와 i 는 printf 에서는 기본적으로 같으나, scanf 에서는 역할이 다르다고 한다. d : 10진수 입력받음, i : 10/8/16진수 입력 받음)
bonus의 과제는 printf()의 아래 부분을 구현하는 것이다
printf() 함수는 형식 태그라고 불리는 것이 추가적으로 들어감 형식태그는 아래와 같다
**%[플래그(flag)][폭(width)][.정밀도][크기(length)]서식 지정자(specifier)**
숫자 : 지정한 숫자만큼 폭을 지정하여 출력한다. 실수는 .(소수점)
, e+
까지 폭에 포함된다.
출력할 값이 지정한 폭보다 작으면 자릿수를 맞추기 위해 공백 또는 0을 채워 넣는다.
출력값이 지정된 폭 보다 크다면 폭은 무시된다.
width에 음수가 할당되면 -플래그(좌측정렬) + width로 간주한다.
폭은 * 를 이용하여 가변인자로도 받을 수 있다.
ex) %5d → 5칸 안에 우측정렬하여 출력
비트플래그 내용 참고
typedef struct s_info {
char type;
char flag;
int width;
int precision;
int count;
const char *fmt;
va_list ap;
} t_info;
비트플래그를 사용하기 위해 플래그들을 임의의 비트로 define한다
# define FLAG_MINUS 0x01
# define FLAG_PLUS 0x02
# define FLAG_SPACE 0x04
# define FLAG_ZERO 0x08
# define FLAG_SHARP 0x10
format을 한글자씩 읽어 해당 글자가 %인 경우 다음이 어떤 특성을 가지는지 parse하도록 parse_format() 함수를 호출하고 %가 아닌 경우 문자를 바로 출력하도록 한다.
% 다음에 어떤 플래그를 사용했는지 확인하고 비트플래그를 이용하여 사용된 플래그를 켠다.
정밀도나 폭들을 확인하여 width, precision에 저장한다.
서식지정자에 따라 각각 print_char(), print_nbr(), print_string()를 호출해준다.
6번의 과정에서 count를 증가시켜 몇글자가 출력되는지 확인하고 구조체에 저장해놓아 이를 return 하도록 한다.