도입 문구
C언어는 다양한 파일 입출력 기능을 제공하며, 이를 통해 커맨드라인에서 파일을 쉽게 처리할 수 있습니다. 본 기사에서는 C언어에서 커맨드라인을 사용하여 파일을 읽고 쓰는 방법을 설명합니다.
커맨드라인 인자 받기
C언어에서 커맨드라인 인자를 처리하려면 main
함수에서 argc
와 argv
를 사용합니다. argc
는 전달된 인자의 개수를 나타내고, argv
는 각 인자의 값을 문자열 배열로 제공합니다. 이 방법을 사용하면 프로그램을 실행할 때 입력된 파일 경로나 옵션을 인자로 받을 수 있습니다.
argc와 argv의 역할
argc
: 프로그램 실행 시 제공된 인자의 개수를 나타냅니다.argv
: 프로그램 실행 시 제공된 각 인자를 문자열 형태로 저장한 배열입니다.argv[0]
은 프로그램 이름을 포함하고, 나머지argv[1]
,argv[2]
는 사용자 입력값입니다.
기본 예시
다음은 간단한 예시 코드입니다. 커맨드라인 인자를 사용하여 파일 경로를 입력받고 해당 경로를 출력하는 프로그램입니다.
#include <stdio.h>
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("사용법: %s <파일경로>\n", argv[0]);
return 1;
}
printf("입력된 파일 경로: %s\n", argv[1]);
return 0;
}
이 코드를 실행하면, 사용자가 입력한 파일 경로를 출력합니다. 예를 들어, ./program /path/to/file
을 실행하면 /path/to/file
이 출력됩니다.
이렇게 커맨드라인 인자를 활용하여 프로그램에 유연성을 더할 수 있습니다.
파일 열기와 닫기
C언어에서 파일을 다루기 위해서는 먼저 파일을 열어야 하며, 작업이 끝난 후에는 반드시 파일을 닫아야 합니다. 파일을 여는 데 사용되는 함수는 fopen
이고, 파일을 닫는 함수는 fclose
입니다. 파일을 열 때는 파일 모드를 설정할 수 있으며, 이는 파일을 읽기, 쓰기, 추가 등의 방식으로 열 수 있게 해줍니다.
fopen 함수
fopen
함수는 파일을 열고 해당 파일에 대한 포인터를 반환합니다. 이 함수는 두 개의 주요 인자를 받습니다: 파일 이름과 파일 모드입니다. 파일 모드는 파일을 어떻게 열 것인지를 지정하며, 대표적인 모드는 다음과 같습니다.
"r"
: 읽기 전용 모드, 파일이 존재하지 않으면 오류 발생"w"
: 쓰기 전용 모드, 파일이 존재하면 내용을 덮어쓰고, 없으면 새로 생성"a"
: 추가 모드, 파일이 존재하면 끝에 데이터를 추가하고, 없으면 새로 생성"rb"
,"wb"
: 바이너리 모드로 읽기/쓰기"r+"
: 읽기 및 쓰기 모드, 파일이 존재하지 않으면 오류 발생
예시 코드
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "w");
if (file == NULL) {
printf("파일을 열 수 없습니다.\n");
return 1;
}
fprintf(file, "Hello, World!\n");
fclose(file);
return 0;
}
위 코드는 example.txt
파일을 쓰기 모드로 열고, 파일에 “Hello, World!”라는 텍스트를 작성한 후 파일을 닫습니다.
fclose 함수
파일 작업이 끝난 후에는 반드시 fclose
를 호출하여 파일을 닫아야 합니다. 파일을 닫지 않으면 데이터가 완전히 저장되지 않거나 메모리 누수 등의 문제가 발생할 수 있습니다.
fclose(file);
파일을 정상적으로 닫으면 fclose
함수는 0을 반환하고, 오류가 발생하면 EOF
를 반환합니다.
파일 읽기 함수
C언어에서 파일을 읽기 위해서는 여러 가지 함수가 제공됩니다. 각 함수는 파일에서 데이터를 읽는 방식이 다르기 때문에, 적절한 함수 선택이 중요합니다. 여기에서는 대표적인 파일 읽기 함수인 fgetc
, fgets
, fread
에 대해 설명합니다.
fgetc 함수
fgetc
함수는 파일에서 한 문자씩 읽어옵니다. 파일의 끝에 도달하면 EOF
를 반환하며, 파일을 순차적으로 한 문자씩 처리할 때 유용합니다.
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "r");
if (file == NULL) {
printf("파일을 열 수 없습니다.\n");
return 1;
}
char c;
while ((c = fgetc(file)) != EOF) {
putchar(c); // 읽은 문자를 화면에 출력
}
fclose(file);
return 0;
}
위 코드는 example.txt
파일을 열고, 한 문자씩 읽어와 화면에 출력합니다.
fgets 함수
fgets
함수는 한 줄을 읽는 데 사용됩니다. 파일에서 한 줄을 읽어와 버퍼에 저장하며, 줄 바꿈 문자(\n
)도 포함하여 읽어옵니다. 읽은 문자의 끝은 \0
으로 자동으로 종료됩니다.
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "r");
if (file == NULL) {
printf("파일을 열 수 없습니다.\n");
return 1;
}
char buffer[100];
while (fgets(buffer, sizeof(buffer), file) != NULL) {
printf("%s", buffer); // 한 줄씩 읽어 출력
}
fclose(file);
return 0;
}
이 코드는 example.txt
파일을 열고, 한 줄씩 읽어서 출력합니다. fgets
함수는 파일에서 한 줄씩 읽어 처리할 때 유용합니다.
fread 함수
fread
함수는 파일에서 지정한 크기만큼 데이터를 읽어옵니다. 주로 바이너리 파일을 처리할 때 사용되며, 파일에서 지정한 바이트 수만큼 읽어오는 방식입니다.
#include <stdio.h>
int main() {
FILE *file = fopen("example.bin", "rb");
if (file == NULL) {
printf("파일을 열 수 없습니다.\n");
return 1;
}
char buffer[10];
size_t bytesRead;
while ((bytesRead = fread(buffer, 1, sizeof(buffer), file)) > 0) {
for (size_t i = 0; i < bytesRead; i++) {
printf("%c", buffer[i]);
}
}
fclose(file);
return 0;
}
위 코드는 example.bin
파일을 바이너리 모드로 열고, 10바이트씩 읽어와 출력하는 예시입니다. fread
는 바이너리 파일을 읽을 때 매우 유용합니다.
파일 쓰기 함수
C언어에서는 파일에 데이터를 쓰기 위한 여러 가지 함수가 제공됩니다. 대표적인 함수로는 fputc
, fputs
, fwrite
가 있으며, 각 함수는 파일에 데이터를 쓰는 방식이 다릅니다. 사용 목적에 따라 적절한 함수 선택이 필요합니다.
fputc 함수
fputc
함수는 파일에 한 문자를 쓰는 함수입니다. 파일의 끝에 문자를 추가하며, 데이터를 한 문자씩 처리할 때 유용합니다. fputc
는 int
형식으로 문자를 전달하고, 반환값은 실제로 기록된 문자입니다.
#include <stdio.h>
int main() {
FILE *file = fopen("output.txt", "w");
if (file == NULL) {
printf("파일을 열 수 없습니다.\n");
return 1;
}
fputc('H', file); // 'H'를 파일에 씁니다.
fputc('i', file); // 'i'를 파일에 씁니다.
fclose(file);
return 0;
}
위 코드는 output.txt
파일을 열고, H
와 i
문자를 파일에 씁니다.
fputs 함수
fputs
함수는 파일에 문자열을 쓰는 함수입니다. 문자열 끝에는 자동으로 널 종료 문자(\0
)가 포함되므로, 문자열을 그대로 파일에 쓸 때 유용합니다. fputs
는 파일에 문자열을 추가하고, 문자열의 끝에 \n
을 자동으로 넣지 않으므로, 줄 바꿈을 직접 추가해야 합니다.
#include <stdio.h>
int main() {
FILE *file = fopen("output.txt", "w");
if (file == NULL) {
printf("파일을 열 수 없습니다.\n");
return 1;
}
fputs("Hello, World!\n", file); // 문자열을 파일에 씁니다.
fclose(file);
return 0;
}
이 코드는 "Hello, World!"
라는 문자열을 output.txt
파일에 씁니다.
fwrite 함수
fwrite
함수는 파일에 지정된 크기만큼 데이터를 쓰는 함수입니다. 주로 바이너리 데이터를 처리할 때 사용되며, 메모리 버퍼에 저장된 데이터를 파일에 일괄적으로 쓸 때 유용합니다. fwrite
는 배열이나 구조체와 같은 데이터를 파일에 쓸 수 있도록 도와줍니다.
#include <stdio.h>
int main() {
FILE *file = fopen("output.bin", "wb");
if (file == NULL) {
printf("파일을 열 수 없습니다.\n");
return 1;
}
char buffer[] = "Binary data example";
fwrite(buffer, sizeof(char), sizeof(buffer) - 1, file); // 바이너리 데이터를 파일에 씁니다.
fclose(file);
return 0;
}
위 코드는 output.bin
파일을 바이너리 모드로 열고, buffer
에 저장된 데이터를 파일에 씁니다. fwrite
함수는 대용량 데이터를 효율적으로 파일에 저장할 때 유용합니다.
파일 오류 처리
파일 입출력 작업을 수행할 때, 파일 열기 실패, 읽기/쓰기 오류 등 여러 가지 문제가 발생할 수 있습니다. C언어는 파일 작업에서 발생할 수 있는 오류를 처리할 수 있는 다양한 방법을 제공합니다. 여기서는 ferror
와 feof
함수를 사용하여 파일 입출력 오류를 처리하는 방법을 다룹니다.
ferror 함수
ferror
함수는 파일 처리 중 오류가 발생했는지를 확인하는 함수입니다. 파일 작업을 진행한 후, 오류가 발생한 경우 이 함수를 호출하여 오류 여부를 확인할 수 있습니다. 오류가 발생하면 ferror
는 0이 아닌 값을 반환합니다.
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "r");
if (file == NULL) {
printf("파일을 열 수 없습니다.\n");
return 1;
}
char c;
while ((c = fgetc(file)) != EOF) {
if (ferror(file)) {
printf("파일 읽기 오류 발생!\n");
fclose(file);
return 1;
}
putchar(c); // 읽은 문자를 출력
}
fclose(file);
return 0;
}
위 코드는 파일을 읽을 때 오류가 발생하면 즉시 ferror
로 오류를 확인하고, 오류 발생 시 메시지를 출력합니다.
feof 함수
feof
함수는 파일의 끝에 도달했는지를 확인하는 함수입니다. 파일을 읽는 도중에 feof
를 사용하여 파일의 끝을 체크할 수 있습니다. feof
는 파일의 끝에 도달하면 true
(1)를 반환하고, 아직 끝에 도달하지 않았으면 false
(0)를 반환합니다.
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "r");
if (file == NULL) {
printf("파일을 열 수 없습니다.\n");
return 1;
}
char c;
while ((c = fgetc(file)) != EOF) {
putchar(c); // 읽은 문자를 출력
}
if (feof(file)) {
printf("\n파일 끝에 도달했습니다.\n");
}
fclose(file);
return 0;
}
이 코드는 파일을 읽고, 파일의 끝에 도달했을 때 feof
를 사용하여 이를 알리는 메시지를 출력합니다. feof
는 파일 끝에 도달했는지 여부를 확인할 때 유용하게 사용됩니다.
ferror와 feof의 차이점
ferror
: 파일 입출력 중 오류가 발생했는지 확인합니다. 오류가 발생하면 0이 아닌 값을 반환합니다.feof
: 파일의 끝에 도달했는지를 확인합니다. 파일의 끝에 도달하면true
를 반환하고, 그 전에는false
를 반환합니다.
이 두 함수는 파일 입출력에서 발생할 수 있는 문제를 효과적으로 처리하는 데 중요한 역할을 합니다.
텍스트 파일과 바이너리 파일
C언어에서는 파일을 텍스트 파일과 바이너리 파일로 구분하여 처리합니다. 이 두 가지 유형의 파일은 읽기/쓰기 방식이 다르며, 각 파일 유형에 맞는 입출력 방법을 사용해야 합니다. 텍스트 파일은 사람이 읽을 수 있는 형식으로 저장되는 반면, 바이너리 파일은 데이터를 이진 형식으로 저장합니다.
텍스트 파일
텍스트 파일은 사람이 읽을 수 있는 형식으로 데이터를 저장한 파일입니다. 일반적으로 문자열과 같은 가독성이 있는 데이터를 저장하는 데 사용됩니다. 텍스트 파일은 각 줄 끝에 개행 문자(\n
)를 포함하며, 텍스트 편집기를 통해 쉽게 열어볼 수 있습니다.
- 텍스트 파일 읽기/쓰기 시,
fgetc
,fgets
,fputc
,fputs
등의 함수가 사용됩니다. - 텍스트 파일은 개행 문자(
\n
)와 같은 특수 문자가 포함되며,\0
(널 종료 문자)은 문자열 끝을 나타냅니다.
텍스트 파일 예시
#include <stdio.h>
int main() {
FILE *file = fopen("textfile.txt", "w");
if (file == NULL) {
printf("파일을 열 수 없습니다.\n");
return 1;
}
fputs("이것은 텍스트 파일입니다.\n", file);
fputs("새로운 줄에 쓰기!\n", file);
fclose(file);
return 0;
}
위 코드는 textfile.txt
라는 텍스트 파일을 열어 두 줄의 문자열을 작성합니다. 텍스트 파일은 사람이 읽을 수 있는 형식으로 작성되기 때문에 편집기에서 내용을 쉽게 확인할 수 있습니다.
바이너리 파일
바이너리 파일은 텍스트가 아닌 이진 데이터를 저장하는 파일입니다. 이진 형식으로 데이터를 처리하므로, 텍스트 파일보다 훨씬 효율적이고 정확한 저장이 가능합니다. 예를 들어, 이미지 파일, 오디오 파일, 실행 파일 등은 대부분 바이너리 파일로 저장됩니다.
- 바이너리 파일은
fread
,fwrite
와 같은 함수로 데이터를 읽고 쓸 수 있습니다. - 바이너리 파일에는 개행 문자나 문자열 종료 문자가 없으며, 데이터는 그대로 이진 형식으로 저장됩니다.
바이너리 파일 예시
#include <stdio.h>
int main() {
FILE *file = fopen("binaryfile.bin", "wb");
if (file == NULL) {
printf("파일을 열 수 없습니다.\n");
return 1;
}
int data = 123456789;
fwrite(&data, sizeof(int), 1, file); // 이진 데이터를 파일에 씁니다.
fclose(file);
return 0;
}
이 코드는 binaryfile.bin
파일을 열어, 정수 데이터를 바이너리 형식으로 저장합니다. 바이너리 파일은 텍스트 편집기에서 열어도 사람이 읽을 수 있는 형식으로 나타나지 않기 때문에, 이진 데이터를 처리할 때 사용됩니다.
텍스트 파일과 바이너리 파일의 차이점
- 텍스트 파일: 사람이 읽을 수 있는 형식으로 데이터를 저장하며, 주로 문자열을 저장합니다. 개행 문자를 사용하여 줄 바꿈을 처리합니다.
- 바이너리 파일: 데이터를 이진 형식으로 저장하며, 텍스트 편집기에서 내용을 이해하기 어렵습니다. 주로 이미지, 비디오, 오디오 등과 같은 비정형 데이터를 저장할 때 사용됩니다.
파일 작업을 할 때 이 두 파일 유형의 차이를 이해하고, 각 파일에 맞는 입출력 방식을 선택하는 것이 중요합니다.
파일 포인터와 버퍼링
파일 입출력을 효율적으로 처리하기 위해서는 파일 포인터와 버퍼링에 대한 이해가 필요합니다. C언어는 파일 작업을 위한 포인터(FILE *
)와 자동 버퍼링을 제공하여, 파일 읽기/쓰기 성능을 향상시킵니다. 이 부분에서는 파일 포인터의 역할과, 버퍼링이 파일 입출력에 미치는 영향을 설명합니다.
파일 포인터
파일 포인터는 FILE
구조체로 선언된 포인터로, 파일에 대한 정보를 저장합니다. 파일을 열 때 fopen
함수는 파일을 지정된 모드로 열고, 이를 가리키는 FILE *
포인터를 반환합니다. 파일 포인터는 파일의 현재 위치를 추적하며, 데이터를 읽거나 쓸 때마다 위치가 변경됩니다.
- 파일 포인터의 이동: 파일 포인터는 기본적으로 파일의 시작 위치에서부터 읽기/쓰기를 진행합니다.
- 파일 포인터의 이동 함수:
fseek
,ftell
,rewind
와 같은 함수로 파일 포인터를 이동시킬 수 있습니다.
fseek과 ftell 함수 예시
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "r");
if (file == NULL) {
printf("파일을 열 수 없습니다.\n");
return 1;
}
fseek(file, 5, SEEK_SET); // 파일 포인터를 파일 시작에서 5바이트 위치로 이동
char c = fgetc(file); // 그 위치에서 문자 읽기
printf("읽은 문자: %c\n", c);
long pos = ftell(file); // 현재 파일 포인터 위치 확인
printf("현재 파일 포인터 위치: %ld\n", pos);
fclose(file);
return 0;
}
위 코드는 example.txt
파일을 열고, fseek
함수로 파일 포인터를 이동시켜 파일 내 특정 위치에서 문자를 읽어옵니다. ftell
함수는 현재 파일 포인터의 위치를 반환합니다.
파일 버퍼링
파일 입출력은 보통 성능 최적화를 위해 버퍼링을 사용합니다. 버퍼링이란, 데이터를 직접 파일에 쓰는 대신 메모리 공간에 임시로 저장한 후, 일정 조건이 만족되었을 때 파일에 한 번에 기록하는 방식입니다. 버퍼링을 사용하면 여러 번의 디스크 I/O 작업을 하나의 대용량 작업으로 합쳐 성능을 향상시킬 수 있습니다.
C언어의 stdio.h
라이브러리는 자동으로 파일에 대해 버퍼링을 적용합니다. 파일을 열 때, 버퍼 크기는 기본적으로 시스템에 의해 설정되며, 이 버퍼를 통해 데이터를 효율적으로 처리할 수 있습니다. 버퍼링은 파일을 여는 모드에 따라 다르게 적용될 수 있습니다.
버퍼링 동작 예시
#include <stdio.h>
int main() {
FILE *file = fopen("buffered_output.txt", "w");
if (file == NULL) {
printf("파일을 열 수 없습니다.\n");
return 1;
}
fputs("버퍼링을 사용한 파일 쓰기 예시.\n", file);
fflush(file); // 버퍼에 남아있는 데이터를 파일에 강제로 기록
fclose(file);
return 0;
}
이 코드는 buffered_output.txt
파일을 열고 데이터를 버퍼에 저장한 후, fflush
함수로 버퍼에 남아있는 데이터를 강제로 파일에 기록합니다. fflush
는 주로 버퍼에 데이터가 남아 있을 때, 즉시 파일에 기록하도록 강제할 때 사용됩니다.
버퍼링의 장점과 단점
- 장점:
- 디스크 I/O 작업을 최소화하여 성능을 향상시킵니다.
- 여러 번의 작은 파일 쓰기 작업을 하나의 큰 작업으로 처리할 수 있습니다.
- 단점:
- 프로그램 종료 전까지 버퍼에 저장된 데이터가 디스크에 기록되지 않을 수 있습니다. (예: 시스템 충돌 시 데이터 손실 가능성)
- 버퍼 크기를 조정하는 데 제한이 있을 수 있습니다.
버퍼링은 파일 입출력 성능을 크게 향상시킬 수 있지만, 데이터를 즉시 디스크에 반영하고 싶은 경우에는 fflush
와 같은 함수로 버퍼를 강제로 비우는 것이 필요합니다.
파일 닫기와 자원 해제
파일 작업이 끝난 후에는 반드시 파일을 닫고, 자원을 해제해야 합니다. C언어에서 파일을 열면 운영체제의 자원을 할당받게 되며, 이 자원을 반환하지 않으면 자원 누수나 시스템 오류가 발생할 수 있습니다. fclose
함수는 파일 작업을 마친 후 파일을 안전하게 닫고, 관련 자원을 반환하는 역할을 합니다.
fclose 함수
fclose
함수는 파일을 닫고 파일에 대한 작업을 종료하는 함수입니다. 이 함수는 파일을 열 때 반환된 FILE *
포인터를 인자로 받아 파일을 닫습니다. 파일을 닫은 후에는 해당 포인터를 사용하여 파일에 접근할 수 없으며, 파일을 다시 열려면 fopen
을 사용해야 합니다.
#include <stdio.h>
int main() {
FILE *file = fopen("output.txt", "w");
if (file == NULL) {
printf("파일을 열 수 없습니다.\n");
return 1;
}
fputs("파일을 안전하게 닫습니다.\n", file);
if (fclose(file) != 0) {
printf("파일을 닫는 중 오류가 발생했습니다.\n");
return 1;
}
printf("파일을 성공적으로 닫았습니다.\n");
return 0;
}
위 코드는 output.txt
파일을 열어 내용을 작성한 후, fclose
함수로 파일을 닫습니다. 만약 fclose
가 실패하면 오류 메시지를 출력합니다. 파일을 닫는 과정에서 오류가 발생할 가능성은 드물지만, 안전한 파일 작업을 위해 오류를 확인하는 것이 좋습니다.
파일을 닫지 않았을 때의 문제
파일을 닫지 않으면 여러 가지 문제가 발생할 수 있습니다. 예를 들어,
- 자원 누수: 파일 포인터와 관련된 자원이 해제되지 않아 시스템 자원이 고갈될 수 있습니다.
- 파일 내용 손실: 버퍼링된 데이터가 파일에 기록되지 않거나, 파일의 끝에 도달한 후의 변경 내용이 저장되지 않을 수 있습니다.
- 동기화 문제: 여러 프로세스나 스레드가 동일한 파일을 동시에 접근할 때, 파일을 닫지 않으면 데이터 동기화 문제가 발생할 수 있습니다.
따라서 파일을 다 사용한 후에는 반드시 fclose
를 호출하여 파일을 닫고, 자원을 해제하는 것이 중요합니다.
파일을 닫은 후 포인터 사용 주의
파일을 닫은 후에는 해당 파일 포인터를 다시 사용하지 않도록 주의해야 합니다. fclose
이후에는 해당 포인터가 가리키는 파일이 더 이상 유효하지 않으므로, 이를 참조하려 하면 예기치 않은 오류가 발생할 수 있습니다.
FILE *file = fopen("example.txt", "r");
fclose(file); // 파일 닫음
// 잘못된 코드: 파일을 닫은 후 다시 포인터를 사용하려는 경우
fgetc(file); // 파일을 이미 닫았으므로 오류 발생
이와 같은 문제를 방지하려면, 파일 작업 후 즉시 파일 포인터를 NULL
로 설정하여 잘못된 사용을 방지할 수 있습니다.
요약
본 기사에서는 C언어에서 커맨드라인에서 파일 입출력을 다루는 방법에 대해 다뤘습니다. 파일을 여는 과정, 읽기와 쓰기 방법, 파일 오류 처리, 텍스트 파일과 바이너리 파일의 차이점, 파일 포인터와 버퍼링, 파일 닫기와 자원 해제까지 다양한 주제를 다루었습니다.
파일 작업 시 중요한 점은 파일을 안전하게 열고, 읽고 쓰는 동안 발생할 수 있는 오류를 처리하는 것이며, 작업이 끝난 후에는 반드시 파일을 닫고 자원을 해제하는 것입니다. 파일 포인터를 적절히 관리하고, 버퍼링을 활용하여 성능을 최적화하는 방법도 중요합니다.
적절한 파일 작업은 프로그램의 안정성을 높이고, 자원 관리와 성능 측면에서도 큰 도움이 됩니다.