`scanf`, `fscanf`, `sscanf` 함수
scanf, fscanf, sscanf 함수
값을 입력받을 때, 단순히 문자나 문자열을 읽는 걸로는 부족해요. "정수 하나, 실수 하나, 문자열 하나"처럼 타입별로 파싱하고 싶잖아요. 그걸 도와주는 게 scanf 계열 함수예요. scanf는 stdin에서, fscanf는 파일 스트림에서, sscanf는 문자열 버퍼에서 서식에 맞춰 읽어 들이죠.
출처: cppreference
본문
함수 원형
<stdio.h>에 정의돼 있어요.
int scanf( const char* format, ... ); // (1)
int fscanf( FILE* stream, const char* format, ... ); // (2)
int sscanf( const char* buffer, const char* format, ... ); // (3)
int scanf_s(const char* restrict format, ...); // (4) (since C11)
int fscanf_s(FILE* restrict stream, const char* restrict format, ...); // (5) (since C11)
int sscanf_s(const char* restrict buffer, const char* restrict format, ...); // (6) (since C11)
설명 (Explanation)
여러 원천에서 데이터를 읽어 format에 따라 해석하고, 결과를 주어진 위치에 저장해요.
stdin에서 데이터를 읽어요.- 파일 스트림
stream에서 데이터를 읽어요. - 널 종료 문자열
buffer에서 데이터를 읽어요. 문자열 끝에 도달하는 것은fscanf의 파일 끝 조건에 도달하는 것과 같아요. 4-6) (1-3)과 같지만,%c,%s,%[변환 지정자가 각각 두 개의 인자(보통 포인터와, 받는 배열의 크기를 나타내는rsize_t값 —%c로 단일char에 읽을 때는 1이 될 수 있음)를 기대한다는 점이 달라요. 또한 다음 오류들을 런타임에 감지해 현재 설치된 제약 처리 함수(constraint handler)를 호출해요.
- 포인터 타입 인자 중 하나가 널 포인터
format,stream,buffer가 널 포인터%c,%s,%[가 썼을 문자 수에 종료 널 문자를 더한 값이, 각 변환 지정자에 준 두 번째(rsize_t) 인자를 넘음- (선택) 알 수 없는 변환 지정자 같은 그 외 감지 가능한 오류
다른 경계 검사 함수와 마찬가지로, scanf_s, fscanf_s, sscanf_s는 구현이 __STDC_LIB_EXT1__을 정의하고, 사용자가 <stdio.h>를 포함하기 전에 __STDC_WANT_LIB_EXT1__을 정수 상수 1로 정의했을 때만 쓰는 게 보장돼요.
매개변수 (Parameters)
stream— 읽어올 입력 파일 스트림buffer— 읽어올 널 종료 문자열을 가리키는 포인터format— 입력을 어떻게 읽을지 지정하는 널 종료 문자열...— 결과를 받는 인자들
형식 문자열 (Format string)
형식 문자열은 다음 요소들로 이뤄져요.
%가 아닌 공백이 아닌 멀티바이트 문자: 각 문자는 입력에서 정확히 같은 문자 하나를 소비해요. 다음 오는 문자가 같지 않으면 함수는 실패해요.- 공백 문자: 형식 문자열의 공백 문자 하나는 입력의 연속된 모든 공백 문자를 소비해요(
isspace를 반복 호출하는 것처럼 판단). 여기서"\n"," ","\t\t"는 구분이 없어요. - 변환 지정자. 각 변환 지정자는 다음 구조를 가져요.
- 시작
%문자 - (선택) 할당 억제 문자
*. 있으면 이 변환의 결과를 어떤 받는 인자에도 저장하지 않아요. - (선택) 0보다 큰 정수: 최대 필드 폭. 현재 변환 지정자가 소비할 수 있는 최대 문자 수를 제한해요.
%s와%[는 폭을 주지 않으면 버퍼 오버플로로 이어질 수 있어요. - (선택) 받는 인자 크기를 지정하는 길이 수정자. 변환 정확도와 오버플로 규칙에 영향. 기본 목적지 타입은 변환 타입마다 달라요.
- 변환 형식 지정자
- 시작
변환 지정자 표
| 지정자 | 설명 | 기본 목적지 타입 (길이 수정자에 따라 달라짐) |
|---|---|---|
% |
리터럴 %와 일치 |
해당 없음 |
c |
문자 또는 문자 시퀀스와 일치. 폭 지정 시 정확히 폭만큼 일치. %s/%[와 달리 배열에 널 문자를 붙이지 않아요 |
char* / (l)wchar_t* |
s |
공백이 아닌 문자들의 시퀀스(문자열)와 일치. 폭 지정 시 폭까지 또는 첫 공백까지. 일치한 문자 외에 항상 널 문자도 저장 | char* / (l)wchar_t* |
[set] |
set에 포함된 문자들의 비어있지 않은 시퀀스와 일치. 첫 문자가 ^면 set에 없는 문자들과 일치. set이 ]나 ^]로 시작하면 ]도 set에 포함. -가 비초기 위치에서 범위를 나타낼 수 있는지는 구현이 정의. 항상 널 문자도 저장 |
char* / (l)wchar_t* |
d |
10진 정수와 일치. strtol에 base 10으로 기대하는 형식과 같음 |
int* 계열 |
b (C23) |
부호 없는 2진 정수와 일치. strtoul에 base 2로 기대하는 형식과 같음 |
unsigned int* 계열 |
i |
정수와 일치. strtol에 base 0으로 기대하는 형식과 같음(첫 문자들로 base 결정) |
int* 계열 |
u |
부호 없는 10진 정수와 일치. strtoul에 base 10 |
unsigned* 계열 |
o |
부호 없는 8진 정수와 일치. strtoul에 base 8 |
unsigned* 계열 |
x/X |
부호 없는 16진 정수와 일치. strtoul에 base 16 |
unsigned* 계열 |
n |
지금까지 읽은 문자 수를 반환. 입력을 소비하지 않고 할당 카운트도 늘리지 않아요. *(할당 억제)가 있으면 동작이 정의되지 않아요 |
int* 계열 |
a/A/e/E/f/F/g/G |
부동소수점 숫자와 일치. strtof가 기대하는 형식과 같음 |
float* / (l)double* / (L)long double* |
p |
포인터를 정의하는 구현 정의 문자 시퀀스와 일치. printf 계열이 %p로 만들어 낸 것과 같은 시퀀스여야 함 |
void** |
길이 수정자(hh, h, l, ll, j, z, t, L)에 따라 위의 목적지 타입이 signed/unsigned char*, short*, long*, long long*, intmax_t*/uintmax_t*, size_t*, ptrdiff_t*, long double* 등으로 바뀌어요. 그중 일부는 C99부터 사용 가능해요.
동작 규칙 (Notes)
n을 제외한 모든 변환 지정자는, 지정된 필드 폭을 넘지 않으면서 변환 지정자가 기대하는 것과 정확히 일치하거나 그 접두어인 입력 문자들의 가장 긴 시퀀스를 스트림에서 소비해요. 소비된 시퀀스 뒤의 첫 문자는 읽히지 않은 채 남아요. 소비된 시퀀스의 길이가 0이거나, 변환할 수 없으면 일치 실패(matching failure)가 발생해요. 단 파일 끝·인코딩 오류·읽기 오류로 입력이 막혔다면 입력 실패(input failure)예요.
%를 제외하면, 입력 항목(또는 %n의 경우 입력 문자 수)은 먼저 변환 지정자에 맞는 타입으로 변환돼요. *로 할당이 억제되지 않았다면, 변환 결과는 아직 쓰이지 않은 다음 인자가 가리키는 객체에 기록돼요. 객체 타입이 맞지 않거나 결과가 객체에 표현될 수 없으면 동작이 정의되지 않아요.
[, c, n을 제외한 모든 변환 지정자는 입력을 파싱하기 전에 앞의 연속된 공백 문자를 모두 소비하고 버려요(isspace 호출처럼 판단). 이 소비된 문자들은 최대 필드 폭에 세지 않아요.
lc, ls, l[ 변환 지정자는 첫 문자가 변환되기 전에 0으로 초기화된 mbstate_t 객체로 mbrtowc를 호출하는 것처럼 멀티바이트→넓은 문자 변환을 수행해요.
s와 [ 변환 지정자는 일치한 문자 외에 항상 널 종료자를 저장해요. 목적지 배열의 크기는 지정된 필드 폭보다 최소 1은 커야 해요. 목적지 배열 크기를 지정하지 않고 %s나 %[를 쓰는 것은 gets만큼이나 안전하지 않아요.
고정 폭 정수 타입(int8_t 등)을 위한 올바른 변환 지정자는 <inttypes.h> 헤더에 정의돼 있어요(SCNdMAX, SCNuMAX 등은 %jd, %ju와 동의어).
각 변환 지정자의 동작 뒤에는 순서점이 있어서, 같은 "sink" 변수에 여러 필드를 저장하는 것이 허용돼요.
지수가 있는데 숫자가 없는 불완전한 부동소수점 값(예: %f로 "100er" 파싱)을 파싱할 때는, 아마 유효한 부동소수점 숫자의 가장 긴 접두어인 "100e"가 소비되고 일치 오류가 나면서 "r"이 남아요. 일부 구현(예: glibc bug 1765)은 이 규칙을 따르지 않고 "100"만 소비하고 "er"를 남기기도 해요.
변환 지정자가 잘못되면 동작이 정의되지 않아요.
반환값 (Return value)
1-3) 성공적으로 할당된 받는 인자의 수를 돌려줘요(첫 받는 인자 할당 전에 일치 실패가 발생하면 0일 수 있어요). 첫 받는 인자 할당 전에 입력 실패가 발생하면 EOF를 돌려줘요.
4-6) (1-3)과 같지만, 런타임 제약 위반이 있으면 EOF도 돌려줘요.
복잡도 (Complexity)
보장되지 않아요. 특히 일부 sscanf 구현은 O(N)이에요(N = strlen(buffer)).
주의할 점 (Notes)
대부분의 변환 지정자가 먼저 연속된 모든 공백을 소비하기 때문에, 다음과 같은 코드는 서로 다른 줄에 입력된 두 정수(두 번째 %d가 첫 번째가 남긴 개행을 소비)나, 공백·탭으로 구분된 같은 줄의 두 정수 모두 정상적으로 읽어요.
scanf("%d", &a);
scanf("%d", &b);
앞 공백을 소비하지 않는 %c 같은 변환 지정자는 형식 문자열에 공백 문자를 넣어 그렇게 만들 수 있어요.
scanf("%d", &a);
scanf(" %c", &c); // consume all consecutive whitespace after %d, then read a char
예제 (Example)
sscanf로 다양한 지정자를 한 번에 써 보는 예시예요.
#define __STDC_WANT_LIB_EXT1__ 1
#include <locale.h>
#include <stddef.h>
#include <stdio.h>
int main(void)
{
int i, j;
float x, y;
char str1[10], str2[4];
wchar_t warr[2];
setlocale(LC_ALL, "en_US.utf8");
char input[] = "25 54.32E-1 Thompson 56789 0123 56ß水";
/* parse as follows:
%d: an integer
%f: a floating-point value
%9s: a string of at most 9 non-whitespace characters
%2d: two-digit integer (digits 5 and 6)
%f: a floating-point value (digits 7, 8, 9)
%*d: an integer which isn't stored anywhere
' ': all consecutive whitespace
%3[0-9]: a string of at most 3 decimal digits (digits 5 and 6)
%2lc: two wide characters, using multibyte to wide conversion */
int ret = sscanf(input, "%d%f%9s%2d%f%*d %3[0-9]%2lc",
&i, &x, str1, &j, &y, str2, warr);
printf("Converted %d fields:\n"
"i = %d\n"
"x = %f\n"
"str1 = %s\n"
"j = %d\n"
"y = %f\n"
"str2 = %s\n"
"warr[0] = U+%x\n"
"warr[1] = U+%x\n",
ret, i, x, str1, j, y, str2, warr[0], warr[1]);
#ifdef __STDC_LIB_EXT1__
int n = sscanf_s(input, "%d%f%s", &i, &x, str1, (rsize_t)sizeof str1);
// writes 25 to i, 5.432 to x, the 9 bytes "Thompson\0" to str1, and 3 to n.
#endif
}
가능한 출력:
Converted 7 fields:
i = 25
x = 5.432000
str1 = Thompson
j = 56
y = 789.000000
str2 = 56
warr[0] = U+df
warr[1] = U+6c34
같이 보기 (See also)
vscanf/vfscanf/vsscanf— 가변 인자 목록을 쓰는 서식 입력fgets— 파일 스트림에서 문자열 가져오기printf/fprintf/sprintf/snprintf— 서식 출력