' [C/C++] 3.1강 - scanf 사용 시 주의 사항!! (white-space 버그)

Programming/C & C++

[C/C++] 3.1강 - scanf 사용 시 주의 사항!! (white-space 버그)

mdisprgm 2021. 6. 6. 19:23
728x90

(1) scanf 사용 시 주의 사항

 

[버퍼를 초기화 해야하는 경우]

1. 여러 변수를 한 번에 입력하면 안되는 경우

아래 코드를 보자

#include <stdio.h>

int main() {
  int a, b, c;
  printf("a를 입력하세요 : ");
  scanf_s("%d", & a);
  printf("b를 입력하세요 : ");
  scanf_s("%d", & b);
  printf("c를 입력하세요 : ");
  scanf_s("%d", & c);
  printf("%d + %d + %d = %d\n", a, b, c, a + b + c);
}

이 코드를 실행하면 이렇게 된다

세 수를 더하는 코드

저번에 한 a, b를 더하는 것과 똑같이 작동하는 코드이다.

그런데 여기서 scanf의 허점이 드러나는데.

입력값을 이렇게 해보쟈ㅏ

a먼저 입력받은 후

b를 입력하라는 메시지를 출력했고 c도 마찬가지인데

 

이것은 a를 입력받고 나서 b, c도 함께 입력받았다는 의미이다.

어떻게 a, b, c가 동시에 입력된 것일까


이유는 간단한데

바로 scanf가 키보드로부터 직접 입력받는 게 아니기 때문이다.

우리가 키보드로 숫자를 입력하면 그 숫자는 os에 의해 Buffer(버퍼)라 불리는 메모리 공간에 저장이 되며, 

 

scanf는 그 버퍼에 저장된 값을 변수에 다시 저장합니다.

그런데 숫자를 2 3 5 이렇게 여러 개를 입력하면

그 여러 개의 숫자가 버퍼에 저장되고

2를 a에 입력하고 나서

b를 입력하라는 메시지를 출력한 후

이미 버퍼에 저장된 3을 b에 가져가는 것이다.


이걸 해결하기 위해선 그 버퍼를 우리가 직접 비워주면 된다.

#include <stdio.h> int main() { int a, b, c; printf("a를 입력하세요 : "); scanf_s("%d", &a); rewind(stdin); printf("b를 입력하세요 : "); scanf_s("%d", &b); rewind(stdin); printf("c를 입력하세요 : "); scanf_s("%d", &c); printf("%d + %d + %d = %d\n", a, b, c, a + b + c); }

stdin이 바로 scanf가 값을 갖다쓰는 그 버퍼이고

rewind 함수가 버퍼를 초기화 해준다.

정확히 말하면 메모리를 초기화한다기보단

커서초기화 하는 것인데.(\r..?)


stdin 같은 버퍼를 응용 소프트웨어 - OS - 하드웨어 간의 정보 흐름을 담당하는 메모리라고 해서

'스트림'이라고 부르는데, 표준 스트림으로는 stdin 말고도 stdout, stderror 가 있고 이것들을 통틀어 stdio라고 한답니다.
(이 표준 스트림과 관련된 함수들이 모여있는 게 우리가 항상 사용하는 stdio.h 헤더파일인 거죠.)


stdin은 내부적으로 '위치 지시자'라는 변수를 가지고 있다.

scanf가 어느 위치에 있는 값을 변수에 저장해야할지 알려주는 변수인데

이걸 초기화해서 새로운 값을 입력받는 원리이다.


2. char형으로 입력받는 경우

지금은 아니지만 나중에 %c로 char형 변수를 입력할 때

개행문자 '\n'도 같이 입력되는 버그를 언젠가는 만날 것인데

지금은 무슨 말일지 몰라도

부디 그때 이 포스팅을 기억하길 바란다,,ㅎㅎ


키보드로 123 을 입력하게 된다면 버퍼에는 이렇게 저장이 된다.

마지막은 개행 문자인데... 그림 못생겨서 죄송


여기서 \n은 white-space 에 속하는데 이게 뭐냐면 scanf가 이건 입력받으면 안 되는구나... 하는 문자가 있다.

' ', '\n,'\t' 같은 거 말이다.

이런 건 %d로 입력받을 때는 정수가 될 수 없으므로

알아서 필터링 하는데 반해


%c로 char형을 입력받을 때는 조금 다르다.

이것들도 하나의 문자이기 때문에 char에 저장될 수 있다.

그 말이 무엇이냐하면..!!

#include <stdio.h>

int main() {
  int a;
  char ch;
  scanf_s("%d", & a);
  printf("입력한 수는 %d\n", a);
  scanf_s("%c", & ch, 1) printf("입력한 문자는 %c\n", ch);
  return 0;
}


이 코드를 실행해보자.

대충 내가 좋아하는 숫자인 1206을 입력해보면

문자가 빈 문자가 나온다.

 

이렇게 봐서는 잘 모르겠으니

 

printf를 이렇게 수정해보면

printf("입력한 문자는 ==%c==\n", ch);

그랬더니 이렇게 됐다.

== 와 == 사이에 줄바꿈이 생겼다.

이게 바로 방금 설명한 현상인데

처음에 정수로 입력받았기 때문에 1206 뒤에 있던 '\n'이 ch에 입력받을 때가 돼서야

변수에 저장된 거다..

이걸 해결하려면 똑같이 rewind를 쓰면 된다.

#include <stdio.h>

int main() {
  int a;
  char ch;
  scanf_s("%d", & a);
  printf("입력한 수는 %d\n", a);
  rewind(stdin);
  scanf_s("%c", & ch, 1); //마지막에 1을 쓴 이유는,, 이게 3강에서 언급한 //scanf_s의 특별한 사용법인데 말한대로 포인터를 배울 때 다시 설명할 예정!!
  printf("입력한 문자는 ==%c==\n", ch);
  return 0;
}



사실 꼭 rewind가 아니더라도 scanf로 %*c로 입력받거나, 아니면 %c로 두 번 입력받거나 getchar로 입력받는 등

어떻게든 '\n'만 없애주면 상관없다.

그러나 rewind가 가장 확실하고 속시원한 방법이라 생각해 rewind를 사용하는 방식만 소개했는데, 궁금하다면 다른 자료를 찾아보는 것도 좋다.




앞으로도 이렇게 보충 설명이 필요한 거나 같은 주제로 내용이 길어질 것 같으면 포스팅을 나눠서 쓰려고 한다.

 

끝.

728x90

'