맨위로가기

Scanf

"오늘의AI위키"는 AI 기술로 일관성 있고 체계적인 최신 지식을 제공하는 혁신 플랫폼입니다.
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.

1. 개요

`scanf`는 C 언어에서 표준 입력을 통해 데이터를 읽어들이는 함수이다. 마이크 레스크가 개발한 이식 가능한 입출력 라이브러리의 일부로, 7판 유닉스부터 유닉스에 포함되었다. `scanf`는 형식 지정자를 사용하여 입력 데이터의 형식을 지정하며, `printf` 함수의 역함수와 유사하게 작동한다. `fscanf`와 `sscanf`는 각각 파일 스트림과 문자열 스트림에서 입력을 받도록 파생된 함수이다. `scanf`는 형식 문자열 공격 및 버퍼 오버플로우에 취약하며, 개행 문자, 공백 문자, 잘못된 입력 처리, 산술 오버플로우 등의 문제점이 존재하여, 보안 및 안정성을 위해 주의하여 사용해야 한다.

광고

더 읽어볼만한 페이지

  • Stdio.h - Printf
    printf는 다양한 프로그래밍 언어에서 형식 지정자를 사용하여 출력 형식을 제어하는 함수이며, 보안 취약점에도 불구하고 C/C++ 기반 시스템 프로그래밍 및 웹 개발에서 널리 사용된다.

2. 역사

마이크 레스크가 개발한 이식 가능한 입출력 라이브러리의 일부로, 버전 7 유닉스에서 공식적으로 유닉스의 일부가 되었다.[1]

3. 사용법

`scanf`는 C에서 비롯된 함수로, 표준 입력(주로 명령 줄 인터페이스)으로부터 숫자나 다른 자료형을 입력받아 읽어내는 기능을 한다.

`scanf` 함수는 ``에 다음과 같이 선언되어 있다.[5]

```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]

지정자의미도입 버전
d10진수 부호 있는 정수모든 버전
i10진수 부호 있는 정수. %d와 유사하지만, 0x가 앞에 오면 16진수로, 0이 앞에 오면 8진수로 숫자를 해석한다.모든 버전
u10진수 부호 없는 정수모든 버전
o8진수 부호 없는 정수모든 버전
x, X16진수 부호 없는 정수모든 버전
a, A부동 소수점 수C99 이후
e, E부동 소수점 수모든 버전
f, F부동 소수점 수모든 버전
g, G부동 소수점 수모든 버전
c문자 (널 문자가 추가되지 않음)모든 버전
s문자열 (공백에서 종료, 널 문자가 끝에 저장됨)모든 버전
p포인터의 값모든 버전
n정수 변수에 출력된 문자 수를 저장모든 버전
%%의 입력모든 버전
[...][ ] 내에 둘러싸인 문자만 가져오고, 그 외의 문자가 나타난 시점 이후는 입력을 종료한다. [^ ... ]로 했을 경우에는 반대로 [ ] 내의 문자가 들어올 때까지 문자를 읽어들인다.모든 버전



형식 지정자에 *를 붙이면 해당 값을 읽어들이지만 변수에 저장하지 않는다.[4]

길이 수식자는 다음과 같다:[4]

수식자의미도입 버전
없음실인수는 `signed int*` 형, `unsigned int*` 형, `char*` 형, `float*` 형 또는 `void**` 형모든 버전
hh실인수는 `signed char*` 형 또는 `unsigned char*` 형C99 이후
h실인수는 `signed short*` 형 또는 `unsigned short*` 형모든 버전
l (엘)실인수는 `signed long*` 형, `unsigned long*` 형, `wchar_t*` 형 또는 `double*` 형`wchar_t*`에 대해서는 C95 이후
ll (엘엘)실인수는 `signed long long*` 형 또는 `unsigned long long*` 형C99 이후
j실인수는 `intmax_t*` 형 또는 `uintmax_t*` 형C99 이후
z실인수는 `size_t*` 형C99 이후
t실인수는 `ptrdiff_t*` 형C99 이후
L실인수는 `long double*` 형모든 버전


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

c

int 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