이 글은 단축링크 https://eatch.dev/s/printf로도 들어올 수 있습니다.
혹시 C를 배우신 적이 있나요? 그렇다면 C를 배우면서 처음으로 작성했던 프로그램을 기억하시나요? 아마 거의 모든 분들이 헬로월드를 기억할 것 같습니다.
#include <stdio.h>
int main(void) {
printf("Hello, world!\n");
return 0;
}
main
함수에 void
가 없었거나 void main
이었을 수도(하지 마세요), H
가 소문자거나 ,
이나 !
나 \n
이 빠졌을 수도, return 0;
이 없었을 수도 있겠지만 C를 처음 배울 때는 항상 이 코드를 작성합니다. 그리고 이 헬로 월드 프로그램에는 항상 printf
가 들어가 있었습니다.
어느 날은 C로 코딩을 하다가 문득 '내가 printf
를 잘 알고 있다고 확신할 수 있나?' 하는 생각이 들었습니다. printf
와 scanf
는 항상 쓰기 때문에 익숙하지만, 오히려 익숙하기 때문에 더 자세히 알아보려고 하지 않는 것 같다는 느낌이 듭니다. 1년 반 전에 비슷한 고민을 하다가 C 타입 시스템을 통째로 다룬 글을 써버렸으니, 이번에도 글 하나를 작성하지 않으면 찜찜할 것 같습니다. 사실은 2021년 10월에 이미 쓰기 시작했다가 귀찮아서 지금까지 미루고 처음부터 다시 쓰고 있습니다.
C 타입 시스템 글과 달리 이번 글은 우선 커다란 치트시트 한 장을 올리고 한 부분씩 뜯어가면서 무엇을 의미하는지 자세히 설명하는 구조로 작성했습니다. 당연히 저장하거나(우클릭을 하거나 길게 눌러주세요) 인쇄하거나 공유해도 되지만, 출처는 지우지 말고 남겨주세요. HTML 버전도 있습니다.
- 업데이트할 것이나 빠진 것이 생기면 제가 그때그때 찾아와서 업데이트할 예정입니다. 글의 맨 위와 치트시트 오른쪽 위에 수정한 날짜가 있으니 자주 찾아와서 바뀐 것이 있는지 확인해 보세요.
- C가 어느 정도 익숙해졌을 때 읽어보는 것을 권장드립니다.
- 대문자 변환 지정자
X
,F
,E
,A
,G
는printf
에서 출력이 소문자 대신 대문자로 되는 것을 제외하면 소문자 변환 지정자와 완전히 같습니다. 이 글에서는 따로 다루지 않겠습니다.
피드백은 글 맨 아래에 있는 댓글창으로 부탁드립니다.
기본 문법
printf
와 scanf
모두 첫 인자로 받는 문자열에 %
로 시작하는 서식 지정자를 넣을 수 있습니다. 자세한 문법은 printf
와 scanf
가 조금 다릅니다.
printf
printf
의 서식 지정자 문법은 %[플래그][최소 폭][.정밀도][길이](변환 지정자)
입니다. (...)
는 필수, [...]
는 선택입니다.
각각에 대한 더 자세한 설명은 별도의 문단에서 각각 하겠습니다.
- 플래그: 다섯 종류가 있고, 하나씩 추가할 때마다 출력의 모양이 조금씩 달라집니다.
-
: 출력이 짧을 때 왼쪽 정렬 방식으로 길이를 맞춥니다.0
: 출력이 짧을 때 0을 채우는 방식으로 길이를 맞춥니다(d
,i
,u
,o
,x
,f
,e
,a
,g
).- 정수 서식(
d
/i
/u
/o
/x
)이고 정밀도 표시가 있거나,-
플래그가 있으면 무시합니다.
- 정수 서식(
+
: 음이 아닌 수를 양의 부호로 표시합니다(d
,i
,f
,e
,a
,g
).- (공백 문자): 음이 아닌 수를 공백 문자로 표시합니다(
d
,i
,f
,e
,a
,g
).+
플래그가 있으면 무시합니다.
#
: C 코드와 호환되는 서식으로 출력합니다(o
,x
,f
,e
,a
,g
).
- 최소 폭: 다음 중 하나를 사용할 수 있습니다.
- 0으로 시작하지 않는 양의 정수
*
문자- 별도 인자로 입력받습니다. 이 값이 음수일 경우 플래그
-
를 적용하고 절댓값을 최소 폭으로 취합니다.
- 별도 인자로 입력받습니다. 이 값이 음수일 경우 플래그
- 정밀도: 다음 중 하나를 사용할 수 있습니다.
.
다음에 양의 정수가 따라붙는 문자열- 문자열
.*
- 별도 인자로 입력받습니다. 이 값이 음수일 경우 무시합니다.
.
- 0으로 취급합니다.
- 길이: 변환 지정자의 바로 앞에 붙어 인자로 받는 타입을 더 큰/작은 것으로 바꿉니다.
scanf
scanf
의 서식 지정자 문법은 %[*][최대 폭][길이](변환 지정자)
입니다. (...)
는 필수, [...]
는 선택입니다.
*
: 있을 경우 일치한 문자열을 버리고, 인자도 받지 않습니다(%
/n
을 제외한 모든 지정자).- 최대 폭: 양의 정수만 사용할 수 있습니다.
- 길이: 변환 지정자의 바로 앞에 붙어 인자로 받는 타입을 더 큰/작은 것으로 바꿉니다.
세부 사항
printf
부호 표시
부호 있는 정수/실수 서식(d
/i
/f
/e
/a
/g
)에서 출력하는 값이 음수일 경우에는 항상 맨 앞에 -
를 붙이지만, 0이거나 양수일 때는 다음과 같은 방식으로 처리할 수 있습니다.
- 생략 (기본): 부호를 붙이지 않습니다.
- 양의 부호 (플래그
+
):+
부호를 붙입니다. - 공백 (플래그 (공백 문자)): 공백 문자를 붙입니다.
지수 부분의 부호는 항상 출력됩니다.
C 코드와의 호환성
보통 이 부분은 "대체"(alternative) 서식이라고만 설명하지만, 이 방식으로 설명하는 것이 이해하기 더 쉬울 것 같아서 이렇게 작성합니다. 참고만 해 주세요.
특정한 변환 지정자로 출력한 문자열은 C 코드에 그대로 붙여넣으면 잘못된 타입이나 값으로 인식될 수 있습니다.
int oct_123 = 0123; // = 83
printf("%o\n", oct_123); // 123
123; // = 123
double double_123_point = 123.; // = 123 (double)
printf("%.0f\n", double_123_point); // 123
123; // = 123 (int)
이때 #
플래그를 추가하면 C 코드에서 올바르게 인식될 수 있도록 진법 접두사와 소숫점이 보존됩니다.
int oct_123 = 0123; // = 83
printf("%#o\n", oct_123); // 0123
0123; // = 83
double double_123_point = 123.; // = 123 (double)
printf("%#.0f\n", double_123_point); // 123.
123.; // = 123 (double)
더 정확히는 다음과 같은 효과를 보입니다.
- 변환 지정자
o
는 출력이0
으로 시작하도록 필요한 만큼 정밀도를 강제로 늘립니다. - 변환 지정자
x
는 값이 0이 아닐 경우 출력의 맨 앞에0x
를 붙입니다. - 변환 지정자
f
/e
/a
/g
는 소숫점 아래에 숫자가 없더라도 소숫점을 항상 출력합니다.- 특히 변환 지정자
g
는 소숫점 아래의 의미 없는0
이 생략되지 않습니다1.
- 특히 변환 지정자
최소 폭
서식 지정자에 최소 폭 표시가 있고 실제 변환된 문자열의 길이가 이것보다 짧다면 특정한 방법을 사용해서 출력하는 길이를 강제로 늘립니다. 말 그대로 최소 폭이기 때문에 변환된 문자열이 최소 폭보다 길다고 해서 잘린 채로 출력되지는 않습니다.
- 오른쪽 정렬 방식 (기본): 문자열의 왼쪽에 공백 문자를 추가합니다.
- 왼쪽 정렬 방식 (플래그
-
): 문자열의 오른쪽에 공백 문자를 추가합니다. - 0 채움 방식 (플래그
0
): 부호나 진법 접두사(있을 경우)와 첫 자리 사이에0
을 추가합니다.
정밀도
특정한 변환 지정자는 정밀도를 추가로 입력받습니다. 정확한 의미와 생략했을 때의 기본값은 각각 다릅니다.
s
: 출력할 최대 길이를 나타냅니다. 기본값은 무한대입니다.d
/i
/u
/o
/x
: 출력할 최소 자릿수를 나타냅니다. 최소 폭과 별개이며, 실제 문자열의 길이가 정밀도보다 짧으면 왼쪽에0
을 추가합니다. 기본값은 1입니다.f
/e
: 소숫점 아래 자릿수를 나타냅니다. 기본값은 6입니다.a
: 소숫점 아래 자릿수를 나타냅니다. 기본값은 전달받은 값을 정확히 나타내는 데 충분하도록 알아서 설정됩니다.g
: 유효 숫자의 개수를 나타냅니다. 기본값은 6, 최솟값은 1입니다.
scanf
매치 실패
대부분의 서식 지정자와 일반 문자는 특정한 모양의 입력만 처리할 수 있으며, 그 형태와 맞지 않는 입력이 들어오면(매치 실패) 입력을 더 이상 읽지 않고 바로 반환합니다.
서식 지정자가 아닌 일반 문자는 1글자의 자기 자신과 같은 문자를 매치합니다. 단, 공백 문자일 경우 0개 이상의 공백 문자를 버리고 항상 매치에 성공합니다. 서식 지정자가 무엇을 매치하는지는 아래의 변환 지정자 문단에 따로 정리해 두었습니다.
최대 폭
서식 지정자에 최대 폭 표시가 있을 경우, 그 서식 지정자는 최대 그만큼의 문자만 읽을 수 있습니다.
%
와 n
을 제외하고 모든 변환 지정자에 적용할 수 있으며, 기본값은 무한대입니다. 단, 변환 지정자 c
는 예외적으로 기본값이 1입니다.
공백 입력
대부분의 변환 지정자는 실제 읽을 문자 앞에 공백 문자가 있을 경우 공백 문자를 모두 버리며, 이 공백 문자는 최대 폭에 포함되지 않습니다.
예외적으로 변환 지정자 c
/[...]
/[^...]
/n
은 공백 문자를 버리지 않습니다. 공백/줄바꿈으로 구분된 문자들을 입력받으려고 하는데 이것 때문에 오류가 생긴다면 scanf("%c", ...)
를 scanf(" %c", ...)
으로 바꾸어 해결할 수 있습니다.
공백 문자 판정은 isspace
와 같은 방법으로 이루어집니다. 이는 서식 문자열의 공백 문자에도 똑같이 적용됩니다.
16진 지수 표기
잘 알려져 있지는 않지만 C에서는 부동소숫점을 10진 지수로 표기하는 문법(1.23e+4
) 이외에 16진 지수로 표기하는 문법(0x1.abp+4
)도 있습니다. 이 문법의 구조는 다음과 같습니다.
- 부호
+
/-
(선택) - 접두사
0x
- 16진 숫자로 이루어진 가수(mantissa) 부분
xxx
xxx.xxx
xxx.
.xxx
- 구분자
p
(필수) - 부호
+
/-
(선택) - 10진 숫자로 이루어진 지표(exponent)
16진 지수 리터럴을 읽을 때는 가수 부분을 일반적으로 16진 소수를 읽듯이 해석하고, 지표 \(p\)에 대해 \(2^p\)를 곱합니다. 예를 들어 위에 예시로 든 0x1.abp+4
는 1.ab(10진수로 1.66796875)에 16을 곱해 26.6875가 됩니다.
printf("%a\n", 26.6875); // 0x1.abp+4
// C 코드에도 그대로 쓸 수 있습니다. 하이라이팅이 깨지는 건 무시해주세요.
double hex_float = 0x1.abp+4; // = 26.6875
변환 지정자
일반적으로 printf
와 scanf
사이에는 변환 지정자의 의미가 비슷합니다. 두 함수 사이에서 의미가 어떻게 다른지는 위의 치트시트에서 확인해 주세요.
%
:%
문자c
: 문자s
: 문자열d
/i
: 부호 있는 정수u
/o
/x
: 부호 없는 정수f
/e
/a
/g
: 부동소숫점p
: 포인터n
: 처리한 문자 개수를 기록함
printf
%
:%
문자를 출력합니다.c
: 문자 하나를 출력합니다.s
: 문자열을 출력합니다.- 최대 정밀도만큼의 문자를 출력하고 멈춥니다.
- 정밀도를 다 채우지 않았거나 생략했을 경우에도 널 문자가 있으면 그 직전에서 멈춥니다.
d
/i
: 부호 있는 10진 정수를 출력합니다.- 최소한 정밀도만큼의 숫자를 출력합니다. 정밀도가 0일 경우
0
은 빈 문자열로 출력됩니다.u
/o
/x
에 똑같이 적용됩니다. - 부호 표시 규칙에 따라 부호를 출력합니다.
- 최소한 정밀도만큼의 숫자를 출력합니다. 정밀도가 0일 경우
u
: 부호 없는 10진 정수를 출력합니다.o
: 부호 없는 8진 정수를 출력합니다.f
: 부동소숫점 실수를 출력합니다.- 정수 부분에는 필요한 만큼 숫자를 출력합니다.
- 소수 부분에는 정확히 정밀도만큼의 숫자를 출력합니다.
e
/a
에 똑같이 적용됩니다. - 소숫점은 플래그
#
이 없고 소숫점 아래 자릿수가 0인 경우 생략합니다.e
/a
/g
에 똑같이 적용됩니다.
e
: 부동소숫점 실수를 10진 지수 표기로 출력합니다.- 정수 부분에는 정확히 1자리를 출력합니다.
- 지수 부분에는 2자리 이상의 범위에서 필요한 만큼 부호와 숫자를 출력합니다.
- 값이 0일 경우 지수 자리는
+00
으로 출력합니다.
a
: 부동소숫점 실수를 16진 지수 표기로 출력합니다.- 정수 부분에는 정확히 1자리를 출력합니다. 이 숫자는 값이 normalized value 범위 안에 있을 경우 0이 아님이 보장됩니다.
- 지수 부분에는 필요한 만큼 숫자를 출력합니다.
- 값이 0일 경우 지수 자리는
+0
으로 출력합니다.
g
: 부동소숫점 실수를 일반 표기와 10진 지수 표기 중 알아서 선택해 출력합니다.- 정밀도 \(p\)에 대해 값의 절댓값이 0이거나 \([10^{-4}, 10^p)\) 구간 안에 있으면 일반 표기로(
f
), 그렇지 않은 경우 10진 지수 표기로(e
) 출력합니다. - 정수 부분과 소수 부분을 통틀어 정밀도는 최대 유효 숫자(의미 없는 0을 제외한 자리)의 개수를 의미합니다.
- 소숫점 아래의 의미 없는 0은 플래그
#
이 없을 경우 생략합니다.
- 정밀도 \(p\)에 대해 값의 절댓값이 0이거나 \([10^{-4}, 10^p)\) 구간 안에 있으면 일반 표기로(
p
: 포인터를 출력합니다.- 정확한 서식은 컴파일러마다 다릅니다.
n
: 지금까지 출력한 문자의 개수를 포인터 인자가 가리키는 주소에 씁니다.snprintf
의 버퍼 크기를 무시합니다.
scanf
작성의 편의를 위해 JavaScript 정규 표현식을 추가로 작성합니다. 정규식 플래그 /isy
를 가정합니다.
%
:%
문자를 입력받습니다./%/
- 서식 지정자와 다른 일반 문자와 달리
%
앞의 공백 문자를 버립니다. 저는 이게 문제가 된다면%*1[%]
로 해결할 것 같습니다.
c
: 하나 이상의 문자를 입력받습니다.- 최대 폭 w에 대해
/.{w}/
- 공백 문자도 그대로 입력받습니다.
- 최대 폭을 생략했을 경우 문자 하나를 입력받습니다.
- 최대 폭이 있을 경우 인자 포인터를 배열로 보고 차례대로 문자를 입력받습니다. 맨 끝에 널 문자를 추가하지 않습니다.
- 최대 폭의 기본값이 1이고 항상 최대 폭만큼의 문자를 입력받는 것으로 생각할 수도 있습니다.
- 최대 폭 w에 대해
s
: 문자열을 입력받습니다./[^\t\n\v\f\r ]+/
- 문자열 앞의 공백 문자를 버립니다.
- 공백 문자 직전까지만 입력받고 멈춥니다.
- 맨 끝에 널 문자를 추가합니다.
[...]
/[^...]
에 똑같이 적용됩니다.
[...]
: 주어진 집합에 속한 문자로만 이루어진 문자열을 입력받습니다./[...]+/
printf
에는 대응하는 변환 지정자가 없습니다. 이는[^...]
도 같습니다.- 집합에 속하지 않는 문자 직전까지만 입력받고 멈춥니다.
- 한 글자도 일치하지 않을 경우 매치에 실패하고
scanf
호출을 종료합니다.[^...]
에 똑같이 적용됩니다. - 집합이
]
로 시작할 경우 그]
도 집합에 포함됩니다. - 일부 컴파일러에서는 문자 범위 문법
-
를 추가로 지원합니다(%[a-z]
).[^...]
에 똑같이 적용됩니다.
[^...]
: 주어진 집합에 속하지 않는 문자로만 이루어진 문자열을 입력받습니다./[^...]+/
- 원래
[...]
과 같은 변환 지정자로 분류하지만 글 작성의 편의상 분리했습니다. - 집합에 속하는 문자 직전까지만 입력받고 멈춥니다.
- 집합이
^]
로 시작할 경우 그]
도 집합에 포함됩니다. %s
는%[^\t\n\v\f\r ]
과 같은 것으로 생각할 수 있습니다.
d
: 부호 있는 10진 정수를 입력받습니다./[+-]?\d+/
i
: 10진, 8진 혹은 16진 정수를 알아서 입력받습니다./[+-]?(0x[0-9a-f]+|[1-9]\d*|0[0-7]*)/
0x
로 시작할 경우 16진수,0
으로 시작할 경우 8진수, 이외의 경우에는 10진수로 취급합니다.
u
: 부호 없는 10진 정수를 입력받습니다./[+-]?\d+/
- 음수도 입력받을 수 있습니다. 이 경우에는 부호 없는 타입 \(T\)의 규칙에 의해 \(\mathrm{mod} \; 2^{\mathrm{sizeof}(T)}\) 연산을 취합니다.
o
/x
에 똑같이 적용됩니다.
o
: 부호 없는 8진 정수를 입력받습니다./[+-]?[0-7]+/
x
: 부호 없는 16진 정수를 입력받습니다./[+-]?(0x)?[\da-f]+/
- 접두사
0x
도 입력받을 수 있으며, 여기서는 특별한 의미를 가지지 않습니다.
f
/e
/a
/g
: 부동소숫점 실수를 입력받습니다.0x
로 시작하면 16진 지수 표기로 취급합니다./[+-]?0x([\da-f]+(\.[\da-f]*)?|\.[\da-f]+)(p[+-]?\d+)?/
- 문법과 해석은 위의 16진 지수 표기 문단과 같지만, 구분자
p
이후 부분은 생략할 수 있습니다.
- 그렇지 않으면 10진/10진 지수 표기로 취급합니다.
/[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?/
- 이외에 다음 값을 입력받을 수 있습니다.
- 양의/음의 무한대 (
/[+-]?inf(inity)?/
) - NaN (
/[+-]?nan/
)- cppreference에는
nan(문자열)
형태도 가능하다는 설명이 있지만 제가 재현을 못 해서 생략합니다.
- cppreference에는
- 양의/음의 무한대 (
p
: 포인터를 입력받습니다.- 정확한 서식은 컴파일러마다 다릅니다.
printf
의%p
에서 출력하는 문자열은scanf
의%p
에서 항상 매치됩니다.
n
: 지금까지 입력받은 문자의 개수를 포인터 인자가 가리키는 주소에 씁니다.- 공백 문자를 포함해 아무 입력도 받지 않습니다.
반환값
printf
printf
는 성공했을 때 이번 호출로 출력한 문자 수를 반환합니다. %n
과 같이 snprintf
의 버퍼 크기를 무시합니다.
출력 오류나 _s
오류가 발생했을 때는 음수를 반환합니다.
scanf
scanf
는 성공하거나 매치 실패로 반환할 경우에 그때까지 대입에 성공한 서식 지정자의 개수를 반환합니다. %n
이나 *
이 있는 것은 세지 않습니다.
입력 오류나 _s
오류가 발생했을 때는 EOF
를 반환합니다.
변종
C 표준 라이브러리에는 그냥 printf
나 scanf
말고도 훨씬 많은 변종이 있다는 걸 알고 계셨나요? 원본과 조금씩 차이가 있는 것들을 합쳐 무려 30가지의 printf
와 24가지의 scanf
가 정의되어 있는데, 각각 printf
/scanf
와 어떻게 다른지 확인해 보겠습니다.
이 수십 가지의 입출력 함수의 차이점은 총 4종류로 구분할 수 있고, 아래에 나열한 순서대로 붙습니다(vswprintf_s
처럼).
- 인자 전달 방식 (
v
) - 입출력 위치 (
f
,s
,sn
) - 문자열의 종류 (
w
) - 범위 확인 (
_s
)
공통
아래 문단에서는 printf
위주로 설명하지만, scanf
에도 똑같이 적용됩니다.
인자 전달 방식
vprintf
는 가변 인자 대신 va_list
를 받습니다.
일반적으로 첫 인자로 서식 문자열을 전달하고 나면 나머지 인자는 콤마로 구분해서 하나씩 전달합니다. 이 부분이 가변 인자이기 때문에 인자의 개수를 아무렇게나 전달할 수 있습니다.
// ... 부분이 가변 인자 자리입니다.
int printf(const char *restrict format, ...);
그런데 printf
와 비슷한데 다른 동작을 하는 함수를 직접 만들려고 하면 가변 인자를 전달할 수 없다는 문제점에 부딪힙니다.
int printf_ln(const char *format, ...) {
va_list args;
va_start(args, format);
int result = printf(format, /* ????? */);
printf("\n");
va_end(args);
return result;
}
이때는 vprintf
를 대신 사용할 수 있습니다.
int printf_ln(const char *format, ...) {
va_list args;
va_start(args, format);
int result = vprintf(format, args);
printf("\n");
va_end(args);
return result;
}
입출력 위치
fprintf
는 서식 문자열 이전에 출력할 FILE *
을 하나 더 받습니다. printf
는 fprintf(stdout, ...)
과, scanf
는 fscanf(stdin, ...)
과 같은 것으로 생각할 수 있습니다.
FILE *file = fopen("foo.txt", "w");
fprintf(file, "%d", 123);
fclose(file);
sprintf
는 서식 문자열 이전에 char *
를 하나 더 받고, 그 포인터가 가리키는 곳에 문자열로 출력합니다.
char buf[64];
sprintf(buf, "%d", 4567);
문자열의 종류
wprintf
는 서식 문자열을 와이드 문자열로 받습니다. w
가 붙은 함수는 헤더 <stdio.h>
대신 <wchar.h>
에 정의되어 있습니다.
wprintf(L"The quick brown fox"); // 문자열 앞의 L은 와이드 문자열 표시입니다.
범위 확인
아마 'scanf': This function or variable may be unsafe. Consider using scanf_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
2로 자주 접한 녀석들일 겁니다. 잠깐 삼천포로 빠지자면 _s
로 끝나는 함수는 C 표준의 부록 K에 실려있긴 하지만 반드시 지원할 필요는 없고, 해당하는 헤더 이전에 매크로 __STDC_LIB_EXT1__
이 정의되어 있고 사용자가 __STDC_WANT_LIB_EXT1__
을 1로 정의해야 사용할 수 있음이 보장됩니다.3
printf_s
계 함수는 기존의 printf
계 함수와 같지만, 다음 상황에 적절하게 예외 처리를 합니다.
- 서식 지정자
%n
이 있음- 포인터에 쓰는 서식 지정자이기 때문에 사용할 수 없습니다.
%s
에 대응하는 인자가 널 포인터임- 서식 문자열이나 출력 파일/문자열로 널 포인터를 전달함
sprintf_s
/snprintf_s
계 함수에서 출력 문자열의 길이가 0이거나RSIZE_MAX
를 초과함- 인코딩 오류
sprintf_s
계 함수에서 출력하는 길이가 출력 문자열의 길이를 초과함
특히 sprintf_s
는 snprintf
처럼 출력하는 문자열 바로 뒤에 그 문자열의 크기를 추가로 받습니다.
scanf_s
계 함수는 기존의 scanf
계 함수와 같지만, 인자를 받는 방식이 다음과 같이 차이가 있고...
*
이 없는%c
/%s
/%[...]
/%[^...]
가 각각 대응하는 인자 바로 다음에 그 포인터가 가리키는 배열의 길이를 추가로 받습니다. 널 문자가 들어갈 자리도 포함합니다.
char string[16];
// 16글자 이상의 문자열이 들어오면 예외 처리를 합니다.
scanf_s("%s", string, 16);
...다음 상황에 적절하게 예외 처리를 합니다.
- 포인터 타입의 인자가 하나라도 널 포인터임
- 서식 문자열이나 입력 파일/문자열로 널 포인터를 전달함
%c
/%s
/%[...]
/%[^...]
가 인자로 받은 길이를 초과함 (널 문자를 포함해서)- 이외 잘못된 변환 지정자 등의 오류
printf
입출력 위치
printf
의 경우에는 fprintf
와 sprintf
이외에도 snprintf
라는 변종이 존재하고, 출력 문자열 바로 다음에 그 문자열의 크기를 추가로 받습니다.
어째서인지 snwprintf
, vsnwprintf
는 표준 라이브러리에서 지원하지 않습니다. 이 함수를 쓰고 싶다면 snwprintf_s
를 하든지, 성공할 때까지 문자열 크기를 계속 늘리면서 swprintf
를 대신 해야 합니다(cppreference에서 권장하는 방법). 이 두 함수 이외에는 가능한 모든 조합을 사용할 수 있습니다.
연습 문제
정리할 내용은 이것으로 끝입니다. 복습할 겸 연습문제를 풀어보는 건 어떨까요? 사실 이 부분은 급하게 마무리하느라 문제 종류가 다양하지 않고, 문제 추천은 언제나 감사히 받겠습니다.
- 백준 11816번 "8진수, 10진수, 16진수"를
scanf
하나,printf
하나씩만 써서 풀기 - 백준 2439번 "별 찍기 - 2"를 한 줄당
printf
안에 서식 지정자 1개와 개행 문자 하나만 써서 풀기%s
한 번으로*
문자를 \(x\)개 찍으려면 충분한 길이의const char stars[] = "*****************...";
를 미리 해둔 뒤&stars[sizeof(stars) - x - 1]
을 하면 됩니다.
- 백준 2445번 "별 찍기 - 8"을 한 줄당
printf
안에 서식 지정자 2개와 개행 문자 하나만 써서 풀기 - 백준 11718번 "그대로 출력하기"를 한 줄당
scanf
하나,printf
하나씩만 써서 풀기
-
C 표준에서는 코드에서의 형태가 다르면 같은 포맷과 값으로 변환하지 않아도 된다고 명시하고 있습니다. 즉,
1.23
과1.230
,123e-2
,123e-02
등이 서로 다른 값을 표현할 수도 있습니다. ↩ -
"'
scanf
': 이 함수나 변수는 불안전할 수 있습니다. 대신scanf_s
사용을 고려하세요. 비권장 경고를 해제하려면_CRT_SECURE_NO_WARNINGS
를 사용하세요. 자세한 사항은 온라인 도움말에서 확인할 수 있습니다." ↩ -
더 삼천포로 빠지자면, 사실 이 표준을 제대로 지키는 C 컴파일러/라이브러리가 거의 없습니다. 툭하면
scanf_s
를 쓰라는 비주얼 스튜디오도__STDC_LIB_EXT1__
도 정의가 안 돼있고 표준 함수 중 하나인set_constraint_handler_s
도 없습니다. ↩