C를 이용한 문법 중, 항상 파일 입출력 부분의 개념이 헷갈렸다. 마침 보고있던 책인 서영진(2020),『사물인터넷을 위한 리눅스 프로그래밍 with 라즈베리파이, 제이펍, p188-p201. 에 내용 정리가 잘 되어있어, 해당 부분을 요약해 포스팅한다.
1. 표준 입출력 라이브러리
유닉스가 개발되었던 초기에는 데이터를 저장하거나 출력하면 입출력 시간 때문에 프로그램이 멈춰있는 시간이 너무 길어졌다. 이러한 문제를 해결하기 위해 등장한 것이 바로 표준 입출력 라이브러리로, 컴퓨터 외부로 출력할 때 데이터를 바로 출력하지 않고 "버퍼(컴퓨터 메모리를 이용)"에 저장하는데, 버퍼가 가득 차거나 일정 시간이 지나면 출력한다.
이런 표준 입출력 라이브러리에 여러 표준 함수들을 추가한 것이 C표준 라이브러리인데, 여러 헤더파일과 라이브러리 루틴이 모여있는 것으로, C프로그래밍 언어에서 입출력과 문자열 처리 작업에 사용한다.
C표준 라이브러리에서 파일의 입출력에 Stream(스트림)을 사용할 수 있다. Stream이란, 사용자 프로그램과 파일 사이의 단방향의 자료 흐름으로, 표준 입출력 라이브러리를 이용하면 스트림을 통해 파일에 접근할 수 있다.
<stdio.h> 파일과 스트림
stdio는 Standard Input/Output 의 약자로, 표준 입출력을 의미한다. 앞에서 설명한 표준 입출력 라이브러리를ㄹ 사용해서 입출력 작업을 할 때마다 덩어리(chunk)로 구성된 버퍼를 사용하는데, 읽기와 쓰기 작업 시 하드웨어를 보다 효율적으로 사용할 수 있다.
<stdio.h> 헤더 파일에는 이러한 표준 입출력과 관련된 함수들이 정의되어 있고 콘솔이나 파일 등의 입출력을 위한 다양한 시스템 출을 제공한다. 표준 입출력 암수들은 또한 출력을 형식화하고 입력을 받아들이는 기능을 제공하는데, 다음과 같이 입출력 단위를 기준으로 나눌 수 있다.
| 구분 | 입력 | 출력 |
| 문자 단위 입출력 함수 | getc(), fgetc() 등 | putc(), fputc() 등 |
| 줄 단위 입출력 함수 | gets(), fgets() 등 | puts(), fputs() 등 |
| 버퍼 기반의 입출력 함수 | fread() 등 | fwrite() 등 |
| 형식화된 입출력 함수 | scanf(), fscanf() 등 | printf(), fprintf() 등 |
<stdio.h> 파일은 표준 입출력 함수와 함께, 파일의 끝을 의미하는 EOF, 널 포인터를 의미하는 NULL, 그리고 표준 입출력 시 사용되는 최적의 버퍼 크기를 의미하는 BUFSIZ같은 매크로들도 제공한다.
C 언어에서 기본적으로 제공하는 입출력 스트림을 표준 스트림이라고 하며, 표준 입출력 함수에서 데이터는 스트림으로 취급된다. 스트립은 데이터의 흐름으로 애플리케이션에서 입출력을 통해서 처리되는 데이터를 위한 추상화된 개념으로 볼 수 있다.
표준 라이브러리의 파일 입출력
C언어에서 스트림은 FILE 구조체라는 파일 포인터를 사용하여 조작할 수 있으며, 이 파일 포인터를 매개로 하여 파일을 조작할 수 있다. FILE 구조체는 하나의 스트림을 다루기 위한 정보를 포함하는 구조체로<stdio.h> 파일에 정의되어 있다.
파일 스트림은 하나의 FILE 구조체로 표현되고, 이 구조체에 대한 포인터를 이용해서 해당 파일을 조작한다.
typdef struct{
int _cnt;
unsigned char *_ptr;
unsigned char *_base;
unsigned char _flag;
unsigned char _file;
} FILE;
FILE 구조체는 스트림을 다루기 위한 파일 디스크립터, 버퍼 공간에 대한 포인터, 버퍼 크기, 버퍼에 남아 있는 문자의 수, 에러 플래그 등의 정보를 담고 있다. 애플리케이션에서 하나의 스트림을 열 때 각 스트림과 연관된 버퍼가 하나씩 할당되며, 할당된 버퍼는 사용자 프로그램과 파일 사이의 데이터 전송을 위해 임시로 사용된다.
UNIX에서 저수준 입출력을 위해 open(), read(), write(), close() 등의 함수를 이용한 것처럼 표준 라이브러리의 입출력에서도 비슷한 함수들을 제공한다. fopen() 함수를 이용하여 스트림을 열 수 있으며, 스트림을 열면 FILE 구조체에 대한 포인터가 반환된다. 스트림의 입출력에는 fread(), fwrite() 함수를 이용하고, 사용이 끝나면 fclose() 함수를 이용해서 닫는다.
fopen() 함수
fopne() 함수는 지정한 경로에 있는 파일의 스트림을 열고 버퍼를 할당한다.
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
첫 번째 인자(path)로 파일의 경로를 명시하고, 두 번째 인자(mode)에는 열 스트림의 형태(type)를 명시하는데, 여는 스트림의 형태에는 다음과 같은 것들이 있다.
| 타입 | 읽기 | 쓰기 | 파일 생성 | open() 함수와 비교 | 비고 |
| r | O | X | X | O_RDONLY | rb |
| r+ | O | O | X | O_RDWR | r+b / rb+ |
| w | X | O | O | O_WRONLY | O_CREAT | O_TRUNC | wb |
| w+ | O | O | O | O_RDWR | O_CREAT | O_TRUNC | w+b / wb+ |
| a | X | O | O | O_WRONLY | O_CREAT | O_APPEND | ab |
| a+ | O | O | O | O_RDWR | O_CREAT | O_APPEND | a+b / ab+ |
fopen() 함수는 내부적으로 FILE 구조체와 각 필드들을 초기화한 후에 opne() 함수를 호출한다. 파일 열기에 성공하면 FILE 포인터를 반환하며, 실패하면 NULL을 반환하고 errno에 에러 정보를 설정한다.
fclose() 함수
fclose()는 FILE 포인터가 가리키는 스트림과 파일을 분리한 후, 파일을 닫고 자원을 반환한다. 출력 버퍼에 있는 데이터는 스트림에 출력(저장)하고 입력 버퍼에 있는 데이터는 버린 후에 버퍼를 해제한다.
#include <stdio.h>
int fclose(FILE *fp);
fclose() 함수의 인자로 FILE 스트림을 사용한다. 함수의 호출에 성공하면 0을 반환하고 실패하면 EOF(-1)값을 반환한다. 프로세스가 정상적으로 종료된 경우에는 열려 있는 모든 표준 입출력 스트림에 대해서 fclose()의 기능을 수행한다.
스트림 입출력: fread(), fwrite()
fread()나 fwrite() 함수는 지정된 스트림에서 원하는 크기만큼의 데이터를 읽고 쓸 수 있는 버퍼기반 입출력 함수이다.
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *fp);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *fp);
fread()나 fwrite() 함수는 비슷한 인자를 가지고 있다. 첫 번째 인자(ptr)는 입력이나 출력을 위한 버퍼 공간이며, 두 번째 인자(size)는 입출력 데이터 하나의 크기이고, 세 번째 인자(nmemb)는 입출력을 위한 데이터의 수이다. 전체 입출력 데이터의 전체 크기는 하나의 크기와 수의 곱(size * nmemb)으로 나타낼 수 있다. 마지막 인자(fp)로 FILE 스트림을 명시한다.
함수가 성공적으로 수행되면 읽은 항목의 수를 반환하는데 항목이 없으면 0을 반환하고, 데이터를 읽기 전에 파일의 끝을 만나거나 에러가 발생하면 EOF를 반환한다. fread() 함수의 실행 후 반환값이 nmemb보다 작으면 에러나 파일의 끝에 도달한 것인데, 관련된 정보는 ferror(), feof()등의 함수를 통해 확인할 수 있다.
fopen.c: C 표준 입출력 함수를 이용한 파일 복사 프로그램
#include <stdio.h>
int main(int argc, char **argv){
int n;
FILE *in, *out;
char buf[BUFSIZ];
if(argc != 3){
fprintf(stderr, "Usage: fcopy file1 file2\n");
return -1;
}
if ((in = fopen(argv[1], "r")) == NULL){
perror(argv[1]);
return -1;
}
if ((out = fopen(argv[2], "w")) == NULL){
perror(argv[2]);
return -1;
}
while ((n = fread(buf, sizeof(char), BUFSIZ, in)) > 0){
fwrite(buf, sizeof(char), n, out);
}
fclose(in);
fclose(out);
return 0;
}
fopen.c는 file1_path, file2_path 두가지 인수를 받아, file1내용을 file2에 복사한다.
출처: 서영진(2020),『사물인터넷을 위한 리눅스 프로그래밍 with 라즈베리파이, 제이펍, p188-p201.
'소프트웨어 > C' 카테고리의 다른 글
| [C] 매크로 함수 (0) | 2025.11.20 |
|---|---|
| [C언어] const 키워드 (0) | 2025.08.24 |
| [C] void* 포인터 (0) | 2025.08.04 |
| [C] 헤더파일에 대하여 (0) | 2025.08.02 |
| [C] C언어의 extern 키워드 (0) | 2025.07.30 |