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 함수에 로직은 가변인자를 초기화 하는 부분에서 시작한다. 가변인자 한개를 받아오고 반복해서 아래의 로직을 실행한다.

  1. % 를 찾는다.
  2. flag 를 파싱한다. (여러개가 존재하므로 반복문을 통해 파싱)
  3. width 를 파싱한다. (atoi 와 비슷하며 * 을 고려해야한다.)
  4. precision 파싱 (.을 먼저 파싱하고 width와 비슷한 과정을 진행한다.)
  5. 파싱이 끝난 위치에 서식지정자가 있는지 확인하여 파싱 오류 체크 후 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


  1. width 판단을 진행한다. width에 상관없이 문자는 꼭 출력해야하므로 width가 -1(처음 초기화 그대로의 상태)이나 0이면 width는 문자 1개 크기인 1로 할당한다.

  2. width 만큼 버퍼를 만들고 ' '(space) 로 초기화를 해준다.

  3. LEFT_FLAG('-')가 있으면 버퍼에 제일 왼쪽에 가변인자로부터 받은 문자 하나를 넣고 LEFT_FLAG가 없다면 제일 오른쪽에 넣는다.

  4. 버퍼를 출력하고 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

  1. 인자 한개를 읽고 NULL 값이면 "(null)" 문자열을 str에 넣어주고 그렇지 않다면 읽은 값을 그대로 넣어준다.

  2. 다음 로직으로 버퍼의 크기를 구하여 버퍼를 만들어준다.
    • widthprecision, str길이를 비교한다.
      • precision 이 없으면 str 길이width 를 비교하여 큰 값을 버퍼의 크기로 정한다.
      • precision 값이 있으면 widthprecision 을 비교한다.
        • width 가 크면 width를 버퍼의 크기로 결정한다.
        • 그렇지 않다면 precision 하고 str 길이를 비교하여 작은 값을 버퍼 사이즈로 한다. (이것의 이유는 printf의 동작은 width가 표현해야 하는 범위가 (precision)보다 작으므로 최대로 표현할 수 있는 길이를 선택해야하기 때문이다.)
  3. 버퍼에 값을 채워 넣는다.

    • precisionLEFT_FLAG 가 없을 때 ZERO_FLAG가 있다면 0으로 패딩을 해야하므로 '0' 값으로 버퍼를 모두 초기화하고 그렇지 않은 경우에는 ' '로 버퍼를 초기화 한다.

    • 버퍼에 str을 복사할 길이를 정한다.

      str에서 얼마만큼을 복사해야 하는지는 precisionstr 길이 에 의해서 결정된다.

      • precision 이 없다면 str의 길이 로 정해진다.
      • 있다면, precisionstr의 길이 중에 짧은 길이를 복사할 길이로한다. (이것의 이유는 printf의 동작 자체에서 precision 의 역할이 str의 표현 범위를 결정하는 것이기 때문이다.)
    • LEFT_FLAG 가 있으면 버퍼의 시작부분에서 복사를 하고 그렇지 않다면 맨 뒤에서 복사할 길이만큼을 뺀 위치에서 복사를 시작한다.

  4. 버퍼를 출력, 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

  1. 인자 값을 불러온다.

  2. 주소 값의 길이를 구한다. (16 진수로 몇 자리수가 되는지 구하는 과정) 이때, precision과 주소 값이 모두 0이라면 0x 만 출력해야 하므로 2의 값을 갖는다. 그렇지 않다면 주소 값의 길이 + 2 의 값으로 설정한다.

  3. width와 주소 값의 길이 중 큰 값을 버퍼의 길이로 하여 버퍼를 생성한다. 그 이후에 ' ' 으로 초기화를 진행한다.

  4. LEFT_FLAG 가 있다면 width - 1 부터 왼쪽으로 가면서 위에서 구해온 주소 값의 길이 - 2 만큼에 16진수로 나눈 값으로 채워 넣어준다. LEFT_FLAG가 없다면 주소 값의 길이 - 1 부터 왼쪽으로 가면서 주소 값의 길이 - 2 만큼을 16진수로 채워 넣어준다.

  5. 연속해서 'x''0'을 채워 넣어준다.

  6. 버퍼를 출력, 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_%

  1. width가 1 이하면 '%'를 출력하고 1을 반환하며 종료한다.

  2. 버퍼를 width만큼 할당하고 ZERO_FLAG이고 LEFT_FLAG가 아니면 '0'으로 초기화하고 아니면 ' '로 초기화한다.

  3. LEFT_FLAG가 있으면 버퍼 제일 왼쪽에 '%'를 넣는다. 없으면 버퍼 제일 오른쪽에 '%' 를 넣는다.

  4. 버퍼를 출력, 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 의 전체적인 틀이 비슷하므로 한번에 묶어서 설명을 이어나간다.

  1. 숫자를 가변 인자에서 꺼내 길이를 구한다. xX 의 경우 16진수 길이로 나머지는 10 진수 길이로 구한다.

  2. flag('+', '#', ' ')가 있거나 값이 음수일 때 필요한 길이를 숫자 길이에 더한다.

  3. width와 숫자의 길이, precision에서 가장 큰 값을 버퍼의 길이로 한다.

  4. 버퍼를 만들고 ' '로 초기화 해주는데, LEFT_FLAG가 아니고 precision이 없을 때 ZERO_FLAG이라면 '0'으로 초기화를 해준다.

  5. 버퍼에 값을 채운다.
    • LEFT_FLAG 일때는 제일 왼쪽에서 오른쪽으로 진행하면서 아래와 같이 로직이 이루어진다.
      1. 음수 부호 또는 flag('+', '#'['0x', '0X')], ' ') 값을 넣는다.
      2. precision이 숫자의 길이보다 크다면 그 차이만큼 '0'를 넣는다.
      3. 숫자의 값을 넣는다. (숫자의 길이 만큼 오른쪽으로 가서 오른쪽에서 왼쪽으로 10진수이면 10으로 나눈 나머지, 16진수이면 16으로 나눈 나머지로 채워 넣는다.)
    • LEFT_FLAG 가 아닐때에는 제일 오른쪽에서 왼쪽으로 진행하면서 아래와 같이 로직이 이루어진다.
      1. 뒤에서 부터 숫자의 값을 넣는다. (바로 위에 3번 내용과 같다.)
      2. precision이 숫자의 길이보다 크다면 그 차이만큼 '0'를 넣는다.
      3. 음수 부호 또는 flag('+', '#'['0x', '0X')], ' ') 값을 넣는다.
  6. 버퍼 값을 출력, 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 함수 부분에서 반복문이 끝나게되고 나머지를 출력함과 동시에 출력했던 모든 길이의 합을 반환하면서 끝이난다.

⤧  Next post AR을 이용한 위치기반 서비스 모바일 앱 Capsule Time ⤧  Previous post Oh-My-C-Lang - Section 7 - 06