Menu

C 언어 형식 지정자: printf와 scanf 완전 레퍼런스

C의 모든 형식 지정자를 한곳에 - %d, %s, %c, %f, %p와 나머지가 뜻하는 것, 길이 수식자 l·ll·h·z, 너비·정밀도·플래그, 그리고 printf와 scanf에서 다르게 동작하는 것들.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

형식 지정자는 printf에게 값을 텍스트로 바꾸는 법을, scanf에게 텍스트를 값으로 바꾸는 법을 알려 줍니다. 인자 타입이 함수에게 보이지 않으므로 기본 설정에서는 컴파일러가 도와줄 수 없는 유일한 부분이고, 그래서 이 페이지가 대조해 볼 표입니다.

명세의 일반적인 모양은 이렇습니다.

%[플래그][너비][.정밀도][길이]변환

필수인 것은 %와 변환 문자뿐입니다. %-8.2lf는 다섯 부분을 모두 씁니다.

변환 표

지정자printf 인자scanf 인자
%d부호 있는 10진 정수intint *
%i부호 있는 정수int (%d와 동일)int * (0x, 0-8진수도 받음)
%u부호 없는 10진수unsigned intunsigned int *
%f10진 부동소수점doublefloat *
%F%f와 같되 INF/NAN 대문자double-
%e / %E지수 표기doublefloat *
%g / %G%f / %e 중 짧은 쪽doublefloat *
%a / %A16진 부동소수점doublefloat *
%c문자 하나int (승격된 char)char *
%s문자열char * ('\0'으로 끝남)char * (버퍼)
%p포인터 주소void *void **
%x / %X부호 없는 16진수unsigned intunsigned int *
%o부호 없는 8진수unsigned intunsigned int *
%n지금까지 쓴 문자 수를 저장int *int *
%%리터럴 %없음리터럴 %와 매칭
%[...]스캔셋 (scanf 전용)-char *

두 항목은 경고가 필요합니다. %n 은 출력이 아니라 메모리에 쓰며, 형식 문자열 취약점의 메커니즘입니다. 사용자 텍스트가 형식 문자열에 닿게 하지 마세요. 그리고 %pvoid *를 요구합니다. int *를 그대로 넘기는 것은 미정의이며, 실제 컴파일러들은 어쨌든 주소를 출력하긴 합니다.

길이 수식자

길이 수식자는 정밀도와 변환 문자 사이에 놓이며, 인자가 실제로 얼마나 큰지를 말해 줍니다.

부호 있는 변환(d i)과 부호 없는 변환(u x o)은 서로 다른 타입을 받습니다. %lu가 원하는 것은 long이 아니라 unsigned long이며, 이를 틀리는 것은 사소한 표기 실수가 아니라 미정의 동작입니다.

수식자d i와 함께 (printf / scanf)u x o와 함께 (printf / scanf)
hhint (signed char에서) / signed char *unsigned int (unsigned char에서) / unsigned char *
hint (short에서) / short *unsigned int (unsigned short에서) / unsigned short *
(없음)int / int *unsigned int / unsigned int *
llong / long *unsigned long / unsigned long *
lllong long / long long *unsigned long long / unsigned long long *
zsize_t의 부호 있는 짝 / 그것에 대한 포인터size_t / size_t *
jintmax_t / intmax_t *uintmax_t / uintmax_t *
tptrdiff_t / ptrdiff_t *ptrdiff_t의 부호 없는 짝

그리고 부동소수점과 문자 계열은 이렇습니다.

수식자함께 쓰는 변환printf 타입scanf 타입
(없음)f e g adoublefloat *
lf e g adouble (받아들이지만 효과 없음)double *
Lf e g along doublelong double *
lc swint_t / wchar_t *wchar_t *

실제로 타이핑하게 되는 조합들입니다.

%zu는 외워 둘 것입니다. sizeof, strlen, 그리고 모든 표준 라이브러리의 크기는 size_t를 반환하는데, 64비트 시스템에서 이는 8바이트인 반면 int는 4바이트입니다. printf("%d", strlen(s))는 일부 플랫폼에서 동작하는 것처럼 보이는 진짜 버그입니다. %zu를 쓰세요.

hhh 수식자는 주로 대상 크기가 중요한 scanf를 위해 존재합니다. printf에서는 short가 호출 전에 int로 승격되므로 %d로도 어차피 올바르게 출력됩니다. %hd는 의도를 문서화할 뿐입니다.

고정 너비 타입

int32_t 같은 <stdint.h> 타입에 대한 이식성 있는 지정자는 <inttypes.h>의 매크로입니다.

PRId32는 여러분의 플랫폼에 맞는 문자로 확장되고, 인접한 문자열 리터럴은 이어 붙으므로 형식이 세 조각으로 나뉘어 있습니다. 보기 흉하긴 합니다. 대안 - long long으로 캐스트하고 %lld를 쓰는 것 - 이 흔히 더 읽기 좋고 언제나 옳습니다.

플래그

플래그는 % 바로 뒤에 오며 표현 방식을 바꿉니다. printf에만 적용됩니다.

플래그효과출력
-값 앞이 아니라 뒤를 채움42에 %-6d|42 |
+부호 있는 값에 항상 부호를 출력42에 %+d+42
공백+가 올 자리에 공백을 출력42에 % d 42
0공백 대신 0으로 채움42에 %06d000042
#대체 형식: %x0x, %o0, %g에서 소수점 유지255에 %#x0xff

플래그는 조합할 수 있습니다. %-+8.2f는 최소 너비 8, 소수 두 자리, 항상 부호, 숫자 뒤를 채우기입니다. 둘 다 쓰면 -0을 이깁니다.

너비와 정밀도

너비는 최소 필드 크기입니다. 그만큼 되도록 채워지며, 맞추려고 잘리는 일은 없습니다. 정밀도(점 뒤)는 계열마다 다른 뜻을 가집니다.

변환정밀도의 뜻
%f %e소수점 아래 자릿수 (기본 6)
%g전체 유효 자릿수 (기본 6)
%s출력할 최대 문자 수
%d %i %u %x %o최소 자릿수, 0으로 채움

두 숫자 어느 쪽이든 자리에 *를 쓰면 값 자체보다 먼저 읽는 int 인자에서 가져옵니다. 열 너비를 런타임에 계산하는 표를 만드는 방법이 바로 이것입니다.

scanf에서 너비는 다르고 훨씬 더 중요한 일을 합니다. 변환이 소비할 입력량을 제한하며, 그것이 %s와 버퍼 오버플로 사이에 서 있는 유일한 것입니다.

printf와 scanf가 갈라지는 곳

둘은 닮았지만 네 군데에서 어긋납니다. 각각이 살아 있는 버그의 원천입니다.

상황printfscanf
double%f (float은 double로 승격)%lf - %ffloat *를 뜻함
너비최소 필드 크기, 채움소비할 최대 입력, 잘라냄
인자포인터 - &x 또는 배열 이름
형식 속의 공백공백을 출력연속된 공백 문자를 건너뜀

2.5 hello를 넣어 보세요. 같은 double을 읽는 형식과 출력하는 형식이 서로 다른 문자를 쓰고, %15s15는 입력을 종료 문자를 뺀 15문자로 제한합니다 - 정확히 word의 크기에 맞습니다.

scanf 스캔셋

%[...]는 공백으로 구분된 단어가 아니라 문자 집합을 읽습니다. scanf로 한 줄 전체를 읽는 방법이지만, fgets가 여전히 더 나은 도구입니다.

[^\n]은 "줄바꿈을 제외한 모든 문자"라는 뜻입니다. %[0-9]는 숫자만, %[abc]는 그 세 글자만 읽습니다. %s와 달리 스캔셋은 앞쪽 공백을 건너뛰지 않으므로, 버퍼에 남은 줄바꿈 하나 때문에 아무것도 매칭되지 않을 수 있습니다.

틀리면 미정의 동작입니다

printf는 여러분 인자의 진짜 타입을 볼 수 없습니다. 지정자가 암시하는 만큼의 바이트를, 지정자가 말하는 대로 해석해 읽습니다. 그러니 불일치는 서식이 어긋나는 문제가 아니라 메모리를 엉뚱한 것으로 읽는 문제입니다.

printf("%d\n", 3.5);        /* 쓰레기 출력: double의 비트를 int로 읽는다 */
printf("%s\n", 42);         /* 42를 주소로 취급 - 크래시 */
printf("%d\n", strlen(s));  /* size_t를 int로 읽는다 - 64비트에서 틀림 */
printf("%f\n", 3);          /* int를 double로 읽는다 - 쓰레기 */

방어 비용은 플래그 하나입니다. -Wall로 컴파일하면 GCC가 리터럴 형식 문자열마다 인자와 대조해 줍니다.

gcc -Wall -Wextra program.c -o program

위 네 줄을 모두 컴파일 타임에 보고합니다. C에서 가장 가치 있는 단일 경고이며, 위 에디터에서는 기본으로 켜져 있습니다.

이들을 실제로 쓰는 쪽 - 정렬된 표, 실수를 읽기 좋게 출력하기, 고전적인 줄바꿈 버그 없이 입력 읽기 - 은 printf와 scanf를 보세요.

자주 묻는 질문

C에서 %d는 무슨 뜻인가요?

%d는 부호 있는 10진 정수 - 즉 int - 를 위한 변환 지정자입니다. printf에서는 인자를 10진수 숫자로 출력하고, scanf에서는 10진수 숫자를 읽어 int *에 넣습니다. printf에서는 %i도 같은 뜻이지만, scanf에서 %i0x 16진수와 앞자리 0의 8진수도 받아들입니다.

C에서 %f와 %lf의 차이는 무엇인가요?

printf에서는 실질적인 차이가 없습니다. float 인자는 double로 승격되므로 %f가 둘 다 출력하고, C99는 %lf를 동의어로 받아들입니다. scanf에서는 차이가 치명적입니다. %ffloat *(4바이트)에, %lfdouble *(8바이트)에 씁니다. double에 %f를 쓰면 메모리가 망가집니다.

C에서 size_t나 long long은 어떻게 출력하나요?

size_t(sizeofstrlen이 반환하는 것)에는 %zu, long long / unsigned long long에는 %lld / %llu를 씁니다. 이들에 %d를 쓰는 것은 미정의 동작입니다. 32비트 시스템에서는 우연히 동작하고 64비트에서는 오작동하는데, 그래서 이 버그가 그토록 오래 살아남습니다.

C에서 잘못된 형식 지정자를 쓰면 어떻게 되나요?

반올림 오차가 아니라 미정의 동작입니다. printf는 지정자가 주장하는 대로 인자를 읽으므로, printf("%d", 3.5)는 쓰레기 값을 출력하고 printf("%s", 42)는 숫자 42를 주소로 따라가 보통 크래시가 납니다. GCC가 형식 문자열과 인자를 대조하게 만드는 -Wall로 컴파일하세요.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기