본문 바로가기

공부(IT)/c

[c] 버퍼문제 1

출처 : http://kin.naver.com/qna/detail.nhn?d1id=1&dirId=1040101&docId=214854487&sp=2&sid=oTchTn5jxw%2BfacTCZPhItA%3D%3D&rank=1&pid=Sr3DVsoRR1lssvCCkTVsssssssd-268661&search_sort=0&qb=Y+yWuOyWtCDrsoTtjbzsnZgg7YGs6riw&spq=0&section=kin&enc=utf8

 

---------------------------------------------------------------------------------------

질문 :

#include <stdio.h>

int main()
{
 int ch=0;
 int count=0;
 while(ch != EOF)
 {
  fputs("Data input (Ctrl+Z to exit) : ",stdout);
  ch = getchar();
  if(ch != EOF)
   count++;
 }
 printf("입력된 문자의 수 : %d",count);
 return 0;
}


이코드를 실행시켜서 A라는 문자를 입력하면

Data input (Ctrl+Z to exit) : Data input (Ctrl+Z to exit) : 이런 문자가 출력됩니다.



이 뜻은 입력버퍼에 Data input (Ctrl+Z to exit) : 라는게 남아있었다 라는 해석이 맞나요??

그리고 저는 A라는 문자 밖에 입력하지않았는데 어떻게 Data input (Ctrl+Z to exit) :  라는 문자(열)이

남아있는지 궁금합니다. 또 입력버퍼를 비우는 방법이 fflush(stdin) 이 아니라는데

그거는 왜그런건가요?? 다른방법은 또 무엇인가요?????


친절하고 쉽게 알려주시면 감사하겟습니다!!!!!

-----------------------------------------------------

답변 :

제가 지식백과에 작성했던 내용입니다. 입력버퍼에 대한 내용과 fflush(stdin)에 대한 내용이 있으니 한번 읽어보시면 도움이 될듯하네요.( 링크와 원문을 같이 달아드릴게요 )


http://kin.naver.com/open100/detail.nhn?d1id=1&dirId=104&docId=1450267


C언어로 입.출력에 관한 프로그래밍을 하다보면 해당 버퍼에 남아 있는 데이터가 있어서 이후 정상적인 입.출력에

의도치 않은 값으로 출력될 때가 있습니다.

 

이러한 문제는 정수형의 데이터를 입력 받을때는 버퍼에서 입력된 값을 '\n'를 만날때까지 입력받아 숫자(내부적으로는 2진수입니다.) 형태로 자료형이 할당된 주소에 저장됩니다. 그렇기 때문에 입력 과 출력에 문제가 발생되지 않습니다.

'\n'까지 읽어들여서 버퍼를 삭제해 주기 때문입니다.

 

그런데 문자나 문자열을 입력받는 형태에는 문제가 발생합니다.

문자를 입력받을때는 입력버퍼에서 문자에 해당하는 1바이트의 크기만 꺼내오게 되고

문자열을 입력받을때는 입력받은 버퍼에서 문자열의 형태에서 NULL을 제외한 크기만큼을 읽어 들입니다.

 

이문자열의 형태가 이해되지 않으시면 문자열 부분을 다시한번 확인해 보셔야합니다.

 

이러한 경우에 대해서 문제가 발생하게 됩니다.

c를 입력할경우 입력버퍼에는 c와 '\n'가 들어오게 됩니다.

'\n'도 하나의 문자입니다. NULL문자로 처리가 됩니다.

 

입력버퍼에서 char에 해다하는 1바이트를 꺼내서 해당 변수에 대입하고 정상 입력 완료 처리를 하게 됩니다.

하지만 입력버퍼에는 아직 '\n'값이 남아 있기때문에 이후 입력을 받을때 값이 입력된 것으로 착각을 하고

엔터값을 꺼내오고 입력이 완료된 것으로 간주하게 됩니다.

 

아래와 같은 코드가 이러한 문제를 야기하게 됩니다.

 



 

출력결과

 

1을 입력하고 one을 출력후에 엔터값을 읽어 들인후 입력값이 없다고 간주하고

default를 실행해서 Wrong을 출력후에 입력을 기다리게 됩니다.

 

문자열을 입력받을때와 이와 동일한 현상이 발생합니다.

아래는 정상코드 입니다.

 


 

 #include <stdio.h>

void main( void )
{
    char cnum[10];

    while (1)
    {
        printf("please input string : ");
        scanf("%s", cnum);

        printf("%s\n",cnum);

    }
}



 

출력 결과

 

입력완료후 abc를 출력하고 정상완료 했습니다.

 

그렇다면 문자열은 어떨때 문제가 발생할까요

아래 같은 코드는 입력버퍼에 값이 남아서 문제가 발생합니다.

 

#include <stdio.h>

void main( void )
{
    char cnum[10];

    while (1)
    {
        printf("please input string : ");
        fgets(cnum,6,stdin );

        printf("%s\n",cnum);

    }
}



 

 

출력 결과

 

이러한 코드는 문제가 발생합니다.

위의 코드는 입력버퍼에서 6버퍼 까지만 읽어라 라고 해석을 하실지 모르겠지만 문자열은 끝에 NULL이 들어가야

하기때문에 5바이트 까지만 읽어 들입니다.

그래서 abcde까지만 읽어 들입니다.

 

입력 버퍼에는 아직 f123이 남아있습니다.

 

다음 입력에 입력 버퍼에 값이 남아있기 때문에 그값을 읽어 들인후 그냥 출력하는데 값이 제대로 출력되지 않습니다.

 

문자와 문자열 입력시 이러한 문제는 입력버퍼를 삭제해 버리는 것으로 해결이됩니다.

 

버퍼를 강제로 비워주는 함수는 대표적으로 fflush가 있습니다.

 

포함 헤더

#include <stdio.h>

 

함수 프로토 타입

int fflush( FILE * stream );

성공시 0, 실패시 EOF 반환

 

 

보통 scanf, fgets등의 입력함수를 읽고 난후 버퍼를 지울때

fflush( stdin ) : 입력 버퍼 지우기 ( Visual Studio 에서만 정상 동작 )

fflush( stdout) : 출력 버퍼 지우기

fflush( stderr) : 에러 버퍼 지우기

 

보통 scanf, fgets 등의 입력 함수를 이용해 데이터를 입력받고난 후 다음 데이터를 입력받기 전에 버퍼에 남아 있는 데이터를 지우기 위해 fflush( stdin ) 코드를 적어서 입력버퍼의 내용을 지우는데 많이 사용됩니다.

 

그러나 fflush( stdin )과 같이 사용하는것은 잘못된 방법이며 위험한 방법입니다.  

 

왜 이함수가 위험 할까?

 

fflush함수는 함수의 인자에 해당하는 출력버퍼 를 비우는 역활을 하는 함수입니다.

여기에서 주목할 것이 출력버퍼 입니다. 출력버퍼를 비운다는 말은 기본 출력 (모니터,프린터)

등으로 해당 버퍼를 전송하고 비운다는 의미 입니다.

 

즉 fflush( stdin ) 이렇게 입력 버퍼를 인자로 넣어 주는 것은 잘못된 방법이라는 것입니다.

하지만 많이 사용하는 컴파일러인 Visual Studio에서는 이방법을 예외적으로 인정해 주는것입니다.

그러니 다른 컴파일러 환경에서는 동작을 할지 안할지 모르는 형태의 코드가 되는 것입니다.

 

저도 이런 코드를 많이 작성 해왔습니다. 교육 할때도 가장 편한 방법이라 이야기는 하지만 이코드는 다른 환경에서는

동작하지 않을수 있다고 이야기 합니다.

 

그렇다면 어떻게 해야 입력 버퍼를 지워줄수 있을까

 

그냥 '\n'을 만날때까지 읽어서 비워주면 되는 아주 간단한 문제 입니다.

getchar 또는 getch()함수를 이용하는 방법인데요

getch( 문자변수) 가 들어가야 하기 때문에 getchar() 함수가 더 편합니다.

 

그냥 한줄의 코드만 추가하면 싹 지워집니다.

while(getchar()!='\n');

 

이코드가 문자를 지워주는 코드입니다. 이코드는 어느 컴파일러 에서도 정상 동작 합니다.

 

아래 코드를 확인해 주세요


 

 #include <stdio.h>

void main( void )
{
    char cnum;

    while (1)
    {
        printf("please enter between 0~9 : ");
        scanf("%c", &cnum);
        
        //< 입력 버퍼 지우기(간단한 한줄코드)
        while(getchar()!='\n');
        switch (cnum)
        {
        case '1': printf("one\n" );  break;
        default: printf("Wrong\n");  break;
        }

    }
}



 

출력 결과


 

 

정상적으로 동작하는 것을 알수 있습니다.

 

위에서 설명한 코드 모두 입력후에 이코드를 실행해주면 정상적으로 동작합니다.

 

어떠한 코드라도 표준에서 동작하는 코드를 작성하는 것이 좋습니다.

 

 

 

 

 

 

------------------------------------

추가:

while(stdin->_cnt){getchar();}

가 좀더 안전하다고 생각한다.

참고 : - FILE의 멤버 _base  , _ptr, _cnt  : _base는 버퍼 시작 주소, _ptr은 처리할 stream시작 주소, _cnt는 아직 처리되지 않은 stream내의 아스키코드 개수.