Programming/C

C언어 - 표준 입출력과 버퍼

JunsuKim 2021. 10. 11.
728x90

앞에서 공부했던 printf, scanf, putc, getc 등의 입출력 함수들을 "표준 입출력"이라 한다.

ANSI C의 표준에서 정의된 함수이기 때문이다.

입출력 함수들에 대해 잘 모르겠다면

https://jjunsu.tistory.com/4?category=971045 

 

C언어 - 헤더파일, printf, scanf

헤더파일 선언의 필요성 c언어를 사용하며 프로그래밍을 한다면 prinf, scanf라는 함수를 사용하게 된다. 이러한 표준 함수를 호출하기 위해서는 함수와 관련된 헤더파일을 선언해야 한다. #include

jjunsu.tistory.com

https://jjunsu.tistory.com/27?category=971045 

 

C언어 - 문자와 문자열 함수

getchar, putchar getchar와 putchar는 문자 입출력 함수이다. getchar는 문자 입력 함수이며, stdin으로 표현되는 표준 입력 스트림으로부터 하나의 문자를 입력받아 반환한다. 함수 호출을 성공할 시 쓰인

jjunsu.tistory.com

위 글들을 한번 보는 것을 추천한다.

표준 입출력 기반의 버퍼

표준 입출력 함수를 통해 데이터를 입출력하는 경우, 해당 데이터들은 운영체제가 제공하는 메모리 버퍼를 통과하게 된다.(메모리 버퍼는 데이터를 임시로 저장하는 메모리 공간이다.)

위 그림을 보자.

키보드를 통해 입력되는 데이터는 입력버퍼에 저장된 다음, 프로그램에서 읽히게 된다.

gets 함수를 사용했을 때 읽어 들이는 문자열은 입력버퍼에 저장된 문자열인 것이다.

키보드로 입력된 데이터가 입력 스트림을 거쳐 입력버퍼로 들어가는 시점은 우리가 엔터(Enter)키를 누를 시점이다.

엔터키를 누르기 전엔 문자열을 읽어 들이지 못하는 것이다.

버퍼링을 하는 이유

데이터를 목적지로 바로 전송하지 않고 입출력 버퍼를 둬서 전송하고자 하는 데이터를 임시 저장하는 이유는

"데이터 전송의 효율성"과 관련이 있다. 키보드, 모니터와 같은 외부 장치에서의 입출력은 생각보다 시간이 많이 필요한 작업이다. 따라서 버퍼링 없이 키보드가 눌릴 때마다 눌린 문자의 정보를 목적지로 바동 이동시키는 것보다 중간에 메모리 버퍼를 둬 데이터를 한 곳에 묶어 이동시키는 것이 효율적이고 빠르다.

쉽게 생각하여 많은 물건을 옮길 때 한 개씩 옮기는 것보다 수레에 가득 채워 한번에 옮기는 것이 더 빠르고 효율적인 것과 같다.

출력 버퍼를 비우는 fflush 함수

출력버퍼가 비워지는 것은 "출력버퍼에 저장된 데이터가 버퍼를 떠나 목적지로 이동했다."를 뜻한다.

그러나 출력버퍼가 비워지는 시점은 시스템과 버퍼의 성격에 따라 달라진다.

버퍼가 꽉 찼을 때 비워지는 버퍼, 하나의 문장이 완전히 입력됐을 때 비워지는 버퍼 등 비워지는 시점이 동일하지 않기 때문에 fflush 함수를 알아두는 것이 좋다.

fflush 함수는 인자로 전달된 스트림의 버퍼를 비우는 역할을 한다.

따라서 출력버퍼를 비우기 위해서는 다음과 같이 호출하면 된다.

fflush(stdout);

이렇게 하면 시스템과 버퍼에 상관없이 버퍼에 저장된 내용이 비워지면서 데이터가 목적지로 이동하게 된다.

입력버퍼를 지우는 방법

입력버퍼를 비우는 것은 출력버퍼를 비우는 것과 개념적으로 차이가 있다.

출력버퍼를 비우는 것은 저장된 데이터가 목적지로 전송된 것을 의미하지만, 입력버퍼를 비우는 것은 데이터의 소멸을 의미한다.

fflush 함수는 출력버퍼를 대상으로 호출하는 함수여서 입력버퍼를 지우는 용도로 사용하고 있지 않다.

다음 예제를 보자.

#include<stdio.h>

int main() {
    char perID[7];
    char name[10];

    fputs("주민번호 앞 6자리 입력: ", stdout);
    fgets(perID, sizeof(perID), stdin);

    fputs("이름 입력: ", stdout);
    fgets(name, sizeof(name), stdin);

    printf("주민번호: %s\n", perID);
    printf("이름: %s\n", name);
    return 0;
}

주민번호의 앞 6자리를 저장해야 하므로 perID의 길이를 7을 주었다.

그러나 입력을 6자리로만 했는데도 문제가 생긴 것을 알 수 있다.

문제가 발생한 이유는 엔터키를 포함한 7문자가 입력되었기 때문이다. 8행을 보면 fgets 함수의 인자로 7이 전달되어 널문자를 제외한 6문자를 읽어 들이는데, 여기서 \n은 입력버퍼에 남아있게 되는 것이다. 11번째 행에서 fgets 함수가 한번 더 호출되는데 fgets 함수는 \n을 만날 때마다 읽어 들여 두 번째 fgets 호출 때는 \n만을 잃어버리는 것이다.

이를 해결하기 위해서는 입력버퍼에 남아있는 \n 문자 하나만 지워버리면 된다.

void ClearLineFromReadBuffer() {
    while(getchar() != '\n');
}

ClearLineFromReadBuffer라는 함수를 만들어 \n문자를 만나기 전까지만 읽어 들이게 하였다. 

위 함수를 추가한 완성된 코드를 보자.

#include<stdio.h>

void ClearLineFromReadBuffer() {
    while (getchar() != '\n');
}

int main() {
    char perID[7];
    char name[10];

    fputs("주민번호 앞 6자리 입력: ", stdout);
    fgets(perID, sizeof(perID), stdin);

    fputs("이름 입력: ", stdout);
    fgets(name, sizeof(name), stdin);

    printf("주민번호: %s\n", perID);
    printf("이름: %s\n", name);
    return 0;
}

입력버퍼에 남아있는 \n을 지워버려 정상적으로 입출력이 이뤄지는 것을 알 수 있다.

728x90

댓글