C언어로 파일을 줄 단위로 읽는 방법과 예제

C언어에서 파일을 줄 단위로 읽는 것은 기본적인 파일 입출력 작업의 핵심입니다. 이 기사를 통해 줄 단위로 데이터를 읽는 방법, 주요 함수의 사용법, 그리고 이를 활용한 응용 예제를 배워보세요. 파일 처리의 기초부터 고급 활용까지 차근차근 설명합니다.

목차

파일 입출력의 기본 개념


C언어에서 파일 입출력은 데이터의 저장 및 읽기를 위해 필수적으로 사용됩니다. 파일 입출력 작업은 stdio.h 라이브러리를 통해 이루어지며, 일반적으로 다음과 같은 작업이 포함됩니다.

파일 스트림


파일 스트림은 프로그램과 파일 간의 데이터 흐름을 나타냅니다. C언어에서는 FILE 구조체를 통해 파일 스트림을 관리합니다.

파일 작업의 주요 단계

  1. 파일 열기: fopen 함수로 파일을 열어 읽기 또는 쓰기 작업을 시작합니다.
  2. 파일 읽기 및 쓰기: 다양한 함수(fgets, fprintf 등)를 사용하여 데이터를 처리합니다.
  3. 파일 닫기: fclose 함수로 파일을 닫아 리소스를 해제합니다.

파일 모드


파일을 열 때는 작업 유형에 따라 모드를 지정합니다. 주요 모드:

  • "r": 읽기 전용 모드
  • "w": 쓰기 전용 모드 (기존 내용 삭제)
  • "a": 추가 모드 (기존 내용 유지)
  • "r+", "w+", "a+": 읽기와 쓰기 혼합 모드

파일 입출력의 이러한 기본 개념을 이해하면 파일을 효율적으로 처리할 수 있습니다.

파일을 열고 닫는 방법

파일 열기: fopen 함수


C언어에서 파일을 열기 위해 fopen 함수를 사용합니다. 이 함수는 파일의 경로와 작업 모드를 입력으로 받아 파일 스트림을 반환합니다.

사용법:

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

매개변수:

  • "filename.txt": 파일의 경로와 이름.
  • "r": 읽기 모드로 열기.

반환값:

  • 성공: 파일 스트림(FILE 포인터).
  • 실패: NULL 반환.

파일 닫기: fclose 함수


열린 파일 스트림은 작업이 끝난 후 반드시 fclose 함수로 닫아야 합니다. 닫지 않으면 메모리 누수나 리소스 문제가 발생할 수 있습니다.

사용법:

if (fclose(file) != 0) {
    perror("Error closing file");
    return 1;
}

주의사항

  1. 파일 열기 실패 처리: fopen 함수가 실패하면 파일 스트림은 NULL을 반환하므로 이를 확인해야 합니다.
  2. 리소스 누수 방지: 열려 있는 모든 파일 스트림은 반드시 fclose를 호출해 닫아야 합니다.

파일을 열고 닫는 것은 파일 작업의 기본으로, 이를 정확히 관리하는 것이 파일 처리의 성공적인 출발점입니다.

줄 단위 읽기의 구현

fgets 함수로 줄 단위 읽기


fgets 함수는 파일에서 한 줄씩 데이터를 읽을 때 가장 일반적으로 사용됩니다. 파일 스트림에서 줄 단위로 데이터를 읽어와 지정된 버퍼에 저장합니다.

사용법:

char buffer[256]; // 읽어올 한 줄을 저장할 버퍼
FILE *file = fopen("example.txt", "r");
if (file == NULL) {
    perror("Error opening file");
    return 1;
}

while (fgets(buffer, sizeof(buffer), file) != NULL) {
    printf("%s", buffer); // 읽어온 줄 출력
}

fclose(file);

매개변수:

  • buffer: 읽어온 데이터를 저장할 배열.
  • sizeof(buffer): 한 번에 읽을 최대 문자 수.
  • file: 파일 스트림.

fgets의 반환값

  • 성공: 읽어온 문자열의 포인터.
  • 실패: NULL 반환 (파일 끝에 도달하거나 오류 발생).

작동 방식

  • fgets는 개행 문자(\n)를 포함해 최대 지정된 크기만큼 데이터를 읽습니다.
  • 버퍼 크기보다 긴 줄이 파일에 있을 경우, 해당 줄의 나머지는 다음 호출에서 계속 읽힙니다.

예제 코드


아래는 파일에서 각 줄을 읽고 줄 번호와 함께 출력하는 예제입니다.

#include <stdio.h>

int main() {
    char buffer[256];
    FILE *file = fopen("example.txt", "r");
    if (file == NULL) {
        perror("Error opening file");
        return 1;
    }

    int line_number = 1;
    while (fgets(buffer, sizeof(buffer), file) != NULL) {
        printf("%d: %s", line_number++, buffer);
    }

    fclose(file);
    return 0;
}

주의사항

  1. 버퍼 오버플로우 방지: fgets 호출 시 항상 버퍼 크기를 지정해 초과 입력을 방지해야 합니다.
  2. 개행 문자 처리: 읽어온 데이터에 포함된 개행 문자를 필요에 따라 제거하거나 처리해야 할 수 있습니다.

fgets를 활용한 줄 단위 읽기는 효율적이고 직관적이며, 파일 데이터의 구조를 분석하거나 특정 정보를 추출하는 데 유용합니다.

EOF 처리와 오류 관리

EOF란 무엇인가?


EOF(End Of File)는 파일의 끝을 나타내는 특별한 상태입니다. 파일에서 데이터를 읽는 작업이 완료되었음을 알려줍니다. C언어에서는 EOF를 상수로 정의하며, 파일 읽기 함수가 EOF를 반환하면 파일 끝에 도달했음을 의미합니다.

EOF 처리


파일을 읽는 동안 fgets와 같은 함수는 NULL을 반환하여 EOF에 도달했음을 알립니다. 이를 확인하여 반복문을 종료해야 합니다.

예제 코드:

char buffer[256];
FILE *file = fopen("example.txt", "r");
if (file == NULL) {
    perror("Error opening file");
    return 1;
}

while (fgets(buffer, sizeof(buffer), file) != NULL) {
    printf("%s", buffer);
}

if (feof(file)) {
    printf("End of file reached.\n");
}

fclose(file);

feof 함수:

  • 파일 스트림이 EOF에 도달했는지 확인하는 함수입니다.
  • 성공적으로 파일 끝에 도달하면 true(0이 아닌 값)를 반환합니다.

오류 관리


파일 작업 중 오류가 발생할 수 있습니다. 예를 들어, 읽기 도중 파일이 손상되었거나 허가 권한이 없는 경우 오류가 발생할 수 있습니다. 이를 처리하기 위해 ferror 함수를 사용할 수 있습니다.

ferror 함수:

  • 파일 스트림에 오류가 발생했는지 확인합니다.
  • 오류가 있으면 true(0이 아닌 값)를 반환합니다.

오류 확인 및 처리:

if (ferror(file)) {
    fprintf(stderr, "Error reading file.\n");
}

EOF와 오류 처리 통합 예제

#include <stdio.h>

int main() {
    char buffer[256];
    FILE *file = fopen("example.txt", "r");
    if (file == NULL) {
        perror("Error opening file");
        return 1;
    }

    while (fgets(buffer, sizeof(buffer), file) != NULL) {
        printf("%s", buffer);
    }

    if (feof(file)) {
        printf("End of file reached.\n");
    }

    if (ferror(file)) {
        fprintf(stderr, "Error occurred while reading the file.\n");
    }

    fclose(file);
    return 0;
}

주의사항

  1. EOF와 오류 구분: EOF는 파일 끝에 도달한 상태일 뿐이며 오류는 아닙니다. 이를 명확히 구분해야 합니다.
  2. 적절한 메시지 출력: 오류 발생 시 사용자에게 알림 메시지를 제공해 문제 해결을 돕는 것이 중요합니다.

EOF 처리와 오류 관리는 안정적인 파일 입출력 프로그램을 작성하는 데 필수적입니다. 이를 통해 파일 처리 작업 중 발생할 수 있는 다양한 예외 상황에 대비할 수 있습니다.

응용: 특정 문자열 찾기

파일에서 특정 문자열 검색


파일에서 특정 문자열을 검색하는 기능은 로그 분석, 데이터 처리, 텍스트 필터링 등 다양한 응용에서 유용합니다. C언어에서는 줄 단위로 파일을 읽으면서 strstr 함수를 사용해 문자열을 검색할 수 있습니다.

구현 방법

  1. fgets로 파일을 줄 단위로 읽습니다.
  2. 읽은 줄에서 strstr을 사용해 특정 문자열이 포함되어 있는지 확인합니다.
  3. 일치하는 줄을 출력하거나 원하는 처리를 수행합니다.

예제 코드


아래 코드는 파일에서 특정 키워드(예: "target")를 찾고, 해당 줄 번호와 내용을 출력합니다.

#include <stdio.h>
#include <string.h>

int main() {
    char buffer[256]; // 줄을 저장할 버퍼
    const char *keyword = "target"; // 검색할 문자열
    FILE *file = fopen("example.txt", "r");
    if (file == NULL) {
        perror("Error opening file");
        return 1;
    }

    int line_number = 1;
    while (fgets(buffer, sizeof(buffer), file) != NULL) {
        if (strstr(buffer, keyword) != NULL) {
            printf("Line %d: %s", line_number, buffer);
        }
        line_number++;
    }

    fclose(file);
    return 0;
}

코드 설명:

  • strstr 함수는 첫 번째 문자열에서 두 번째 문자열을 검색하여 포인터를 반환합니다.
  • 검색 결과가 NULL이 아니면 문자열이 포함된 것입니다.
  • 각 줄 번호를 출력해 검색 결과를 명확히 표시합니다.

예제 입력 파일


example.txt:

This is a sample text.
The target keyword is here.
Another line without the keyword.
The target is found again here.

예제 출력

Line 2: The target keyword is here.
Line 4: The target is found again here.

응용 가능성

  • 특정 로그 메시지 필터링.
  • 데이터 파일에서 중요한 정보 추출.
  • 코드 파일에서 특정 함수나 변수 검색.

주의사항

  1. 대소문자 구분: strstr 함수는 기본적으로 대소문자를 구분합니다. 필요하면 문자열을 변환하여 처리하세요.
  2. 대용량 파일 최적화: 매우 큰 파일에서는 읽기 및 검색을 최적화하는 방법을 고려해야 합니다.
  3. 부분 일치 처리: 검색 키워드의 부분 일치 여부를 명확히 정의하고 처리할 필요가 있습니다.

이 방법을 활용하면 파일 내에서 필요한 정보를 효과적으로 찾고 작업에 적용할 수 있습니다.

성능 최적화 팁

버퍼 크기 조정


파일에서 데이터를 읽을 때 적절한 버퍼 크기를 설정하면 성능이 향상될 수 있습니다. 너무 작은 버퍼는 읽기 작업을 자주 수행하게 만들고, 너무 큰 버퍼는 메모리를 낭비할 수 있습니다. 일반적으로 256~1024 바이트 사이의 크기가 적합합니다.

예제:

char buffer[1024]; // 큰 버퍼로 읽기 성능 향상
while (fgets(buffer, sizeof(buffer), file) != NULL) {
    // 파일 읽기 작업
}

메모리 매핑 활용


매우 큰 파일을 처리할 때는 파일의 일부를 메모리에 매핑하여 읽기 작업을 최적화할 수 있습니다. 이는 일반적인 파일 읽기보다 속도가 빠르지만 구현이 복잡할 수 있습니다.

입출력 함수의 선택

  • fgets는 줄 단위로 읽을 때 유용하지만, 데이터가 줄 단위로 구성되어 있지 않은 경우 fread와 같은 함수가 더 적합할 수 있습니다.
  • fread를 사용하면 데이터를 블록 단위로 읽을 수 있어 대용량 파일 처리에서 성능이 향상됩니다.

예제:

size_t bytesRead;
char buffer[4096];
while ((bytesRead = fread(buffer, 1, sizeof(buffer), file)) > 0) {
    // 데이터 처리
}

불필요한 작업 최소화

  1. 조건 검사 단순화: 파일을 읽는 반복문 내부에서 불필요한 조건 검사와 연산을 피합니다.
  2. 반복적 메모리 할당 방지: 반복문 내에서 메모리를 반복적으로 할당하거나 해제하지 않도록 합니다.

병렬 처리


멀티스레드를 활용하면 대용량 파일을 여러 스레드로 나눠 동시에 처리할 수 있습니다. 다만, 병렬 처리를 구현하려면 파일 데이터가 독립적으로 처리 가능한 구조인지 확인해야 합니다.

특정 조건에 따라 읽기 제한


파일 전체를 읽지 않고, 필요한 데이터만 찾으면 작업 시간을 줄일 수 있습니다. 예를 들어, 특정 키워드가 포함된 줄만 읽는 방법을 적용할 수 있습니다.

예제:

if (strstr(buffer, "keyword") != NULL) {
    // 조건에 맞는 줄만 처리
}

파일 포인터 조작

  • fseek를 사용해 파일 포인터를 이동하면 불필요한 읽기 작업을 줄일 수 있습니다.
  • 예를 들어, 특정 오프셋에서만 데이터를 읽거나 필요한 부분만 처리할 때 유용합니다.

예제:

fseek(file, 100, SEEK_SET); // 파일의 100바이트 위치로 이동
fgets(buffer, sizeof(buffer), file);

디버깅 도구 사용


파일 입출력 속도를 측정하고 병목 현상을 분석하기 위해 디버깅 도구나 프로파일러를 활용합니다. 이를 통해 코드의 성능 문제를 파악하고 개선할 수 있습니다.

요약

  • 적절한 버퍼 크기 설정, 효율적인 함수 선택, 그리고 조건부 읽기를 통해 성능을 최적화할 수 있습니다.
  • 병렬 처리와 메모리 매핑은 대용량 파일 작업에서 효과적입니다.
  • 불필요한 작업을 줄이고 필요한 데이터만 처리함으로써 성능을 극대화할 수 있습니다.

이와 같은 최적화 기법을 적용하면 파일 처리 성능을 크게 향상시킬 수 있습니다.

요약


C언어로 파일을 줄 단위로 읽는 방법은 fgets 함수를 활용해 간단하고 효율적으로 구현할 수 있습니다. 본 기사에서는 파일 입출력의 기본 개념부터 특정 문자열 검색, EOF 처리, 성능 최적화 팁까지 다루었습니다. 이를 통해 안정적이고 빠른 파일 처리를 위한 지식을 습득할 수 있습니다.

목차