[Linux] C 표준 입출력 라이브러리 함수 (1)

개요

 

ㆍ 표준 입출력 라이브러리를 사용하기 위해서는 stdio.h를 포함시켜야 한다.

#include<stdio.h>

ㆍ 표준 입출력 라이브러리는 고수준 입출력 함수이다. 참고로 저수준 입출력 함수는 system call(시스템 호출 함수)를 의미한다.

ㆍ고수준 함수는 스트림형 구조체(FILE, DIR)를 사용한다. 바이트 단위로 입출력을 하는 것이 아니라 버퍼를 통해 읽고 쓰기 작업을 하기 때문에 문자단위, 행단위, 버퍼 단위로 다양하게 입출력이 가능하다.

ㆍfopen류 함수호출 성공시 FILE포인터가 리턴되고, 실패시 NULL 리턴. 실패 이유에는 파일이 존재하지 않거나, 접근권한이 없거나, 한번에 파일을 열 수 있는 최대 개수를 초과하는 경우가 있다.

 

 

 

1. fopen(3)

매개변수로 들어온 경로의 파일을 매개변수의 접근 권한으로 연다.

#include <stdio.h>
FILE* fopen(const char* pathname, const char* mode);

@param pathname: 열려고 하는 파일명(경로)

@param mode: 접근 권한

@return FILE*: 파일 스트림형 구조체. 실패시 NULL 리턴

 

 

mode - 파일의 접근 권한

"r", "rb": 읽기 전용
"w", "wb": 쓰기 전용
"a", "ab": 데이터 추가
"r+", "rb+", "r+b": 없으면 실패(return NULL), 있으면 유지 - 읽기, 쓰기 가능
"w+", "wb+", "w+b": 없으면 생성, 있으면 덮어쓰기 - 읽기, 쓰기 가능
"a+", "ab+", "a+b": 없으면 생성, 있으면 유지 - 읽기, 쓰기 가능

 

- 활용 예시

#include<stdio.h>
#include<stdlib.h>

int main() {
	char* fname = "ssu_test.txt";
	char* mode = "r";

	//fopen이 실패하여 NULL을 return하는 경우
	if(fopen(fname, mode)==NULL) {
		fprintf(stderr, "fopen error for %s\n", fname);
		exit(1);
	}
	else
		printf("Success!\nFilename:<%s>, mode:<%s>\n", fname, mode);

	exit(0);
}

fopen mode

일반적으로 b옵션은 바이너리 파일 모드를 나타내지만 요즘은 크게 다를 것이 없다.

 

r => O_RDONLY

w => O_WRONLY | O_CREAT | O_TRUNC

a => O_WRONLY | O_APPEND | O_CREAT

r+ => O_RDWR. 없으면 오류, 있으면 수정

w+ => O_RDWR. 없으면 생성, 있으면 덮어쓰기

a+ => O_RDWR. 없으면 생성, 있으면 수정

 

 

 

 

 

2. freopen(3)

#include<stdio.h>
FILE* freopen(const char* pathname, const char* mode, FILE* fp);

@param pathname: 파일명 혹은 경로

@param mode: 접근 권한

@param fp: 파일 스트림.

@return FILE*: pathname fopen결과 리턴, 실패시 NULL 리턴

 

 freopen함수는 1. 먼저 세번째 인자로 전달된 스트림(fp)에 해당하는 파일을 닫은 후, 2. 첫번째 인자로 전달된 파일을 두번째 인자로 전달된 방식에 맞게 fopen한다.

이 함수는 이미 정의된 스트림(stdin, stdout, stderr)와 같은 파일 스트림들을 특정한 파일 스트림으로 변경할 수 있다.

 

 

- 활용 예시

#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>

int main() {

	char* fname = "test.txt";
	int fd;
	printf("First printf : Hello1!!\n");
	
    //open(2)
	if((fd = open(fname, O_RDONLY))<0) {
		fprintf(stderr, "open error for %s\n", fname);
		exit(1);
	}
	if(freopen(fname, "w", stdout) != NULL) //stdout을 닫고 대신 fname파일을 fopen (스트림 변경 효과를 가짐)
		printf("Second printf : Hello2!!\n");

	exit(0);
}

 

 

위 코드를 보기 전, 먼저 stdin, stdout, stderr에 대해서 알아보자.

stdin, stdout, stderr는 파일 스트림형 포인터 (FILE*)이고, 파일 포인터 구조체 내부에는 파일 디스크립터가 위치하여 유닉스 시스템에서 시스템 콜을 이용해서 접근할 파일을 정수로 지정한다. 기본적으로 시스템상으로 stdin, stdout, stderr의 파일 디스크립터 값은 시스템상 각각 0,1,2값으로 고정된다.

 

그런데 위 코드와 같이 freopen(fname, "w", stdout)과 같이 함수를 호출하면, stdout의 파일 포인터에 위치하는 파일을 fclose()하고, 디스크립터 멤버값을 fname의 그것으로 교체한 후 주어진 권한대로 fopen()한다. 따라서 이후로는 stdout으로 출력하는 printf를 호출하더라도 fname으로 출력이 전달되는 redirection이 이루어진다.

 

/*********************************************************************************************************

그러면 다시 stdout의 파일 디스크립터 멤버값을 기본값(모니터)로 되돌리려면 어떻게 해야할까? 

디스크립터 값이 1(stdout)인 파일 포인터를 stdout에 지정해주면 될 것이라고 생각했다.

int main() {

	char* fname = "ssu_test.txt";
	int fd;
	FILE* fp;
	printf("First printf : Hello, OSLAB!!\n");

	if((fd = open(fname, O_RDONLY))<0) {
		fprintf(stderr, "open error for %s\n", fname);
		exit(1);
	}
	if((fp = freopen(fname, "w", stdout)) != NULL)
		printf("Second printf : Hello, OSLAB!!\n");

	printf("Third printf : Hello3!!\n");
	stdout = fdopen(1, "w");
	printf("Fourth printf : Hello4!!\n");
	exit(0);
}

위 코드를 실행시키면 Third printf는 fname파일에, Fourth printf는 다시 화면에 쓰일 것이라고 생각했는데 안됨 

>> 2시간이 넘게 테스트하고 구글링했는데 나오질않는다.. 사실검증 안됨.

***************************************************************************************************************/

 

 

 

 

 

3. fclose(3)

 파일 포인터를 전달받아서 open된 스트림을 닫아주는 라이브러리 함수이다. 만약 스트림의 버퍼에 데이터들이 남아 있다면 데이터는 모두 방출하고 입출력 스트림을 모두 닫는다.

#include<stdio.h>
int fclose(FILE* fp);

@param fp: 닫을 파일 스트림 포인터

 

- 활용 예제

#include<stdio.h>
#include<stdlib.h>

int main() {

	char* fname = "ssu_test.txt";
	FILE* fp;

	if((fp=fopen(fname, "r"))==NULL) {
		fprintf(stderr, "fopen error for %s\n", fname);
		exit(1);
	}
	else {
		printf("Success!\n");
		printf("Opening \"%s\" in \"r\" mode!\n", fname);
	}
	
	fclose(fp);
	exit(0);
}

fopen으로 연 파일 포인터를 인자로 전달해주면 닫을 수 있다.

 

 

 

 

4. setbuf(3), setvbuf(3)

표준 입출력 라이브러리가 제공하는 스프림 버퍼를 설정하는 라이브러리 함수. 

#include<stdio.h>
void setbuf(FILE* fp, char* buf);
int setvbuf(FILE* fp, char* buf, int mode, size_t size);

@param fp : 작업을 수행할 스트림의 FILE객체를 가리키는 포인터

@param buf : 사용자가 직접 할당한 버퍼. non-buffered 버퍼링 상태로 바꾸려면 NULL포인터를 전달하면 된다.

 

@param mode : 파일 버퍼링의 형식을 지정한다.

@param size : 지정할 버퍼의 크기(byte) - 배열의 크기보다는 작거나 같아야 한다.

@return : setvbuf의 경우 버퍼가 성공적으로 지정되었다면 0, 그렇지 않으면 0이 아닌 값이 리턴된다.

 

 

- 표준 입출력 라이브러리가 지원하는 버퍼링

Full-buffered (_IOFBF)
ㆍ표준 입출력 버퍼가 꽉 찼을 때 실제 입출력 발생
ㆍ주로 디스크에 있는 파일에 full-buffered가 적용
ㆍfull-buffered에 사용되는 버퍼는 주어진 한 스트림에 대하여 입출력 함수가 처음 수행될 시 malloc()을 호출해서 할당

Line-buffered (_IOLBF)
ㆍ입출력시 개행문자를 만났을 때 입출력을 수행
ㆍ터미널을 가리키는 스트림용 (stdin, stdout)

None-buffered (_IONBF)
ㆍ문자들을 버퍼링하지 않으며, 즉시 출력
ㆍstderr

 

기본적으로 setbuf와 setvbuf는 오픈된 파일에서 데이터 입출력 작업 전에 호출되어 버퍼를 동적으로 할당한다는 공통점을 가진다. 이 때 buf에 NULL이 들어가면 버퍼를 해제한다는 것을 의미한다. 다만 setvbuf의 경우 버퍼링의 타입(mode)와 크기(size)를 지정할 수 있다.

 

- 예제

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#define BUFFER_SIZE 1024

int main() {

	char buf[BUFFER_SIZE];
	setbuf(stdout, buf);
	printf("Hello!");
	sleep(1);
	printf("\n");
	sleep(1);
	printf("check how setbuf works");
	sleep(1);
	setbuf(stdout, NULL);
    //버퍼가 해제되었으므로 한번에 출력
	printf("How");
	sleep(1);
	printf(" are");
	sleep(1);
	printf(" you?");
	sleep(1);
	printf("\n");
	exit(0);
}

위 예제에서 stdout에 버퍼를 설정하였다. 원래대로라면 버퍼가 가득 차거나 fflush함수를 이용한 버퍼 출력이 이루어지기 전까지는 출력이 이루어지지 않겠지만, setbuf(stdout, NULL);과 같이 버퍼를 해제해주는 순간 How까지의 문자열이 한번에 출력이 이루어진다. 즉, setbuf로 기존에 설정해놓은 버퍼를 해제하면 그 시점에 출력이 이루어짐을 알 수 있다.

 

int main() {

	char buf[BUFFER_SIZE];
	int a,b;
	int i;
	setbuf(stdin, buf);
	scanf("%d %d", &a, &b);
	for(i=0; buf[i]!='\n'; i++) {
		putchar(buf[i]);	
	}

	putchar('\n');
	exit(0);
}

이번에는 stdin에 버퍼를 설정한 모습이다. scanf를 통해서 버퍼로 입력받고, putchar는 버퍼에 존재하는 문자열을 하나씩 검사하여 개행문자 전까지 출력한다.

 

Q 근데 입력을 1\n2와같이 주면 왜 1만 출력되는게 아니라 2만 출력되는걸까? putchar의 출력문제? ㄴㄴ 1 2와같이 주면 1 2로 출력됨 그러면 buf[0]='1' buf[1]=' ' buf[2] = '2'라는건데? 근데 왜 개행을 사이에 넣어주면 문제가생길까?