저번에 입력스트림과 출력스트림에 대해서 말씀드렸습니다. 우리가 키보드로 데이터를 입력하면 입력스트림을 타고 데이터가 들어가 프로그램에 읽혀진다고 했습니다. 하지만 우리가 키보드를 두드리는 족족 입력스트림이 하나하나 가져가는 행동이 일어난다면 CPU사용량은 엄청 올라갈것이고 이는 곧바로 프로램의 속도저하로 이어집니다. 왜냐하면 사실 입력과 출력의 행위는 생각보다 걸리는 시간이 길기 때문입니다. 따라서 데이터가 발생하는 족족 입출력이 일어난다면 프로그램은 엄청 느려질겁니다. 이를 방지하기 위해서 사실 스트림의 중간에 버퍼라는 것을 둡니다. 버퍼란 간단하게 말해 데이터들이 임시로 거쳐가는 메모리 저장소라고 보면돼요.

집을 짓는 공사장이 있다고 칩시다. 흙이 쌓여있는 곳에서 흙이 필요한 장소로 흙을 옮겨야하고 이는 50m정도가 된다고 생각해 봅시다. 이 때 한 삽 한 삽 뜨는 족족 흙을 이동하는것과 바퀴가 달린 구루마에 일정량을 모아서 옮기는 것 중 어떤게 효율적일까요? 후자가 훨씬 효율적입니다. 이 구루마에 해당하는 것이 버퍼입니다.

우리가 키보드를 사용해서 데이터를 입력하다가 엔터키가 입력되는 순간 데이터들이 버퍼로 이동합니다.

buffer

키보드와 모니터로 이루어진 콘솔 입출력에서 fgets()함수나 scanf() 함수등을 써서 사용자로부터 데이터를 입력받을 때 우리가 엔터를 누르기 전까지는 아무 일도 일어나지 않은 이유는 바로 버퍼 때문입니다. 프로그램은 버퍼로부터 데이터를 읽어들이는데 우리가 엔터, 즉 '\n' 이 읽히기 전까지는 우리가 입력한 데이터들이 버퍼에 들어가지 않기 때문입니다.

이 버퍼때문에 일어나는 C언어의 골치아픈 문제가 몇 개 있는데 코드를 통해 한 번 봅시다.

#include <stdio.h>

int main(void)
{
    char str1[50];
    char str2[50];

    printf("Type a sentence : ");
    scanf("%s", str1);
    printf("sentence 1 : %s\n", str1);

    printf("Type a sentence : ");
    scanf("%s", str2);
    printf("sentence 2 : %s\n", str2);

    return 0;
}

위의 코드를 통해서 우리는 문장을 두 번 입력받아 각각 str1과 str2에 저장하려 합니다. 하지만 출력결과는 우리 의도와 다르게 나올겁니다.

실행결과
Type a sentence : what the
sentence 1 : what
Type a sentence : sentence 2 : the
계속하려면 아무 키나 누르십시오 . . .

첫 번째 문장으로 what the를 입력했는데 str1에 what만 담기고 나머지가 str2에 저장되어 버렸습니다. 이유를 알기위해서 scanf함수가 언제 데이터를 가져와 저장하는지 알아야 합니다.

scanf()함수는 띄어쓰기, 개행, 탭 등의 공백문자를 기준으로 데잍터를 구분하여 가져옵니다. 그래서 str1에는 "what" 까지만 저장이 되고 버퍼에는 "the" 문자열이 남아있는 상태가 됩니다. 그 다음 str2에 "the"가 저장이 되고 그 다음 "\n"이 나와서 저장은 끝이 납니다.

예제를 하나 더 봅시다.

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

    fputs("Type sentence1 : ", stdout);
    fgets(str1, sizeof(str1), stdin); //null문자 제외 문자 10개 저장 가능

    fputs("Type sentence2 : ", stdout);
    fgets(str2, sizeof(str2), stdin); //null문자 제외 문자 10개 저장 가능

    printf("sentence1 : %s\n", str1);
    printf("sentence2 : %s\n", str2);

    return 0;
}

위의 코드를 작성함으로써 우리가 바라는 것은 문장을 입력할 기회를 두 번 가지고 각각의 입력한 문장을 마지막에 printf함수를 이용해서 출력해 보는 것입니다.

실행결과

Type sentence1 : 123456789

Type sentence2 : sentence1 : 123456789

계속하려면 아무 키나 누르십시오 . . .

null문자 포함 10개의 문자를 str1에 저장하고 이어서 str2에도 문자열을 저장하려 했는데. 두 번째 문자열은 저장할 기회조차 없었습니다.

사실 우리가 처음 입력한 문장은 엔터키를 치는 것까지 포함하여 "123456789\n" 입니다. \n이 입력되는순간(엔터키를 치는 순간) 버퍼로 123456789\n이 이동되고 fgets 함수는 입력버퍼에서 123456789 9개만 읽어들이고 마지막 하나는 null문자로 채웁니다. 그럼 버퍼에 아직 \n이 남아있는겁니다. 이어서 두 번째 fgets 함수가 실행되면 아직 버퍼에 \n이 남아있는것을 보고 바로 가져와서 str2에 저장합니다. str2에는 \n이 저장되어 있는겁니다. 따라서 마지막 문장 sentence2 : 다음에는 printf문에 포함된 \n에 의해 한 번, str2에 저장된 \n이 %s를 통해 또 한 번 출력이 되어 개행이 총 두 번 이뤄진겁니다.

위에서 예로 든 코드들을 보시면 아시겠지만 우리는 이처럼 원하는 출력결과가 나오게 하거나 그러지 않았을 때 이유를 파악하기 위해서는 입출력 스트림과 버퍼에 대한 개념을 잘 알아두실 필요가 있습니다.

버퍼에 관련된 함수들 중에서 버퍼를 비울 수 있는 함수가 있습니다.

출력버퍼를 비우는 함수 : fflush

위의 함수는 출력버퍼를 비워주는 역할을 합니다. 출력버퍼를 비운다는 것은 강제로 출력버퍼에 남아있는 데이터들을 출력스트림으로 이동시키는 것을 뜻해요. 가령 fflush(stdout) 이라는 문장을 실행시키면 표준출력 스트림을 통해서 데이터를 모두 이동시키라는 의미입니다.

입력버퍼를 비우는 함수

입력버퍼를 비우는 함수는 따로 존재하지 않습니다. 하지만 간단히 만들어서 사용할 수 있습니다. 위에서 예제로 들었던 코드를 입력버퍼를 비우는 함수를 추가하고 실행해봅시다.

#include <stdio.h>

void Clear(void)
{
    while(getchar()!="\n")
    {
        //입력버퍼에서 \n을 가져올 때 까지 getchar함수로 데이터를 읽어들임

    }
}

int main(void)
{
    char str1[10];
    char str2[10]

    fputs("Type sentence1 : ", stdout);
    fgets(str1, sizeof(str1), stdin);

    Clear(); // 입력버퍼 비우기

    fputs("Type sentence2 : ", stdout);
    fgets(str1, sizeof(str2), stdin);

    printf("sentence1 : %s\n", str1);
    printf("sentence2 : %s\n", str2);

    return 0;
}

실행결과

Type sentence1 : 123456789

Type sentence2 : 987654321

sentence1 : 123456789

sentence2 : 987654321

계속하려면 아무 키나 누르십시오 . . .

아까와는 다르게 입력버퍼에서 "\n"을 소멸시켜주고 실행해보니 원하는 결과를 얻었습니다.

'프로그래밍 언어 > C' 카테고리의 다른 글

(C언어) 39 - 구조체(2)  (0) 2020.05.14
(C언어) 38 - 구조체(1)  (0) 2020.05.14
(C언어) 36 - string  (0) 2020.05.14
(C언어) 35 - 문자열 입출력 함수들  (0) 2020.05.14
(C언어) 34 - 문자 입출력 함수들  (0) 2020.05.14

+ Recent posts