Skip to content

62. 파일 문자열 쓰기/읽기


1. 키워드

  • 파일 문자열
  • 파일 포인터
  • fopen(File Open)
  • fprintf(File printf)
  • fclose(File Close)
  • fscanf(File scanf)
  • 파일 스트림(File Stream)
  • fputs(File puts)
  • fwrite(File Write)
  • fgets(File gets)
  • fread(File Read)


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

  • 프로그래밍에서 중요한 축을 차지하는 부분이 파일 처리이다.
  • 파일에서 값을 쓰는 방법과 읽는 방법을 알아보자.


3. 서식을 지정하여 파일에 문자열 쓰기

  • 지금까지 printf로 서식을 지정하여 문자열을 화면에 출력하고, sprintf로 서식을 지정하여 문자열을 생성했다.
  • 파일에 문자열을 쓸 때는 먼저 fopen 함수로 파일을 열어서 파일 포인터를 얻은 뒤 fprintf 함수를 사용한다.


FILE *포인터이름 = fopen(파일명, 파일모드);
성공하면 파일 포인터를 반환, 실패하면 NULL을 반환

fprintf(파일포인터, 서식, 값1, 값2, ...);
성공하면  문자열의 길이를 반환, 실패하면 음수를 반환

fclose(파일포인터);
성공하면 0 반환, 실패하면 EOF(-1) 반환
#define _CRT_SECURE_NO_WARNINGS // fopen 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>              // fopen, fprintf, fclose 함수가 선언된 헤더 파일

int main()
{
    FILE *fp = fopen("hello.txt", "w"); // hello.txt 파일을 쓰기 모드(w)로 열기.
                                        // 파일 포인터를 반환

    fprintf(fp, "%s %d\n", "Hello", 100); // 서식을 지정하여 문자열을 파일에 저장

    fclose(fp); // 파일 포인터 닫기

    return 0;
}


  • 파일을 사용하기 위해서는 fopen 함수로 파일을 열어서 파일 포인터를 얻어야 한다.
  • 다음과 같이 읽을 파일 이름은 hello.txt로 지정하고, 파일을 생성하여 내용을 쓸 것이므로 파일 모드를 "w"로 지정해 준다.
  • 파일 열기에 성공하면 파일 포인터를 반환하고, 실패하면 NULL을 반환한다.


//    ↓ 파일 포인터
FILE *fp = fopen("hello.txt", "w"); // hello.txt 파일을 쓰기 모드(w)로 열기. 파일 포인터를 반환
//                   ↑          ↖ 파일 모드
//        파일 이름 또는 파일 경로


  • 여기서 FILE 구조체는 stdio.h 헤더 파일에 정의되어 있으며 보통 FILE*를 합쳐서 파일 포인터라고 부른다.


  • 다음은 파일 모드의 종류이다.


001


  • 파일 모드는 보통 "rb", "rt", "w+b", "w+t"와 같이 읽기/쓰기 모드와 텍스트/바이너리 모드를 조합해서 사용한다.
  • tb는 단독으로 사용할 수 없다.


  • 이제 파일 포인터를 얻었으니 fprintf 함수로 문자열을 파일에 쓴다.
  • 먼저 앞에서 얻은 파일 포인터를 지정하고 서식과 값을 순서대로 지정하면 된다.


fprintf(fp, "%s %d\n", "Hello", 100); // 서식을 지정하여 문자열을 파일에 저장


  • 이렇게 하면 "Hello"와 정수 100을 합쳐서 문자열 "Hello 100"hello.txt 파일에 저장된다.


  • 파일 쓰기가 끝났으면 반드시 fclose 함수로 파일 포인터를 닫아준다.
  • 파일 포인터 fp도 구조체 FILE 크기만큼 동적 메모리를 할당한 것이기 때문에 fclose 함수로 닫아주지 않으면 메모리 누수가 발생한다.


fclose(fp); // 파일 포인터 닫기


  • 서식을 지정하여 파일에 문자열을 쓰는 과정을 그림으로 나타내면 다음과 같다.


002


  • 한 가지 특이한 점은 fprintf 함수로도 화면에 문자열을 출력할 수 있다는 점인데, stdout이라는 매크로를 활용하면 된다.


fprintf(stdout, "%s %d\n", "Hello", 100); // 서식을 지정하여 화면(stdout)에 문자열 출력


  • 즉, fprintf(stdout, ...);printf 함수와 동작이 같다.


4. 서식을 지정하여 파일에서 문자열 읽기

  • 이제 앞에서 생성한 hello.txt 파일의 내용을 읽어보자.
  • 파일을 읽을 때도 fopen 함수로 파일을 열어서 파일 포인터를 얻은 뒤 fscanf 함수로 서식을 지정하여 파일의 내용을 읽는다.


fscanf(파일포인터, 서식, 변수의주소1, 변수의주소2, );
성공하면 읽어온 값의 개수를 반환, 실패하면 EOF(-1) 반환
#define _CRT_SECURE_NO_WARNINGS // fopen 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>              // fopen, fscanf, fclose 함수가 선언된 헤더 파일

int main()
{
    char s1[10];
    int num1;

    FILE *fp = fopen("hello.txt", "r"); // hello.txt 파일을 읽기 모드(r)로 열기.
                                        // 파일 포인터를 반환

    fscanf(fp, "%s %d", s1, &num1); // 서식을 지정하여 파일에서 문자열 읽기

    printf("%s %d\n", s1, num1); // 파일에서 읽은 값을 출력

    fclose(fp); // 파일 포인터 닫기

    return 0;
}

// Hello 100


  • 먼저 fopen 함수를 사용하여 hello.txt 파일을 읽기 모드(r)로 연다.


FILE *fp = fopen("hello.txt", "r"); // hello.txt 파일을 읽기 모드(r)로 열기.
                                    // 파일 포인터를 반환


  • fscanf 함수는 파일 포인터를 넣는다는 점 말고는 scanf 함수와 사용법이 같다.
  • 즉, 파일에서 문자열을 읽은 뒤 서식에 맞춰서 값을 변수에 저장한다.
  • 여기서는 서식을 "%s %d"로 지정했으므로 "Hello" 부분은 문자 배열 s1에 저장되고, 100 부분은 정수형 변수 num1에 저장된다.


fscanf(fp, "%s %d", s1, &num1); // 서식을 지정하여 파일에서 문자열 읽기


  • printfs1num1의 값을 출력해 보면 "Hello"100이 나온다.


printf("%s %d\n", s1, num1); // 파일에서 읽은 값을 출력


  • 마찬가지로 파일 읽기 작업이 끝났다면 fclose 함수로 파일 포인터를 닫아준다.


fclose(fp); // 파일 포인터 닫기


  • 서식을 지정하여 파일에서 문자열을 읽는 과정을 그림으로 나타내면 다음과 같다.


003


  • fscanf 함수는 stdin 매크로를 활용하면 사용자 입력한 값을 변수에 저장할 수 있다.


char s1[10];
int num1;
fscanf(stdin, "%s %d", s1, &num1); // 서식을 지정하여 표준 입력(stdin)에서 문자열 읽기


  • 즉, fscanf(stdin, ...);scanf 함수와 동작이 같다.


파일 스트림

  • fprintf, fscanf 등의 함수는 매개변수에서 파일 포인터 부분을 보면 FILE *const _Stream과 같이 스트림이라고 되어있다.
  • 보통 파일 포인터를 파일 스트림이라고도 하는데 스트림은 물 등의 액체가 흐르는 것을 뜻한다.
  • 파이프 속에 물이 계속 흘러다니는 것처럼 파일 스트림도 파일의 데이터를 연속적으로 처리한다고 해서 스트림이다.
  • 즉, 파일에서 데이터를 처리할 때마다 매번 파일을 여는 것이 아니라 파일 스트림을 한 번 생성해서 계속 데이터를 쓰거나 가져오는 방식이다.
  • 여기서 fopen으로 파일을 읽기 전용으로 열면 입력 스트림, 쓰기 전용으로 열면 출력 스트림, 읽기/쓰기로 열면 입출력 스트림이다.
  • 파일 모드에 따라 단방향, 양방향이 된다.
  • 마찬가지로 stdin은 입력 스트림, stdout, stderr는 출력 스트림이다.


5. 파일에 문자열 쓰기

  • fputs 함수를 사용하면 문자열을 파일에 쓸 수 있다.


fputs(버퍼, 파일포인터);
성공하면 음수가 아닌 값을 반환, 실패하면 EOF(-1) 반환


  • 간단하게 "Hello, world!" 문자열을 파일에 저장해 보자.


#define _CRT_SECURE_NO_WARNINGS // fopen 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>              // fopen, fputs, fclose 함수가 선언된 헤더 파일

int main()
{
    FILE *fp = fopen("hello.txt", "w"); // hello.txt 파일을 쓰기 모드(w)로 열기.
                                        // 파일 포인터를 반환

    fputs("Hello, world!", fp); // 파일에 문자열 저장

    fclose(fp); // 파일 포인터 닫기

    return 0;
}


  • 먼저 파일에 문자열을 써야 하므로 fopen 함수를 사용하여 파일을 쓰기 모드(w)로 연다.


FILE *fp = fopen("hello.txt", "w"); // hello.txt 파일을 쓰기 모드(w)로 열기.
                                    // 파일 포인터를 반환


  • 이제 파일 포인터를 얻었으니 fputs 함수로 문자열을 파일에 쓴다.
  • fputs는 파일에 쓸 문자열(배열이나 동적 메모리를 할당한 포인터도 가능)과 파일 포인터 fp를 넣어준다.


fputs("Hello, world!", fp); // 파일에 문자열 저장


  • 이렇게 하면 "Hello, world!" 문자열만 hello.txt 파일에 저장된다.


  • 파일 쓰기가 끝났으면 반드시 fclose 함수로 파일 포인터를 닫아준다.


fclose(fp); // 파일 포인터 닫기


  • 지금까지 파일에 문자열을 쓴 과정을 그림으로 표현하면 다음과 같다.


004


  • fputs 함수도 파일 포인터 대신 stdout을 지정하면 문자열이 표준 출력(화면)에 출력된다.


fputs("Hello, world!", stdout); // 표준 출력(stdout)에 문자열 출력


  • 이번에는 fwrite 함수를 사용하여 문자열을 파일에 써보자.


fwrite(버퍼, 쓰기크기, 쓰기횟수, 파일포인터);
성공한 쓰기 횟수를 반환, 실패하면 지정된 쓰기 횟수보다 작은 값을 반환
#define _CRT_SECURE_NO_WARNINGS // fopen 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>              // fopen, fwrite, fclose 함수가 선언된 헤더 파일
#include <string.h>             // strlen 함수가 선언된 헤더 파일

int main()
{
    char *s1 = "Hello, world!";

    FILE *fp = fopen("hello.txt", "w"); // hello.txt 파일을 쓰기 모드(w)로 열기.
                                        // 파일 포인터를 반환

    fwrite(s1, strlen(s1), 1, fp); // strlen으로 문자열의 길이를 구함.
                                   // 문자열의 길이만큼 1번 파일에 저장

    fclose(fp); // 파일 포인터 닫기

    return 0;
}


  • 먼저 파일에 문자열을 써야 하므로 fopen 함수를 사용하여 파일을 쓰기 모드(w)로 연다.


FILE *fp = fopen("hello.txt", "w"); // hello.txt 파일을 쓰기 모드(w)로 열기.
                                    // 파일 포인터를 반환


  • fwrite 함수는 fputs 함수와는 달리 쓰기 크기와 쓰기 횟수를 지정해야 한다.
  • 따라서 먼저 파일에 쓸 문자열의 포인터 s1을 지정한다.
  • 그리고 쓰기 크기에는 strlen(s1)과 같이 문자열의 길이를 구해서 넣고, 쓰기 횟수에는 1을 넣는다.
  • 마지막에는 파일 포인터 fp를 넣어준다.


fwrite(s1, strlen(s1), 1, fp); // strlen으로 문자열의 길이를 구함.
                               // 문자열의 길이만큼 1번 파일에 저장


  • 이렇게 하면 "Hello, world!" 문자열만 hello.txt 파일에 저장된다.


  • 파일 쓰기가 끝났으면 반드시 fclose 함수로 파일 포인터를 닫아준다.


fclose(fp); // 파일 포인터 닫기


  • 지금까지 파일에 문자열을 쓴 과정을 그림으로 표현하면 다음과 같다.


005


  • fwrite 함수도 파일 포인터 대신 stdout을 지정하면 문자열이 화면에 출력된다.


char *s1 = "Hello, world!";
fwrite(s1, strlen(s1), 1, stdout); // Hello, world!
                                   // 문자열 길이만큼 표준출력(stdout)에 문자열 출력


6. 파일에서 문자열 읽기

  • 이제 앞에서 생성한 hello.txt 파일의 내용을 읽어보자.
  • 파일을 읽을 때는 fopen 파일을 열어서 파일 포인터를 얻은 뒤 fgets 함수로 파일의 내용을 읽는다.


fgets(버퍼, 버퍼크기, 파일포인터);
성공하면 읽은 문자열의 포인터를 반환, 실패하면 NULL을 반환
#define _CRT_SECURE_NO_WARNINGS // fopen 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>              // fopen, fgets, fclose 함수가 선언된 헤더 파일

int main()
{
    char buffer[20]; // 파일을 읽을 때 사용할 임시 공간

    FILE *fp = fopen("hello.txt", "r"); // hello.txt 파일을 읽기 모드로 열기.
                                        // 파일 포인터를 반환

    fgets(buffer, sizeof(buffer), fp); // hello.txt에서 문자열을 읽음

    printf("%s\n", buffer); // 파일의 내용 출력

    fclose(fp); // 파일 포인터 닫기

    return 0;
}


  • 먼저 파일을 읽을 때 사용할 임시 공간(버퍼)를 선언한다.
  • 여기서는 크기가 20char 타입 배열을 선언했다.
  • char 타입 포인터를 선언한 뒤 동적 메모리를 할당해도 된다.


char buffer[20]; // 파일을 읽을 때 사용할 임시 공간


  • fopen 함수를 사용하여 hello.txt 파일을 읽기 모드(r)로 연다.


FILE *fp = fopen("hello.txt", "r"); // hello.txt 파일을 읽기 모드로 열기.
                                    // 파일 포인터를 반환


  • 이제 파일 포인터를 얻었으니 fgets 함수로 파일의 내용을 읽는다.
  • 먼저 버퍼에는 앞에서 선언한 buffer를 넣고, 버퍼 크기에는 sizeof(buffer)와 같이 버퍼의 크기를 구해서 넣는다.
  • 그리고 마지막에는 파일 포인터 fp를 넣어준다.


fgets(buffer, sizeof(buffer), fp); // hello.txt에서 문자열을 읽음


  • printfbuffer의 내용을 출력해 보면 hello.txt에 저장되어 있던 문자열이 출력되는 것을 볼 수 있다.


printf("%s\n", buffer); // 파일의 내용 출력


  • 파일 읽기가 끝났으면 반드시 fclose 함수로 파일 포인터를 닫아준다.


fclose(fp); // 파일 포인터 닫기


  • 지금까지 파일에서 문자열을 읽은 과정을 그림으로 표현하면 다음과 같다.


006


  • 한 가지 특이한 점은 fgets 함수가 파일을 읽는 방식이다.
  • 예를 들어 다음과 같이 hello.txt에 문자열이 저장되어 있다.


Hello, world! Hello, world! Hello, world! Hello, world!


  • 이 상태에서 fgets의 버퍼 크기를 20바이트로 지정한 뒤 파일을 읽으면 딱 버퍼 크기만큼만 읽는다.
  • 따라서 널 문자를 포함하여 20바이트를 읽으며 실제 문자열은 19바이트이다.


// 파일 열기, 닫기 생략
char buffer[20];
fgets(buffer, sizeof(buffer), fp); // 버퍼 크기 20바이트
fputs(buffer, stdout);             // 널 문자를 포함하여 20바이트를 읽음. 문자열은 19바이트


  • 하지만 다음과 같이 hello.txt에 줄바꿈(\n)이 있으면 버퍼 크기와는 상관없이 \n까지만 문자열을 읽는다.


Hello, wo
rld!


  • 즉, 이 상태에서 fgets로 파일을 읽으면 buffer에는 "Hello, wo\n"까지만 들어간다.


// 파일 열기, 닫기 생략
char buffer[20];
fgets(buffer, sizeof(buffer), fp); // 버퍼 크기 20바이트
fputs(buffer, stdout);             // \n까지 문자열을 읽음(\n도 포함)


  • 이처럼 fgets 함수는 \n에 따라 읽은 결과가 달라지므로 사용에 주의해야 한다.


  • fgets 함수도 stdin을 지정하면 사용자가 입력한 문자열을 버퍼에 저장한다.
  • 다음과 같이 버퍼 크기를 20으로 지정하면 사용자가 입력한 문자열 중에서 널 문자 포함 20바이트만 버퍼에 저장한다.


char buffer[20];
fgets(buffer, sizeof(buffer), stdin); // 표준 입력(stdin)에서 20바이트만큼 문자열 읽기
                                      // 널 문자 1바이트, 실제 문자열 19바이트


  • 물론 지정한 버퍼 크기보다 작은 상태에서 엔터 키를 누르면 \n이 되므로 fgets 함수는 \n까지 입력을 받는다.


  • 이번에는 fread 함수를 사용하여 파일에서 문자열을 읽어보자.


fread(버퍼, 읽기크기, 읽기횟수, 파일포인터);
성공한 읽기 횟수를 반환, 실패하면 지정된 읽기 횟수보다 작은 값을 반환
#define _CRT_SECURE_NO_WARNINGS // fopen 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>              // fopen, fread, fclose 함수가 선언된 헤더 파일

int main()
{
    char buffer[20] = { // 파일을 읽을 때 사용할 임시 공간, 미리 0으로 전부 초기화
        0,
    };

    FILE *fp = fopen("hello.txt", "r"); // hello.txt 파일을 읽기 모드로 열기.
                                        // 파일 포인터를 반환

    fread(buffer, sizeof(buffer), 1, fp); // hello.txt에서 버퍼 크기(20바이트)만큼 1번 값을 읽음

    printf("%s\n", buffer); // 파일의 내용 출력

    fclose(fp); // 파일 포인터 닫기

    return 0;
}

// Hello, world!


  • 파일을 읽을 때 사용할 임시 공간(버퍼)를 선언해야 하는데 fread 함수를 사용할 때는 char 타입 배열을 선언한 뒤 반드시 0으로 초기화해야 한다.
  • char 타입 포인터에 동적 메모리를 할당한 뒤 0으로 초기화해도 된다.


char buffer[20] = { // 파일을 읽을 때 사용할 임시 공간, 미리 0으로 전부 초기화
    0,
};


  • fopen 함수를 사용하여 hello.txt 파일을 읽기 모드(r)로 연다.


FILE *fp = fopen("hello.txt", "r"); // hello.txt 파일을 읽기 모드로 열기.
                                    // 파일 포인터를 반환


  • 이제 파일 포인터를 얻었으니 fread 함수로 파일의 내용을 읽는다.
  • 먼저 앞에서 선언한 buffer를 넣어준다.
  • 그리고 읽기 크기에는 sizeof(buffer)와 같이 버퍼의 크기를 구해서 넣고, 읽기 횟수에는 1을 지정하여 버퍼 크기만큼 읽는다.
  • 마지막에는 파일 포인터 fp를 넣어준다.


fread(buffer, sizeof(buffer), 1, fp); // hello.txt에서 버퍼 크기(20바이트)만큼 1번 값을 읽음


  • 즉, fread 함수에서 파일을 읽는 크기는 읽기 크기 * 읽기 횟수이다.


  • 특히 fread 함수는 fgets 함수와는 달리 \n이 있든 없든 무조건 지정된 크기만큼 읽는다.
  • printfbuffer의 내용을 출력해 보면 hello.txt에 저장되어 있던 문자열이 출력되는 것을 볼 수 있다.


printf("%s\n", buffer); // 파일의 내용 출력


  • 만약 앞에서 buffer0(NULL)으로 초기화하지 않고 fread로 파일을 읽으면 "Hello, world!" 이외에도 쓸데없는 값들이 함께 출력된다.
  • 왜냐하면 C로 만든 프로그램의 문자열 포인터, 배열에 저장된 문자열의 끝에는 NULL이 들어있지만, 파일에 저장된 문자열의 끝에는 NULL이 들어있지 않다.
  • 그러다 보니 파일에서 문자열만 읽어서 buffer에 가져오면 이전에 메모리에 저장되어 있던 값과 합쳐지게 된다.
  • 즉, 문자열 끝에 NULL이 없어서 buffer의 내용을 모두 출력하게 되는 것이다.


007


  • 파일 읽기가 끝났으면 반드시 fclose 함수로 파일 포인터를 닫아준다.


fclose(fp); // 파일 포인터 닫기


  • 지금까지 파일에서 문자열을 읽은 과정을 그림으로 표현하면 다음과 같다.


008


  • fread 함수도 stdin을 지정하면 사용자가 입력한 문자열을 버퍼에 저장한다.
  • 단, fread 함수는 무조건 지정된 크기만큼만 읽으므로 버퍼 끝에 NULL이 들어갈 수 있도록 읽기 크기는 버퍼 크기보다 1이 적도록 만들어준다.


char buffer[20] = { 0, };
fread(buffer, sizeof(buffer) - 1, 1, stdin); // 표준 입력(stdin)에서 문자열 읽기
                                             // 버퍼 끝에 NULL이 들어갈 수 있도록 sizeof(buffer) - 1을 지정


stdin, stdout, stderr도 파일 포인터이다.

  • 보통 stdin은 키보드 입력, stdoutstderr는 콘솔 출력이다.
  • 특히 C에서는 stdin, stdout, stderr가 파일 포인터(FILE *)이므로 fprintf, fscanf, fread, fwrite 등 파일 포인터를 받는 함수에 사용할 수 있다.
  • 즉, fscanf, fgets 함수로 stdin을 읽으면 키보드 입력 값을 가져오게 되며 fprintf, fputs 함수로 stdout, stderr에 값을 쓰면 콘솔에 값을 출력하게 된다.

References