Algorithm/C

[c언어 개념] #14 파일의 개방 모드, 파일 입출력 함수, 파일 위치 지시자

하노정 2022. 7. 27. 18:27

c언어 개념 정리와 문제 풀이를 통해 알고리즘 수업을 준비하는 과정입니다. 그동안 수강했던 c언어 수업 자료와 윤성우의 열혈 C 프로그래밍의 내용을 포함해 저에게 기록할 가치가 있는 내용이 담겨 있습니다. 


파일의 개방 모드

fopen 함수의 두 번째 인자로 "rt", "wt" 외에도 형성할 스트림의 종류는 다양하다.

 

다음 2가지 기준을 통해서 스트림을 구분할 수 있다. 

1. 읽기 위한 스트림 or 쓰기 위한 스트림

데이터의 이동방향을 통해서 스트림을 다음 4가지로 구분할 수 있다.

  • 데이터 READ 스트림 - 읽기만 가능
  • 데이터 WRITE 스트림 - 쓰기만 가능
  • 데이터 APPEND 스트림 - 쓰되 덧붙여 쓰기만 가능
  • 데이터 READ/WRITE 스트림 - 읽기, 쓰기 모두 가능

 

c언어는 이를 바탕으로 총 6가지로 스트림을 세분화한다.

  • r - 읽기 가능 (파일이 없으면 에러)
  • w - 쓰기 가능 (파일이 없으면 생성)
  • a - 파일의 끝에 덧붙여 쓰기 가능 (파일이 없으면 생성)
  • r+ - 읽기/쓰기 가능 (파일이 없으면 에러)
  • w+ - 읽기/쓰기 가능 (파일이 없으면 생성)
  • a+ - 읽기/덧붙여 쓰기 가능 (파일이 없으면 생성)

 

이를 참조해서 필요로 하는 스트림이 특성과 일치하는 '파일의 개방 모드'를 선택하면 된다.

그 모드의 이름이 fopen 함수의 두 번째 인자가 된다.

  • 모드의 +는 읽기, 쓰기가 모두 가능한 스트림의 형성
  • 모드의 a는 쓰기가 가능한 스트림을 형성하는데, 여기서 말하는 쓰기란 덧붙이기이다.

근데 웬만하면 r, w, a 중에서 선택하는 게 좋다. 

+모드는 읽기, 쓰기를 전환할 때마다 메모리 버퍼를 비워줘야 하는 등의 불편이 있다.

 

2. 텍스트 데이터를 위한 스트림 or 바이너리 데이터를 위한 스트림

  • 텍스트 파일 - 사람이 인식할 수 있는 문자를 담고 있는 파일
  • 바이너리 파일 - 컴퓨터가 인식할 수 있는 데이터를 담고 있는 파일 

 

데이터 입출력을 위해 스트림을 형성할 때 신경 쓸 부분은 '문장의 끝을 의미하는 개행의 표현방식'이다.

개행은 줄이 바뀌었다는 현상일 뿐 그 자체가 하나의 데이터로 존재하는 것은 아니다.

아무런 약속 없이 흰 종이에 개행 하나만을 표시할 수는 없다.

그래서 c언어에서는 개행을 \n으로 표현하도록 약속했다. 

모든 컴퓨터 환경에서의 약속이 아닌 c언어만의 약속이다.

  • MS-DOS(Windows)의 파일 내 개행 - \r\n
  • Mac(Mackintosh)의 파일 내 개행 - \r
  • Unix 계열의 파일 내 개행 - \n

 

그렇다면 c언어와 다른 운영체제에서는 개행 정보를 파일에 어떻게 저장해야 할까?

-> 파일을 텍스트 모드로 개방하면 된다. 그럼 자동으로 각 운영체제에 맞게 개행 문자의 변환이 일어난다.

 

텍스트 모드의 파일 개방을 위해서는 fopen 함수의 두 번째 인자로 다음 중 하나를 전달해야 한다.

rt, wt, at, r+t, w+t, a+t

위에서 정리한 파일 개방 모드에 텍스트 모드를 의미하는 t가 붙은 형태이다.

 

반대로 바이너리 데이터를 저장하고 있는 파일은 아무런 변환도 일어나면 안 되기 때문에 바이너리 모드로 파일을 개방해야 한다. 그리고 이를 위해선 fopen 함수의 두 번째 인자로 다음 중 하나를 전달해야 한다.

rb, wb, ab, r+b, w+b, a+b

위에서 정리한 파일 개방 모드에 바이너리 모드를 의미하는 b가 붙은 형태이다.

 

개방 모드에 t, b 아무것도 붙이지 않으면 파일은 텍스트 모드로 개방된다. 

 

파일 입출력 함수의 기본

fgetc, fputc, fgets, fputs

 

문자열이 파일에 저장될 때는 문자열의 끝을 의미하는 널 문자는 저장되지 않는다.

때문에 파일에서는 개행을 기준으로 문자열을 구분한다.

fgets가 호출 될 때마다 개행 문자를 만날 때까지 문자열을 읽어 들이게 된다. 

따라서 fgets 함수 호출을 통해서 읽어 들일 문자열의 끝에는 반드시 \n 문자가 존재해야 한다.

 

feof 함수

#include <stdio.h>
int feof(FILE * stream);

인자로 전달된 FILE 구조체의 포인터를 대상으로, 더 이상 읽어 들일 데이터가 존재하지 않으면(파일의 끝까지 모두 읽어 들인 상태이면) 0이 아닌 값을 반환한다. 파일의 끝을 확인해야 하는 경우에 유용하게 사용된다.

 

fread, fwrite 함수

바이너리 데이터의 입출력 함수이다.

#include <stdio.h>
size_t fread(void * buffer, size_t size, size_t count, FILE * stream);
size_t fwrite(const void * buffer, size_t size, size_t count, FILE * stream);

 

 

fscanf, fprintf 함수

하나의 파일을 대상으로 텍스트 데이터와 바이너리 데이터  두 형태를 동시에 입출력 해야 하는 상황일 때는?

텍스트 데이터와 바이너리 데이터를 입출력 해야 하는 상황에서 제일 먼저 생각할 수 있는 방법은

fscanf, fprintf 함수의 호출이다. 이 두 함수는 scanf, printf와 비슷한데 입출력 대상이 콘솔이 아닌 파일이라는 차이가 있다.

 

첫 번째 전달인자로 FILE 구조체의 포인터가 오는 것이 printf와 차이점이다.

fprintf 함수는 첫 번째 인자로 전달된 FILE 구조체 포인터가 지칭하는 파일로 출력이 이뤄진다. 

fprintf(fp, "%s %c %d", name, sex, age);

텍스트 데이터와 바이너리 데이터를 하나의 문자열로 묶어서 저장하는 셈이다.

 

첫 번째 전달인자로 FILE 구조체의 포인터가 오는 것이 scanf와 차이점이다.

fscanf 함수는 첫 번째 인자로 전달된 FILE 구조체 포인터가 지칭하는 파일로 입력이 이뤄진다. 

ret = fscanf(fp, "%s %c %d", name, &sex, &age);

fprintf 함수 호출을 통해서 저장된 데이터는 동일하게 서식을 지정해서 fscanf 함수 호출을 통해 읽을 수 있다.

fscanf 함수는 파일의 끝에 도달하거나 오류가 발생하면 EOF를 반환한다. 

 

텍스트와 바이너리 데이터의 집합체인 구조체 변수의 입출력

실제 프로그램에서는 보통 위의 데이터들을 하나의 구조체로 묶어서 정의한다.

그럼 구조체 변수 단위로의 파일 입출력을 고민할 필요가 있다.

구조체 변수 단위의 입출력이 구조체 멤버 단위의 입출력보다 입출력의 형태가 단순하다.

 

구조체 변수를 통째로 저장하고 통째로 읽어 들이는 방법은?

구조체 변수를 하나의 바이너리 데이터로 인식하고 처리하면 된다. 

그럼 fwrite, fread 함수를 통해 통째로 복원 가능하다.

 

파일 위치 지시자

경우에 따라서 파일 중간, 마지막 부분에 저장된 데이터의 일부를 읽어야 하는 경우도 있다.

이럴 때는 파일 위치 지시자를 해당 부분으로 이동시켜야 한다.

 

FILE 구조체의 멤버 중에는 파일의 위치 정보를 저장하고 있는 멤버가 있는데, 이 멤버 값은 fgets, fputs, fgetc, fputc와 같은 함수가 호출될 때마다 참조 및 갱신된다. 이처럼 이 멤버에 저장된 위치 정보의 갱신을 통해서 데이터를 읽고 쓸 위치 정보가 유지되는 것이다. 이 멤버를 '파일 위치 지시자'라고 한다.

 

파일 위치 지시자는 파일이 처음 개방되면 무조건 파일의 맨 앞부분을 가리킨다.

따라서 파일 중간, 마지막 부분을 읽거나 쓰고 싶으면 파일 위치 지시자를 이동시켜야 한다.

 

fseek

#include <stdio.h>
int fseek( FILE * stream, long offset, int wherefrom );

파일 위치 지시자를 직접 이동시킬 때 fseek 함수를 호출한다.

stream으로 전달된 파일 위치 지시자를 wherefrom에서부터 offset 바이트만큼 이동시키는 것이다.

 

매개변수 wherefrom에 전달될 수 있는 상수와 그 의미는 다음과 같다.

  • SEEK_SET(0) - 파일 위치 지시자는 파일 맨 앞에서부터 이동 시작
  • SEEK_CUR(1) - 파일 위치 지시자는 현재 위치에서부터 이동 시작
  • SEEK_END(2) - 파일 위치 지시자는 파일 맨 끝에서부터 이동 시작

매개변수 offset에는 양, 음의 정수 모두 올 수 있다.

  • 양의 정수가 전달되면 파일의 마지막을 향해서 파일 위치 지시자가 이동한다.
  • 음의 정수가 전달되면 파일의 시작 위치를 향해서 파일 위치 지시자가 이동한다.

ftell

#include <stdio.h>
long ftell( FILE * stream );

파일 위치 지시자의 위치 정보를 반환한다.

파일 위치 지시자가 첫 번째 바이트를 가리키면 0을, 세 번째 바이트를 가리키면 2를 반환한다. 

파일 위치 지시자의 정보를 임시로 저장할 때 유용하게 사용된다.