Proven C BookEnglish GitHub

부록 B — printf·scanf 서식 완전 정리

22장에서 최소 집합(%d, %s, %%)을 익혔고, 61장에서 서식 문자열을 해부했다. 이 부록은 찾아보기 위한 자리다 — 필요한 줄만 짚어 읽으면 된다. 규칙 하나만 앞세운다: 서식과 인자의 타입이 어긋나면 계약 밖(정의되지 않은 동작)이다(47·61장).

서식 지정의 뼈대

출력과 입력의 문법은 닮았지만 뜻이 다르다. 먼저 두 뼈대를 나란히 둔다.

출력(printf 계열)입력(scanf 계열)
% [플래그] [폭] [.정밀도] [길이] 변환% [*] [최대폭] [길이] 변환
폭 = 최소 글자 수 — 모자라면 채우고, 넘쳐도 자르지 않는다폭 = 최대 읽을 글자 수 — 버퍼를 지키는 수단이다
정밀도가 있다(.)정밀도가 없다
*는 폭·정밀도를 인자로 받는다*는 읽되 저장하지 않는다(억제)

표 98.1

출력 변환 — 전체 목록

변환인자 타입출력 모양메모
%d %iint십진 부호 있는 정수출력에서는 둘이 같다
%uunsigned int십진 부호 없는 정수
%ounsigned int8진#를 붙이면 앞에 0
%x %Xunsigned int16진 소문자·대문자#를 붙이면 0x·0X
%f %Fdouble123.456000기본 정밀도 6
%e %Edouble1.234560e+02지수 표기
%g %Gdouble짧은 쪽 자동 선택뒤따르는 0을 지운다
%a %Adouble0x1.edp+616진 부동소수(C99) — 비트를 정확히 본다
%cint(문자로)문자 하나인자는 승격되어 int로 온다
%schar *NUL까지의 문자열정밀도로 최대 길이를 제한할 수 있다
%pvoid *구현 정의 표기대개 16진 주소
%nint *(출력 없음)★ 찍은 글자 수를 쓴다 — 보안상 쓰지 않는다
%%% 한 글자

표 98.2

플래그, 폭, 정밀도

자리표기
플래그-왼쪽 정렬(기본은 오른쪽)
0남는 자리를 0으로 채운다(-와 함께면 무시)
+양수에도 부호를 붙인다
빈칸양수 앞에 빈칸 하나
#대체 형식 — %#o0, %#x0x, %#f→소수점 항상
%8d최소 8칸
%*d폭을 인자로 받는다: printf("%*d", 8, n)
정밀도%.3f소수 셋째 자리까지(반올림)
%.5d정수를 최소 5자리로(앞을 0으로)
%.4s문자열을 최대 4글자만
%.*s최대 길이를 인자로: printf("%.*s", len, p)

표 98.3

%.*s는 이 책에서 자주 쓴 형태다 — 길이를 따로 지니고 다니는 문자열(58·90장의 뷰)을 NUL 종단 없이 그대로 찍을 수 있기 때문이다.

다만 한 가지를 지켜야 한다. *가 받는 폭·정밀도 인자의 타입은 int다. 길이를 size_t로 들고 다니는 코드(뷰가 대개 그렇다)에서 그대로 넘기면 가변 인자의 타입이 어긋난다 — 58장에서 본 그 불일치다. 길이를 확인한 뒤 캐스트해 넘기는 것이 정석이다.

if (v.size <= INT_MAX)
    printf("%.*s", (int)v.size, (const char *)v.ptr);

길이 수식어 — 타입의 폭을 알려 주는 자리

가변 인자에는 타입 정보가 실려 가지 않으므로(58장), 이 글자가 곧 계약 이다. 틀리면 스택을 잘못 읽는다.

수식어함께 쓰는 변환출력 인자입력 인자
(없음)d i u o x Xint / unsigned intint * / unsigned *
hhd i u o x Xint(char 폭으로 해석)signed char *
hd i u o x Xint(short 폭으로 해석)short *
ld i u o x Xlonglong *
lld i u o x Xlong longlong long *
jd i u o x Xintmax_tintmax_t *
zd i u o x Xsize_tsize_t *
td i u o x Xptrdiff_tptrdiff_t *
Lf e g along doublelong double *
lc / swint_t / wchar_t *wchar_t *

표 98.4

실수에는 특별한 주의가 필요하다. 출력에서 float은 기본 진급으로 double이 되므로 %f 하나면 되고 %lf도 같은 뜻이다. 입력에서는 진급이 없으므로 %ffloat *, %lfdouble *로 반드시 갈라야 한다(61장의 오개념 상자).

고정 폭 정수의 서식

int32_t 같은 타입은 플랫폼마다 실제 타입이 달라서, 서식을 손으로 적으면 어긋날 수 있다. 표준은 <inttypes.h>에 매크로를 두었다.

#include <inttypes.h>

uint64_t total = 1234567890123ULL;
printf("total = %" PRIu64 "\n", total);   /* 문자열 이어붙이기다 */

int32_t n = 0;
sscanf(line, "%" SCNd32, &n);

이름 규칙은 단순하다 — 출력은 PRI + 변환 + 폭(PRId32, PRIu64, PRIx16), 입력은 SCN + 변환 + 폭(SCNd32, SCNu64). 폭 자리에는 8·16·32·64 외에 MAX(PRIdMAX)와 PTR(PRIxPTR)도 온다.

입력 변환 — 전체 목록

변환읽는 것인자메모
%d십진 정수int *앞선 공백을 건너뛴다
%i정수(진법 자동)int *0x면 16진, 0이면 8진
%u부호 없는 십진unsigned *음수 부호도 받아들여 감긴다
%o %x8진·16진unsigned *
%f %e %g실수float *double에는 %lf
%c글자 그대로char *★ 공백을 건너뛰지 않는다. 폭을 주면 그만큼 읽는다
%s공백 전까지char[]★ 반드시 최대 폭을 준다(%63s)
%[...]집합에 속한 글자들char[]%[^,]는 쉼표가 아닌 글자. 공백도 읽는다
%p포인터 표기void **%p로 찍은 것만 되읽는다
%n(읽지 않음)int *지금까지 소비한 글자 수를 쓴다
%%% 글자

표 98.5

억제 문자 *를 끼우면 읽되 저장하지 않는다 — sscanf(s, "%*d %d", &n)은 첫 수를 버리고 둘째 수만 받는다. 억제된 항목은 반환값에도 세지 않는다.

집합 지정자는 작은 도구 하나에 가깝다. %[abc]는 a·b·c만, %[^,\n]은 쉼표와 개행이 아닌 글자를 모은다. 범위 표기(%[a-z])는 널리 쓰이지만 표준이 보장하지는 않는다.

입력에서만 통하는 규칙들

반환값 정리

함수성공 시실패·경계
printf fprintf찍은 글자 수음수
sprintf쓴 글자 수(NUL 제외)음수
snprintf필요했던 글자 수음수. 반환값이 버퍼 크기 이상이면 잘렸다
scanf sscanf채운 항목 수0(매칭·변환 실패) 또는 EOF
fgets버퍼 포인터널(파일 끝 또는 오류)
fputs puts음수가 아닌 값EOF

표 98.6

snprintf의 반환값 규칙이 특히 중요하다 — 쓴 글자 수가 아니라 필요했던 글자 수이므로, 잘림 검사는 need >= (int)sizeof buf로 한다(85장).

자주 하는 실수 모음

틀린 코드무슨 일이 일어나는가고침
printf("%d", sz)size_t64비트에서 폭이 어긋난다%zu
printf("%s", 42)정수를 주소로 읽는다 — 붕괴%d
printf(user)형식 문자열 취약점(61장)printf("%s", user)
scanf("%d", n)주소가 아니라 값을 넘겼다&n
scanf("%s", buf)경계를 넘어 쓴다%63s처럼 폭 지정
scanf("%f", &d)double절반만 채워진다%lf
scanf("%c", &c) 연달아남은 개행을 읽는다" %c"
반환값 무시실패가 조용히 지나간다항목 수를 확인
%n 사용메모리 쓰기 통로가 된다쓰지 않는다

표 98.7