ft_printf - printf만들기

yeham·2022년 11월 6일
0

42Seoul

목록 보기
2/18
post-thumbnail

들어가기에 앞서

42서울에서는 printf를 일반적인경우 사용을 제한했습니다.
그 이유는 write(int fd, const void *buf, size_t n) 함수를 활용해서 파일디스크립터에 대해 생각하고, 출력의 내용을 담는 버퍼에 대해 배우고 이해하기 위함이라 생각합니다.

  • 이번에 만들 ft_printf를 통해서 가변인가자 무엇인지 배우게 되고, 문자열에 따른 파싱을 진행하게 됩니다.

  • 단순해보이는 printf에도 다양한 기능이 있고, 내부적으로 어떻게 돌아가는지 파악 할 수 있는 과제입니다.

  • printf 함수의 Format Placeholder Syntax(서식 표기 구문)은 %로 시작하는 형식 태그로 표기 되며, 그 구문은 아래와 같습니다.

  • printf의 많은 필드 중에서 flag 필드의 -, 0, ., * 그리고 widthprecision 필드,type 필드의 c, s, p, d, i, u, x, X, %만 구현 하면 됩니다.

  • 최대한 실존하는 printf와 유사하게 만드는것이 최종 목표입니다.

🖥️ Mandatory

가변인자란 이름 그대로 '인자가 변할수 있다.' 라는 의미로 인자의 타입과 개수가 정해지지 않습니다.

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이란 문자열 선택적 인자이후에 ...이란 가변인자를 받습니다.

  • va_list :: 가변 인자 목록
  • va_start :: 가변 인자를 가져올 수 있도록 가변 인자 시작 주소 참조하는 포인터 설정
  • va_arg :: 가변 인자를 참조하는 포인터를 통해 역참조 후, 해당 데이터의 크기만큼 밀어 다음 인자를 참조
  • va_end :: 가변 인자를 모두 처리 후, 가변인자를 가리키는 포인터를 NULL로 초기화

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 필드의 요소만 구현하는걸로 진행합니다.

💻 bonus

%[parameter][flags][width][.precision][length]type

printf의 flag 필드의 -, 0, ., * 그리고 widthprecision 필드를 추가적으로 구현하면 되는 과제입니다.

보너스 파트는 이론 자체는 쉬운편이지만, 하나하나 실제 printf를 찍어가며 예외처리를 찾아야하고, 다양한 컴파일 경고 파악, 파싱위주의 노가다성이 짙은 과제라 넘어갔습니다.
그래도 서식지정자가 어떤위치에서 어떻게 동작하는지를 제대로 파악하는것은 충분히 공부할 만한 가치가 있다고 생각합니다.

✅ 배운점

printf("%d", printf(" ", ...));

해당 구문을 실행했을때 문자열 길이를 리턴하는 함수인지 에러시 -1 리턴하는지 모르고 써왔는데, 간결하게나마 내부적으로 어떤 방식으로 돌아가는지 알 수 있었습니다.

이번 ft_printf를 통해 printf에 다양한 필드가 존재하고 필드마다 어떠한 동작을 하는지 알게 되었고, 가변인자가 무엇인지 배우게 되고, 기본적인 파싱에 대해 이해하는 파트였습니다.

https://github.com/zerowin96/my_little_printf

profile
정통과 / 정처기 & 정통기 / 42seoul 7기 Cardet / 임베디드 SW 개발자

0개의 댓글