42서울에서는 printf를 일반적인경우 사용을 제한했습니다.
그 이유는 write(int fd, const void *buf, size_t n) 함수를 활용해서 파일디스크립터에 대해 생각하고, 출력의 내용을 담는 버퍼에 대해 배우고 이해하기 위함이라 생각합니다.
이번에 만들 ft_printf를 통해서 가변인가자 무엇인지 배우게 되고, 문자열에 따른 파싱을 진행하게 됩니다.
단순해보이는 printf에도 다양한 기능이 있고, 내부적으로 어떻게 돌아가는지 파악 할 수 있는 과제입니다.
printf 함수의 Format Placeholder Syntax(서식 표기 구문)은 %
로 시작하는 형식 태그
로 표기 되며, 그 구문은 아래와 같습니다.
printf의 많은 필드 중에서 flag
필드의 -
, 0
, .
, *
그리고 width
및 precision
필드,type
필드의 c
, s
, p
, d
, i
, u
, x
, X
, %
만 구현 하면 됩니다.
최대한 실존하는 printf와 유사하게 만드는것이 최종 목표입니다.
가변인자
란 이름 그대로 '인자가 변할수 있다.' 라는 의미로 인자의 타입과 개수가 정해지지 않습니다.
C 언어에서 함수를 사용하다보면 매개 변수의 개수가 정해져있지 않은 함수가 있는데 대표적인 예로 printf가 있습니다.
우리가 일반적인 pritf를 사용할때,
printf("int == %d, double == %lf, char == %c, str == %s", i, j, k, l)
이와 같이 첫 인자 문자열을 제외하면 마음대로 작성할 수 있습니다.
int ft_printf(const char *bs, ...)
{
va_list ap;
int answer;
answer = 0;
va_start(ap, bs);
answer = check(bs, ap);
va_end(ap);
return (answer);
}
printf의 함수 원형은 int printf(const char *format, ...)
이렇게 상황에 따라 함수에 인자의 개수가 다르게 할당되어도 처리할 수 있게 해주는 것이 가변 인자 입니다.
가변 인자
를 포함한 함수에는 두 종류의 인자가 요구됩니다.
여기서 말한 선택적 인자
는 가변 인자
인데, 가변 인자
를 받기 위해선 사전에 필수 인자
가 무조건 요구됩니다. 선택적 인자
인 가변 인자
는 말 그대로 인자의 수가 정해져 있지 않기 때문에 선택적 인자
를 먼저 받게 되면 함수의 원형에서 어느 인자를 필수 인자
로 받은 것인지 알 수 없기 때문에, 필수 인자
를 먼저 받은 다음 선택적 인자
를 받는 것을 원칙으로 합니다다.
format이란 문자열 선택적 인자
이후에 ...이란 가변인자
를 받습니다.
printf가 문자열의 길이를 리턴하는 함수이며, 오류가 발생 시 -1
을 리턴해줍니다.
이를 고려하여 길이를 구해주는 함수를 작성해줍니다.
int check(const char *bs, va_list ap)
{
int i;
int type_len;
if (bs == 0)
return (-1);
i = 0;
while (*bs)
{
if (*bs == '%')
{
bs++;
type_len = check_type(bs, ap);
if (type_len == -1)
return (-1);
i += type_len;
}
else
{
if (write(1, bs, 1) == -1)
return (-1);
i++;
}
bs++;
}
return (i);
}
인자로 받은 문자열을 %
가 나올때까지 넘겨주다 %
가 나오면 다음 인자가 무엇인지 확인해줍니다.
int check_type(const char *bs, va_list ap)
{
if (*bs == 'c')
return (go_c(ap));
else if (*bs == 's')
return (go_s(ap));
else if (*bs == 'p')
return (go_p(ap));
else if (*bs == 'd' || *bs == 'i')
return (go_d(ap));
else if (*bs == 'u')
return (go_u(ap));
else if (*bs == 'x')
return (go_x(ap));
else if (*bs == 'X')
return (go_lx(ap));
else if (*bs == '%')
{
write(1, "%", 1);
return (1);
}
else
return (-1);
}
%
다음 인자가 무엇인지 확인 후 해당문자에 따른 함수를 실행하게 합니다.
printf
함수의 Format Placeholder Syntax(서식 표기 구문)은 %
로 시작하는 형식 태그
로 표기 되며, 그 구문은 아래와 같습니다.
%[parameter][flags][width][.precision][length]type
Mandatory 파트에서는 type
필드의 요소만 구현하는걸로 진행합니다.
%[parameter][flags][width][.precision][length]type
printf의 flag
필드의 -
, 0
, .
, *
그리고 width
및 precision
필드를 추가적으로 구현하면 되는 과제입니다.
보너스 파트는 이론 자체는 쉬운편이지만, 하나하나 실제 printf를 찍어가며 예외처리를 찾아야하고, 다양한 컴파일 경고 파악, 파싱위주의 노가다성이 짙은 과제라 넘어갔습니다.
그래도 서식지정자가 어떤위치에서 어떻게 동작하는지를 제대로 파악하는것은 충분히 공부할 만한 가치가 있다고 생각합니다.
printf("%d", printf(" ", ...));
해당 구문을 실행했을때 문자열 길이를 리턴하는 함수인지 에러시 -1
리턴하는지 모르고 써왔는데, 간결하게나마 내부적으로 어떤 방식으로 돌아가는지 알 수 있었습니다.
이번 ft_printf를 통해 printf에 다양한 필드가 존재하고 필드마다 어떠한 동작을 하는지 알게 되었고, 가변인자가 무엇인지 배우게 되고, 기본적인 파싱에 대해 이해하는 파트였습니다.