본문 바로가기
GD's IT Lectures : 기초부터 시리즈/C, C++ 기초부터 ~

[C/C++ 프로그래밍] 13. 파일 입출력

by GDNGY 2023. 5. 16.

Chapter 13. 파일 입출력

이 장에서는 C/C++에서 파일 입출력에 대해 상세하게 다루게 됩니다. 먼저 파일과 스트림에 대한 개념을 이해하고, 그 후에 파일 입출력의 기본적인 방법에 대해 알아보게 됩니다. 이를 위해 파일을 열고 닫는 방법, 그리고 파일에서 문자 및 문자열을 읽고 쓰는 방법을 배웁니다. 다음으로, 파일 입출력 함수인 fputc, fgetc, fputs, fgets, fprintf, fscanf에 대해 설명하고, 실제로 어떻게 사용하는지에 대한 예제 코드를 통해 설명합니다. 그 후에는 파일 위치 지시자에 대해 배우고, fseek 함수와 ftell 함수의 사용법을 숙지하게 됩니다. 이어서 이진 파일 입출력에 대한 이해를 증진시키기 위해, 이진 파일과 텍스트 파일의 차이점을 학습하고, 이진 파일에서 데이터를 읽고 쓰는 방법을 실습합니다. 마지막으로, 파일 입출력의 오류 처리 방법에 대해 알아보고, 파일 입출력에서 발생할 수 있는 오류의 종류와, 이를 검사하고 처리하는 feof 함수와 ferror 함수에 대해 배우게 됩니다.

 

반응형

 


[Chapter 13. 파일 입출력]

 

13.1. 파일과 스트림의 이해
13.1.1. 파일의 개념
13.1.2. 스트림의 개념

13.2. 파일 입출력의 기초
13.2.1. 파일 열기와 닫기
13.2.2. 파일에서 문자 읽고 쓰기
13.2.3. 파일에서 문자열 읽고 쓰기

13.3. 파일 입출력 함수
13.3.1. fputc, fgetc 함수
13.3.2. fputs, fgets 함수
13.3.3. fprintf, fscanf 함수

13.4. 파일 위치 지시자
13.4.1. 파일 위치 지시자의 이해
13.4.2. fseek 함수
13.4.3. ftell 함수

13.5. 이진 파일 입출력
13.5.1. 이진 파일과 텍스트 파일의 차이
13.5.2. 이진 파일에서 데이터 읽고 쓰기

13.6. 파일 입출력의 오류 처리
13.6.1. 파일 입출력에서의 오류 종류
13.6.2. feof 함수
13.6.3. ferror 함수

 


13.1. 파일과 스트림의 이해

파일은 컴퓨터에 정보를 저장하는 기본적인 수단입니다. 텍스트, 이미지, 음악 등 다양한 형태의 데이터를 파일에 저장할 수 있습니다. 스트림은 데이터가 프로그램과 파일 사이에서 움직이는 경로를 가리킵니다. C/C++에서는 스트림을 통해 파일에서 데이터를 읽거나 파일에 데이터를 쓰는 작업을 수행합니다. 이 장에서는 이 두 가지 개념을 중심으로 파일 입출력에 대해 배웁니다.

13.1.1. 파일의 개념

파일은 컴퓨터에서 데이터를 영구적으로 저장하는 하나의 단위입니다. 우리는 일상에서 다양한 형태의 파일들과 마주칩니다. 예를 들어, 워드 문서, 이미지, 음악 파일 등이 있습니다. 이러한 파일들은 모두 컴퓨터의 디스크에 저장되어 있고, 이를 통해 데이터를 영구적으로 보관할 수 있습니다.

 

C/C++ 프로그래밍에서는 이러한 파일을 사용하여 데이터를 읽고 쓰는데, 이를 '파일 입출력'이라고 합니다. 파일 입출력은 매우 중요한 개념으로, 실무에서 자주 사용되므로 반드시 숙지해야 합니다.

 

파일을 사용하기 위해서는 먼저 '파일 포인터'를 생성해야 합니다. 파일 포인터는 파일을 가리키는 변수로, 파일에 접근하기 위해 사용됩니다.

 

C 언어에서 파일 포인터는 아래와 같이 생성합니다.

 

[예제] C

FILE *fp;

 

C++에서는 fstream 라이브러리를 사용하여 파일 스트림을 생성합니다.

[예제] C++

#include <fstream>
std::fstream file;


여기서 fp와 file이 바로 파일 포인터입니다. 파일 포인터를 선언한 후에는 'fopen' 함수(C) 또는 'open' 메서드(C++)를 사용하여 파일을 열 수 있습니다. 이 함수들은 파일 이름과 모드를 인자로 받아 파일을 열고, 파일 포인터를 반환합니다.

[예제] C

fp = fopen("test.txt", "r");
if(fp == NULL)
{
    printf("파일 열기 실패!\n");
    return 1;
}


[예제] C++

file.open("test.txt", std::ios::in);
if(!file)
{
    std::cout << "파일 열기 실패!" << std::endl;
    return 1;
}

 

위 예제에서는 "test.txt"라는 이름의 파일을 읽기 모드('r' 또는 'ios::in')로 열고 있습니다. 만약 파일 열기가 실패하면 에러 메시지를 출력하고 프로그램을 종료합니다.

 

이렇게 파일을 성공적으로 열었다면, 이제 이 파일에서 데이터를 읽거나 쓸 수 있습니다. 데이터를 다 사용한 후에는 반드시 'fclose' 함수(C) 또는 'close' 메서드(C++)를 사용하여 파일을 닫아야 합니다.

 

[예제] C

fclose(fp);


[예제] C++

file.close();

 

파일을 사용한 후에는 반드시 닫아야 하며, 이를 잊으면 메모리 누수나 다른 프로그램이 해당 파일에 접근할 수 없는 문제가 발생할 수 있습니다. 따라서 파일 입출력을 할 때는 항상 파일을 열고 사용한 후에 닫는 것을 습관화해야 합니다.

 

이로써 파일의 기본적인 개념에 대해 알아보았습니다. 이어서 다양한 파일 입출력 함수를 사용하는 방법에 대해 배워보도록 하겠습니다.

 

13.1.2. 스트림의 개념

스트림은 프로그램과 파일 사이에서 데이터가 이동하는 경로를 의미합니다. 스트림의 도입으로 프로그래밍에서 파일 입출력을 더욱 편리하게 처리할 수 있게 되었습니다. 쉽게 말해, 스트림은 데이터가 흐르는 '통로' 혹은 '파이프'와 같은 개념입니다.

 

C/C++에서는 스트림을 통해 파일에 데이터를 쓰거나 파일로부터 데이터를 읽습니다. 이 때 데이터는 일방향으로만 흐르므로 스트림은 일반적으로 입력 스트림과 출력 스트림으로 나뉩니다. 입력 스트림은 파일로부터 데이터를 읽어 프로그램으로 가져오는 역할을 하고, 출력 스트림은 프로그램에서 생성한 데이터를 파일로 보내는 역할을 합니다.

 

C 언어에서는 FILE 포인터를 이용하여 스트림을 관리합니다. FILE은 C 언어에서 제공하는 파일 입출력을 위한 라이브러리에 정의된 구조체로, 스트림을 추상화한 것입니다.

 

[예제] C

FILE *fp;
fp = fopen("test.txt", "r");

 

이렇게 fopen 함수를 사용하여 파일을 열면, 이 파일에 대한 스트림이 생성되고 이를 가리키는 FILE 포인터가 반환됩니다. 이제 fp를 통해 스트림에 접근하여 파일 입출력을 수행할 수 있습니다.

C++에서는 fstream 라이브러리를 사용하여 파일 스트림을 관리합니다. fstream 라이브러리는 ifstream(입력 스트림), ofstream(출력 스트림), 그리고 fstream(입력/출력 스트림) 클래스를 제공합니다.

 

[예제] C++

std::fstream file;
file.open("test.txt", std::ios::in);

 

C++에서는 open 메소드를 사용하여 파일을 열고, 이에 대한 스트림을 생성합니다. 이렇게 생성된 스트림은 file 객체를 통해 접근할 수 있습니다.

 

스트림을 사용하는 가장 큰 장점은 입출력 장치의 차이를 프로그래머가 의식하지 않아도 된다는 점입니다. 즉, 파일이든 키보드든 모니터든 스트림을 통하면 동일한 방식으로 데이터를 읽고 쓸 수 있습니다. 이로 인해 코드의 유연성이 향상되고 프로그래밍이 편리해집니다.

 

스트림에 대한 이해는 파일 입출력뿐만 아니라, 네트워크 통신 등 다양한 분야에서 중요하므로 반드시 숙지해야 합니다. 


13.2. 파일 입출력의 기초

파일 입출력의 기초는 파일을 열고 닫는 것부터 시작합니다. C/C++에서는 파일 포인터를 통해 파일에 접근하며, fopen 또는 fstream 라이브러리를 이용하여 파일을 열 수 있습니다. 파일에서 데이터를 읽고 쓴 후에는 반드시 fclose 또는 close 메서드를 호출하여 파일을 닫아야 합니다. 또한, 파일에서 문자나 문자열을 읽고 쓸 수 있으며, 이는 fgetc, fputc, fgets, fputs 등의 함수를 통해 가능합니다. 이번 섹션에서는 이러한 기초적인 파일 입출력 방법에 대해 배웁니다.

13.2.1. 파일 열기와 닫기

파일을 사용하기 위해서는 먼저 파일을 '열어야' 합니다. 파일을 열다는 것은 파일과 프로그램 간에 데이터를 주고받을 수 있는 경로, 즉 '스트림'을 생성하는 것을 의미합니다. 파일을 열 때는 일반적으로 파일의 이름과 파일을 열 모드를 지정해야 합니다.

 

C 언어에서는 fopen 함수를 사용하여 파일을 열 수 있습니다. fopen 함수는 파일 이름과 모드를 인자로 받아 파일을 열고, 해당 파일의 스트림을 가리키는 FILE 포인터를 반환합니다.

 

[예제] C

FILE *fp = fopen("test.txt", "r");

 

이 코드는 "test.txt"라는 이름의 파일을 읽기 모드("r")로 열고, 이 파일의 스트림을 가리키는 FILE 포인터 fp를 생성합니다. 여기서 모드는 파일을 어떻게 열 것인지를 나타내는 문자열로, "r"은 읽기 모드, "w"는 쓰기 모드, "a"는 추가 모드를 의미합니다.

 

파일을 열고 나서 파일 작업을 마친 후에는 반드시 fclose 함수를 호출하여 파일을 닫아야 합니다. fclose 함수는 열려 있는 파일의 스트림을 닫고, 이에 연관된 자원을 해제합니다.

 

[예제] C

fclose(fp);

 

C++에서는 fstream 라이브러리의 ifstream, ofstream, fstream 클래스를 사용하여 파일을 열 수 있습니다. 파일을 열 때는 open 메소드를 호출하고, 파일 이름과 모드를 인자로 전달합니다.

[예제] C++

std::fstream file;
file.open("test.txt", std::ios::in);

이 코드는 "test.txt"라는 이름의 파일을 읽기 모드로 열고, 이 파일의 스트림을 가리키는 file 객체를 생성합니다. 여기서 모드는 ios 클래스의 멤버로, ios::in은 읽기 모드, ios::out은 쓰기 모드, ios::app은 추가 모드를 의미합니다.

 

파일 작업을 마친 후에는 fstream 클래스의 close 메소드를 호출하여 파일을 닫아야 합니다.

 

[예제] C++

file.close();

 

이렇게 파일을 열었다가 닫는 것은 파일 작업을 안전하게 수행하기 위해 필수적입니다. 파일을 제대로 닫지 않으면 데이터가 제대로 저장되지 않을 수 있으며, 다른 프로그램이 해당 파일에 접근하는 데 문제가 생길 수 있습니다. 따라서 파일 작업을 항상 열고 닫는 것을 습관화해야 합니다.

 

그리고 파일을 열 때는 항상 열기에 성공했는지 확인해야 합니다. 파일 열기는 다양한 이유로 실패할 수 있기 때문입니다. 예를 들어, 파일이 존재하지 않거나, 파일에 접근 권한이 없거나, 파일이 이미 다른 프로그램에 의해 사용 중일 수 있습니다. 파일 열기가 실패하면 fopen 함수는 NULL을 반환하고, fstream의 open 메서드는 failbit를 설정합니다. 이를 확인하여 파일 열기에 실패했을 때 적절한 처리를 수행할 수 있습니다.

 

[예제] C

FILE *fp = fopen("test.txt", "r");
if (fp == NULL) {
    perror("Error opening file");
    return -1;
}

// file operations...

fclose(fp);

 

[예제] C++

std::fstream file;
file.open("test.txt", std::ios::in);
if (file.fail()) {
    std::cerr << "Error opening file" << std::endl;
    return -1;
}

// file operations...

file.close();

 

다음 섹션에서는 이렇게 열린 파일에서 어떻게 문자와 문자열을 읽고 쓸 수 있는지에 대해 알아보겠습니다.

 

13.2.2. 파일에서 문자 읽고 쓰기

파일에서 문자를 읽거나 쓰는 것은 파일 입출력의 가장 기본적인 작업 중 하나입니다. 이러한 작업은 각각 fgetc 함수와 fputc 함수를 사용하여 수행할 수 있습니다.

 

C 언어에서 fgetc 함수는 FILE 포인터를 인자로 받아 해당 파일에서 한 문자를 읽고, 이를 int 형으로 반환합니다. fgetc 함수는 읽은 문자가 파일의 끝에 도달하거나 오류가 발생하면 EOF를 반환합니다.

 

[예제] C

FILE *fp = fopen("test.txt", "r");
if (fp == NULL) {
    perror("Error opening file");
    return -1;
}

int ch;
while ((ch = fgetc(fp)) != EOF) {
    putchar(ch);
}

fclose(fp);

 

이 코드는 "test.txt" 파일에서 한 문자씩 읽어 화면에 출력하는 코드입니다. 여기서 putchar 함수는 int 형의 문자를 화면에 출력하는 함수입니다.

 

반대로, fputc 함수는 한 문자와 FILE 포인터를 인자로 받아 해당 파일에 한 문자를 씁니다. fputc 함수는 성공적으로 문자를 쓰면 쓴 문자를 int 형으로 반환하고, 오류가 발생하면 EOF를 반환합니다.

 

[예제] C

FILE *fp = fopen("test.txt", "w");
if (fp == NULL) {
    perror("Error opening file");
    return -1;
}

char str[] = "Hello, World!";
for (int i = 0; str[i] != '\0'; i++) {
    fputc(str[i], fp);
}

fclose(fp);

 

이 코드는 "test.txt" 파일에 "Hello, World!"라는 문자열을 한 문자씩 쓰는 코드입니다.

 

C++에서는 fstream 라이브러리의 get과 put 메소드를 사용하여 문자를 읽거나 쓸 수 있습니다. get 메서드는 파일에서 한 문자를 읽어 char 형으로 반환하고, 파일의 끝에 도달하면 eofbit를 설정합니다. put 메서드는 한 문자를 인자로 받아 파일에 쓰고, 오류가 발생하면 failbit를 설정합니다.

 

[예제] C++

std::fstream file;
file.open("test.txt", std::ios::in);
if (file.fail()) {
    std::cerr << "Error opening file" << std::endl;
    return -1;
}

char ch;
while (file.get(ch)) {
    std::cout << ch;
}

file.close();

 

[예제] C++

std::fstream file;
file.open("test.txt", std::ios::out);
if (file.fail()) {
    std::cerr << "Error opening file" << std::endl;
    return -1;
}

char str[] = "Hello, World!";
for (int i = 0; str[i] != '\0'; i++) {
    file.put(str[i]);
}

file.close();

 

문자 단위로 파일을 읽거나 쓸 때는 주의해야 할 점이 있습니다. 먼저, 파일을 읽거나 쓸 때 항상 반환 값을 확인하여 파일의 끝에 도달하거나 오류가 발생했는지 확인해야 합니다. 또한, 문자 단위로 파일을 읽거나 쓸 때는 처리 효율이 낮을 수 있으므로, 가능하면 문자열 단위로 파일을 읽거나 쓰는 것이 좋습니다. 

 

13.2.3. 파일에서 문자열 읽고 쓰기

파일에서 문자열을 읽고 쓰는 것은 파일 처리에서 빈번하게 발생하는 작업입니다. 문자열을 처리하는데는 C 언어에서 fgets, fputs 함수와 C++에서 getline, operator<< 함수를 사용합니다.

 

C 언어에서 fgets 함수는 버퍼의 크기와 FILE 포인터를 인자로 받아 해당 파일에서 한 줄을 읽습니다. 버퍼의 크기만큼 읽거나 개행 문자를 만나면 읽기를 중지하고 버퍼를 반환합니다. 파일의 끝에 도달하거나 오류가 발생하면 NULL을 반환합니다.

 

[예제] C

FILE *fp = fopen("test.txt", "r");
if (fp == NULL) {
    perror("Error opening file");
    return -1;
}

char buffer[256];
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
    printf("%s", buffer);
}

fclose(fp);

 

이 코드는 "test.txt" 파일에서 한 줄씩 읽어 화면에 출력하는 코드입니다.

 

반대로, fputs 함수는 문자열과 FILE 포인터를 인자로 받아 해당 파일에 문자열을 씁니다. fputs 함수는 성공적으로 문자열을 쓰면 양의 값을 반환하고, 오류가 발생하면 EOF를 반환합니다.

 

[예제] C

FILE *fp = fopen("test.txt", "w");
if (fp == NULL) {
    perror("Error opening file");
    return -1;
}

char *str = "Hello, World!\n";
fputs(str, fp);

fclose(fp);

 

이 코드는 "test.txt" 파일에 "Hello, World!\n" 문자열을 쓰는 코드입니다.

 

C++에서는 fstream 라이브러리의 getline 함수와 operator<< 함수를 사용하여 문자열을 읽거나 쓸 수 있습니다. getline 함수는 istream 객체와 문자열을 인자로 받아 파일에서 한 줄을 읽습니다. 개행 문자를 만나면 읽기를 중지하고 istream 객체를 반환합니다. 파일의 끝에 도달하면 eofbit를 설정합니다.

 

[예제] C++

std::fstream file;
file.open("test.txt", std::ios::in);
if (file.fail()) {
    std::cerr << "Error opening file" << std::endl;
    return -1;
}

std::string line;
while (getline(file, line)) {
    std::cout << line << std::endl;
}

file.close();

 

이 코드는 "test.txt" 파일에서 한 줄씩 읽어 화면에 출력하는 코드입니다.

 

반대로, operator<< 함수는 ostream 객체와 문자열을 인자로 받아 파일에 문자열을 씁니다. 파일의 끝에 도달하거나 오류가 발생하면 failbit 또는 badbit를 설정합니다.

 

[예제] C++

std::fstream file;
file.open("test.txt", std::ios::out);
if (file.fail()) {
    std::cerr << "Error opening file" << std::endl;
    return -1;
}

std::string str = "Hello, World!\n";
file << str;

file.close();

 

이 코드는 "test.txt" 파일에 "Hello, World!\n" 문자열을 쓰는 코드입니다.

파일에서 문자열을 읽고 쓸 때는 항상 오류를 처리하고 파일을 올바르게 닫는 것이 중요합니다. 이를 위해 C 언어에서는 perror 함수와 fclose 함수를, C++에서는 fail 함수와 close 함수를 사용합니다.

이렇게 하면 파일에서 문자열을 읽고 쓰는 기본적인 작업을 수행할 수 있습니다. 

 


13.3. 파일 입출력 함수

C/C++에서 제공하는 다양한 파일 입출력 함수에 대해 알아볼 것입니다. 문자나 문자열을 읽고 쓰는 간단한 함수에서부터, 데이터의 형식을 지정하여 입출력하는 복잡한 함수까지 다양하게 살펴봅니다. 각 함수의 사용법과 예제 코드를 통해 실질적인 활용 방법을 배우는 것이 이 섹션의 핵심입니다. 이해를 돕기 위해 간단한 예제코드도 함께 제공될 예정입니다.

13.3.1. fputc, fgetc 함수

C/C++에서 파일 입출력을 다룰 때에는 'fputc'와 'fgetc'라는 두 가지 기본 함수를 자주 사용합니다. 이 두 함수는 각각 파일에 문자를 쓰거나 파일에서 문자를 읽는 역할을 합니다.

 

'fputc' 함수는 파일에 문자를 쓰는 함수입니다. 이 함수의 프로토타입은 다음과 같습니다.

 

[예제]

int fputc(int char, FILE *stream);

 

이 함수는 'char' 인자로 전달받은 문자를 'stream' 인자로 전달받은 파일에 씁니다. 만약 함수가 성공하면 쓰여진 문자를 반환하고, 실패하면 EOF를 반환합니다.

 

다음은 'fputc' 함수를 사용하는 예제입니다.

 

[예제]

#include <stdio.h>

int main() {
    FILE *fp = fopen("test.txt", "w");
    if (fp == NULL) {
        printf("File open error!\n");
        return 0;
    }

    char str[] = "Hello, World!";
    int i = 0;
    while (str[i]) {
        fputc(str[i++], fp);
    }

    fclose(fp);

    return 0;
}

 

위의 코드는 "test.txt"라는 파일을 쓰기 모드로 열고, "Hello, World!"라는 문자열을 파일에 쓴 후에 파일을 닫습니다.

 

'fgetc' 함수는 파일에서 문자를 읽는 함수입니다. 이 함수의 프로토타입은 다음과 같습니다.

 

[예제]

int fgetc(FILE *stream);

 

이 함수는 'stream' 인자로 전달받은 파일에서 문자를 읽습니다. 만약 함수가 성공하면 읽은 문자를 반환하고, 파일의 끝에 도달하거나 오류가 발생하면 EOF를 반환합니다.

 

다음은 'fgetc' 함수를 사용하는 예제입니다.

 

[예제]

#include <stdio.h>

int main() {
    FILE *fp = fopen("test.txt", "r");
    if (fp == NULL) {
        printf("File open error!\n");
        return 0;
    }

    int ch;
    while ((ch = fgetc(fp)) != EOF) {
        putchar(ch);
    }

    fclose(fp);

    return 0;
}

 

위의 코드는 "test.txt"라는 파일을 읽기 모드로 열고, 파일의 내용을 끝까지 읽은 후에 파일을 닫습니다.

 

'fputc'와 'fgetc' 함수는 파일의 입출력을 문자 단위로 다루기 때문에 매우 기본적인 입출력 함수입니다. 하지만 이 함수들을 이용하면 파일에 문자를 쓰거나 읽는 것 외에도 다양한 작업을 수행할 수 있습니다.

 

13.3.2. fputs, fgets 함수

C/C++에서 파일 입출력을 다루기 위해 사용되는 또 다른 중요한 함수 쌍은 'fputs'와 'fgets'입니다. 이들은 각각 파일에 문자열을 쓰거나 파일에서 문자열을 읽는 역할을 합니다.

 

'fputs' 함수는 파일에 문자열을 쓰는 함수입니다. 이 함수의 프로토타입은 다음과 같습니다.

 

[예제]

int fputs(const char *str, FILE *stream);

 

이 함수는 'str' 인자로 전달받은 문자열을 'stream' 인자로 전달받은 파일에 씁니다. 만약 함수가 성공하면 0 이상의 값을 반환하고, 실패하면 EOF를 반환합니다.

 

다음은 'fputs' 함수를 사용하는 예제입니다.

 

[예제]

#include <stdio.h>

int main() {
    FILE *fp = fopen("test.txt", "w");
    if (fp == NULL) {
        printf("File open error!\n");
        return 0;
    }

    char str[] = "Hello, World!";
    fputs(str, fp);

    fclose(fp);

    return 0;
}

 

위의 코드는 "test.txt"라는 파일을 쓰기 모드로 열고, "Hello, World!"라는 문자열을 파일에 쓴 후에 파일을 닫습니다.


'fgets' 함수는 파일에서 문자열을 읽는 함수입니다. 이 함수의 프로토타입은 다음과 같습니다.


[예제]

char *fgets(char *str, int n, FILE *stream);

 

이 함수는 최대 'n-1' 개의 문자를 읽어서 'str'에 저장하고, 문자열의 끝에 널 문자('\0')를 추가합니다. 만약 줄 바꿈 문자나 파일의 끝에 도달하면 읽기를 중단합니다. 만약 함수가 성공하면 'str'을 반환하고, 실패하면 NULL을 반환합니다.

 

다음은 'fgets' 함수를 사용하는 예제입니다.

 

[예제]

#include <stdio.h>

#define SIZE 100

int main() {
    FILE *fp = fopen("test.txt", "r");
    if (fp == NULL) {
        printf("File open error!\n");
        return 0;
    }

    char str[SIZE];
    while (fgets(str, SIZE, fp) != NULL) {
        printf("%s", str);
    }

    fclose(fp);

    return 0;
}

 

위의 코드는 "test.txt"라는 파일을 읽기 모드로 열고, 파일의 내용을 끝까지 읽은 후에 파일을 닫습니다.

 

'fputs'와 'fgets' 함수는 파일의 입출력을 문자열 단위로 다루기 때문에 문자나 문자열이 아닌 다른 데이터를 다루고 싶을 때에는 다른 함수를 사용해야 합니다. 이런 경우에는 'fprintf'나 'fscanf' 같은 함수를 사용할 수 있습니다.

 

13.3.3. fprintf, fscanf 함수

C/C++에서 파일 입출력을 할 때, 특정 형식에 따라 데이터를 입출력하고 싶을 경우 'fprintf'와 'fscanf' 함수를 사용합니다. 이 함수들은 'printf'와 'scanf' 함수의 파일 버전이라고 할 수 있습니다.

 

'fprintf' 함수는 파일에 형식에 맞게 데이터를 출력하는 함수입니다. 이 함수의 프로토타입은 다음과 같습니다.

 

[예제]

int fprintf(FILE *stream, const char *format, ...);

 

이 함수는 'stream'으로 전달받은 파일에 'format'에 지정된 형식에 맞게 데이터를 출력합니다. '...'은 가변 인자로, 필요한 만큼의 인자를 전달받을 수 있습니다.

 

다음은 'fprintf' 함수를 사용하는 예제입니다.

 

[예제]

#include <stdio.h>

int main() {
    FILE *fp = fopen("test.txt", "w");
    if (fp == NULL) {
        printf("File open error!\n");
        return 0;
    }

    int num = 123;
    char str[] = "Hello, World!";
    fprintf(fp, "%d\n%s\n", num, str);

    fclose(fp);

    return 0;
}

 

위의 코드는 "test.txt"라는 파일을 쓰기 모드로 열고, 정수와 문자열을 파일에 씁니다. 이때 'fprintf' 함수를 사용해서 출력 형식을 지정합니다.

 

'fscanf' 함수는 파일에서 형식에 맞게 데이터를 입력받는 함수입니다. 이 함수의 프로토타입은 다음과 같습니다.

 

[예제]

int fscanf(FILE *stream, const char *format, ...);

 

이 함수는 'stream'으로 전달받은 파일에서 'format'에 지정된 형식에 맞게 데이터를 입력받습니다. '...'은 가변 인자로, 필요한 만큼의 인자를 전달받을 수 있습니다.

 

다음은 'fscanf' 함수를 사용하는 예제입니다.

 

[예제]

#include <stdio.h>

int main() {
    FILE *fp = fopen("test.txt", "r");
    if (fp == NULL) {
        printf("File open error!\n");
        return 0;
    }

    int num;
    char str[100];
    fscanf(fp, "%d", &num);
    fscanf(fp, "%s", str);
    printf("num: %d, str: %s\n", num, str);

    fclose(fp);

    return 0;
}

 

위의 코드는 "test.txt"라는 파일을 읽기 모드로 열고, 정수와 문자열을 파일에서 읽습니다. 이때 'fscanf' 함수를 사용해서 입력 형식을 지정합니다.

 

'fprintf'와 'fscanf' 함수는 다양한 형식의 데이터를 파일 입출력 할 수 있게 해주므로 매우 유용합니다. 하지만 이 함수들을 사용할 때는 형식 지정자와 전달하는 인자의 타입이 잘 맞는지 주의해야 합니다.

 


13.4. 파일 위치 지시자

파일에서 데이터를 읽고 쓸 때 중요한 개념인 '파일 위치 지시자'에 대해 알아보겠습니다. 파일 위치 지시자는 현재 파일에서 읽고/쓰는 위치를 가리키는 지표입니다. 이를 이해하면 파일의 특정 위치로 직접 이동하여 데이터를 읽거나 쓸 수 있습니다. 이 섹션에서는 파일 위치 지시자가 어떤 역할을 하는지, 그리고 fseek와 ftell 등의 함수를 사용하여 파일 위치 지시자를 어떻게 제어할 수 있는지 배웁니다.

13.4.1. 파일 위치 지시자의 이해

'파일 위치 지시자'는 파일 내에서 현재 우리가 어디에 위치해 있는지를 알려주는 매우 중요한 개념입니다. 파일을 읽거나 쓸 때, 우리는 항상 어떤 위치에서 시작하는지를 알아야 합니다. 그래야만 원하는 위치에서부터 읽기를 시작하거나 쓰기를 시작할 수 있기 때문입니다.

 

파일 위치 지시자는 파일의 시작점에서부터 몇 번째 바이트에 있는지를 나타냅니다. 이 값은 0부터 시작하며, 파일을 처음 열면 파일 위치 지시자는 자동으로 0으로 설정됩니다. 그렇게 되면 우리는 파일의 맨 처음부터 데이터를 읽거나 쓸 수 있게 됩니다.

 

다음은 파일 위치 지시자의 개념을 이해하기 위한 C 언어 예제입니다.

 

[예제] C

#include <stdio.h>

int main() {
    FILE *fp = fopen("example.txt", "r");
    
    if (fp == NULL) {
        printf("Failed to open file\n");
        return -1;
    }
    
    char ch;
    
    while((ch = fgetc(fp)) != EOF) {
        printf("%c", ch);
    }
    
    fclose(fp);
    
    return 0;
}

 

위의 코드에서는 example.txt라는 파일을 읽기 모드('r')로 열고, 파일의 끝까지 각 문자를 읽어서 화면에 출력합니다. fgetc 함수를 호출할 때마다 파일 위치 지시자가 자동으로 1씩 증가하면서 다음 문자를 가리키게 됩니다.

 

다음은 파일 위치 지시자의 개념을 이해하기 위한 C++ 언어 예제입니다.

 

[예제] C++

#include <fstream>
#include <iostream>

int main() {
    std::ifstream file("example.txt");
    
    if (!file.is_open()) {
        std::cout << "Failed to open file\n";
        return -1;
    }
    
    char ch;
    
    while(file.get(ch)) {
        std::cout << ch;
    }
    
    file.close();
    
    return 0;
}

 

이 C++ 코드도 C 코드와 동일한 작업을 수행합니다. ifstream 객체는 파일의 내용을 읽고, 각 문자를 화면에 출력합니다. get 함수를 호출할 때마다 파일 위치 지시자가 자동으로 1씩 증가하면서 다음 문자를 가리키게 됩니다.

 

이렇게 파일 위치 지시자를 이해하는 것은 파일을 효율적으로 읽고 쓰는데 중요한 기본적인 단계입니다. 다음 섹션에서는 파일 위치 지시자를 이동시키는 방법을 알아보겠습니다.

13.4.2. fseek 함수

파일 위치 지시자를 이동시키는 방법을 배우는 것은 파일 입출력에서 중요한 단계입니다. 그 중에서 'fseek' 함수는 특히 중요한 도구입니다. 이 함수는 파일 위치 지시자를 원하는 위치로 이동시켜 주는 역할을 합니다.

 

'fseek' 함수는 다음과 같이 세 개의 인자를 받습니다.

 

FILE 포인터: 위치 지시자를 변경하고자 하는 파일의 FILE 객체에 대한 포인터입니다.
long offset: 파일의 시작 지점에서부터 얼마나 떨어진 곳으로 지시자를 이동할지를 나타내는 정수 값입니다.
int whence: offset의 기준점을 결정합니다. 이는 세 가지 상수, SEEK_SET, SEEK_CUR, SEEK_END 중 하나를 사용할 수 있습니다. SEEK_SET는 파일의 시작 위치, SEEK_CUR는 현재 위치, SEEK_END는 파일의 끝을 의미합니다.
이제 이 개념을 몇 가지 예제와 함께 살펴봅시다.

 

먼저, C언어 예제입니다.

 

[예제] C

#include <stdio.h>

int main() {
    FILE *fp = fopen("example.txt", "r+");
    
    if (fp == NULL) {
        printf("Failed to open file\n");
        return -1;
    }
    
    // Move file position indicator 10 bytes ahead from the start
    fseek(fp, 10, SEEK_SET);
    
    char ch = 'A';
    fputc(ch, fp);
    
    fclose(fp);
    
    return 0;
}

 

위의 예제에서, 우리는 "example.txt"라는 파일을 열고, 'fseek' 함수를 이용해 파일 위치 지시자를 파일 시작으로부터 10바이트 뒤로 이동시킵니다. 그리고 'fputc' 함수를 이용해 그 위치에 문자 'A'를 씁니다.

 

C++ 예제를 보겠습니다.

 

[예제] C++

#include <fstream>

int main() {
    std::fstream file("example.txt", std::ios::in | std::ios::out);
    
    if (!file.is_open()) {
        std::cout << "Failed to open file\n";
        return -1;
    }
    
    // Move file position indicator 10 bytes ahead from the start
    file.seekp(10, std::ios::beg);
    
    char ch = 'A';
    file.put(ch);
    
    file.close();
    
    return 0;
}

 

이 C++ 코드도 C 코드와 동일한 작업을 수행합니다. std::fstream 객체를 이용해 파일을 열고, 'seekp' 함수를 이용해 파일 위치 지시자를 파일 시작으로부터 10바이트 뒤로 이동시키고, 'put' 함수를 이용해 그 위치에 문자 'A'를 씁니다.

 

이처럼 'fseek' 함수를 이해하고 활용하는 것은 파일에서 원하는 위치에 데이터를 읽거나 쓰는데 큰 도움이 됩니다. 이 함수를 잘 활용하면 파일 입출력을 보다 효율적으로 수행할 수 있습니다.

 

13.4.3. ftell 함수

파일 입출력에서 'ftell' 함수는 매우 중요합니다. 'ftell' 함수는 파일 위치 지시자의 현재 위치를 리턴합니다. 이 함수는 파일이 열린 상태에서만 사용되며, 파일의 시작 위치로부터 파일 위치 지시자가 얼마나 떨어져 있는지를 바이트 단위로 리턴합니다.

 

'ftell' 함수는 다음과 같이 사용됩니다:

 

[예제]

long ftell(FILE *stream);

 

여기서 stream은 파일 위치 지시자의 위치를 알고자 하는 파일의 FILE 객체에 대한 포인터입니다. 이 함수는 파일의 시작 위치로부터 파일 위치 지시자의 현재 위치를 바이트 단위로 리턴합니다.

 

아래는 C 언어를 사용한 예제입니다:

 

[예제]

#include <stdio.h>

int main() {
    FILE *fp = fopen("example.txt", "r");
    
    if (fp == NULL) {
        printf("Failed to open file\n");
        return -1;
    }
    
    fseek(fp, 10, SEEK_SET);
    long position = ftell(fp);
    printf("The file position indicator is %ld bytes away from the start\n", position);
    
    fclose(fp);
    
    return 0;
}

 

이 예제에서, 'fopen' 함수로 "example.txt" 파일을 열고, 'fseek' 함수로 파일 위치 지시자를 파일 시작으로부터 10바이트 뒤로 이동시킵니다. 그 후 'ftell' 함수를 이용해 파일 위치 지시자의 현재 위치를 확인합니다.

 

다음은 C++에서 동일한 작업을 수행하는 예제입니다:

 

[예제] C++

#include <fstream>
#include <iostream>

int main() {
    std::fstream file("example.txt", std::ios::in);
    
    if (!file.is_open()) {
        std::cout << "Failed to open file\n";
        return -1;
    }
    
    file.seekg(10, std::ios::beg);
    long position = file.tellg();
    std::cout << "The file position indicator is " << position << " bytes away from the start\n";
    
    file.close();
    
    return 0;
}

 

이 C++ 코드는 std::fstream을 이용해 파일을 열고, 'seekg' 함수로 파일 위치 지시자를 파일 시작으로부터 10바이트 뒤로 이동시키고, 'tellg' 함수로 파일 위치 지시자의 현재 위치를 확인합니다.

 

이처럼 'ftell' 함수는 파일 위치 지시자의 현재 위치를 확인하고, 그 정보를 활용하여 특정 위치에서의 파일 입출력을 조작하는데 사용됩니다. 이 함수를 잘 이해하고 활용하면 파일 입출력을 보다 효과적으로 수행할 수 있습니다.

 


13.5. 이진 파일 입출력

이진 파일 입출력은 C/C++에서 중요한 개념입니다. 이는 텍스트 파일과 달리, 데이터를 바이너리 형태로 저장하고 읽는 방식입니다. 이진 파일은 효율적인 저장 및 액세스를 가능하게 합니다. 예를 들어, 텍스트 파일로 저장된 정수 '123'은 문자 '1', '2', '3'으로 저장되지만, 이진 파일에는 단일 4바이트 정수로 저장됩니다. C/C++에서는 'fread'와 'fwrite' 함수를 통해 이진 파일을 읽고 쓸 수 있습니다. 이 함수들은 버퍼, 요소 크기, 요소 수, 파일 포인터를 인자로 받습니다.

13.5.1. 이진 파일과 텍스트 파일의 차이

텍스트 파일과 이진 파일의 가장 큰 차이는 저장되는 데이터의 형태입니다. 텍스트 파일은 일반적으로 ASCII 또는 유니코드와 같은 텍스트 인코딩을 사용하여 문자 데이터를 저장합니다. 이는 사람이 읽고 해석할 수 있는 형태입니다. 예를 들어, 텍스트 파일에서 숫자 '123'은 개별 문자 '1', '2', '3'로 저장됩니다. 

 

[예제] C

// C에서 텍스트 파일 작성
FILE *file = fopen("textfile.txt", "w");
if (file != NULL) {
    fprintf(file, "%d", 123);
    fclose(file);
}

 

[예제] C++

// C++에서 텍스트 파일 작성
#include<fstream>
std::ofstream file("textfile.txt");
if (file.is_open()) {
    file << 123;
    file.close();
}

 

반면에, 이진 파일은 데이터를 원시 바이트로 저장합니다. 이 방식은 사람이 직접 해석하는 것이 어렵지만, 컴퓨터가 더 효율적으로 해석하고 저장할 수 있습니다. 이진 파일에서 '123'은 하나의 4바이트 정수로 저장됩니다. 

 

[예제] C

// C에서 이진 파일 작성
FILE *file = fopen("binaryfile.bin", "wb");
if (file != NULL) {
    int num = 123;
    fwrite(&num, sizeof(num), 1, file);
    fclose(file);
}

 

[예제] C++

// C++에서 이진 파일 작성
#include<fstream>
std::ofstream file("binaryfile.bin", std::ios::binary);
if (file.is_open()) {
    int num = 123;
    file.write(reinterpret_cast<char*>(&num), sizeof(num));
    file.close();
}

 

각 파일 유형은 특정 상황에 가장 적합하며, 필요에 따라 적절하게 선택하여 사용해야 합니다. 일반적으로, 텍스트 파일은 사용자가 직접 확인하거나 수정해야 하는 데이터에 적합하며, 이진 파일은 큰 데이터 세트나 구조체와 같은 복잡한 데이터 타입을 저장하고 읽어야 하는 상황에 적합합니다. 

 

13.5.2. 이진 파일에서 데이터 읽고 쓰기

이진 파일에 데이터를 쓰거나 읽을 때는 fread와 fwrite 함수를 사용합니다. 이 함수들은 이진 데이터를 읽고 쓸 때 특히 유용합니다. 

 

  • 데이터 쓰기: fwrite 함수는 이진 파일에 데이터를 쓰는데 사용됩니다. 이 함수는 기본적으로 4개의 매개 변수를 필요로 합니다.

[예제]

size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream);
  • ptr: 데이터의 주소
  • size: 데이터의 크기 (바이트 단위)
  • count: 쓸 데이터 항목의 개수
  • stream: 출력 파일의 포인터

예제는 다음과 같습니다:

[예제]

FILE *file = fopen("binaryfile.bin", "wb");
if (file != NULL) {
    int numbers[] = {1, 2, 3, 4, 5};
    fwrite(numbers, sizeof(int), sizeof(numbers)/sizeof(int), file);
    fclose(file);
}

 

이 코드는 int 타입의 배열을 이진 파일에 씁니다.

 

  • 데이터 읽기: fread 함수는 이진 파일에서 데이터를 읽는 데 사용됩니다. 이 함수도 fwrite와 비슷하게 4개의 매개 변수를 필요로 합니다. 

 

[예제]

size_t fread(void* ptr, size_t size, size_t count, FILE* stream);
  • ptr: 읽은 데이터를 저장할 위치의 주소
  • size: 데이터의 크기 (바이트 단위)
  • count: 읽을 데이터 항목의 개수
  • stream: 입력 파일의 포인터

예제는 다음과 같습니다:

[예제]

FILE *file = fopen("binaryfile.bin", "rb");
if (file != NULL) {
    int numbers[5];
    fread(numbers, sizeof(int), sizeof(numbers)/sizeof(int), file);
    fclose(file);
}

 

이 코드는 이진 파일에서 int 타입의 배열을 읽습니다.

 

C++에서는 std::ifstream과 std::ofstream을 이용해서 이진 파일에서 읽고 쓸 수 있습니다. 'read'와 'write' 멤버 함수를 사용합니다. 이 함수들은 바이너리 데이터를 다룰 때 특히 유용합니다. 이 함수들은 모두 두 개의 매개변수를 받습니다. 첫 번째 매개변수는 데이터가 저장될 위치의 주소를 나타내는 포인터이며, 두 번째 매개변수는 읽거나 쓸 바이트의 개수입니다. 

 

C++ 예제는 다음과 같습니다:

[예제]

// 데이터 쓰기
std::ofstream ofs("binaryfile.bin", std::ios::binary);
if (ofs.is_open()) {
    int numbers[] = {1, 2, 3, 4, 5};
    ofs.write((char*)&numbers, sizeof(numbers));
    ofs.close();
}


// 데이터 읽기
std::ifstream ifs("binaryfile.bin", std::ios::binary);
if (ifs.is_open()) {
    int numbers[5];
    ifs.read((char*)&numbers, sizeof(numbers));
    ifs.close();
}

 

이 예제는 이진 파일에 int 타입의 배열을 쓰고 읽는 방법을 보여줍니다. 'write'와 'read' 함수는 첫 번째 매개변수로 char 형 포인터를 필요로 하기 때문에, 데이터 배열의 주소를 char 형 포인터로 캐스팅해야 합니다. 

 

이진 파일 입출력은 대용량의 데이터를 효율적으로 처리할 수 있어서, 이미지나 사운드 파일 같은 멀티미디어 데이터 또는 데이터베이스에서 많이 사용됩니다. 그러나 이진 파일은 텍스트 에디터로 열었을 때 해석이 어렵기 때문에, 애플리케이션에서 직접 생성하고 사용하는 경우가 많습니다.

 

초보자가 이진 파일 입출력을 이해하는 데 어려움을 겪을 수 있으니, 여러 가지 예제를 통해 실습하면서 익히는 것이 중요합니다. 이진 파일 입출력에 익숙해지면, 효율적인 데이터 처리가 가능하고 더욱 다양한 프로그래밍을 경험할 수 있게 될 것입니다. 

 


13.6. 파일 입출력의 오류 처리

C/C++에서 파일 입출력을 다룰 때 오류 처리는 필수적인 부분입니다. 파일이 존재하지 않을 수도 있고, 읽기 전용일 수도 있으니 쓰기 도중 문제가 발생할 수 있습니다. 이때, fopen 함수는 NULL을 반환하고, C++의 fstream 클래스는 실패 시 is_open() 메서드가 false를 반환합니다. 또한, ferror 함수와 clear 메서드를 사용하여 입출력 도중 발생한 오류를 체크할 수 있습니다. 이와 같은 방법으로 오류를 감지하고 적절히 처리하는 것은 안정적인 프로그램을 만드는 데 중요합니다.

13.6.1 파일 입출력에서의 오류 종류

파일 입출력 과정에서 발생할 수 있는 오류는 다양합니다. 여기서 몇 가지 기본적인 오류와 이러한 오류를 감지하고 처리하는 방법에 대해 알아보겠습니다.

 

파일 열기 실패: 가장 흔하게 발생하는 오류 중 하나는 파일을 열지 못할 때입니다. 이럴 때는 fopen 함수가 NULL을 반환하며, 이는 파일이 존재하지 않거나, 사용 권한이 없거나, 파일 경로가 잘못된 경우 등 다양한 이유로 발생할 수 있습니다.

 

[예제]

FILE* fp = fopen("non_existent_file.txt", "r");
if (fp == NULL) {
    perror("Error opening file");
    return -1;
}

 

위 코드는 파일을 여는 도중 오류가 발생했을 때 이를 감지하고, 오류 메시지를 출력한 다음 프로그램을 종료합니다.

 

파일 읽기/쓰기 실패: 파일이 성공적으로 열렸다 하더라도, 읽기 또는 쓰기 도중 오류가 발생할 수 있습니다. 이 경우 ferror 함수를 사용하여 오류를 감지할 수 있습니다.

 

[예제]

char ch;
FILE* fp = fopen("file.txt", "r");
if (fp == NULL) {
    perror("Error opening file");
    return -1;
}
while ((ch = fgetc(fp)) != EOF) {
    putchar(ch);
}
if (ferror(fp)) {
    printf("Error reading from file.\n");
}
fclose(fp);

 

위 코드는 파일을 읽는 도중 오류가 발생하면 이를 감지하고 오류 메시지를 출력합니다.

 

파일 닫기 실패: fclose 함수를 호출할 때 오류가 발생할 수도 있습니다. 이는 파일이 이미 닫힌 경우, 혹은 닫으려는 파일이 실제로 존재하지 않는 경우 등에 발생할 수 있습니다. fclose 함수는 오류가 발생하면 EOF를 반환하며, 이를 통해 오류를 감지할 수 있습니다.

 

[예제]

FILE* fp = fopen("file.txt", "r");
//...
if (fclose(fp) == EOF) {
    perror("Error closing file");
}

 

위 코드는 파일을 닫는 도중 오류가 발생했을 때 이를 감지하고 오류 메시지를 출력합니다.

 

C++에서도 비슷한 방식으로 오류를 처리할 수 있습니다. 파일 스트림(fstream, ifstream, ofstream)의 멤버 함수 is_open(), fail(), bad(), eof() 등을 사용하여 다양한 상황에서의 오류를 감지하고 적절하게 처리할 수 있습니다.

 

C++에서의 오류 처리 예제

[예제]

#include <fstream>
#include <iostream>

int main() {
    std::ifstream file("non_existent_file.txt");
    if (!file.is_open()) {
        std::cerr << "Error opening file\n";
        return -1;
    }

    char ch;
    while (file.get(ch)) {
        std::cout << ch;
    }

    if (file.bad()) {
        std::cerr << "Error reading from file.\n";
    }
    file.close();
    
    if (!file.good()) {
        std::cerr << "Error closing file.\n";
    }
    return 0;
}

 

위의 C++ 코드는 C 예제와 비슷하게 파일 열기, 읽기, 닫기에서 발생할 수 있는 오류들을 감지하고 처리하는 방법을 보여줍니다. C++에서는 파일 입출력 오류를 좀 더 쉽게 관리할 수 있도록 다양한 멤버 함수들이 제공됩니다.

 

C와 C++ 언어에서 제공하는 이러한 파일 입출력 오류 처리 방법은 프로그램의 안정성을 크게 향상시킵니다. 특히, 사용자로부터 입력받은 파일 이름이 실제로 존재하지 않거나, 혹은 파일에 접근할 권한이 없는 경우에 대비할 수 있게 됩니다. 또한, 파일을 정상적으로 읽거나 쓰지 못했을 때, 적절한 오류 메시지를 출력하거나 복구 동작을 수행할 수 있게 됩니다.

 

다만, C와 C++에서의 오류 처리 방식은 약간 차이가 있습니다. C에서는 오류 상황이 발생하면 전역 변수 errno가 설정되며, 이를 통해 오류를 확인하고 처리할 수 있습니다. 반면에, C++에서는 스트림 객체의 멤버 함수들을 사용하여 오류 상황을 직접 확인하고 처리합니다. 이러한 차이점에 주의하면서 언어에 적합한 방법을 사용하여 파일 입출력 오류를 처리해야 합니다.

 

마지막으로, 파일 입출력에서 발생할 수 있는 오류들은 신중하게 처리해야 합니다. 이러한 오류들은 프로그램의 동작에 큰 영향을 미치며, 경우에 따라서는 프로그램의 정상적인 동작을 방해하거나 데이터 손실을 유발할 수 있습니다. 따라서, 프로그램을 작성할 때는 파일 입출력 오류를 최대한 잘 처리하여 안정적인 동작을 보장해야 합니다.

 

13.6.2 feof 함수

C 언어에서 파일의 끝(End Of File)에 도달했는지 확인하는데 사용하는 함수가 feof입니다. 이 함수는 FILE 포인터를 인자로 받아 해당 파일 스트림이 EOF에 도달했다면 0이 아닌 값을, 그렇지 않다면 0을 반환합니다.

 

간단한 사용 예를 보자면, 파일에서 문자를 읽어 화면에 출력하는 코드는 다음과 같습니다:

 

[예제]

#include <stdio.h>

int main() {
    FILE* fp = fopen("file.txt", "r");
    if (fp == NULL) {
        perror("Error opening file");
        return -1;
    }

    int ch;
    while ((ch = fgetc(fp)) != EOF) {
        putchar(ch);
    }

    if (feof(fp)) {
        printf("\nEnd of file reached.\n");
    }
    else if (ferror(fp)) {
        printf("\nAn error occurred.\n");
    }

    fclose(fp);
    return 0;
}

 

위 코드에서 feof 함수는 파일의 끝에 도달했는지 확인합니다. fgetc 함수를 통해 파일의 끝에 도달하면 EOF가 반환되어 while 반복문이 종료되고, feof 함수는 참을 반환하여 "End of file reached."라는 메시지를 출력합니다.

 

그러나 feof 함수를 사용할 때 주의해야 할 점이 있습니다. feof 함수는 파일에서 읽기 또는 쓰기를 시도한 후에 파일의 끝에 도달했음을 감지합니다. 즉, 파일의 끝에 도달했을 때 feof 함수가 참을 반환하지만, 그전까지는 거짓을 반환하므로, 파일의 끝 직전의 데이터를 처리할 때 오류를 유발할 수 있습니다.

 

예를 들어, 반복문의 조건문에 feof 함수를 사용하는 경우를 보겠습니다:

 

[예제]

while (!feof(fp)) {
    ch = fgetc(fp);
    putchar(ch);
}

 

위 코드는 파일의 끝에 도달한 후에도 한 번 더 반복문이 수행되어 EOF가 화면에 출력되는 문제가 있습니다. 이런 문제를 방지하기 위해 반복문의 조건문에 feof 함수를 사용하는 것은 권장되지 않으며, 대신 fgetc 함수와 같은 입출력 함수의 반환값을 확인하는 방법을 사용해야 합니다.

 

feof 함수의 사용에 대한 이해를 깊게 하기 위해, 조금 더 복잡한 예제를 보도록 하겠습니다. 아래 예제는 파일에서 숫자를 읽어와 그 합계를 계산하는 코드입니다:

 

[예제]

#include <stdio.h>

int main() {
    FILE* fp = fopen("numbers.txt", "r");
    if (fp == NULL) {
        perror("Error opening file");
        return -1;
    }

    int num, sum = 0;
    while (fscanf(fp, "%d", &num) == 1) {
        sum += num;
    }

    if (feof(fp)) {
        printf("End of file reached. Sum = %d\n", sum);
    }
    else if (ferror(fp)) {
        printf("An error occurred while reading the file.\n");
    }

    fclose(fp);
    return 0;
}

 

위 코드에서, fscanf 함수는 파일에서 숫자를 읽어 num 변수에 저장하고, 읽은 항목의 개수를 반환합니다. 따라서 숫자를 정상적으로 읽었으면 반환값은 1이 됩니다. fscanf 함수의 반환값을 확인하여 반복문을 제어하면, feof 함수의 문제점을 피할 수 있습니다. feof 함수는 여전히 파일의 끝에 도달했는지 확인하는데 사용되지만, 이제 반복문의 제어에는 영향을 미치지 않습니다.

 

다른 입출력 함수와 마찬가지로, feof 함수 또한 오류가 발생했을 때 해당 오류를 알려주는 ferror 함수와 함께 사용됩니다. ferror 함수는 FILE 포인터를 인자로 받아 해당 파일 스트림에서 오류가 발생했다면 0이 아닌 값을, 그렇지 않다면 0을 반환합니다.

 

파일 입출력은 시스템 자원을 사용하는 작업이므로 다양한 오류가 발생할 수 있습니다. 예를 들어, 디스크 공간이 부족하거나 파일에 대한 권한이 없는 경우, 파일을 열거나 읽거나 쓰는 도중에 오류가 발생할 수 있습니다. 이런 오류들을 잘 처리하려면 항상 ferror 함수를 사용하여 오류를 확인하고 적절한 조치를 취해야 합니다.

 

13.6.3. ferror 함수

C 언어에서 파일 입출력은 다양한 이유로 인해 실패할 수 있습니다. 디스크 공간이 부족하거나, 파일에 대한 권한이 없거나, 물리적 손상 등 여러 가지 상황에서 오류가 발생할 수 있습니다. 이러한 경우, 어떻게 알 수 있을까요? C 언어는 이러한 상황에 대해 처리할 수 있는 함수인 ferror를 제공합니다.

 

ferror 함수는 파일 입출력 오류를 확인하는 데 사용됩니다. ferror 함수의 프로토타입은 다음과 같습니다:

 

[예제]

int ferror(FILE *stream);

 

ferror 함수는 인수로 주어진 파일 스트림에 오류가 발생했는지 확인합니다. 오류가 발생하면 0이 아닌 값을 반환하고, 오류가 없으면 0을 반환합니다.

 

예를 들어, 파일 읽기 작업 중에 오류를 확인하려면 다음과 같이 작성할 수 있습니다:

 

[예제]

FILE* file = fopen("somefile.txt", "r");
if (file == NULL) {
    perror("Error opening file");
    return -1;
}

char buffer[100];
while (fgets(buffer, 100, file) != NULL) {
    // Do something with buffer
}

if (ferror(file)) {
    printf("An error occurred while reading the file.\n");
}

fclose(file);

 

위의 코드는 fgets 함수를 사용하여 파일에서 한 줄을 읽어옵니다. 만약 파일 읽기 중에 오류가 발생하면, ferror 함수는 0이 아닌 값을 반환하므로 "An error occurred while reading the file." 메시지가 출력됩니다.

 

ferror 함수는 오류를 검출하고 처리하는 중요한 도구입니다. 항상 파일 입출력 작업 후에는 ferror 함수를 호출하여 오류가 발생했는지 확인하는 것이 좋습니다. 그래야 프로그램의 안정성과 신뢰성을 보장할 수 있습니다.

 

그러나 ferror 함수만으로는 어떤 오류가 발생했는지 알 수 없습니다. 어떤 오류가 발생했는지 구체적인 정보를 얻기 위해서는 perror 함수 또는 strerror 함수와 같은 다른 함수들을 사용해야 합니다.

 

C++에서도 C 언어의 ferror 함수와 동일한 방식으로 작동합니다. C++에는 추가적인 오류 처리 기능을 제공하는 예외 처리 기능이 있지만, 표준 C++ 라이브러리의 파일 입출력 함수들은 기본적으로 예외를 발생시키지 않습니다. 따라서 C++에서도 파일 입출력 오류를 확인하기 위해서는 ferror 함수를 사용해야 합니다.

 

다음은 C++에서 ferror 함수를 사용하는 예시입니다:

 

[예제]

#include <cstdio>

int main() {
    FILE* file = fopen("somefile.txt", "r");
    if (file == NULL) {
        perror("Error opening file");
        return -1;
    }

    char buffer[100];
    while (fgets(buffer, 100, file) != NULL) {
        // Do something with buffer
    }

    if (ferror(file)) {
        printf("An error occurred while reading the file.\n");
    }

    fclose(file);
    return 0;
}

 

이 예제에서는 C++에서도 C 언어의 표준 파일 입출력 함수들을 사용할 수 있음을 보여줍니다. ferror 함수를 사용하여 파일 읽기 작업 중에 오류가 발생했는지 확인하고, 오류가 발생하면 메시지를 출력합니다.

 

이렇게 ferror 함수를 통해 파일 입출력 작업 중에 발생할 수 있는 오류를 감지하고 적절히 대응할 수 있습니다. 그러나 ferror 함수는 오류가 발생했는지만 알려주고, 발생한 오류의 세부적인 정보를 알려주지는 않습니다. 오류의 세부적인 정보를 알고 싶다면, perror 함수나 strerror 함수 등을 사용해야 합니다. 이러한 함수들은 오류 번호에 대응하는 오류 메시지를 출력해 줍니다. 

 

 

 

2023.05.16 - [GD's IT Lectures : 기초부터 시리즈/C, C++ 기초부터 ~] - [C/C++ 프로그래밍] 12. 구조체

 

[C/C++ 프로그래밍] 12. 구조체

Chapter 12. 구조체 구조체의 개념과 선언 방법, 그리고 초기화를 배우게 됩니다. 구조체를 사용해 변수를 선언하고 접근하는 방법, 구조체 배열, 그리고 함수와의 상호작용에 대해 다룹니다. 또한,

gdngy.tistory.com

 

반응형

댓글