프로그램 저지 사이트를 돌아다니면서 코딩을 해보면 의외로 시간을 잡아먹는 부분이 입력 처리 부분이다. 내가 C언어를 쉽게 쉽게, 띄엄 띄엄 배워서 그런지 디테일이 영 엉망이라 곤혹스럽다.
관련 이슈를 정리한다.
https://groups.google.com/forum/#!msg/han.comp.lang.c/HmiWd6iVO_E/NkBFztcimHgJ
질문.
아래 소스 중 scanf(" %d %d\n", &a, &b); 여기서요, \n 이 없으면 제대로
작동하는데 \n 만 들어가면 영 이상해집니다. 전 \n 의 역할은 변수를 받은 후
줄바꿈하는 걸로만 생각했는데 아니더라구요.
이 소스에서 \n 역할이 뭔지 좀 알려주세요
void main() { int a, b; printf("정수 두개 입력해\n"); scanf("%d %d\n", &a,&b); if(a > b) { printf("앞에 게 뒤에 것보다 크네\n"); } else if(a < b) { printf("뒤에 게 크네\n"); } else { printf("앞에 것과 뒤에 게 같다\n"); } }
답변
http://www.cppreference.com/stdio_details.html#scanf
scanf 함수의 개념은 "입력 버퍼(stdin)에서 format에서 지정한 대로 읽어들
인다" 입니다. 위의 두개의 차이는 다음과 같습니다.
"%d %d" - stdin에서 숫자, 1자 이상의 공백문자(white-space character), 숫
자를 읽어들인다.
"%d %d\n" - stdin에서 숫자, 1자 이상의 공백문자, 숫자, 1자 이상의 공백문
자를 읽어들인다.
C99의 FCD인 N869 문서의 7.19.6.2.5절을 인용하겠습니다.
A directive composed of white-space character(s) is executed by reading
input up to the first non-white-space character (which remains unread),
or until no more characters can be read.
형식(format) 문자열에 있는 공백문자(white-space)는 그 다음에 공백 문자가
아닌 문자(non-white-space)가 올때까지 입력버퍼(stdin)에서 읽어들이라는
뜻입니다. 즉, "%d %d\n"는 숫자 2개, 1자 이상의 공백문자, 공백문자가 아닌
문자를 받아야만 입력이 완전하게 끝나는 것입니다. 참고로 "%d %d\n"과 "%d
%d ", "%d %d\t"는 같은 뜻을 가집니다.
말이 어렵게 되어버렸는데, 예제로 설명하겠습니다.
scanf("%d %d", &a, &b);
숫자값, 1자 이상의 공백문자, 숫자값이 입력버퍼에 있기를 기대한다.
숫자값, 1자 이상의 공백문자, 숫자값까지 읽어들인다. 그 나머지는 입력 버
퍼(stdin)에 그대로 놔둔다.
실행 결과 - 1 2를 입력했을 경우 형식(format) 문자열과 완벽히 일치한다.
그러므로 a와 b에 읽어들인 값을 저장하고 함수(scanf)를 종료한다.
scanf(%d %d\n", &a, &b);
숫자값, 1자 이상의 공백문자, 숫자값, 1자 이상의 공백문자, 공백문자가 아
닌 문자가 입력버퍼에 있기를 기대한다.
숫자값, 1자 이상의 공백문자, 숫자값, 1자 이상의 공백문자까지 읽어들인다.
그 나머지는 입력 버퍼(stdin)에 그대로 놔둔다.
실행 결과 - 1 2를 입력했을 경우 "%d %d"까지 일치가 되지만 \n 문자 때문에
1자 이상의 공백문자와 그 뒤에 공백문자가 아닌 문자를 기다리게 됩니다. 여
기에 3을 또 입력하게 되면 공백문자까지 읽어들이게 되고 나머지(3)는 그대
로 입력버퍼에 남게 됩니다. 이 남아있는 3은 다음 scanf문이 실행될 때 영향
을 주게 됩니다.
그리고 한가지 더. 다음의 예제 코드를 봐 주십시오.
#include <stdio.h> int main(void) { int a, b; printf("\n>>"); scanf("%d %d\n", &a, &b); printf("[%d,%d]\n>>", a, b); scanf("%d %d\n", &a, &b); printf("[%d,%d]\n>>", a, b); return 0; }
이 프로그램의 실행 결과는 다음과 같습니다.
$ gcc 3.c -o 3 plsj@localhost: ~ $ ./3 >>1 2 <- 1과 2를 입력한다. 3 <- 숫자 2개가 입력되었는데도 입력을 기다린다. 3을 입력한다. [1,2] <- 입력된 숫자 출력 >>4 5 <- 숫자 2개를 또 입력 [3,4] <- 4 5를 출력하는게 아니라 3과 4를 출력한다. >>plsj@localhost: ~ $ ./3 >>1 2 <- 1과 2를 입력한다. a <- 숫자 2개가 입력되었는데도 입력을 기다린다. 3을 입력한다. [1,2] <- 입력받은 값을 출력한다. >>[1,2] <- 여기에서 입력을 한번 받아야되는데 받지 않고 입력받은 값을 또한번 출력한다. >>plsj@localhost: ~ $
첫번째 실행에서 1 2가 입력되면, "%d %d\n" 중에서 "%d %d"까지 일치가 됩니
다. 그러나 \n이 남아있기 때문에 1자 이상의 공백문자와 공백문자가 아닌 문
자의 입력을 기다리게 됩니다. 여기에 3을 입력하면 3 이전의 공백문자까지
읽어들이게 되어 첫번째 scanf 함수가 끝나게 되고 3은 입력버퍼에 그대로 남
습니다. 그다음의 scanf 함수가 실행되면 입력버퍼에 있는 3을 읽어들이게 되
고 " %d\n"이 남게 됩니다. 여기에 4 5를 입력하게되면 "4 "까지 읽어들이게
되고 5는 그대로 입력버퍼에 남게 됩니다. 즉 a에는 3이, b에는 4가 입력되는
것이죠.
두번째 실행에서도 마찬가지 입니다. 다만 첫번째 scanf 함수가 끝난 후에 3
대신 a가 입력버퍼에 남게 되고, 두번째 scanf 함수가 실행되면 형식(format)
문자열의 %d가 a와 일치가 안되기 때문에 두번째 scanf 함수는 하는일 없이
그대로 끝나게 됩니다. 따라서 1 2가 두번 출력되게 됩니다.
설명이 좀 길어졌습니다. 저는 능력이 없어서 짧고 간단하게 설명할 재주가
없군요-_-;; 양해하시길 바랍니다.
--
======================================================================
지금 상상이 되는 시나리오는gets/scanf를 사용하기 전에 g
지금 상상이 되는 시나리오는
gets/scanf를 사용하기 전에 getchar/scanf로 한 글자를 입력받은 것입니다. (흔히 있는 시나리오죠 :) ) 그 입력 마지막에 엔터를 쳤고, 그 입력을 처리하지 않아 gets에서는 마치 입력을 안받는 것처럼 느끼시는 것 같습니다.
gets는 \n을 보면 입력의 끝인가보다 하고 넘어갔을테고, scanf는 원래 whitespace를 무시하므로 입력을 받은 것 같습니다.
코드로 표현하자면
ch = getchar(); /* 어쩌고 저쩌고 */ gets(buffer);
또는
scanf("%c", &ch); /* 어쩌고 저쩌고 */ gets(buffer);
이렇게 했을 때 gets가 입력을 못받는 것처럼 느끼시는 듯 합니다.
이럴 때 제가 손쉽게 해결하는 방법은
scanf("%c ", &ch); /* 어쩌고 저쩌고 */ gets(buffer);
이렇게 하는 것입니다.
문맥상 gets를 쓰긴 했지만, 물론 gets(buffer);는 쓰면 안되고 fgets(buffer, sizeof(buffer), stdin); 등과 같이 바꿔써야 합니다. fgets는 마지막에 \n까지 받아온다는 것을 잊지 마시고.
======================================================================
#include <stdio.h>#include
#include <stdio.h> #include <string.h> #define NSTR 1 << 7 char *safe_gets(char *buf, size_t len) { scanf(" "); fgets(buf, len - 1, stdin); buf[strlen(buf) - 1] = '\0'; return buf; } int main() { char str[NSTR]; getchar(); safe_gets(str, NSTR); puts(str); }
매크로로 만들어 쓰셔도 좋습니다.
덧. 다행스럽게도, 빈 입력에도 \n\0 즉, strlen(buf) = 1이 나오기 때문에 저 코드에서 첨자값이 -1이 된다던지 하는 일은 없을 겁니다.
Real programmers /* don't */ comment their code.
If it was hard to write, it should be /* hard to */ read.
=================================================================
프로그램에서 임의로 삭제하는것이 어떤입력의경우 우리가 원하지 않는 입력까지 같이 들어오는경우가 있습니다. 특히 사용하는 함수에 따라서..
그렇다면.. 새로입력받은경우에 그값을 입력받으려하는데 버퍼에서 오래된
다른값을 넘겨준다면 문제가 되기때문에 일단비우고 새로 받는것입니다.
일반적으로 Text 기반 프로그램작성할때 종종 저렇게 하지요..
"해주면 된다" 라고 생각하시면 될듯합니다...^^;;
저를 포함해 위에서 답변해 주신 분들의 공통적인 의견은
"해서는 안 된다"
입니다. 물론, 입력에 프로그램이 필요로 하지 않은 부적절한 내용이 있어
유의미한 부분이 나올 때까지 무시하는 것 자체가 항상 잘못된 행동은 아니
지만, 그러한 행동을 위해 fflush(stdin); 을 선택하는 것은 많은 경우에
부적절 합니다. 기대한대로 동작하지 않을 여러 가능성은 제 처음 답변에
링크되어 있습니다. 그보다는 지극히 정상적이고 예측 가능한 방법으로 입
력을 처리하는 것이 더 낫습니다. 예를 들어, 현재부터 다음 newline 까지
의 (stdin 으로 오는) 입력을 무시해야 하는 상황이라면,
void throw_all_characters_until_the_next_newline_inclusive_into_the_trash(void) { int c; while ((c=getchar()) != EOF && c != '\n'); }
와 같은 함수를 사용하는 것이 좋습니다 - 물론, 함수명은 좀 줄여야 겠지
요. ;-)
그럼...
--
Jun, Woong (woong at gmail.com)
http://www.woong.org
=================================================================
OS-level input buffer..에 존재하는 모든 데이터를 flush시키기
#include <stdio.h>
int main()
{
int c;
while((c = getchar()) != '\0'){
putchar(c);
}
return 0;
}
이렇게 일반적으로 데이터를 입력받아 사용하는데요..
만약에,
c = getchar();
putchar(c);
로 단순히 코딩을 하고 실행시켜서 값을 입력하는데 있어서..
ABCD 를 입력하면 A만 출력이 되겠죠.
여기서, BCD는 OS-level input buffer에 존재한다고 들었는데요.
여기까지의 질문 내용에 문제가 없는지 일단 좀 가르쳐 주세요..
다음으로 OS-level input buffer라는 것이 무엇인지 알려주세요.
대충은 알겠습니다. input이 있으면 output도 있겠죠. 물론, 시스템에 따라
그 사이즈도 다르겠죠.
마지막으로 OS-level input buffer에 들어있는 나머지 데이터를 없에는
이식성을 가지는 방법은 없는지 알고 싶습니다.
그러니까?
c = getchar();
putchar(c);
요 코드 다음에 어떻게 하라.. 정도로 알려주시면 더욱 감사하겠습니다.
=================================================================
만약에,
c = getchar();
putchar(c);
마지막으로 OS-level input buffer에 들어있는 나머지 데이터를 없에는
이식성을 가지는 방법은 없는지 알고 싶습니다.
로 단순히 코딩을 하고 실행시켜서 값을 입력하는데 있어서..
ABCD 를 입력하면 A만 출력이 되겠죠.
여기서, BCD는 OS-level input buffer에 존재한다고 들었는데요.
아직 읽히지 않은 BCD가 존재하는 곳은 *일반적*으로 OS-level input buffer라고 하기보다는,
C Library Buffer라고 하는게 더 정확할 듯 싶습니다..
물론 일반적이지 않게, setvbuf()로 unbuffered로 설정할 수도 있지만요...
예를 드신게, getchar()와 같은 stdio, 즉 Stream I/O인 경우를 물어보셨으니 그에 대해 설명드리면...
Stream I/O는 다음과 같은 buffer 구조를 통해서 실제 데이타를 read/write하게 됩니다..
(Application) <--> (C Library Buffer) <--> (System Buffer) <--> (Device)
말씀하신데로, ABCD를 입력하게되면, "Input device"인 키보드로부터 ABCD가 입력되고, 이 ABCD값은 "System Buffer"를 거쳐,
"C Library Buffer"에 저장이 됩니다... 즉, 실제 getchar()는 "C Library Buffer"에서 읽어드리게 되는겁니다...
여담으로 output에 관련된 것이기는 하지만, fflush(3C)은 "C Library Buffer"의 내용을 "System Buffer"로 flush를 시키는 것이며,
sync(1)과 sync(2), fsync(3C)는 "System Buffer"의 내용을 "Device", 즉 Disk에 flush 시키는 것입니다.
(물론, sync(1)과 sync(2)는 그밖의 작업을 더 수행합니다.)
input이 있으면 output도 있겠죠.
output buffer가 input과 분리되어 존재하기보다는 하나의 buffer를 공유해, I/O가 같이 일어날 것으로 생각됩니다..
물론, 시스템에 따라
그 사이즈도 다르겠죠.
*순수한* 버퍼만의 사이즈는 512K(block사이즈)의 배수로, 리눅스와 솔라리스의 경우 8K로, AIX의 경우 4K로 알고 있습니다...
마지막으로 OS-level input buffer에 들어있는 나머지 데이터를 없에는
이식성을 가지는 방법은 없는지 알고 싶습니다.
이식성을 가지는 방법은 없습니다.
즉, C 표준에 input buffer를 clear시키는 방법에 대해서는 언급하고 있지 않습니다.
정 하고 싶으시다면, 위에 적으신 예제처럼, getchar()로 EOF까지 읽은뒤, 혹은 다른 방법으로 읽은 데이타를 버리는 수밖에 없습니다.
--------------------------
Donghyun Jung
=================================================================
eungkyu wrote:OS레벨이기보단 C의 standard IO libarary의 버퍼에 들어가겠죠. 물론, OS레벨에도 버퍼가 있긴 하겠지만, 그건 또 다른 문제고.
왜 그 버퍼를 비우려 하시는지는 모르겠지만, 프로그램이 끝나면 알아서 비워집니다 -_-;; 어떤 상황에서 왜 비우려 하시는지..
아래 부분의 내용을 확인하기 위해서 질문 드린 것입니다. 음.. 그렇네요. 라이브러리 버퍼겠죠. 프로그래이 끝나면 당연히 없어지겠죠.
저는 코드에서 버퍼에 남아있는 것을 플러쉬시키고 다시 받기 위해서 질문 드린 것입니다... ^^
standard io가 다루는 것은 stream입니다. stream은 물줄기처럼 밀려오고(입력) 또한 밀려나가는(출력) 것이죠. 물줄기와 현재 처리하는 위치가 있을 뿐이죠. 중간을 뚝 짤라버리는 행위는 스트림 처리에서 매우 이상한 행동입니다. 게다가, 버퍼라는 것은 standard library에서 성능을 위해서 제공하는 것으로서, 그 라이브러리를 사용하는자의 입장에서는 어느정도 transparency하게 내부적으로 제공되는 것입니다.
설사 입력 버퍼를 지우는 함수가 존재한다고 합시다. 지금이야 손으로 입력을 받는 프로그램이라서 그런 식으로 생각해도 말이 되는 듯 합니다. 하지만 상대가 파일이 되면 (progarm < file 이렇게 실행하면) 버퍼의 한계가 불명확해지고 버퍼를 비우면 얼마나 지워질지 짐작하기도 힘듭니다. (프로그램이 제대로 동작하지 않을 수 있다는 뜻입니다) 즉, 버퍼를 비우는 행위는 스트림 처리에서 매우 이상한, 말도 안되는 행동일 뿐더러 standard io library가 애써 만들어놓은 transparency도 깨뜨릴 수 있는 행위입니다. standard io library 내부를 다루는 프로그램이 아니라면, 버퍼를 건드리는 행위는 자제해야 합니다.
개념을 바꿔서 버퍼를 비운다보다는, 쓸데없는 입력을 무시한다로 생각하시면 프로그래밍하기가 편해집니다. 원래 스트림이 그런겁니다.