C 저수준 파일 입출력은 시스템 호출을 사용하여 파일을 읽고 쓸 수 있는 강력한 기능을 제공합니다.
이는 그 어떤 파일 입출력 함수보다 빠르게 접근할 수 있으며 대용량 파일 다루기에 최적화 됩니다.
저수준 입출력
- 파일 및 장치에 직접 액세스할 수 있습니다.
- 복잡하며 버퍼 관리는 프로그래머가 직접 관리 해야 합니다.
- I/O 기능을 사용할 때 로우 레벨 I/O는 하이 레벨 I/O에 비해 더 빠릅니다.
파일 디스크립터 ( File Descriptor) 를 사용하여 파일의 상태를 추적합니다.
파일 디스크립터 ( File Descriptor )
파일 디스크립터는 파일을 열 때 필요합니다. 쓰기 작업이나 읽기 작업을 수행할 때, 새 파일을 만들어서 쓸 수도 있고, 기존 파일을 사용하면서 이전 내용을 삭제할 수도 있습니다. 읽기 작업이나 쓰기 작업을 수행하기 위해 특정 권한이 필요합니다.
파일에 쓰기 작업을 할 때, 시스템은 해당 파일의 권한을 확인하고 권한이 있다면 시스템은 “파일 디스크립터”라 불리는 음수가 아닌 정수를 반환합니다. 저수준 I/O 함수에서 파일 디스크립터는 이후의 모든 읽기 또는 쓰기 작업을 식별하는 데 사용됩니다.
저수준 I/O 함수는 다음과 같은 용도로 사용됩니다:
- 파일과 장치에 직접 접근하기 위해
- 이진 파일을 대량으로 읽기 위해
- 빠르고 효율적인 I/O 작업을 수행하기 위해
파일을 열고 읽고 쓰는 기본적인 예제를 보여드립니다.
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
int file_descriptor;
char data[] = "Hello, File I/O!";
// 파일 열기 - 쓰기 모드
file_descriptor = open("output.txt", O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
if (file_descriptor == -1) {
printf("파일 열기 실패\n");
return 1;
}
// 파일에 데이터 쓰기
if (write(file_descriptor, data, sizeof(data)) == -1) {
printf("파일 쓰기 실패\n");
return 1;
}
// 파일 닫기
if (close(file_descriptor) == -1) {
printf("파일 닫기 실패\n");
return 1;
}
printf("파일 쓰기 완료\n");
return 0;
}
이 코드는 “output.txt” 파일을 쓰기 모드로 열고, “Hello, File I/O!”를 파일에 씁니다. 파일을 열 때 open
함수를 사용하고, 쓰기는 write
함수를 사용합니다. 마지막으로 close
함수를 통해 파일을 닫습니다.
저수준 파일 입출력은 좀 더 복잡하고 다양한 기능을 제공합니다.
아래 각 함수들을 설명함으로 자세하게 다뤄 보겠습니다.
C의 저수준 I/O 시스템은 파일과 장치에 접근할 수 있는 함수를 제공합니다.
- Open()
- Close()
- Read()
- Write()
저수준 파일 입출력 함수들
open() 함수
C 프로그래밍에서 open() 함수는 파일을 열 때 사용됩니다. 이 함수는 파일 디스크립터를 반환하는데, 파일 디스크립터란 파일을 식별하는 번호입니다. 이 함수는 다양한 모드로 파일을 열 수 있도록 해줍니다.
open() 함수의 기본 형식은 다음과 같습니다:
int open(const char *path, int oflag [, mode_t mode]);
여기서 filename
은 열고자 하는 파일의 이름이고, flags
는 파일을 어떤 모드로 열지에 대한 옵션을 나타냅니다. flags
에는 여러 옵션을 함께 사용할 수 있으며, 주요 옵션으로는 다음과 같은 것들이 있습니다:
O_RDONLY
: 읽기 전용으로 파일을 엽니다.O_WRONLY
: 쓰기 전용으로 파일을 엽니다.O_RDWR
: 읽기와 쓰기 모두 가능한 상태로 파일을 엽니다.O_CREAT
: 파일이 존재하지 않을 경우 새로 생성합니다.O_TRUNC
: 파일을 열 때 이미 존재하는 경우 내용을 삭제합니다.O_APPEND
: 파일을 열 때 이미 존재하는 경우 파일의 끝에 추가합니다.
예를 들어, 파일을 쓰기 전용으로 열고자 할 때는 다음과 같이 open()
함수를 사용할 수 있습니다:
int file_descriptor = open("파일명.txt", O_WRONLY | O_CREAT);
이렇게 파일을 열면 파일 디스크립터를 반환하여 파일에 대한 작업을 수행할 수 있습니다. 이후에는 해당 파일 디스크립터를 이용하여 read()
나 write()
함수 등을 사용하여 파일을 읽거나 쓸 수 있습니다.
close() 함수
close()
함수는 파일 디스크립터를 닫을 때 사용됩니다. 파일을 열었을 때 반환된 파일 디스크립터는 작업을 마친 후에는 반드시 닫아주어야 합니다. 파일을 닫지 않으면 시스템 자원이 낭비될 수 있습니다.
close()
함수는 다음과 같은 형식을 갖습니다:
int close(int file_descriptor);
여기서 file_descriptor
는 닫으려는 파일의 파일 디스크립터를 나타냅니다. 이 함수를 호출하면 해당 파일 디스크립터와 연결된 파일이 닫히게 됩니다.
예를 들어, 파일을 열고 작업을 마친 후에는 다음과 같이 close()
함수를 사용하여 파일을 닫을 수 있습니다:
int file_descriptor = open("파일명.txt", O_WRONLY | O_CREAT);
// 파일 작업 수행
close(file_descriptor); // 파일 닫기
close()
함수를 통해 파일을 닫으면 해당 파일 디스크립터는 더 이상 사용할 수 없게 되며, 시스템 리소스도 반환됩니다. 파일을 올바르게 닫아주는 것은 프로그램의 안정성과 시스템 자원의 효율적인 관리에 중요한 역할을 합니다.
read() 함수
read()
함수는 파일에서 데이터를 읽을 때 사용됩니다. 이 함수는 파일 디스크립터에서 지정한 위치부터 데이터를 읽어와 지정한 버퍼에 저장합니다.
read()
함수의 기본 형식은 다음과 같습니다:
ssize_t read(int file_descriptor, void *buffer, size_t count);
여기서 file_descriptor
는 데이터를 읽을 파일의 파일 디스크립터를 나타내며, buffer
는 데이터를 저장할 버퍼의 위치를 나타냅니다. count
는 읽고자 하는 데이터의 바이트 수를 나타냅니다.
예를 들어, 파일에서 데이터를 읽어와 버퍼에 저장하고자 할 때는 다음과 같이 read()
함수를 사용할 수 있습니다:
int file_descriptor = open("파일명.txt", O_RDONLY);
char buffer[100]; // 데이터를 저장할 버퍼
ssize_t bytes_read;
bytes_read = read(file_descriptor, buffer, sizeof(buffer));
if (bytes_read == -1) {
printf("파일 읽기 오류\n");
return 1;
}
// 읽은 데이터 출력
printf("읽은 데이터: %s\n", buffer);
close(file_descriptor); // 파일 닫기
read()
함수는 파일 디스크립터에서 count
만큼의 데이터를 읽어와서 buffer
에 저장하고, 실제로 읽은 바이트 수를 반환합니다. 이렇게 읽은 데이터는 후속 작업에서 활용될 수 있습니다. 파일을 읽을 때는 파일이 성공적으로 열렸는지와 읽기 작업이 성공적으로 수행되었는지를 항상 확인해야 합니다.
write() 함수
write()
함수는 파일에 데이터를 쓸 때 사용됩니다. 이 함수는 파일 디스크립터에 지정한 버퍼의 데이터를 씁니다.
write()
함수의 기본 형식은 다음과 같습니다:
ssize_t write(int file_descriptor, const void *buffer, size_t count);
여기서 file_descriptor
는 데이터를 쓸 파일의 파일 디스크립터를 나타내며, buffer
는 쓸 데이터가 담긴 버퍼를 가리킵니다. count
는 쓰고자 하는 데이터의 바이트 수를 나타냅니다.
예를 들어, 파일에 데이터를 쓰고자 할 때는 다음과 같이 write()
함수를 사용할 수 있습니다:
int file_descriptor = open("파일명.txt", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
char data[] = "파일에 쓰는 예제입니다."; // 쓸 데이터
ssize_t bytes_written;
bytes_written = write(file_descriptor, data, strlen(data));
if (bytes_written == -1) {
printf("파일 쓰기 오류\n");
return 1;
}
close(file_descriptor); // 파일 닫기
write()
함수는 버퍼에 있는 데이터를 파일 디스크립터로 지정된 파일에 씁니다. 이 때 count
에 명시한 만큼의 바이트 수를 씁니다. 함수는 실제로 쓴 바이트 수를 반환하며, 이를 통해 쓰기 작업이 성공적으로 수행되었는지 확인할 수 있습니다. 파일을 쓸 때는 파일이 성공적으로 열렸는지와 쓰기 작업이 성공적으로 수행되었는지를 항상 확인해야 합니다.
lseek() 함수
lseek()
함수는 파일의 오프셋(위치)을 이동시킬 때 사용됩니다. 이 함수를 이용하면 파일에서 원하는 위치로 이동하여 데이터를 읽거나 쓸 수 있습니다.
lseek()
함수의 기본 형식은 다음과 같습니다:
off_t lseek(int file_descriptor, off_t offset, int whence);
여기서 file_descriptor
는 오프셋을 변경하고자 하는 파일의 파일 디스크립터를 나타내며, offset
은 이동할 바이트 수를 나타냅니다. whence
는 오프셋 이동의 기준을 나타내며, 주로 SEEK_SET
, SEEK_CUR
, SEEK_END
등이 사용됩니다.
SEEK_SET
: 파일의 시작을 기준으로 오프셋을 설정합니다.SEEK_CUR
: 현재 파일 위치를 기준으로 오프셋을 설정합니다.SEEK_END
: 파일의 끝을 기준으로 오프셋을 설정합니다.
예를 들어, 파일의 특정 위치로 이동하고자 할 때는 다음과 같이 lseek()
함수를 사용할 수 있습니다:
int file_descriptor = open("파일명.txt", O_RDONLY);
off_t new_offset;
// 파일의 10번째 바이트로 이동
new_offset = lseek(file_descriptor, 10, SEEK_SET);
if (new_offset == -1) {
printf("오프셋 이동 오류\n");
return 1;
}
// 이후 파일 처리 작업 수행
close(file_descriptor); // 파일 닫기
lseek()
함수를 통해 파일의 오프셋을 이동시켜 원하는 위치로 이동할 수 있습니다. 이를 통해 파일에서 원하는 위치의 데이터를 읽거나 쓸 수 있습니다. 파일을 읽거나 쓸 때 파일 내 특정 위치로 이동할 필요가 있을 때 lseek()
함수를 활용하면 효율적으로 처리할 수 있습니다.
remove() 함수
remove()
함수는 파일을 삭제할 때 사용됩니다. 지정된 파일을 삭제하고 해당 파일이 존재하면 파일 시스템에서 제거합니다.
remove()
함수의 기본 형식은 다음과 같습니다:
int remove(const char *filename);
여기서 filename
은 삭제하려는 파일의 이름을 나타냅니다. 해당 파일이 존재하면 삭제를 시도하고, 삭제에 성공하면 0을 반환하고 실패하면 -1을 반환합니다.
예를 들어, 파일을 삭제하고자 할 때는 다음과 같이 remove()
함수를 사용할 수 있습니다:
if (remove("삭제할_파일.txt") == 0) {
printf("파일이 성공적으로 삭제되었습니다.\n");
} else {
printf("파일 삭제에 실패했습니다.\n");
}
remove()
함수는 파일을 삭제하는 데 사용되며, 삭제 시도 후에 해당 파일이 성공적으로 삭제되었는지 여부를 반환값을 통해 확인할 수 있습니다. 파일을 삭제할 때는 삭제 권한이 있는지와 파일이 정상적으로 삭제되었는지를 항상 확인해야 합니다.
오류 처리
파일 접근 시 발생하는 오류 코드들은 다양한 상황을 나타냅니다. C 프로그래밍에서 파일 접근 오류 코드를 다루는 몇 가지 일반적인 코드를 살펴보겠습니다.
ENOENT
– 파일 또는 디렉토리를 찾을 수 없음: 파일이나 디렉토리를 찾을 수 없는 경우 발생합니다.EACCES
– 액세스 거부: 파일에 액세스할 수 있는 권한이 없는 경우 발생합니다.EISDIR
– 디렉토리에 대한 작업을 파일로 수행: 파일 대신 디렉토리에 대한 작업을 수행하려고 할 때 발생합니다.EIO
– 입출력 오류: 입출력 오류가 발생했을 때 발생하는 일반적인 오류 코드입니다.ENOMEM
– 메모리 부족: 시스템 메모리 부족으로 파일을 열거나 작업할 수 없을 때 발생합니다.ENOSPC
– 공간 부족: 디스크 공간이 부족하여 파일을 생성하거나 작업할 수 없을 때 발생합니다.
이러한 오류 코드들은 파일 작업 중에 발생하는 다양한 문제를 식별하는 데 도움이 됩니다. 이러한 코드들을 사용하여 파일 작업 중 발생할 수 있는 예외 상황을 처리하고, 적절한 오류 메시지를 표시하여 사용자에게 알려줄 수 있습니다.
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
int main() {
int file_descriptor;
// 파일을 읽기 모드로 열기
file_descriptor = open("존재하지_않는_파일.txt", O_RDONLY);
if (file_descriptor == -1) {
if (errno == ENOENT) {
printf("파일을 찾을 수 없습니다.\n");
} else if (errno == EACCES) {
printf("파일에 액세스할 권한이 없습니다.\n");
} else if (errno == EIO) {
printf("입출력 오류가 발생했습니다.\n");
} else {
printf("다른 오류가 발생했습니다.\n");
}
return 1;
}
close(file_descriptor);
return 0;
}
이 예제는 파일을 읽기 모드로 열 때 발생할 수 있는 오류 코드를 확인하는 방법을 보여줍니다. open()
함수가 -1
을 반환하면 errno
변수를 사용하여 발생한 오류 코드를 확인하고, 해당하는 오류 메시지를 출력합니다. 파일을 열거나 다룰 때 발생할 수 있는 다양한 오류를 확인하고 적절히 처리함으로써 안정성을 높일 수 있습니다.
마무리
이번 글에서는 C 에서 저수준 파일 입출력 방법과 함수들을 알아보았습니다. 이제 빠르게 파일 접근 및 핸들링을 할 수 있습니다.