본 프로젝트는 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
의 모든 문자에 대하여 차례대로:
%
가 아닌 문자가 어디서 어디까지 있는지 기록해둔다.%
를 만날 시:
STDOUT
에 출력한다.전자는 format
의 문자를 순회하는 반복문 안에서 해석->변환->출력 과정이 모두 일어나야 하기 때문에 제어 구조가 길고 에러 핸들링이 복잡하다. 반면에 후자의 방식은 첫 반복문에서는 해석만 하고, 두번째 반복문에서는 변환 및 출력만 담당하기 때문에 첫 반복문에서는 가변 인자에 접근할 필요가 없고 파일 I/O 관련 에러가 발생하지 않는다. 본 프로젝트에서는 후자의 구조를 채택한다.
ft_printf
의 구현에는 크게 2개 기능이 필요하다.
format
문자열 해석: 해석기format
을 여러 부분으로 나누어 어느 부분이 형식 문자열이고 어느 부분이 그대로 출력되어야 하는지, 그 부분의 개수는 몇 개이며 각 형식 지정자별로 어떤 플래그를 고려하여 변환을 수행해야 하는지 파악이 필요하다. 다음과 같은 문자열을 보자.
"Hello, %-7.5s!"
Hello,
문자열s
변환 지정자, 왼쪽 정렬, 최소 너비 7, 정밀도 5!
문자열format을 3개의 부분으로 나누어 처리해야 한다. 이때 각 부분은 다음과 같은 정보를 가진다.
변환의 종류
- 변환 없음(이하
plain
),c
,s
,p
,d
,i
,u
,x
,X
,%
- 원본
format
에서 차지하는 부분'#'
“alt form” 플래그 활성화 여부'0'
“zeropad” 플래그 활성화 여부'-'
“left” 플래그 활성화 여부' '
“blank” 플래그 활성화 여부'+'
“sign” 플래그 활성화 여부- 최소 너비 “minimum field width” 활성화 여부
- 정밀도 “precision” 활성화 여부
- 최소 너비의 값
- 정밀도의 값
이러한 정보를 기록한 변수를 티켓이라고 명명한다. 해석기는 format
문자열을 해석하여 필요한 티켓을 필요한 개수만큼 발부한다.
위 문자열은 대략 다음과 같이 해석될 것이다.
plain
변환, H
부터 %
전까지, 모든 플래그 비활성화