File I/O in C Library
지난 포스팅에서는 리눅스에서 제공하는 file-i/o 를 위한 system call 이었다.
File I/O system call in LINUX
UNIX 계열 운영체제에서 파일의 종류는 크게 두 종류이다. 하나는 regular file, 우리가 흔힌 생각하는 파일이다. 예를 들면 txt, c, cpp 등등 또 다른 하나는 I/O device를 file 로 추상화한 special file이다...
josushell.tistory.com
이와 비슷하게 파일 입출력 함수를 C 표준 라이브러리에서도 제공한다.
다음은 가장 자주 쓰이는 함수들이다.
- fopen
- fgetc/ fputc
- fgets/ fputs
- fread
- fwrite
- fseek
- ftell
- rewind
- fflush
그렇다면 fopen() 과 open() 의 차이점은 무엇일까?
바로 파일을 다루는 단위의 차이이다.
open() 함수는 파일을 byte 단위로 다룬다. 이에 반해 fopen()은 파일을 스트림 단위로 다룬다.
또 open 함수는 파일 I/O 를 할때마다 kernel I/O 또한 같이 하게 된다. 하지만 fopen 함수는 사용자 메모리에서 데이터를 쌓아두다가 buffer가 찼을 경우에만 kernel I/O 를 하게 되어 시스템의 부하를 줄인다.
stream 또한 시스템에 미리 정의되어 있는 것들이 있다.
#include <stdio.h>
FILE *stdin
FILE *stdout
FILE *stderr
파일 오픈 함수
#include <stdio.h>
FILE *fopen(char *filename, char *type)
함수 명세
return | FILE 타입의 포인터, 실패 시 NULL |
description | type으로 모드와 파일 타입을 부여하여 파일을 연다. |
파라미터
char *filename | 파일 명 |
char *type | access mode + file type |
세 번째 파라미터인 type은 파일 I/O type 을 의미한다.
access mode: r(읽기), w(쓰기), a(추가), r+(읽기 + 쓰기), w+(쓰기 + 읽기), a+(추가 + 읽기)
file type: t(text file), b(binary file)
이때 w+, r+ 차이는 어떤게 우선순위에 있냐는 차이이다.
즉 r+ 로 열게 된다면 읽기 기반에 쓰기도 추가이므로 데이터가 날라가지는 않는다.
하지만 w+ 로 열게 된다면 쓰기 기반에 읽기 추가이므로 데이터가 날라간다.
file type 은 두가지의 선택 사항이 있다.
text file: 모든 데이터가 ASCII 코드만으로 이루어진 경우
binary file: text file 이 아닌 모든 파일
파일 닫기 함수
#include <stdio.h>
int fclose(FILE *fp)
함수 명세
return | 성공 시 0, 실패 시 EOF |
description | 파일을 닫는다. |
파라미터
FILE *fp | 파일 포인터 |
파일 읽기/ 쓰기 함수
C 표준 라이브러리에서 제공해주는 함수들은 입출력을 stream 단위로 한다. 이때 얼마나 읽는지 그 기준이 되는 것은 다양하다.
character, line, buffer 등등 상황에 맞게 사용할 수 있다.
character I/O : 한 글자씩 I/O
# include <stdio.h>
int getc(FILE *fp)
int fgetc(FILE *fp)
함수 명세
return | 읽어 온 글자, 실패 또는 error의 경우 EOF(-1) |
description | 파일에서 한 글자를 읽어온다. |
파라미터
FILE *fp | 파일 포인터 |
이 두 함수의 차이점은 바로 매크로의 유무이다.
getc() 는 매크로인 반면
fgetc() 는 일반 함수이다.
# include <stdio.h>
int putc(int ch, FILE *fp)
int fputc(int ch, FILE *fp)
함수 명세
return | 성공 시 ch, 실패 시 EOF |
description | 파일로 한 글자를 출력한다. |
파라미터
int ch | 출력 할 문자 ch |
FILE *fp | 파일 포인터 |
line I/O: 한 줄씩 I/O
#include <stdio.h>
char *fgets(char *buf, int max, FILE *fp)
함수 명세
return | 성공 시 읽어 온 buf, 실패 또는 파일 끝에서 NULL |
description | 파일로부터 한 줄을 읽어서 buf에 저장한다. 이때 최대 읽을 수 있는 수는 max 이다. |
파라미터
char *buf | 출력 할 문자 ch |
int max | 버퍼의 최대 크기 |
FILE *fp | 파일 포인터 |
이 함수에서 중요한 점은 개행문자까지 한 줄로 입력 받는다는 것이다.
또한 버퍼의 최대크기가 max 이지만 NULL로 끝나야 하므로 max-1 byte 만큼 저장 가능하다.
#include <stdio.h>
int fputs(char *str, FILE *fp)
함수 명세
return | 성공 시 non-negative value, 실패 시 EOF(-1) |
description | 파일로 한 줄을 출력한다. |
파라미터
char *str | 출력 할 문자열 str |
FILE *fp | 파일 포인터 |
이 함수의 특징은 출력 시 개행문자가 붙지 않는다는 것이다.
따라서 개행문자까지 읽고, 출력시에는 추가로 개행문자를 붙이지 않는 것을 원한다면 fgets()와 fputs()를 활용 가능하다.
Binary I/O: buffer 단위로 I/O (binary file만 사용 가능)
# include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nobj, FILE *fp)
함수 명세
return | 성공 시 읽어온 객체의 수, 실패 또는 end of file 일 때 0 |
description | 파일에서 object를 읽어서 미리 지정한 ptr에 저장한다. |
파라미터
void *ptr | 파일에서 데이터를 읽어서 저장할 버퍼 |
size_t size | 읽어 올 데이터의 크기 |
size_t nobj | 읽어 올 데이터의 최대 갯수 |
FILE *fp | 파일 포인터 |
이 함수의 특징은 ASCII 코드가 아닌 바이너리 데이터도 읽어서 저장할 수 있다는 점이다. 이때 읽어서 저장하고자 하는 구조체 혹은 데이터의 자료구조를 void pointer로 넘기게 된다.
# include <stdio.h>
size_t fwrite(void *ptr, size_t size, size_t nobj, FILE *fp)
함수 명세
return | 성공 시 실제로 쓴 객체의 수, 실패 또는 end of file 일때 0 |
description | 파일에 nobj 수 만큼 데이터를 쓴다. |
파라미터
void *ptr | 파일에 쓸 데이터가 저장된 버퍼 |
size_t size | 파일에 쓸 데이터의 크기 |
size_t nobj | 파일에 쓸 데이터의 최대 갯수 |
FILE *fp | 파일 포인터 |
예를 들면 이 두 함수는 다음과 같이 사용 가능하다.
FILE *fp;
struct record temp;
struct record temp_array[10];
// 파일 읽기
fread(&temp, sizeof(record), 1, fp);
// 파일 쓰기
fwrite(temp_array, sizeof(record), 10, fp);
위치 조정 함수
# include <stdio.h>
# include <unistd.h>
int fseek(FILE *fp, long offset, int whence)
함수 명세
return | 성공 시 0, 실패 시 nonzero |
description | whence 부터 offset만큼 파일 포인터의 위치를 옮긴다. |
파라미터
FILE *fp | 파일 포인터 |
long offset | 파일 포인터를 옮길 offset (얼마나 옮길지) |
int whence | 어디서 부터 파일 포인터를 옮길지에 대한 기준점 |
이때 세 번째 파라미터로 들어가는 기준점은 다음과 같은 매크로를 사용 가능하다.
- SEEK_SET(0) : 맨 앞
- SEEK_CUR(1) : 현재 위치
- SEEK_END(2) : 맨 끝
# incldue <stdio.h>
long ftell(FILE *fp)
함수 명세
return | 성공 시 현재 파일 포인터의 위치, 실패 시 -1L |
description | 현재 파일 포인터의 위치를 반환한다. |
파라미터
FILE *fp | 파일 포인터 |
# include <stdio.h>
void rewind(FILE *fp)
함수 명세
return | nothing |
description | 파일 포인터의 위치를 파일 맨 처음으로 이동시킨다. |
파라미터
FILE *fp | 파일 포인터 |
example
지난 포스팅에서 구현한 cp 함수는 linux system call을 사용하였다.
그렇다면 이번에는 c library를 사용하여 구현해보자.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
FILE *fp1, *fp2;
int ch;
if (argc != 3)
exit(1);
fp1 = fopen(argv[1], "rt");
if (fp1 < 0)
exit(1);
fp2 = fopen(argv[2], "wt");
if (fp2 < 0)
exit(1);
while((ch = fgetc(fp1)) > 0)
fputc(ch, fp2);
fclose(fp1);
fclose(fp2);
}
example2
파일 포인터 위치 조정 함수를 이용해서 파일의 사이즈를 구하는 함수를 만들어보자
핵심은 파일 포인터를 끝까지 이동시키면 그때의 파일 포인터가 파일 사이즈가 되는 것이다.
long file_size(FILE *fp)
{
long temp;
long size;
temp = ftell(fp);
if (fseek(fp, 0L, SEEK_END) == 0)
size = ftell(fp);
fseek(fp, temp, SEEK_SET);
return size;
}
fseek() 함수를 맨 끝에서 0L 만큼 이동하면 파일 포인터를 파일 맨 끝으로 옮기게 되고 그때의 파일 포인터의 위치는 파일 사이즈이다.
파일 포인터를 파라미터로 받아서 이를 이동하게 되면 원래 위치를 잃어버리므로, 현재 파일 포인터의 위치를 temp에 저장해두고 마지막에 복구해준다.
'STUDY > linux' 카테고리의 다른 글
Files and Directories in LINUX (0) | 2022.03.31 |
---|---|
SIGNAL in LINUX (0) | 2022.03.29 |
Process and Threads in LINUX (0) | 2022.03.28 |
File I/O system call in LINUX (0) | 2022.01.04 |
shell commands (0) | 2022.01.04 |