Language/C

📌 Chapter 15 - 스트림과 파일 입출력

e-cko 2025. 6. 21. 14:35
반응형

1. 스트림(Stream)

  • 입력 또는 출력 장치로부터 데이터가 한 방향으로 흐르는 개념이다.
  • 스트림에는 버퍼(buffer)가 포함되어 있으며, 이는 CPU와 입출력 장치 간의 속도 차이를 보완하기 위해 사용된다.

주요 표준 스트림
stdin
표준 입력 스트림: 주로 키보드 입력에서 사용되며, scanf() 함수와 연결된다.
stdout
표준 출력 스트림: 주로 모니터 출력에서 사용되며, printf() 함수와 연결된다.
stderr
표준 에러 출력 스트림: 오류 메시지를 출력할 때 사용된다.

각 함수(printf, scanf )를 사용하면 자동으로 스트림이 생성된다.


2. 파일 입출력

1) 표준 입출력 라이브러리(Standard I/O Library)

  • 보조 기억 장치(: HDD, SSD)에 저장된 파일을 단위로 데이터를 읽고 쓸 수 있다.
  • C 언어에서는 텍스트 파일바이너리 파일 모두를 처리할 수 있으며, 파일 형식에 따라 사용해야 할 함수가 달라진다.

2) 바이너리와 문자열의 차이

구분 바이너리(Binary) 문자열(Text)
데이터 인식 숫자 자체로 인식 아스키코드로 인식
크기 판단 기준 메모리 크기 (: sizeof) 널 문자('\0') 기준
예시 sizeof(char[8]) → 8 strlen(char[8]) → 문자열 길이만큼

바이너리 속성이 적용된 파일을 바이너리 파일, 문자열 속성이 적용된 파일을 텍스트 파일이라 한다.


3) 파일 포인터와 FILE 구조체

  • 파일 입출력을 위해서는 FILE형 구조체 포인터를 사용해야 한다.
  • 이 포인터는 실제 파일과 프로그램을 연결하는 다리 역할을 하며, stdio.h에 다음과 같이 정의되어 있다:

typedef struct {

    short level;

    unsigned flags;

    char fd;

    unsigned char hold;

    short bsize;

    unsigned char *buffer;

    unsigned char *curp;

    unsigned istemp;

    short token;

} FILE;


4) 파일 열기와 닫기

  • 파일을 열 때는 fopen_s() 함수를 사용하며, 반환타입은 errno_t(typedef로 생성)이다.

errno_t err = fopen_s(&파일포인터, "파일명", "파일모드");

파일 열기 모드 요약표:

모드 설명
"r" 읽기 전용, 파일 없으면 오류
"r+" 읽기/쓰기, 파일 없으면 오류
"w" 쓰기 전용, 기존 파일이면 내용 삭제
"w+" 읽기/쓰기, 기존 파일이면 내용 삭제
"a" 추가 모드, 파일 없으면 새로 생성
"a+" 추가 + 읽기, 파일 없으면 새로 생성
"b" 바이너리 모드 (⚠️ 생략 불가)
"t" 텍스트 모드 (생략 가능)

파일을 연 후에는 반드시 fclose()로 닫아야 한다. 그렇지 않으면 버퍼에 남은 데이터가 저장되지 않을 수 있다.


5) 문자 입출력 함수

문자 읽기

  • fgetc(FILE *stream)
    ·
    파일에서 문자 하나를 읽어 아스키 코드 값(정수)로 반환한다.
    · EOF
    또는 오류 발생 시 -1을 반환.
  • getc(FILE *stream)
    · fgetc()
    와 동일한 기능을 하는 매크로 함수이다.

문자 출력

  • fputc(int ch, FILE *stream)
    ·
    하나의 문자를 파일에 출력한다. 성공 시 해당 문자의 아스키 코드 반환, 실패 시 EOF 반환.
  • putc(int ch, FILE *stream)
    · fputc()
    의 매크로 버전으로, 효율이 더 좋을 수 있다.

6) 문자열 입출력 함수

  • fgets(char *str, int size, FILE *stream)
    ·
    파일에서 한 줄을 읽어 문자열로 저장한다.
    ·
    줄바꿈 문자를 포함하며, 널 문자 \0로 종료된다.
  • fputs(const char *str, FILE *stream)
    ·
    문자열 전체를 파일에 출력한다.
    ·
    자동으로 줄바꿈이 되지 않기 때문에, 줄바꿈이 필요할 경우 \n을 포함시켜야 한다.

💻 예제 코드 - 문자열 입출력

#include <stdio.h>

 

int main() {

    FILE *fp;

    fopen_s(&fp, "str.txt", "w");

    if (fp) {

        fputs("첫 번째 줄\n", fp);

        fputs("두 번째 줄", fp); // 줄바꿈 없음

        fclose(fp);

    }

 

    fopen_s(&fp, "str.txt", "r");

    char buffer[100];

    while (fgets(buffer, sizeof(buffer), fp)) {

        printf("읽은 줄: %s", buffer);

    }

    fclose(fp);

    return 0;

}


7) 텍스트 파일 형식 입출력

  • fprintf(FILE *stream, const char *format, ...)
    ·
    형식 문자열을 사용하여 텍스트 파일에 출력.
  • fscanf_s(FILE *stream, const char *format, ...)
    ·
    형식 문자열에 따라 텍스트 파일에서 입력.
    ·
    공백 기준으로 분리되며 문자열 입력 시 버퍼 크기를 반드시 지정해야 한다.

💻 예제 코드

 

#include <stdio.h>

 

int main() {

    FILE *fp;

    fopen_s(&fp, "data.txt", "w");

    fprintf(fp, "%d %s\n", 42, "Hello");

    fclose(fp);

 

    int num;

    char word[20];

    fopen_s(&fp, "data.txt", "r");

    fscanf_s(fp, "%d %s", &num, word, (unsigned)_countof(word));

    printf("읽은 데이터: %d, %s\n", num, word);

    fclose(fp);

    return 0;

}


8) 바이너리 파일 입출력

·        바이너리 속성 파일은 데이터를 숫자로만 판단하기에 직접 데이터 크기를 선언해야한다.

·        실제 데이터 크기는 저장할 데이터 크기’ * ‘반복 횟수이다.

  • fwrite(const void *ptr, size_t size, size_t count, FILE *stream)
    · ptr
    이 가리키는 데이터 블록을 count만큼 파일에 이진 형식으로 저장.
  • fread(void *ptr, size_t size, size_t count, FILE *stream)
    ·
    파일로부터 count개의 데이터 블록을 읽어 ptr에 저장.
  •  

💻 예제 코드

#include <stdio.h>

 

int main() {

    FILE *fp;

    int data[3] = {100, 200, 300};

 

    fopen_s(&fp, "bin.dat", "wb");

    fwrite(data, sizeof(int), 3, fp);

    fclose(fp);

 

    int result[3];

    fopen_s(&fp, "bin.dat", "rb");

    fread(result, sizeof(int), 3, fp);

    fclose(fp);

 

    for (int i = 0; i < 3; i++) {

        printf("읽은 값: %d\n", result[i]);

    }

 

    return 0;

}


9) 파일 포인터 접근 함수

  • 지금까지 했던 방식은 앞에서 부터 하나씩 접근하는 순차 접근(sequential access)이다.
  • 입의 접근(random access)임의의 값에 따라 접근하는 방식이다.
  • fseek(FILE *stream, long offset, int origin)
    ·
    파일 포인터를 offset만큼 이동시킨다.
    ·
    기준값(origin):
  • ftell(FILE *stream)
    ·
    현재 파일 포인터 위치를 바이트 단위로 반환.
  • rewind(FILE *stream)
    ·
    파일 포인터를 파일 시작 위치로 초기화.
  • feof(FILE *stream)
    ·
    파일의 끝(EOF)에 도달했는지 여부를 확인.
    ·
    루프에서 파일 읽기를 반복할 때 자주 사용된다.

💻 예제 코드

#include <stdio.h>

 

int main() {

    FILE *fp;

    int nums[] = {1, 2, 3, 4, 5};

 

    fopen_s(&fp, "offset.dat", "wb");

    fwrite(nums, sizeof(int), 5, fp);

    fclose(fp);

 

    fopen_s(&fp, "offset.dat", "rb");

    fseek(fp, sizeof(int) * 2, SEEK_SET); // 세 번째 정수 위치

    int val;

    fread(&val, sizeof(int), 1, fp);

    printf("3번째 값: %d\n", val);

 

    rewind(fp);

    while (!feof(fp)) {

        fread(&val, sizeof(int), 1, fp);

        if (!feof(fp)) // 마지막 EOF 중복 출력 방지

            printf(": %d\n", val);

    }

 

    fclose(fp);

    return 0;

}


마무리 요약

  • 스트림은 입력/출력 흐름을 나타내며, 버퍼를 이용해 효율적으로 데이터를 전달한다.
  • FILE * 포인터는 파일과 프로그램을 연결하는 매개체이다.
  • 문자와 문자열 입출력은 각각 fgetc/fputc, fgets/fputs 함수를 이용한다.
  • fprintf, fscanf_s는 텍스트 형식의 데이터를 파일로 읽고 쓸 때 유용하다.
  • fread, fwrite는 바이너리 데이터를 다룰 때 반드시 사용된다.
  • fseek, ftell, rewind 등으로 파일 내부를 자유롭게 탐색할 수 있다.
  • feof()는 파일의 끝을 만났는지를 판단할 때 사용한다.

 

반응형

 

반응형