본문 바로가기
Language&Framework&Etc/C

문자와 문자열 관련 함수(21-4) 표준 입출력과 버퍼

by 머리올리자 2020. 12. 11.

표준 입출력 기반의 버퍼

지금까지 공부해 온 입출력 함수들 : '표준 입출력 함수'

ANSI(American National Standards Institute) C의 표준에서 정의된 함수이기 때문

printf와 scanf 그리고 fputc와 fgetc도 모두 표준 입풀력 함수

 

이러한 표준 입출력 함수를 통해서 데이터를 입출력 하는 경우, 해당 데이터들은 운영체제가 제공하는 '메모리 버퍼'를 중간에 통과

 

메모리 버퍼 : 데이터를 임시로 모아두는(저장하는) 메모리 공간

 

키보드를 통해 입력되는 데이터 → 입력버퍼에 저장(버퍼링)  → 프로그램에서 읽혀짐

 

fgets 함수가 읽어 들이는 문자열은 입력버퍼에 저장된 문자열

 

키보드로부터 입력된 데이터가 입력 스트림을 거쳐 입력버퍼로 들어가는 시점?

 

엔터 키가 눌리는 시점

 

그래서 키보드로 아무리 문자열을 입력해도 엔터 키가 눌리기 전에는 fgets 함수가 문자열을 읽어 들이지 못하는 것

 

엔터 키가 눌리기 전에는 입력버퍼가 비워져 있다.

 

버퍼링(buffering)을 하는 이유?

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

 

'데이터 전송의 효율성'과 관련이 있음

 

키보드나 모니터와 같이 외부 장치와의 데이터 입풀력은 생각보다 시간이 많이 걸리는 작업

따라서 버퍼링 없이 키보드가 눌릴 때마다 눌린 문자의 정보를 목적지로 바로 이동시키는 것보다

중간에 메모리 버퍼를 둬서 데이터를 한데 묶어서 이동시키는 것이 보다 효율적

 

ex) 창고에 물건을 하나씩 나르는 것보다 손수레에 가득 채워서 나루는 게 더 효율적

 

출력버퍼를 비우는 fflush 함수

출력버퍼가 비워진다는 것 : 출력버퍼에 저장된 데이터가 버퍼를 떠나서 목적지로 이동됨

 

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

 

버퍼가 꽉 찼을 때 비워지는 버퍼도 있고, 하나의 문장이 완전히 입력되었을 때마다 비워지는 버퍼도 있다.

 

이렇듯 버퍼가 비워지는 시점은 동일하지 않기 때문에 다음 함수를 알아 둘 필요가 있다.

#include <stdio.h>
int fflush(FILE * stream);

- 함수호출 성공 시 O, 실패 시 EOF 반환

 

위는 인자로 전달된 스트림의 버퍼를 비우는 기능을 제공

 

fflush(stdout); // 표준 출력버퍼를 비워라!

어떠한 시스템의 어떠한 표준 출력버퍼라 할지라도 버퍼에 저장된 내용이 비워지면서 데이터가 목적지로 이동한다.

 

위의 함수는 파일을 대상으로도 호출이 가능.

 

입력버퍼는 어떻게 비움?

'입력버퍼의 비워짐' 과 '출력버퍼의 비워짐'과 개념적으로 차이가 있음

'출력버퍼의 비워짐' : 저장된 데이터가 목적지로 전송됨

'입력버퍼의 비워짐' : 데이터의 소멸

 

가끔 입력버퍼에 남아있는 불필요한 데이터의 소멸을 위해 입력 버퍼를 비워야 하는 경우가 종종 있다.

 

그렇다면 이렇게 비워야 함?

fflush(stdin); // 어던 의미로 해석?

fflush 함수는 출력버퍼를 대상으로 호출하는 함수

정확히는, C언어 표준에서 위의 결과에 대해 정의하고 있지 않다.

 

따라서 위의 함수호출 결과는 예측이 불간으.

 

물론 일부 컴파일러는 위의 형태로 함수가 호출되었을 때 입력버퍼를 비워주기도 한다.

하지만 그 이외의 컴파일러들은 전혀 다른 결과를 보임

 

예제로 고민.

#include<stdio.h>

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

	fputs("주민등록 번호 앞 6자리 입력 : ", stdout);
	fgets(perID, sizeof(perID), stdin); // stdin으로부터 문자열을 입력 받아서 배열 perID에 저장하되 sizeof(perID)의 길이만큼 저장

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

	printf("주민등록번호 : %s \n", perID);
	printf("이름 : %s \n", name);

	return 0;
}

  • 이름을 입력할 수 없었다.
  • 왜냐?
  • 입력한 데이터는 아래와 같다.
  • 010101」(enter)
  • 엔터키를 포함하여 총 7문자가 입력
  • 그런데 fgets 함수의 인자로 7이 전달되었으니, 널 문자를 제외하고 최대 6문자를 읽어 들인다.
  • 따라서 \n을 제외한 나머지 여섯 문자만 읽혀지고 \n은 입력버퍼에 남아 있음.
  • 다음, 이름 입력을 위한 fgets 함수는 \n를 만날 때까지 읽어 들이는 함수이니, 버퍼에 남아있는 \n만 읽어버리고 만다.
  • 때문에 위와 같은 실험 결과가 나옴

또 다른 예시

  • 주민등록 번호 앞 6자리만 입력하라고 했는데, -를 포함하여 총 14자리의 주민번호를 전보 입력하였다.
  • 그래서 fgets 함수호출을 통해서 여섯 개의 문자가 읽혀지고, 다음 fgets 함수호출을 통해서 나머지 문자들이 읽혀진 것.
  • 이렇듯 명시한대로 행동하지 않는 프로그램 사용자를 고려한다면, 주민등록번호 앞 6자리를 제외한 나머지 문자들을 입력버퍼에서 지워줘야 한다.

다음을 이용해서 입력 버퍼 삭제

void clearlinefromreadbuffer(void)
{
	while(getchar()!='\n')
}
#include<stdio.h>

void clearlinefromreadbuffer(void);

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

	fputs("주민등록 번호 앞 6자리 입력 : ", stdout);
	fgets(perID, sizeof(perID), stdin); // stdin으로부터 문자열을 입력 받아서 배열 perID에 저장하되 sizeof(perID)의 길이만큼 저장
	clearlinefromreadbuffer();

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

	printf("주민등록번호 : %s \n", perID);
	printf("이름 : %s \n", name);

	return 0;
}

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

정의한 함수는 입력버퍼를 통째로 비우는 함수가 아니라, \n이 읽혀질 때까지 입력버퍼에 저장된 문장들을 지우는 함수.

 

프로그램 사용자가 잘못 입력을 해도, 필요한 만큼만 읽어 들이고 나머지는 지워버리기 때문에 위에서 보이는 바와 같이 정상적으로 동작

 

 

참고 : [윤성우 열혈 C 프로그래밍] - 대부분의 내용 및 코드는 이 책에서 개인 공부 정리 목적으로 참고하였습니다.