brunch

매거진 Sinclair

You can make anything
by writing

C.S.Lewis

by Sinclair Feb 07. 2016

함수와 표준 I/O

내가 그의 이름을 불러 주었을 때 III



Buffering과 Stream이라는 시냇물  



우리가 표준 입출력함수들과 더불어 알아야 할 것은 표준 입출력 Stream입니다.


쉽게 말해 Stream은 시냇물이고, 잘 알다시피 우리에겐 시냇물과 관련된 아주 유명한 노래도 있습니다. "시냇물은 졸졸 졸졸 고기들은 왔다 갔다~" 하지만 지금 우리의 Stream들은 물고기들의 시냇물이 아니라 데이터가 흘러가는 메모리에 존재하는 시냇물입니다. 그 시냇물에 데이터를 얹으면 고기들이 왔다 갔다 하듯이 데이터가 흘러 나가거나 흘러 들어옵니다. 프로그램은 Stream을 연결하기만 하면 각 디바이스 드라이버를 통해 데이터를 입출력 합니다. 멋지지 않습니까?

기본적으로 C언어는 다섯 개의 표준 Stream과 파일 Stream 만을 제공합니다.

모두 바이트단위로 처리하는 FILE *들 입니다.


FILE *는 파일들에 대한 메모리 정보를 담은 구조체에 대한 포인터로 파일을 다루며 다음에 자세히 설명할 것입니다.


*표가 붙은 Stream은 현재 DOS에서만 지원하고 있습니다.



표준 입출력 함수들은 주로 stdin과 stdout을 사용합니다. 출력용인 stderr과 stdout은 모두 모니터 출력입니다. 그렇다면 에러가 아닌 것을 stderr로 에러인 것을 stdout으로 출력하면 에러일까요? 사실 에러인지 아닌지는 컴퓨터가 어떻게 알겠습니까?

 

stderr과 stdout의 차이는 바로 buffering에 있습니다. stdout은 buffering을 하기 때문에 stdout을 사용하는 함수는 호출하는 즉시 바로 출력 되지 않습니다.


뭐요?!! buffering이요?


buffering은 또 무엇일까요?

그건 바로 buffer를 사용하는 일을 말합니다.


컴퓨터가 실행하는 모든 작업은 CPU가 제어해야 하는데 buffer는 고속의 CPU와 저속의 주변 장치 사이의 속도 차이 때문에 고가의 CPU가 오랫동안 I/O검사에 종속되는 문제를 해결하기 위해 OS가 사용하는 임시 기억장소를 말합니다. buffer를 사용하면 CPU는 그 동안 다른 작업을 할 수 있게 됩니다. 일반적으로 입력이 끝나거나 buffer가 꽉 차게 되면 OS는 CPU에게 알립니다. 실제로 scanf()나 printf()를 비롯 gets(), getchar(), putchar() 등의  C 표준 함수들은 모두 호출한다고 해서 호출한 바로 그 시점에서 입출력이 일어나지 않습니다.

어랏? 분명히 타이핑을 하면 화면에는 그 타이핑한 문자가 바로 바로 보이는 데 입력이 안 된다는 것은 무슨 황당한 소리일까요? 그것은 타이핑하고 있는 순간에는 CPU와는 관계없이 buffer로 입력되기 때문입니다. 그러니 현재 화면에 보이는 문자들은 아직 입력되지 않은 문자들입니다. 최종적으로 enter키를 입력하게 되면 입력이 끝났다는 것을 인식하고 바로 그때 buffer에 있는 데이터를 입력 함수들이 실제 데이터를 저장할 메모리로 읽어옵니다. 그러니 scanf()는 키보드로부터 데이터를 읽는 것이 아니라 stdin이라는 stream으로부터 읽어 오는 함수입니다.


일반적으로 표준 입력함수를 사용하면 buffer가 가득 차거나 enter키를 입력하기 전까지는 입력 함수들은 동작하지 않습니다.


 



getchar()함수를 썼다고 한 문자만 타이핑할 수 있는 게 아닙니다.

많은 문자들을 타이핑해도 결국 한 문자만 buffer에서 읽어 갑니다. 그래서 나머지 문자들은 buffer에 그대로 남아 있게 되고 결국 다음에 다른 입력 함수를 호출하면 키보드 타이핑을 안 했음에도 불구하고 buffer의 값들을 읽어가는 불상사가 발생하고 맙니다.


출력하는 함수도 마찬가지입니다. stdout을 사용하는 printf()는 호출하는 순간 바로 모니터로 출력되는 것이 아니라 stdout으로 보내집니다. '\n'문자를 만나거나 fflush(stdout) 함수로 버퍼를 비우면, 또는 프로그램이 정상 종료되면 모니터로 출력되는 것입니다.


기억하세요. scanf()는 키보드가 아니라 stdin으로부터 입력되는 함수이며 printf()함수는 모니터가 아니라 stdout으로 출력한다는 것입니다.


그러나 stderr은 buffering을 하지 않고 바로 출력을 합니다. 아래 두 프로그램을 비교 해 보기 바랍니다. 꼭 직접 타이핑하여 결과를 확인해 보길 바랍니다. 똑같이 나온다구요? 왜 그럴까요? 아, 다른게 나온다고요? 그건 또 왜 그럴까요? (아직 배열을 모른다구요? 아, 그러면 나중에 배열을 배우고 나서 다시 한번 보길 바랍니다. 꼭이요!!!!)




stdout을 사용하여 배열을 출력하는 프로그램  


#include <stdio.h>  

/*

* copyleft (l) 2006 - 2017 programmed by Sinclair

*/   

main() {

        int array[10] = { 10,9,8,7,6,5,4,3,2,1 } ;

        register int i ; // for index

        for( i = sizeof array / sizeof *array ; i > 0 ; ) {

            printf("%d\t" , array[--i]) ;

            // printf()는 stdout을 사용합니다.

            // fprintf(stdout , "%d\t" , array[--i]) ;

            // 과 같은 동작을 합니다

            sleep(1) ; // 표준함수가 아니어서 제공되지 않을 수도 있습니다.

        } // end for

        putchar('\n') ;  

        return 0 ;  

} // end main()    




stderr을 사용하여 배열을 출력하는 프로그램  


#include <stdio.h>  

/*

* copyleft (l) 2006 - 2017 programmed by Sinclair

*/   

main() {

        int array[10] = { 10,9,8,7,6,5,4,3,2,1 } ;

        register int i ; // for index

        for( i = sizeof array / sizeof *array ; i > 0 ; ) {

            fprintf(stderr , "%d\t" , array[--i]) ;

            // fprintf()는 stream을 지정해야 합니다.

            sleep(1) ; // 표준함수가 아니어서 제공되지 않을 수도 있습니다. 

        } // end for

        putchar('\n') ;  

        return 0 ;  

} // end main()  



고민해 보니 이제 그 차이를 알 수 있을 것입니다. 이번에는 stdin을 사용하는 scanf() 함수의 문제점을 알아보도록 하겠습니다. buffering은 잘 알고 사용하면 우리에게 많은 유익함을 주지만 잘 모르고 사용하면 원하지 않는 결과를 불러 들입니다.




입력 버퍼링 문제  


#include <stdio.h>  

/*

* copyleft (l) 2006 - 2017 programmed by Sinclair

*/   

main() {

        int array[10] = { 10,9,8,7,6,5,4,3,2,1 } ;

        register int i ; // for index

        for( i = sizeof array / sizeof *array ; i > 0 ; ) {

            printf("정수형 데이터를 입력하세요... ") ;

            scanf("%d" , &array[--i] ) ;  

        } // end for  

        for( i = sizeof array / sizeof *array ; i > 0 ; ) {

            printf("%d\t" , array[--i] ) ;

        } // end for

        putchar('\n') ;  

        return 0 ;  

} // end main()    



데이터를 입력할 때 한 개씩 입력하지 말고 한줄로  1 2 3 4 5 6 7 8 9 10 이렇게 공백으로 구별하여 한꺼번에 데이터를 입력해 보면 입력 buffering을 확인할 수 있습니다. scanf()는 결코 키보드로부터 입력 받지 못합니다. stdin buffer로부터 입력을 받습니다.


이러한 입력 buffering 문제를 해결하기 위해 fflush(stdin)을 사용한다면 일부 컴파일러에서는 동작하지 않는 것을 볼 수 있습니다. 그 이유는 fflush(stdin)는 ANSI 표준에 위배 되기 때문입니다.

표준 C에 의하면 fflush() 함수는 출력 buffer나 update buffer 용으로 한정되어 있습니다. 때문에 동작하는 컴파일러는 오히려 표준을 위배한 셈이 되는 것입니다. 그렇다면 이러한 입력 buffering 문제를 어떻게 해결해야 할까요?


while( getchar() ^ '\n' ) ;


getchar()함수는 역시 키보드가 아닌 stdin으로부터 한 바이트를 읽어옵니다. ^ 연산자에는 같지 않은지 비교하는 기능이 있다고 했습니다. '\n'는 stdin으로 입력되는 enter키 값입니다. 결국 stdin에 존재하는 입력된 데이터를 한 바이트씩 읽어서 '\n'인지 비교하여 '\n'문자일 때까지 반복합니다. 왜 하필 '\n'일까요? 그것은 '\n'가 buffer입력의 끝을 알려주기 때문입니다. 때문에 '\n'는 입력의 마지막이라 할 수 있습니다.

그 동안의 키보드 입력을 생각해보면 항상 enter로 입력이 끝났다는 것을 알 수 있습니다. 거의 모든 프로그램들이 enter를 입력할 때까지 아무 동작도 안하고 있습니다. 아닌가요?


이 한 줄의 소스는 모든 문제를 다 해결한 단계는 아니지만 적절히 사용한다면 앞에서 호출된 입력 함수가 남긴 쓰레기 값들이 현재 입력으로 들어가는 입력 buffering 문제를 효과적으로 해결해 줄 것 입니다.












#Sinclair #씽클레어 #싱클레어 #씽클레어도씨 #씨언어 #씨프로그래밍  #C언어 #Cprogramming #C_Programming #C #Programming #Clanguage #C_Language

매거진의 이전글 함수와 표준 I/O
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari