HOWTO · C
C의 파일 디스크립터
이 문서에서는 C 프로그래밍 언어의 파일 설명자에 대해 설명합니다.
이 페이지의 내용
이 글에서는 C 프로그래밍 언어의 파일 디스크립터를 살펴봅니다.
파일 디스크립터의 작동 방식
파일 디스크립터는 프로세스가 열린 I/O 리소스를 가리키는 핸들로 사용하는 0 이상의 작은 정수입니다. 이 정수는 해당 프로세스의 디스크립터 테이블에 있는 항목입니다. 파일 자체가 아니며 리소스를 전역적으로 식별하지도 않습니다. 운영 체제는 현재 파일 오프셋과 접근 모드를 포함한 열린 파일 상태를 디스크립터와 연결합니다. 예를 들어 디스크립터를 복제하거나 프로세스를 만들 때 상속하면 여러 디스크립터가 같은 열린 파일 상태를 가리킬 수 있습니다.
open()이 성공하면 호출한 프로세스에서 사용 가능한 가장 낮은 디스크립터를 반환합니다. 표준 디스크립터가 이미 사용 중인 예제에서는 3 같은 값이 흔히 나오지만, 프로그램은 특정 숫자를 가정하지 말고 호출이 반환한 값을 사용해야 합니다. 디스크립터는 닫히거나 다른 값으로 대체될 때까지 사용할 수 있습니다. (read())
C의 표준 파일 디스크립터
유닉스 계열 환경에서는 일반적으로 프로세스가 세 개의 표준 디스크립터와 함께 시작합니다.
| 디스크립터 | 일반적인 이름 | 주된 용도 |
|---|---|---|
0 |
표준 입력(stdin) |
입력 읽기 |
1 |
표준 출력(stdout) |
일반 프로그램 출력 쓰기 |
2 |
표준 오류(stderr) |
진단 메시지 쓰기 |
이는 관례일 뿐, 특정 장치를 가리킨다는 보장은 아닙니다. 셸은 표준 입력이나 출력을 파일 또는 파이프로 리디렉션할 수 있으며, 프로그램이 이 번호에 다른 리소스를 전달받을 수도 있습니다. C에서는 <stdio.h>가 stdin, stdout, stderr 스트림 객체를 제공합니다. POSIX 환경에서는 이 스트림의 일반적인 기반 디스크립터가 각각 0, 1, 2입니다. FILE * 스트림에는 스트림 함수를 사용하고, 정수 디스크립터를 직접 다룰 때는 디스크립터 함수를 사용하세요. 같은 열린 파일에 대해 버퍼링된 스트림 작업과 직접 디스크립터 작업을 조정 없이 섞지 마세요. 버퍼 상태와 오프셋이 서로 달라질 수 있습니다.
C에서 파일 열기, 읽기, 닫기
POSIX 인터페이스 선언은 <fcntl.h>와 <unistd.h>에 있습니다. open()은 경로와 플래그를 받아 디스크립터를 반환합니다. 실패하면 -1을 반환하고 errno를 설정합니다. O_RDONLY, O_WRONLY, O_RDWR로 접근 모드를 선택하세요. 필요한 경우에만 O_CREAT 같은 플래그를 추가합니다. O_CREAT가 있으면 0600과 같은 세 번째 mode 인수를 전달해야 합니다. 프로세스의 umask가 요청된 모드에서 권한 비트를 제거할 수 있습니다. O_CREAT는 없는 파일을 만들지만 기존 파일을 잘라내지는 않습니다. 기존 내용을 의도적으로 바꿀 때만 O_TRUNC를 사용하세요.
read(fd, buffer, count)는 최대 count바이트를 저장하고 실제로 읽은 바이트 수를 반환합니다. 정규 파일에서 count가 양수일 때 반환값 0은 현재 파일 오프셋이 파일 끝(EOF)에 있음을 뜻합니다. 다른 유형의 디스크립터에서는 0바이트 결과가 항상 EOF를 의미하지는 않습니다. 예를 들어 터미널 설정에 따라 EOF가 아닌 경우에도 0이 반환될 수 있으므로, 해당 디스크립터 유형의 동작에 맞게 결과를 해석해야 합니다. count가 0이면 EOF를 뜻하지 않더라도 read()가 0을 반환할 수 있습니다. 양수를 반환하더라도 요청한 바이트 수보다 적을 수 있으므로, 반환된 수만큼만 처리하고 입력이 더 필요하면 계속 읽으세요. -1은 실패를 뜻합니다. 인터럽트될 수 있는 호출은 errno가 EINTR이면 다시 시도하고, 다른 오류는 애플리케이션에 맞게 처리하세요. 버퍼에는 C 문자열이 아닐 수도 있는 바이트가 들어 있습니다. 할당된 공간 안에 종료 문자를 추가하지 않았다면 %s로 출력하지 마세요.
close(fd)는 디스크립터를 해제하며 성공하면 0, 실패하면 -1을 반환합니다. 닫았다고 해서 모든 쓰기 내용이 영구 저장되었다는 뜻은 아닙니다. 데이터의 지속성이 필요한 코드는 추가 동기화가 필요합니다. 중요한 경우 close()의 반환값을 확인하세요. close()가 실패한 뒤 같은 디스크립터를 무조건 다시 닫으려 하지 마세요. 상태는 플랫폼에 따라 다를 수 있으며 해당 번호가 이미 재사용되었을 수 있습니다.
다음은 자체적으로 실행할 수 있는 POSIX 예제입니다. 임시 파일을 만들고 짧은 데이터를 쓴 다음, 파일을 다시 읽기 위해 열어 바이트를 표준 출력으로 복사하고 임시 파일을 삭제합니다. 시스템 호출이 실패하면 표준 오류에 진단 메시지를 쓰며, 전송이나 정리 중 어느 단계에서든 실패하면 main()은 실패 상태를 반환합니다. 성공적으로 실행하면 표준 출력에는 데이터만 기록됩니다.
이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
static int write_all(int fd, const char *data, size_t length) {
size_t done = 0;
while (done < length) {
ssize_t n = write(fd, data + done, length - done);
if (n < 0 && errno == EINTR) continue;
if (n == 0) errno = EIO;
if (n <= 0) return -1;
done += (size_t)n;
}
return 0;
}
int main(void) {
char path[] = "/tmp/file-descriptor-example-XXXXXX";
const char payload[] = "File descriptor example\n";
char buffer[16];
int created = mkstemp(path);
if (created < 0) {
perror("mkstemp");
return EXIT_FAILURE;
}
int failed = 0;
if (write_all(created, payload, sizeof(payload) - 1) < 0) {
perror("write temporary file");
failed = 1;
}
if (close(created) < 0) {
perror("close temporary file");
failed = 1;
}
if (failed) {
if (unlink(path) < 0) perror("unlink temporary file");
return EXIT_FAILURE;
}
int fd = open(path, O_RDONLY);
if (fd < 0) {
perror("open temporary file");
if (unlink(path) < 0) perror("unlink temporary file");
return EXIT_FAILURE;
}
failed = 0;
for (;;) {
ssize_t n = read(fd, buffer, sizeof(buffer));
if (n < 0 && errno == EINTR) continue;
if (n < 0) {
perror("read temporary file");
failed = 1;
break;
}
if (n == 0) break;
if (write_all(STDOUT_FILENO, buffer, (size_t)n) < 0) {
perror("write standard output");
failed = 1;
break;
}
}
if (close(fd) < 0) {
perror("close input file");
failed = 1;
}
if (unlink(path) < 0) {
perror("unlink temporary file");
failed = 1;
}
return failed ? EXIT_FAILURE : EXIT_SUCCESS;
}
File descriptor example
읽기 루프는 읽은 바이트 범위를 쓴 다음 파일 끝에 도달할 때까지 다시 읽어 짧게 읽힌 경우를 처리합니다. 중단된 읽기와 쓰기는 다시 시도하고, 정상 처리 경로와 읽기 또는 출력 실패 경로에서 디스크립터를 닫습니다. 예제에서는 설명을 위해 mkstemp()로 임시 파일을 만듭니다. 실제 애플리케이션에서는 목적에 맞는 경로와 생성 정책으로 이 부분을 바꾸세요. 이 예제는 POSIX 인터페이스를 사용하므로 ISO C만으로 된 코드는 아닙니다.