[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 외부 라이브러리를 포함한 컴파일
단계별 과제 수행
일단 나는 이런 순서대로 수행하고 있다.
ft_printf("string%d");
를 수행한다고 가정하고, 입력받은 스트링의 문자를 하나하나 %인지 체크한다. %가 아니라면write()
해 출력한다.- 만약 %를 만났다면 % 뒤의 문자를 불러와 형식 지정자 유형에 맞는 문자를 찾는다. 이 때, 어쨌든 뒤에 형식 지정자(c, s, p, d, i, u, x)가 나올 때까지 뒤 문자를 하나하나 불러와 찾는다. 예를 들어
%aaad
인 경우 d가 나올 때까지 a는 무시하고 넘어가며 찾으면 된다. 함수를 사용하는 방법이 잘못된 거니까 그냥 d가 나올 때까지 찾고 a는write()
하지 않고 넘어가면 된다. 우선 큰 그림만 그리기 위해 c, s, d의 기본 출력 기능만 구현했다. - printf 원함수에도 있는 기능인데, printf 함수에는 리턴값이 있다. 이 리턴값 출력을 위해 비어있는
rtn
변수를 만들고, 한 개의 문자 출력 시rtn++
을 해 주는 기능을 구현한다. - 이 다음에는 지시자들의 옵션을 체크해서, 한 개 한 개 기능을 구현해 나가면 된다. 사실 여기서부터가 진짜 시작이다.
아래는 위에서 설명한 수준까지 작성된 코드다. 처음부터 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);
}
댓글을 남겨주세요