ft_printf 구현하기
42 Seoul Project 중 하나인 ft_printf에 대해서 포스팅을 진행합니다.
1. ft_printf 구현 개요
- ft_printf 구현
- 전체에서 서식 지정자 c, s, d, x 등을 구현한다.
- flag, width, precision 을 구현한다.
2. 전체 구조

3. 구현
ft_printf의 구현은 undefined behavior 를 다루지 않는다. 따라서 구현에서 출력이 불가능한 부분은 -1 을 반환하도록 구현하였고 실제 printf 와 다른 부분이 있을 수 있다.
1. ft_printf, ft_print, Parsing specifier과정
ft_printf 함수에 로직은 가변인자를 초기화 하는 부분에서 시작한다. 가변인자 한개를 받아오고 반복해서 아래의 로직을 실행한다.

%를 찾는다.flag를 파싱한다. (여러개가 존재하므로 반복문을 통해 파싱)width를 파싱한다. (atoi와 비슷하며*을 고려해야한다.)precision파싱 (.을 먼저 파싱하고 width와 비슷한 과정을 진행한다.)- 파싱이 끝난 위치에 서식지정자가 있는지 확인하여 파싱 오류 체크 후
flag,width,precision이 모두 담긴 specifier 구조체와 가변인자를 인자로 넣어서 문자열에 이어서 나오는 서식지정자에 맞는 함수(ft_print_format)를 실행한다.
int ft_printf(const char *format, ...)
{
va_list ap;
const char *find;
int res;
int length;
if (format == NULL)
return (ERROR_VALUE);
va_start(ap, format);
res = 0;
while ((find = ft_strchr(format, '%')))
{
res += write(STDOUT, format, find - format);
if ((length = ft_print(find + 1, ap)) < 0)
return (ERROR_VALUE);
res += length;
format = pars_format(find + 1) + 1;
}
res += write(STDOUT, format, ft_strlen(format));
va_end(ap);
return (res);
}
이제부터는 서식지정자에 따른 출력부분이다.
2. format_c
width 판단을 진행한다. width에 상관없이 문자는 꼭 출력해야하므로 width가 -1(처음 초기화 그대로의 상태)이나 0이면 width는 문자 1개 크기인 1로 할당한다.
width 만큼 버퍼를 만들고
' '(space)로 초기화를 해준다.LEFT_FLAG('-')가 있으면 버퍼에 제일 왼쪽에 가변인자로부터 받은 문자 하나를 넣고LEFT_FLAG가 없다면 제일 오른쪽에 넣는다.버퍼를 출력하고 free를 한 후 출력한 길이 반환한다.
int ft_print_c(t_spec spec, va_list ap)
{
int res;
char c;
char *buf;
c = (char)va_arg(ap, int);
spec.width = spec.width <= 0 ? 1 : spec.width;
if ((buf = malloc((spec.width) * sizeof(char))) == NULL)
return (ERROR_VALUE);
ft_memset(buf, ' ', spec.width);
if (spec.flag[FLAG_LEFT])
*buf = c;
else
*(buf + spec.width - 1) = c;
res = write(STDOUT, buf, spec.width);
free(buf);
return (res);
}
3. format_s
인자 한개를 읽고
NULL값이면"(null)"문자열을 str에 넣어주고 그렇지 않다면 읽은 값을 그대로 넣어준다.- 다음 로직으로 버퍼의 크기를 구하여 버퍼를 만들어준다.
width와precision,str길이를 비교한다.precision이 없으면str 길이와width를 비교하여 큰 값을 버퍼의 크기로 정한다.precision값이 있으면width와precision을 비교한다.width가 크면width를 버퍼의 크기로 결정한다.- 그렇지 않다면
precision하고str 길이를 비교하여 작은 값을 버퍼 사이즈로 한다. (이것의 이유는 printf의 동작은 width가 표현해야 하는 범위가(precision)보다 작으므로 최대로 표현할 수 있는 길이를 선택해야하기 때문이다.)
버퍼에 값을 채워 넣는다.
precision과LEFT_FLAG가 없을 때ZERO_FLAG가 있다면 0으로 패딩을 해야하므로'0'값으로 버퍼를 모두 초기화하고 그렇지 않은 경우에는' '로 버퍼를 초기화 한다.버퍼에
str을 복사할 길이를 정한다.str에서 얼마만큼을 복사해야 하는지는precision과str 길이에 의해서 결정된다.precision이 없다면str의 길이로 정해진다.- 있다면,
precision과str의 길이중에 짧은 길이를 복사할 길이로한다. (이것의 이유는printf의 동작 자체에서precision의 역할이str의 표현 범위를 결정하는 것이기 때문이다.)
LEFT_FLAG가 있으면 버퍼의 시작부분에서 복사를 하고 그렇지 않다면 맨 뒤에서 복사할 길이만큼을 뺀 위치에서 복사를 시작한다.
- 버퍼를 출력, free, 출력길이 반환을 진행한다.
int ft_print_s(t_spec spec, va_list ap)
{
int len_buf;
int res;
char *str;
char *buf;
str = (char *)va_arg(ap, char *);
if (str == NULL)
str = "(null)";
len_buf = ft_get_bufsize_s(&spec, str);
if ((buf = malloc((len_buf) * sizeof(char))) == NULL)
return (ERROR_VALUE);
ft_bufset_s(spec, len_buf, str, buf);
res = write(STDOUT, buf, len_buf);
free(buf);
return (res);
}
4. format_p
인자 값을 불러온다.
주소 값의 길이를 구한다. (16 진수로 몇 자리수가 되는지 구하는 과정) 이때,
precision과 주소 값이 모두 0이라면0x만 출력해야 하므로 2의 값을 갖는다. 그렇지 않다면주소 값의 길이 + 2의 값으로 설정한다.width와 주소 값의 길이 중 큰 값을 버퍼의 길이로 하여 버퍼를 생성한다. 그 이후에' '으로 초기화를 진행한다.LEFT_FLAG가 있다면width - 1부터 왼쪽으로 가면서 위에서 구해온주소 값의 길이 - 2만큼에 16진수로 나눈 값으로 채워 넣어준다.LEFT_FLAG가 없다면주소 값의 길이 - 1부터 왼쪽으로 가면서주소 값의 길이 - 2만큼을 16진수로 채워 넣어준다.연속해서
'x'와'0'을 채워 넣어준다.버퍼를 출력, free, 출력 길이 반환을 한다.
int ft_print_c(t_spec spec, va_list ap)
{
int res;
char c;
char *buf;
c = (char)va_arg(ap, int);
spec.width = spec.width <= 0 ? 1 : spec.width;
if ((buf = malloc((spec.width) * sizeof(char))) == NULL)
return (ERROR_VALUE);
ft_memset(buf, ' ', spec.width);
if (spec.flag[FLAG_LEFT])
*buf = c;
else
*(buf + spec.width - 1) = c;
res = write(STDOUT, buf, spec.width);
free(buf);
return (res);
}
5. format_%
width가 1 이하면'%'를 출력하고 1을 반환하며 종료한다.버퍼를
width만큼 할당하고ZERO_FLAG이고LEFT_FLAG가 아니면'0'으로 초기화하고 아니면' '로 초기화한다.LEFT_FLAG가 있으면 버퍼 제일 왼쪽에'%'를 넣는다. 없으면 버퍼 제일 오른쪽에'%'를 넣는다.버퍼를 출력, free, 출력 길이를 반환한다.
int ft_print_perc(t_spec spec)
{
char *buf;
int len;
if (spec.width <= 1)
return (write(STDOUT, "%", 1));
if ((buf = malloc(sizeof(char) * spec.width)) == NULL)
return (ERROR_VALUE);
if (spec.flag[FLAG_ZERO] && !spec.flag[FLAG_LEFT])
ft_memset(buf, '0', spec.width);
else
ft_memset(buf, ' ', spec.width);
if (spec.flag[FLAG_LEFT])
*buf = '%';
else
*(buf + spec.width - 1) = '%';
len = write(STDOUT, buf, spec.width);
free(buf);
return (len);
}
6. format_d, i, u, x, X
d, i, u, x, X 의 전체적인 틀이 비슷하므로 한번에 묶어서 설명을 이어나간다.
숫자를 가변 인자에서 꺼내 길이를 구한다.
x와X의 경우 16진수 길이로 나머지는 10 진수 길이로 구한다.flag('+', '#', ' ')가 있거나 값이 음수일 때 필요한 길이를 숫자 길이에 더한다.width와 숫자의 길이,precision에서 가장 큰 값을 버퍼의 길이로 한다.버퍼를 만들고
' '로 초기화 해주는데,LEFT_FLAG가 아니고precision이 없을 때ZERO_FLAG이라면'0'으로 초기화를 해준다.- 버퍼에 값을 채운다.
LEFT_FLAG일때는 제일 왼쪽에서 오른쪽으로 진행하면서 아래와 같이 로직이 이루어진다.- 음수 부호 또는
flag('+', '#'['0x', '0X')], ' ')값을 넣는다. precision이 숫자의 길이보다 크다면 그 차이만큼'0'를 넣는다.- 숫자의 값을 넣는다. (숫자의 길이 만큼 오른쪽으로 가서 오른쪽에서 왼쪽으로 10진수이면 10으로 나눈 나머지, 16진수이면 16으로 나눈 나머지로 채워 넣는다.)
- 음수 부호 또는
LEFT_FLAG가 아닐때에는 제일 오른쪽에서 왼쪽으로 진행하면서 아래와 같이 로직이 이루어진다.- 뒤에서 부터 숫자의 값을 넣는다. (바로 위에 3번 내용과 같다.)
precision이 숫자의 길이보다 크다면 그 차이만큼'0'를 넣는다.- 음수 부호 또는
flag('+', '#'['0x', '0X')], ' ')값을 넣는다.
- 버퍼 값을 출력, free, 출력한 길이를 반환한다.
int ft_print_d(t_spec spec, va_list ap)
{
int arg;
char *buf;
int len;
int bufsize;
int sign;
arg = (int)va_arg(ap, int);
len = ft_get_numsize_d(arg, spec);
sign = 0;
if (arg < 0 || spec.flag[FLAG_SPACE] || spec.flag[FLAG_PLUS])
sign = 1;
if (spec.width <= 0 && spec.prec == 0 && arg == 0 && !sign)
return (0);
if ((spec.width <= 0 && spec.prec == 0 && arg == 0 && sign) ||
(spec.width == -1 && spec.prec == -1))
bufsize = len + sign;
else if (spec.width > spec.prec)
bufsize = len + sign < spec.width ? spec.width : len + sign;
else
bufsize = len < spec.prec ? spec.prec + sign : len + sign;
if ((buf = ft_setbuf_d(arg, bufsize, len, spec)) == NULL)
return (ERROR_VALUE);
len = write(STDOUT, buf, bufsize);
free(buf);
return (len);
}
char *ft_setbuf_d(long long arg, int bufsize, int len, t_spec spec)
{
t_box box;
if ((box.buf = malloc(sizeof(char) * bufsize)) == NULL)
return (NULL);
box.start = box.buf;
spec.flag[FLAG_ZERO] && !spec.flag[FLAG_LEFT] && spec.prec == -1 ?
ft_memset(box.buf, '0', bufsize) : ft_memset(box.buf, ' ', bufsize);
box.i = spec.prec;
if (spec.flag[FLAG_LEFT])
{
ft_setsign_d(arg, &(box.buf), spec, box.start);
while ((box.i)-- > len)
*(box.buf++) = '0';
ft_setnum_d(arg, box.buf + len - 1, spec);
}
else
{
ft_setnum_d(arg, box.buf + bufsize - 1, spec);
box.buf = box.buf + bufsize - 1 - len;
while ((box.i)-- > len)
*(box.buf--) = '0';
ft_setsign_d(arg, &(box.buf), spec, box.start);
}
return (box.start);
}
7. 마무리
더 이상 '%'를 못찾게 되면 ft_printf 함수 부분에서 반복문이 끝나게되고 나머지를 출력함과 동시에 출력했던 모든 길이의 합을 반환하면서 끝이난다.