[42 Seoul] ft_printf 과제를 수행한 기록

업데이트:

ft_printf 과제를 하면서 코드를 짜는 과정을 정말 세세하게 하나하나 정리하고자 한다. 코드 짜는 것 자체가 너무 오랜만이라, 세분화해서 단계별로 아주 디테일하게 작성해보려고 한다. 이 과제를 수행하며 어떻게 해야할 지 감이 안 오는 사람들에게도 많은 도움이 되었으면 좋겠다.

ft_printf 과제가 요구하는 것들

간단하다. printf 함수를 똑같이 구현하면 된다.

  • 함수의 프로토타입은 int ft_printf(const char *, ...); 이다.
  • 다만 함수의 모든 부분을 구현하기에는 다소 빡센 부분들이 있으므로 100% 똑같지는 않다. 단, 보너스 과제를 수행하려면 %f 형식 지정자라던지 몇 가지를 더 해야 하는데 여기서는 생략한다.
  • 고로 아래의 형식 지정자에 대해서만 구현하면 된다.

    • c : 문자, character
    • s : 문자열, string
    • p : 메모리 주소값, 아마도 pointer?
    • d : 10진수 4바이트 정수
    • i : 10진수 4바이트 정수, d랑 완전히 같다. 아마도 integer인듯
    • u : 부호가 없는 10진수 정수, 아마도 unsigned인듯
    • x : 부호가 없는 16진수 정수, 알파벳은 소문자로 표현
  • 그리고 아래의 플래그만 구현하면 된다.

    • - : 왼쪽 정렬
    • 0 : 출력하는 폭의 남는 공간을 0으로 채움
    • . : 정밀도 - 지정한 숫자만큼 소수점 아래 자리 출력
    • * : 출력할 너비를 인자로 받는다. 예를 들어 ft_printf("%*d", 5) 를 입력하게 되면 %5d 가 되는 식이다.
  • 보너스 과제 수행을 원한다면, 형식 지정자 n f g e 에 대해서 구현하고, 플래그 l ll h hh 도 구현하면 된다.

사전에 알아둬야 할 지식 1 : 가변 인자

가변 인자에 대해서 알아둬야 하며, 아래 내용을 익혀야 한다.

  • 가만보면 printf함수는 인자를 1개만 넣어도 작동하고, 2개를 넣어도 작동하고, 3개 혹은 4개 그 이상을 넣어도 잘만 작동한다. 아니 C언어에 이런 기능이…? 아무튼 이런걸 가능하게 하는 인자를 가변 인자라고 한다.
  • 가변인자 함수를 정의할 때는 인자가 몇 개가 될 지 모르니까 ‘…’ 으로 표시한다. 예를 들면 int ft_printf(const char *, ...)와 같이 맨 마지막에 …을 넣는다. (처음에 과제에서 제시한 함수 프로토타입을 보고 그냥 해당 부분을 생략한 건 줄 알았는데, 가변 인자 표시일 줄이야..)
  • stdarg.h 헤더 파일을 추가한 후에 사용할 수 있다.
  • va_list : 가변 인자들을 저장하는 ‘가변인자 변수’을 정의하는 일종의 타입이라고 한다. 사실 완벽하게 이해하지 못해서 뭐라 설명은 못하겠지만, 아무튼 각 가변 인자의 시작 주소를 가리키는 포인터라고 보면 된다. 처음 선언(?) 하면 첫 번째 가변 인자 주소를 해당 변수에 저장한다.
    • 복잡하게 생각할 것 없이va_list ap; 로 사용한다. 이후에 아래 설명한 va_arg() 의 인자로 ap를 계속 사용하면 되고…
  • va_start : va_list로 만들어진 ap 포인터에게 가변인자 중 첫 번째 인자의 주소를 가르쳐주는 중요한 매크로라고 하는데 쓰지 않아도 정상 작동을 해서 다소 의아한 상황. 이유를 가르쳐주실 분을 찾습니다…
  • va_arg() : ap 포인터가 위치한 곳의 데이터(즉, 가변 인자)를 읽어 반환한다. 이 때, 자동으로 다음 가변 인자를 가리키는 곳으로 포인터가 이동하므로 필요에 따라 단 한 번만 호출해야 한다. va_arg(ap, *int) 식으로 사용하며 가변인자의 값을 반환하고 자동으로 ap는 다음 가변인자의 시작점으로 이동하게 된다.
  • va_end : 사용한 가변인자 변수 ap를 끝낼 때 사용한다. 단순히 ap를 널포인터로 돌려주는 매크로인데, 어떤 일이 생길 지 모르므로 잊지 않고 마무리할 때 넣어줘야 한다고 한다.

사전에 알아둬야 할 지식 2 : GCC 외부 라이브러리를 포함한 컴파일

단계별 과제 수행

일단 나는 이런 순서대로 수행하고 있다.

  1. ft_printf("string%d"); 를 수행한다고 가정하고, 입력받은 스트링의 문자를 하나하나 %인지 체크한다. %가 아니라면 write() 해 출력한다.
  2. 만약 %를 만났다면 % 뒤의 문자를 불러와 형식 지정자 유형에 맞는 문자를 찾는다. 이 때, 어쨌든 뒤에 형식 지정자(c, s, p, d, i, u, x)가 나올 때까지 뒤 문자를 하나하나 불러와 찾는다. 예를 들어 %aaad인 경우 d가 나올 때까지 a는 무시하고 넘어가며 찾으면 된다. 함수를 사용하는 방법이 잘못된 거니까 그냥 d가 나올 때까지 찾고 a는 write() 하지 않고 넘어가면 된다. 우선 큰 그림만 그리기 위해 c, s, d의 기본 출력 기능만 구현했다.
  3. printf 원함수에도 있는 기능인데, printf 함수에는 리턴값이 있다. 이 리턴값 출력을 위해 비어있는 rtn 변수를 만들고, 한 개의 문자 출력 시 rtn++ 을 해 주는 기능을 구현한다.
  4. 이 다음에는 지시자들의 옵션을 체크해서, 한 개 한 개 기능을 구현해 나가면 된다. 사실 여기서부터가 진짜 시작이다.

아래는 위에서 설명한 수준까지 작성된 코드다. 처음부터 printf가 완전히 완성된 코드를 보면 이해하기가 정말 힘들 것이므로(내가 그렇듯이) 중간 단계의 코드를 남겨둔다.

#include "ft_printf.h"

void	spec(const char **a, va_list ap)
{
	int tmp; //출력할 문자를 잠시 복사해 둘 비어있는 변수, char도 결국엔 int이므로 int형으로 선언해도 된다.
	char *tmp2; //복사할 문자열을 위한 빈 포인터 변수
	
	if ( !(tmp2 = (char*)malloc(sizeof(char) + 1)))
		return ;
	while (**a)
	{
		if ((char) **a == 'c')
		{
			tmp = va_arg(ap, int);
			write(1, &tmp, 1);
			(*a)++;
			break ;
		}
		if ((char) **a == 'd')
		{
			tmp2 = ft_itoa(va_arg(ap, int)); //int형의 여러 자리 수를 문자열로 변환하여 출력할 수 있게끔 복사해 둔다.
			while (*tmp2)
			{
				write(1, &*tmp2, 1);
				tmp2++;
			}
			(*a)++;
			break ;
		}
		if ((char) **a == 's')
		{
			tmp2 = va_arg(ap, char*);
			while (*tmp2)
			{
				write(1, &*tmp2, 1);
				tmp2++;
			}
			(*a)++;
			break ;
		}
		(*a)++;
	}
}

int		ft_printf(const char *input, ...)
{
	va_list ap;

	va_start(ap, input); 
	while (*input)
	{
		if (*input == '%')
		{
			spec(&input, ap);
			continue ;
		}
		else
			write(1, &*input, 1);
		input++;
	}
	return (0);
}

댓글을 남겨주세요