Study/C & C++

[C] 버퍼(stdin)에 대한 이해와 scanf의 문제점

게임 만드는 나무꾼 2022. 9. 14. 21:07

1. 버퍼(stdin)이란?

  • 컴퓨터에 키보드 등으로 값을 입력할 때, 입력이 완료되기 전에 임시적으로 값을 가지고 있는 저장소
  • stdin은 특히, c언어에서 키보드 입력을 받는 버퍼이다.

2. scanf함수

  • scanf() 는 인자로 주어진 서식 지정자 (%d, %c, %s)에 따라 stdin에 저장된 값을 두 번째 인자로 주어진 변수에 할당한다.
  • 이 과정에서 버퍼에 남아 있는 값 때문에, 이상 동작할 수 있다.

3. scanf의 문제점

  • %c로 값을 받아올 때 문제가 발생할 수 있다.
#include <stdio.h>
int main() {
  int num;
  char c;

  printf("숫자를 입력하세요 : ");
  scanf("%d", &num);

  printf("문자를 입력하세요 : ");
  scanf("%c", &c);
  return 0;
}

// 실행 결과 //
숫자를 입력하세요 : 0
문자를 입력하세요 :

위 프로그램은 두 번째 입력을 받지 않고 프로그램이 종료된다.

이런 현상이 발생하는 이유는 scanf가 작동하는 방식 때문인데,

먼저, 사용자가 0을 입력하고 "엔터로 입력을 종료했을 때", stdin 버퍼에는 '0'과 '\n' 개행 문자가 들어가게 된다.

이후 scanf함수는 stdin으로부터 값을 읽는데, %d로 서식 지정자가 주어지면 "오직 숫자 데이터만" 버퍼에서 받아오게 된다.

그렇다면 버퍼의 현재 상태는? '\n'이 남아있는 상태가 된다. (참고로 scanf는 ' ', '\n', '\t' 를 만날 때까지 입력을 받는다.)

이후 두 번째 scanf가 실행될 때는?

%c로 매개변수가 주어지면, 이유를 불문하고 한 개의 문자를 받고 실행을 멈춘다.

따라서 scanf는 개행문자가 입력으로 주어졌다고 생각하고 프로그램이 종료되는 것

 

개행문자에 따라 scanf가 버퍼로부터 읽기를 종료하는 조건은 다음과 같다.

  • %d : 숫자 데이터 아닐 때까지 읽는다
  • %c : 무조건 한 개의 문자를 읽는다. (당연히 숫자 포함)
  • %s : 문자를 만날 때까지 ' ', '\n', '\t' 등을 무시하다가, 문자를 만난 이후에는 ' ', '\n', '\t'가 등장하면 종료한다.

위 코드에서 %c로 입력을 받는 부분을 %s로 교체하면 정상적으로 입력을 받는다.

#include <stdio.h>
int main() {
  char str[30];
  int i;

  scanf("%d", &i);
  scanf("%s", str);

  printf("str : %s", str);

  return 0;
}

 

%s로 입력을 받는 경우에도 버퍼에 의한 문제가 발생할 수 있다.

아래 코드는 정상적인 입력을 받지 못한다.

#include <stdio.h>
int main() {
  char str1[10], str2[10];

  printf("문자열을 입력하세요 : ");
  scanf("%s", str1);
  printf("입력한 문자열 : %s \n", str1);

  printf("문자열을 입력하세요 : ");
  scanf("%s", str2);
  printf("입력한 문자열 : %s \n", str2);

  return 0;
}

// 실행 결과 //
문자열을 입력하세요 : hello baby
입력한 문자열 : hello 
문자열을 입력하세요 : 입력한 문자열 : baby

이번에도 두 번째 scanf를 무시하고 지나간다.

scanf의 작동 방식과 버퍼의 상태를 생각해보면 이유를 알 수 있다.

  1. "hello baby"를 입력받은 직후 버퍼의 상태 : 'h', 'e', 'l', 'l', 'o', ' ', 'b', 'a', 'b', 'y', '\n'
  2. stdin으로부터 첫 번쨰 scanf가 문자열을 받아오고 나서의 버퍼 상태 : ' ', 'b', 'a', 'b', 'y', ''\n'
  3. 두 번째 scanf 실행
    1. %s가 인자로 주어지면, 의미가 있는 문자열을 만날 때까지 공백 문자를 무시한다!
    2. 따라서 '\n'이 나올 때까지 데이터를 가져오게 되고, 두 번째 사용자 입력을 받지 않게 된다.

4. 버퍼 문제 해결하기

간단하게, 버퍼를 비우는 함수를 통해 해결할 수 있다.

#include <stdio.h>
int main() {
  int num;
  char c;

  printf("숫자를 입력하세요 : ");
  scanf("%d", &num);

  fflush(stdin);

  printf("문자를 입력하세요 : ");
  scanf("%c", &c);
  return 0;
}

fflush(stdin)은 stdin을 비우라는 의미의 함수이다.

버퍼에 남아있는 개행문자를 지우기 때문에, 두 번쨰 scanf가 정상적으로 입력을 받을 수 있다.

참고로 fflush는 MS계열의 컴파일러에서만 정상적으로 컴파일된다.


하지만 내 Visual Studio 2019 버전에서는 작동하지 않았다.
이유를 찾아보니 fflush함수는 Visual Studio 2015버전 이후부터는 정상적으로 작동하지 않는다는 글을 발견했다.


getchar()함수를 사용해서 버퍼를 비우는 방법도 있다.

getchar()함수는 버퍼로부터 stdin에서 한 문자를 읽어와서 그 값을 리턴하는 함수이다.

ch = getchar();
prinf("%c", ch);

getchar()로부터도 %c에 의한 문제가 발생할 수 있다.

#include <stdio.h>
int main() {
  int num, i;
  char c;

  printf("숫자를 입력하세요 : ");
  scanf("%d", &num);

  getchar();

  printf("문자를 입력하세요 : ");
  scanf("%c", &c);

  printf("입력한 문자 : %c", c);
  return 0;
}

// 실행 결과 //
숫자를 입력하세요 : 123abc
문자를 입력하세요 : 입력한 문자 : b

위 코드도 버퍼의 상태를 생각해보면 왜 이상 동작하는지 알 수 있다.

123abc\n가 입력된 이후, %d로 버퍼의 값을 받아오면, 버퍼에는 abc\n이 남아 있다.

이 버퍼에서 getchar()로 데이터를 읽어버리면 버퍼에 남은 값은 bc\n이 된다.

두 번째 scanf의 %c로 읽어온 값은 b이고, 당연히 입력을 받지 않고 프로그램이 종료된다.


결론은, 되도록이면 %c를 사용하지 않는것이 좋다는 것이다.

문자를 받아오고 싶으면, %s로 읽은 다음 맨 첫번째 문자만 취하자.


위 내용은 모두의 코드 (modoocode.com) 의 <씹어먹는 C 언어 강좌>를 정리한 내용입니다.