Scanf
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.
1. 개요
`scanf`는 C 언어에서 표준 입력을 통해 데이터를 읽어들이는 함수이다. 마이크 레스크가 개발한 이식 가능한 입출력 라이브러리의 일부로, 7판 유닉스부터 유닉스에 포함되었다. `scanf`는 형식 지정자를 사용하여 입력 데이터의 형식을 지정하며, `printf` 함수의 역함수와 유사하게 작동한다. `fscanf`와 `sscanf`는 각각 파일 스트림과 문자열 스트림에서 입력을 받도록 파생된 함수이다. `scanf`는 형식 문자열 공격 및 버퍼 오버플로우에 취약하며, 개행 문자, 공백 문자, 잘못된 입력 처리, 산술 오버플로우 등의 문제점이 존재하여, 보안 및 안정성을 위해 주의하여 사용해야 한다.
더 읽어볼만한 페이지
- Stdio.h - Printf
printf는 다양한 프로그래밍 언어에서 형식 지정자를 사용하여 출력 형식을 제어하는 함수이며, 보안 취약점에도 불구하고 C/C++ 기반 시스템 프로그래밍 및 웹 개발에서 널리 사용된다.
2. 역사
마이크 레스크가 개발한 이식 가능한 입출력 라이브러리의 일부로, 버전 7 유닉스에서 공식적으로 유닉스의 일부가 되었다.[1]
`scanf`는 C에서 비롯된 함수로, 표준 입력(주로 명령 줄 인터페이스)으로부터 숫자나 다른 자료형을 입력받아 읽어내는 기능을 한다.
3. 사용법
`scanf` 함수는 `
```c
int scanf(const char *format, ...);
```
C99 이후에는 `restrict` 형 한정자를 사용한다.
```c
int scanf(const char *restrict format, ...);
```
`scanf` 함수는 첫 번째 인수인 `format`을 통해 그 뒤에 오는 가변 길이의 실인수들의 변환 방법(서식)을 지정한다. 반환값은 입력에 성공한 항목의 개수이다.
`scanf`는 표준 입력에서만 읽도록 지정되어 있으므로, PHP와 같이 인터페이스를 가진 프로그래밍 언어들은 `scanf` 대신 `sscanf` 및 `fscanf`와 같은 파생 함수를 사용한다.
3. 1. 기본 사용법
c
#include
int main(void)
{
int n;
while (scanf("%d", &n) == 1)
printf("%d\n", n);
return 0;
}
```
위의 예시에서 `scanf` 함수는 표준 입력에서 서식이 지정되지 않은 10진수 정수를 읽어 각각을 별도의 줄에 출력한다.
입력 예시:
```text
456 123 789 456 12
456 1
2378
```
출력 예시:
```text
456
123
789
456
12
456
1
2378
```
`scanf` 함수를 이용하여 단어를 출력하는 예시는 다음과 같다.
```c
#include
int main(void)
{
char word[20];
if (scanf("%19s", word) == 1)
puts(word);
return 0;
}
```
`scanf` 함수의 인수는 프로그램에서 읽기를 원하는 자료형에 관계없이 메모리를 가리키는 포인터여야 한다. 그렇지 않으면 함수가 올바르게 수행되지 않는다. 예를 들어, 정수형 변수 `x`의 값을 입력받으려면 다음과 같이 한다.
```c
int x; /* 스캔 결과를 저장하는 변수. */
if (scanf("%d", &x) == 1) { // &x는 x의 주소
```
`scanf` 함수 호출 시 주소 연산자 `&`를 사용하여 변수 `x`의 주소를 넘겨준다.
3. 2. 형식 지정자
scanf 함수는 다양한 형식 지정자(format specifier)를 사용하여 입력 데이터의 형식을 지정한다. 주요 형식 지정자는 다음과 같다:[4]
형식 지정자에 *를 붙이면 해당 값을 읽어들이지만 변수에 저장하지 않는다.[4]
길이 수식자는 다음과 같다:[4]
3. 3. 반환 값
`scanf` 함수는 성공적으로 읽어들인 데이터 항목의 개수를 반환한다.[5] 예를 들어, `scanf("%d", &x)` 에서 정수 하나를 성공적으로 읽으면 1을 반환한다. 만약 입력이 없거나, 형식 지정자와 맞지 않는 입력(예를 들어, 정수를 읽으려는데 문자열이 입력)이 들어오면 입력 실패가 발생하고 `EOF` (End-of-File, 일반적으로 -1)를 반환한다.
다음은 반환 값을 이용하여 입력 오류를 처리하는 예시이다.
```c
#include
int main(void)
{
int x;
printf("x = ? ");
if (scanf("%d", &x) == 1) {
printf("스캔 결과 = %d\n", x);
}
else {
printf("스캔 실패\n");
}
return 0;
}
```
위 코드에서 `scanf`의 반환값이 1인지 (즉, 정수 하나를 제대로 읽었는지) 확인하여 입력 성공 여부를 판단한다.
4. 관련 함수
`fscanf` 함수는 파일 스트림에서 입력을 읽어오고, `sscanf` 함수는 문자열에서 입력을 읽어온다. C 언어에서 표준 입력은 `stdin`이라는 `FILE` 포인터로 나타내므로, `fscanf(stdin, ...)`는 scanf와 완전히 동일하다. `scanf`에서 에러 처리를 구현할 때 에러가 발생하지 않고 다시 입력을 기다리는 패턴이 나타나는 경우가 있다. 따라서 표준 입력에서 숫자를 입력받을 때는 `scanf`를 직접 사용하기보다 fgets 함수 등으로 문자열을 읽어들인 후 `sscanf`로 처리하는 경우가 많다.
4. 1. fscanf
cint fscanf(FILE *fp, const char *format, ...);
```
`fscanf`는 첫 번째 인수로 `FILE` 포인터를 지정하여 표준 입력이 아닌 파일 스트림에서 입력을 읽어들인다. C 언어에서 표준 입력은 `stdin`이라는 `FILE` 포인터로 나타내므로, `fscanf(stdin, ...)`는 scanf와 완전히 동일하다.
4. 2. sscanf
`sscanf`는 표준 입력이 아닌 문자열 스트림에서 읽기 위해 첫 번째 인수에 문자열에 대한 포인터를 지정한다. `scanf`에서 에러 처리를 구현하려고 하면 에러가 발생하지 않고 다시 입력을 기다리는 패턴이 있는데, 이 때문에 표준 입력에서 숫자를 입력하는 경우에는 직접 `scanf`를 사용하지 않고 일단 fgets 함수 등으로 문자열로 읽어들인 다음 `sscanf`로 처리하는 경우가 많다.5. 문제점 및 주의 사항
`scanf` 함수는 C 언어 입문서에 자주 등장하지만, 몇 가지 문제점이 있어 사용에 주의해야 한다.
`scanf`는 형식 문자열 공격에 취약하다. 형식 문자열에 문자열 및 배열 크기에 대한 제한을 포함하도록 주의해야 한다. 사용자 입력 문자열의 크기는 `scanf` 함수 실행 전에 예측하기 어렵기 때문에, 길이 지정자가 없는 `%s` 자리 표시자는 버퍼 오버플로우를 유발할 수 있다. 또한, 구성 파일 등에 저장된 동적 형식 문자열을 허용하는 경우, 형식 문자열을 미리 확인하고 제한을 적용하지 않으면 보안상 취약점이 발생할 수 있다.[8]
실제 가변 인자 목록과 일치하지 않는 형식 자리 표시자가 사용될 경우, 스택에서 잘못된 값을 추출하거나 안전하지 않은 포인터를 참조할 수 있다.
Clang의 정적 코드 분석 도구인 Clang-Tidy는 `scanf` 계열 함수를 사용한 코드에 대해 경고를 발생시킨다.[12]
5. 1. 개행 문자 처리
`%c` 형식 지정자를 사용하면 이전 입력의 개행 문자(Enter 키)가 남아있어 의도하지 않은 입력이 발생할 수 있다.예를 들어, 다음과 같은 C 코드가 있다고 가정해 보자.
```c
char a, b, c;
scanf("%c", &a);
scanf("%c", &b);
scanf("%c", &c);
```
이 코드는 사용자로부터 3개의 문자를 입력받을 것으로 예상되지만, 실제로는 2번만 입력이 진행된다. 이는 첫 번째 `scanf` 함수에서 문자를 입력받은 후, 스트림 상에 개행 문자(Enter 키)가 남게 되고, 두 번째 `scanf` 함수에서 이 개행 문자를 입력으로 받아들이기 때문이다. `%d`나 `%s`와 같은 형식 지정자는 개행 문자를 무시하지만, `%c`는 스트림 상의 다음 바이트를 무조건 반환하기 때문에 이러한 현상이 발생한다.
이 문제를 해결하는 방법은 두 가지가 있다.
첫 번째 방법은 `scanf` 함수 호출 시 형식 지정자 앞에 공백을 추가하는 것이다.
```c
scanf("%c", &a);
scanf(" %c", &b);
scanf(" %c", &c);
```
이렇게 하면 입력 전에 공백(개행 문자 포함)을 건너뛰게 된다. 다만, 이 경우 a, b, c에 반각 스페이스를 대입할 수 없다.
두 번째 방법은 `%*c`를 사용하여 입력 시의 개행 문자를 제거하는 것이다.
```c
scanf("%c%*c", &a);
scanf("%c%*c", &b);
scanf("%c%*c", &c);
```
이 방법은 입력 시의 개행 문자를 `%*c`로 제거하여 문제를 해결한다.
5. 2. 공백 문자 처리
`scanf` 함수는 공백, 탭, 개행 문자를 모두 같은 구분 문자로 취급한다.[1] 예를 들어,```c
char a[20];
scanf("%s", a);
```
와 같이 `%s` 형식 지정자를 사용하여 문자열을 입력받는 경우, "This is a pen"을 입력하면 `a`에는 "This"까지만 저장된다.[1] " is a pen"이라는 나머지 문자열은 입력 스트림에 그대로 남아있게 된다.[1] 이 때문에 다음에 `scanf`를 호출하면, 사용자가 다른 문자열을 입력하더라도 "is"가 입력되는 문제가 발생한다.[1]
이 문제를 해결하기 위해 다음과 같은 방법을 사용할 수 있다.[1]
```c
scanf("%[^\n]", a);
```
이 코드는 개행 문자(`\n`)를 제외한 모든 문자를 `a`에 저장하므로, 공백이나 탭을 포함한 문자열을 입력받을 수 있다.[1]
입력 시 개행 코드 처리를 고려할 경우,[1]
```c
scanf("%[^\n]%*c", a);
```
와 같이 작성할 수 있다.[1]
5. 3. 버퍼 오버플로우 취약성
`scanf` 함수는 형식 문자열 공격에 취약하며, 특히 길이 지정자가 없는 `%s` 자리 표시자를 사용할 경우 버퍼 오버플로우가 발생할 수 있다.[8] 이는 사용자 입력 문자열의 크기가 `scanf` 함수 실행 전에 예측 불가능하기 때문이다.예시:```c
char a[20];
scanf("%s", a);
```
위 코드에서 `a`에 입력 가능한 문자열은 종료 문자를 제외하고 19바이트까지이다. 그 이상의 문자열이 입력되면 버퍼 오버플로우가 발생한다. 입력 문자열 길이를 예측할 수 없으므로, `a`의 길이를 아무리 크게 설정해도 버퍼 오버플로우 위험을 완전히 제거할 수 없다.
해결 방법:이 문제를 방지하기 위해 최대 필드 폭을 지정하는 방법을 사용할 수 있다.
```c
char a[20];
scanf("%19s", a);
```
위 코드는 `a`에 처음 19바이트만 읽어들이고 입력을 종료한다. 그러나 이 경우 나머지 문자열은 입력 스트림에 남게 되므로, 다음과 같이 처리하는 것이 일반적이다.
```c
char a[20];
scanf("%19s%*[^\n]", a);
getchar();
```
이는 19바이트를 읽은 후 줄바꿈 문자(`\n`)까지의 나머지 문자열을 무시하고, `getchar()`를 통해 남아있는 줄바꿈 문자를 제거하는 방법이다. 공백 문자까지 고려하는 경우 아래와 같이 작성할 수 있다.
```c
char a[20];
scanf("%19[^\n]%*[^\n]", a);
getchar();
```
마이크로소프트 비주얼 C++(Microsoft Visual C++) (버전 8.0 (2005) 이후) C 런타임 라이브러리(CRT)는 버퍼 크기를 지정할 수 있는 `scanf_s()` 함수를 제공한다.[9] `scanf_s`는 C11 표준이지만, 구현은 선택 사항이다.[10]
5. 4. 잘못된 입력 처리
`scanf` 함수는 예상치 못한 값이 입력되면 해당 값을 읽지 않고 스트림에 남겨둔다. 예를 들어, 다음과 같이 코드를 작성했을 때[1]:```c
int i;
scanf("%d", &i);
```
입력이 숫자가 아니면 `scanf`는 스트림에 해당 문자열을 남겨둔 채 종료된다. 이 때문에 다음에 `scanf`가 호출될 때 해당 스트림상의 버퍼가 실인수에 대입되어 다른 `scanf`에도 이상한 결과를 반환하게 된다.[1]
예를 들어 1이 입력되면 종료, 그 외에는 다시 입력을 요구하는 프로그램을 생각해 보자. 만약 이 프로그램을 다음과 같이 작성한 경우[1]:
```c
int i;
while (1) {
scanf("%d", &i);
if (i == 1) {
break;
}
}
```
숫자가 아닌 문자를 입력하면 무한 루프에 빠지게 된다. 이러한 현상을 방지하는 수단으로 `scanf`의 반환값을 이용하는 방법이 사용된다. 이는 `scanf`가 대입에 성공한 변수의 수를 반환값으로 반환하기 때문에, 지정한 실인수의 수와 `scanf`의 반환값이 일치하지 않을 때 입력 버퍼를 클리어함으로써 회피할 수 있다. 예를 들어 위의 예시의 경우 다음과 같다.[1]
```c
int i;
while (1) {
if (scanf("%d", &i) != 1) {
scanf("%*s");
if (feof(stdin)) {
/* 에러 처리 */
}
continue;
}
if (i == 1) {
break;
}
}
```
입력 버퍼 클리어는 여기서는 문자열을 비워 읽음으로써 실현된다 (입력 버퍼 클리어 방법은 상황에 따라 다른 방법이 생각될 수 있다).[1]
5. 5. 산술 오버플로우
atoi, atol, atof와 마찬가지로, scanf 계열 함수에서는 산술 오버플로(부동 소수점의 경우 산술 언더플로도 발생할 수 있음)를 검출할 수 없다.[11] 따라서, 엄밀한 입력 검사가 필요한 응용 소프트웨어에서는 사용해서는 안 된다.대안으로, strtol이나 strtod와 같이 오버플로 검사를 할 수 있는 변환 함수를 이용한다.[11]
Clang의 정적 코드 분석 도구 Clang-Tidy에서는, scanf 계열을 사용한 코드에 대해 경고를 발생시킨다.[12]
6. 응용
`scanf` 함수를 활용하여 간단한 계산기를 만들 수 있다. 다음은 `scanf`를 이용해 만든 계산기 예제이다.[1]
calculator영어 함수는 `symbol` 변수의 값에 따라 `num1`과 `num2`에 대한 연산을 수행한다. `symbol` 값에 따른 연산은 다음과 같다.
- `symbol`이 1이면 덧셈을 한다.
- `symbol`이 2이면 뺄셈을 한다.
- `symbol`이 3이면 곱셈을 한다.
- `symbol`이 4이면 나눗셈을 한다.
- `symbol`이 5이면 나머지 연산을 한다.
`main` 함수에서는 `scanf`를 사용하여 사용자로부터 계산할 기호(`A`)와 두 개의 숫자(`N1`, `N2`)를 입력받는다. 입력받은 값들을 `calculator` 함수에 전달하여 계산을 수행하고, "프로그램 종료" 메시지를 출력한다.
하지만, 위 코드는 [https://blog.naver.com/hyhgo1/221194170230 hyhgo1님의 블로그]에서 가져온 것으로, 더 정교하고 완전한 계산기 예제는 === 계산기 예제 === 하위 섹션에서 확인할 수 있다.
6. 1. 계산기 예제
c#include
#include
int main(void)
{
int n1, n2, nadd, nsub, nmul, ndiv, nmod;
printf("첫 번째 입력 숫자:");
if (scanf("%d", &n1) != 1) {
puts("스캔 실패");
return -1;
}
printf("두 번째 입력 숫자:");
if (scanf("%d", &n2) != 1) {
puts("스캔 실패");
return -1;
}
if (n2 == 0) {
/* 0으로 나누는 것을 방지하기 위한 체크. */
puts("두 번째 숫자는 0이 아닌 값을 입력해주세요");
return -1;
}
if (n1 == INT_MIN && n2 == -1) {
/* 나눗셈과 나머지 연산의 오버플로 에러를 방지하기 위한 체크. */
puts("오버플로가 발생하지 않는 숫자의 조합을 입력해주세요");
return -1;
}
nadd = n1 + n2;
nsub = n1 - n2;
nmul = n1 * n2;
ndiv = n1 / n2;
nmod = n1 % n2;
printf("%d + %d = %d\n", n1, n2, nadd);
printf("%d - %d = %d\n", n1, n2, nsub);
printf("%d * %d = %d\n", n1, n2, nmul);
printf("%d / %d = %d + %d / %d\n", n1, n2, ndiv, nmod, n2);
return 0;
}
```
위 코드는 scanf 함수를 사용하여 두 개의 정수를 입력받고, 이 두 정수에 대한 사칙연산(덧셈, 뺄셈, 곱셈, 나눗셈, 나머지 연산)을 수행하는 간단한 계산기 예제이다.
코드 설명:
- `#include
`: 표준 입출력 라이브러리 함수(`printf`, `scanf` 등)를 사용하기 위해 포함한다. - `#include
`: 정수형의 최댓값/최솟값(`INT_MIN`, `INT_MAX`)을 사용하기 위해 포함한다. - `main` 함수: 프로그램의 시작점이다.
- 변수 선언:
- `n1`, `n2`: 사용자로부터 입력받을 두 정수를 저장한다.
- `nadd`, `nsub`, `nmul`, `ndiv`, `nmod`: 각 사칙연산의 결과를 저장한다.
- `printf` 함수: 사용자에게 숫자를 입력하라는 안내 메시지를 출력한다.
- `scanf` 함수: 사용자로부터 두 정수를 입력받아 `n1`, `n2`에 저장한다.
- `if (scanf(...) != 1)`: `scanf` 함수가 정상적으로 값을 읽어오지 못했을 경우 (예: 사용자가 숫자가 아닌 문자를 입력) 오류 메시지를 출력하고 프로그램을 종료한다.
- `if (n2 == 0)`: 0으로 나누는 것을 방지하기 위해 `n2`가 0인지 확인한다. 0이면 오류 메시지를 출력하고 프로그램을 종료한다.
- `if (n1 == INT_MIN && n2 == -1)`: `n1`이 정수 최솟값(`INT_MIN`)이고 `n2`가 -1일 때 나눗셈과 나머지 연산에서 오버플로가 발생할 수 있으므로, 이를 방지하기 위해 확인한다. 해당 조건에 맞으면 오류 메시지를 출력하고 프로그램을 종료한다.
- 사칙연산 수행: `n1`과 `n2`에 대해 덧셈, 뺄셈, 곱셈, 나눗셈, 나머지 연산을 수행하고 결과를 각 변수에 저장한다.
- `printf` 함수: 각 연산 결과를 출력한다. 나눗셈의 경우 몫과 나머지를 함께 출력한다.
- `return 0`: 프로그램이 정상적으로 종료되었음을 나타낸다.
출력 결과 예시:```text
1번째 입력 숫자:60
2번째 입력 숫자:21
60 + 21 = 81
60 - 21 = 39
60 * 21 = 1260
60 / 21 = 2 + 18 / 21
참조
[1]
간행물
A Research Unix reader: annotated excerpts from the Programmer's Manual, 1971–1986
http://www.cs.dartmo[...]
[2]
문서
C99 standard, §7.19.6.2 "The fscanf function" alinea 11.
[3]
웹사이트
scanf Type Field Characters
https://docs.microso[...]
2022-10-26
[4]
문서
scanf
Linux
[5]
웹사이트
restrict type qualifier (since C99) - cppreference.com
https://en.cpprefere[...]
[6]
웹사이트
Arithmetic types - cppreference.com (C)
https://en.cpprefere[...]
[7]
웹사이트
Fundamental types - cppreference.com (C++)
https://en.cpprefere[...]
[8]
웹사이트
'[迷信] scanfではバッファオーバーランを防げない'
https://www.kijineko[...]
株式会社きじねこ
2024-11-02
[9]
웹사이트
scanf_s, _scanf_s_l, wscanf_s, _wscanf_s_l | Microsoft Learn
https://learn.micros[...]
[10]
웹사이트
scanf, fscanf, sscanf, scanf_s, fscanf_s, sscanf_s - cppreference.com
https://en.cpprefere[...]
[11]
웹사이트
INT06-C. 文字列トークンを整数に変換するには strtol() 系の関数を使う
https://www.jpcert.o[...]
[12]
웹사이트
clang-tidy - cert-err34-c — Extra Clang Tools git documentation
https://clang.llvm.o[...]
본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.
문의하기 : help@durumis.com