본 프로젝트는 stdio.h의 printf()를 구현할 것을 요구한다.
구현할 함수의 프로토타입은 다음과 같다.
int ft_printf(const char *format, ...);
이때 libc의 printf처럼 버퍼 관리를 수행할 필요는 없다. c s p d i u x X %의 총 9가지 변환을 수행해야 한다.
각 변환에 대해 다음 플래그를 구현한다:
-: “left adjusted”0: “zero padding”.: “precision”#: “alternate form”' '(sp): “blank”+: “sign”FreeBSD의 매뉴얼(man 3 printf)에서는 다음과 같이 명세한다.
const char *format으로 주어지는 문자열에서 %가 아닌 문자는 그대로 출력 스트림에 복사한다. 이 과정 중 %를 만날 경우 형식 문자열을 파싱하려 시도하고, 파싱이 성공적일 시 각 변환이 요구하는 매개 변수를 소모하게 된다.
형식 문자열은 다음과 같이 구성된다.
{flags: #0- +}{minimum field width: 숫자}{precision: .과 숫자들}{conversion specifier: [cspdiuxX%]}
flags
'#': a.k.a. “alt form”
x에 대하여: 숫자 앞에 0x를 추가한다.X에 대하여: 숫자 앞에 0X를 추가한다.'0': a.k.a. “zeropad”
'-'가 적용될 경우 무시diuxX에 '.'가 적용될 경우 무시0을 대신 출력한다.'-': a.k.a “left”
' ': a.k.a “blank”
'+'가 적용될 경우 무시di에 대하여: 양수의 경우 숫자 좌측(부호가 있을 자리)에 공백 1개를 출력한다.'+': a.k.a “sign”
di에 대하여: 양수의 경우 숫자 좌측에 +를 출력한다.minimum field width
변환된 결과의 길이가 최소 너비보다 짧을 시 공백을 그 수만큼 출력한다. 기본적으로 좌측에 출력하여 우측 정렬을 실시하고 '-' 플래그가 켜졌을 시 우측에 출력한다.
precision
. 이후에 숫자를 적어서 지정한다. 숫자가 없을 시 0으로 지정된다.
diuxX에 대하여: 나타내야 하는 최소 자릿수를 뜻한다.s에 대하여: 출력할 수 있는 최대 문자 개수를 뜻한다.conversion specifier
c: int 형식으로 받은 값을 unsigned char로 변환한 결과를 출력한다.s: char * 형식으로 받은 값이 가리키는 주소에서 시작하는 문자열을 \\0을 만날 때까지 출력한다.
\\0은 출력되지 않는다.p: void * 형식으로 받은 값을 16진수로, %#x 혹은 %#lx로 변환하여 출력한다.d, i: int 형식으로 받은 값을 부호 있는 10진 정수로서 출력한다.
0이 출력된다.u: int 형식으로 받은 값을 부호 없는 10진 정수로서 출력한다.
0이 출력된다.x, X: int 형식으로 받은 값을 부호 없는 16진 정수로서 출력한다. 이때 x 변환에서는 10 이상의 자릿수에 abcdef를 사용하고 X 변환에서는 ABCDEF를 사용한다.
0이 출력된다.%: %를 출력한다.출력한 문자의 총 개수를 반환한다. 오류가 발생했을 시 음의 정수를 반환한다.
int ft_printf(const char *format, ...)
format부터 \\0을 발견할 때까지 문자를 순차적으로 탐색한다. %가 아닌 문자는 STDOUT에 그대로 출력한다. %를 마주칠 시 형식 문자열 해석기(이하 해석기)를 시작한다. 규격에 맞지 않는 문자 때문에 해석이 실패한 경우 도중의 결과를 폐기하고 문제가 되는 문자 다음부터 탐색을 재개한다. 해석이 성공했다면 가변 인자에서 그 형식이 요구하는대로 데이터를 입력받아 문자열로 변환한 후 STDOUT에 출력한다.
이때 2가지 선택지가 존재한다.
format의 모든 문자에 대하여 차례대로:
%가 아닌 문자는 만날 때마다 STDOUT에 출력한다.%를 만날 시:
STDOUT에 출력한다.format의 모든 문자에 대하여 차례대로:전자는 format의 문자를 순회하는 반복문 안에서 해석->변환->출력 과정이 모두 일어나야 하기 때문에 제어 구조가 길고 에러 핸들링이 복잡하다. 반면에 후자의 방식은 첫 반복문에서는 해석만 하고, 두번째 반복문에서는 변환 및 출력만 담당하기 때문에 첫 반복문에서는 가변 인자에 접근할 필요가 없고 파일 I/O 관련 에러가 발생하지 않는다. 본 프로젝트에서는 후자의 구조를 채택한다.
ft_printf의 구현에는 크게 2개 기능이 필요하다.
format 문자열 해석: 해석기format을 여러 부분으로 나누어 어느 부분이 형식 문자열이고 어느 부분이 그대로 출력되어야 하는지, 그 부분의 개수는 몇 개이며 각 형식 지정자별로 어떤 플래그를 고려하여 변환을 수행해야 하는지 파악이 필요하다. 다음과 같은 문자열을 보자.
"Hello, %-7.5s!"
Hello, 문자열s 변환 지정자, 왼쪽 정렬, 최소 너비 7, 정밀도 5! 문자열format을 3개의 부분으로 나누어 처리해야 한다. 이때 각 부분은 다음과 같은 정보를 가진다.
변환의 종류
이러한 정보를 기록한 변수를 티켓이라고 명명한다. 해석기는 format 문자열을 해석하여 필요한 티켓을 필요한 개수만큼 발부한다.
위 문자열은 대략 다음과 같이 해석될 것이다.
plain 변환, H부터 % 전까지, 모든 플래그 비활성화