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
함수 부분에서 반복문이 끝나게되고 나머지를 출력함과 동시에 출력했던 모든 길이의 합을 반환하면서 끝이난다.