File High-Level I/O에 대해 알아보자

File: High-level I/O

I/O Stream

  • Stream: 물줄기, 개울, 시내

    • I/O stream: 데이터가 흘러가는 것. 데이터의 이동을 표현하는 말
      • 예) Media streaming
      • Source 에서 Destination 으로 데이터가 이동함
      • 프로그램은 데이터가 이동하는 통로로, source 이자 destination 일 수 있음
  • Input stream: Source로부터 데이터가 들어오는 경로

    • 예) Keyboard -> Program (scanf() 로 stream 을 연결할 수 있음)
  • Output stream: Destination으로 데이터가 나가는 경로

    • 예) Program -> Console (printf() 로 stream 을 연결할 수 있음)
  • 입력 스트림(input stream): 다른 곳에서 프로그램으로 들어오는 경로

    • 자료가 떠나는 시작 부분이 자료 원천부 (data source)
    • 표준입력: 원천부가 키보드
      • 파일입력: 파일이면 파일로부터 자료를 읽는 것
      • 스크린입력: 터치스크린이면 스크린에서 터치 정보
      • 네트워크입력: 다른 곳에서 프로그램으로 네트워크를 통해 자료가 전달
  • 출력 스트림(output stream) : 프로그램에서 다른 곳으로 나가는 경로

    • 자료의 도착 장소가 자료 목적부 (data destination)
    • 표준출력: 목적부가 콘솔
      • 파일출력: 파일이면 파일에 원하는 값을 저장
      • 프린터출력: 프린터이면 프린터에 출력물
      • 네트워크출력: 네트워크이면 네트워크 출력이 되어 다른 곳으로 자료가 이동

텍스트 파일과 이진파일

  • 텍스트 파일: 메모장(notepad) 같은 편집기로 작성된 파일

    • 내용이 아스키 코드(ascii code)와 같은 문자 코드값으로 저장
    • 메모리에 저장된 실수와 정수와 같은 내용도 문자 형식으로 변환되어 저장
    • 텍스트 편집기를 통하여 그 내용을 볼 수 있고 수정 가능
  • 이진 파일: 실행파일과 그림 파일, 음악 파일, 동영상 파일 등

    • 목적에 알맞은 자료가 이진 형태(binary format)로 저장되는 파일
    • 자료는 메모리 자료 내용에서 어떤 변환도 거치지 않고 그대로 파일에 기록
    • 입출력 속도도 텍스트 파일보다 빠름
    • 메모장과 같은 텍스트 편집기로는 그 내용을 볼 수 없음
    • 내용을 이미 알고 있는 특정한 프로그램에 의해 인지될 때 의미가 있음

파일 스트림과 포인터

  • 표준 입출력 라이브러리에서 제공하는 고수준 파일 입출력
    • File stream 이라는 서비스를 사용자에게 제공한다.
      • 저수준IO: 파일 vs. 고수준IO: 파일 스트림
  • FILE* : 파일 포인터
    • 고수준 파일 입출력에서 열린 파일을 가리키는 포인터
    • 자료형으로 FILE * 형을 사용 -> 구조체에 대한 포인터

image

파일 스트림 열기

  • 파일 스트림 열기: fopen(3)
1#include <stdio.h>
2
3FILE *fopen(const char *pathname, const char *mode);
  • Pathname으로 지정한 파일을 mode로 지정한 모드에 따라 열고 파일 포인터를 리턴
  • mode 값
    • 파일모드에서 +의 삽입은 수정(update) 모드 의미
      • 원래의 모드에서 읽기 또는 쓰기가 추가되는 모드
      • 수정(update) 모드에서는 모드 간의 전환이 가능
    • 파일모드 r+
      • 처음에 읽기 모드로 파일을 열어 쓰기 모드로 전환 가능
      • 파일이 없으면 오류가 발생
    • 파일모드 w+
      • 처음에 쓰기 모드로 파일을 열어 필요하면 읽기 모드로 전환 가능
      • 만일 파일이 존재한다면 이전의 내용은 모두 사라짐
    • 파일모드 a+
      • 처음에 추가 모드로 파일을 열어 필요하면 읽기 모드로 전환 가능

파일 스트림 닫기

  • 파일 스트림 닫기: fclose(3)
1#include <stdio.h>
2
3int fclose(FILE *stream);
  • Fopen() 으로 오픈한 파일 스트림을 닫는다.
  • Close() 와 마찬가지로, 파일 스트림의 사용 종료를 알리는 역할이며, 저장 장치에 내용이 기록되는 것을 보장하지는 않는다
    • 저장 장치에 즉시 기록하려면: fflush(3)를 사용
  • RETURN VALUE
    • On success: 0
    • On error: EOF
      • 성공하든, 실패하든 파일 접근은 더 이상 하면 안 됨

버퍼 기반 읽기 및 쓰기

  • 버퍼 기반 입력함수: fread(3)
    • Stream으로 지정한 파일로부터, 항목의 크기가 size인 데이터를 nitems에 지정한 개수만큼 읽어 ptr에 저장 (구조체의 입출력에 적합함)
    • 성공하면 읽어온 항목 수를 리턴
    • 읽을 항목이 없으면 0을 리턴
1#include <stdio.h>
2
3size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream);
  • 버퍼 기반 출력함수: fwrite(3)
    • 항목의 크기가 size인 데이터를 nitems에서 지정한 개수만큼 ptr에서 읽어서 stream으로 지정한 파일에 출력
    • 성공하면 출력한 항목의 수를 리턴
    • 오류가 발생하면 EOF를 리턴
1#include <stdio.h>
2
3size_t fwrite(const void *ptr, size_t size, size_t nitems, FILE *stream);

파일 오프셋 지정

  • 파일 오프셋 이동: fseek(3)
    • stream이 가리키는 파일에서 offset에 지정한 크기만큼 오프셋을 이동
    • whence는 lseek()와 같은 값을 사용
    • 성공하면 0을, 실패하면 EOF를 리턴
1#include <stdio.h>
2
3int fseek(FILE *stream, long offset, int whence);
  • 현재 오프셋 구하기: ftell(3)
    • 현재 오프셋을 리턴. 오프셋은 파일의 시작에서 현재 위치까지의 바이트 수
1#include <stdio.h>
2
3long ftell(FILE *stream);
  • 함수 fseek(f, 100L, SEEK_SET)의 호출
    • 파일 위치를 파일의 처음 위치에서 100바이트 떨어진 위치로 이동
  • 함수 fseek(f, 100L, SEEK_CUR)의 호출
    • 파일의 현재 위치에서 100바이트 떨어진 위치로 이동
  • 함수 fseek(f, -100L, SEEK_END)의 호출
    • 파일 끝 위치에서 앞으로 100바이트 떨어진 위치로 이동
  • 함수 fseek()에서 offset
    • 양수이면 파일의 끝점으로,
    • 음수이면 파일의 시작점으로의 이동방향을 표시

image

  • 처음 위치로 오프셋 이동: rewind(3)
    • 오프셋을 파일의 시작 위치로 즉시 이동
1#include <stdio.h>
2
3void rewind(FILE *stream);
  • 오프셋의 저장과 이동: fsetpos(3), fgetpos(3)
    • fgetpos() : 파일의 현재 오프셋을 pos가 가리키는 영역에 저장
    • fsetpos() : pos가 가리키는 위치로 파일 오프셋을 이동
1#include <stdio.h>
2
3int fsetpos(FILE *stream, const fpos_t *pos);
4int fgetpos(FILE *stream, fpos_t *pos);

Text File I/O

fprintf() and fscanf()

  • 함수 fprintf()와 fscanf() 또는 fscanf_s()를 이용
    • 텍스트 파일에 자료를 쓰거나 읽기 위하여
    • 헤더 파일 stdio.h를 포함
      • 첫 번째 인자는 입출력에 이용될 파일
      • 두 번째 인자는 입출력에 이용되는 제어 문자열
      • 다음 인자들은 입출력될 변수 또는 상수 목록
1#include <stdio.h>
2
3int fprintf(FILE *stream, const char *format, ...);
4
5int fscanf(FILE *stream, const char *format, ...);
  • 함수 fprintf()와 fscanf() 또는 fscanf_s()의 첫 번째 인자에 각각 stdin 또는 stdout를 이용하면 표준 입력, 표준 출력으로 이용이 가능
  • Return value
    • fprintf(): 기록한 문자 수. 문자열 마지막의 NULL 문자는 제외
    • fscanf(): 읽은 아이템 수 혹은 파일의 끝에 도달한 경우 EOF

feof() and ferror()

  • 함수 feof(): 파일 스트림의 EOF(End Of File) 표시를 검사하는 함수
    • 읽기 작업이 파일의 이전 부분을 읽으면 0을 반환하고 (EOF 아님)
      • 그렇지 않으면 0이 아닌 값을 반환 (파일 끝!)
      • 이전 읽기 작업에서 EOF 표시에 도달하면 0이 아닌 값으로 지정 (파일 끝!)
    • 단순히 파일 지시자가 파일의 끝에 있더라도 feof()의 결과는 0
      • 파일 끝에 도달한 다음, 한 번 더 읽기 동작이 수행되어야 EOF 가 셋팅됨
      • 이 동작 때문에 실제 사용 시, 잘못 사용하는 경우가 많음 (사용 지양)
  • 함수 ferror(): 파일 처리에서 오류가 발생했는지 검사하는 함수
    • 이전 파일 처리에서 오류가 발생하면 0이 아닌 값을 반환 (오류!)
      • 오류가 발생하지 않으면 0을 반환 (정상)
    • 헤더파일 stdio.h 필요

Additional services: fgetc() and fputc()

1#include <stdio.h>
2int fgetc(FILE *stream);
3int fputc(int c, FILE *stream);
  • 함수 fgetc()
    • 파일로부터 문자 하나를 입력받는 함수
    • Return value
      • int 형 으로 casting 된 문자 하나를 반환
      • 만약 파일 끝인 경우, EOF 를 반환하며, 에러에는 0이 아닌 값
  • 함수 fputc()
    • 문자 하나를 파일로 출력하는 함수
    • 함수들은 문자 하나의 입출력의 대상인 파일 포인터를 인자로 이용
  • getc()putc()도 존재함. 그러나 잊어버릴 것.
    • Macro 로 구현되어 있을 가능성이 있어, fgetc(), fputc()보다 빠를 수도 있지만, 예상치 못한 문제점이 생길 수 있음

Additional services: fgets() and fputs()

1#include <stdio.h>
2char *fgets(char *s, int size, FILE *stream);
3int fputs(const char *s, FILE *stream);
  • 함수 fgets(): 파일로부터 한 행의 문자열을 입력 받는 함수
    • 파일로부터 문자열을 개행문자(\n)까지 읽어 마지막 개행문자를 \0문자로 바꾸어 입력 버퍼 문자열에 저장
      • 첫 번째 인자는 문자열이 저장될 문자 포인터
      • 두 번째 인자는 입력할 문자의 최대 수
      • 세 번째 인자는 입력 문자열이 저장된 파일
    • 텍스트 파일을 처리할 때, 라인 별로 처리가 가능하여 아주 편리함
  • 함수 fputs(): 파일로 한 행의 문자열을 출력하는 함수
    • 문자열을 한 행에 출력
      • 첫 번째 인자는 출력될 문자열이 저장된 문자 포인터
      • 두 번째 인자는 문자열이 출력되는 파일
    • fprintf() 면 충분하지 않을까?

Binary file I/O

Text vs. Binary

  • Text I/O: fscanf() and fprintf()
    • 본래 파일은 “Collection of Bytes”
    • 텍스트 파일은? 자료의 입출력을 텍스트 모드(아스키 코드)로 처리
      • 텍스트 파일의 내용은 모두 지정된 아스키 코드와 같은 문자 코드값
    • 함수 fprintf()를 이용
      • int 형 변수 cnt의 값을 파일 f에 출력하는 과정
      • 실제로 파일에 저장되는 자료는 정수값 10에 해당하는 각 문자의 아스키 값
        • 각각의 문자 ‘1’과 ‘0’을 아스키 코드값으로 변환: 0x31, 0x30
        • 변환한 결과값을 저장함

image

  • Binary I/O: fread() and fwrite()
    • C 언어의 자료형을 유지하면서, 변환없이 그대로 바이트 단위로 저장
    • 입출력은 Low-level I/O(read() and write())와 같이 바이트 단위로 수행되지만,
    • 자료형에 따라 데이터 개체 단위로 이용할 수 있도록 인터페이스가 다름
      • Low-level I/O: file, buf, size
      • High-level I/O: file, buf, size and number of data objects
    • 예) char[4] 형 자료의 쓰기
      • char name[4]; // sizeof(name) = 4
      • Low-level: write(fd, name, 4); // 4B 사이즈의 데이터
      • High-level: fwrite(name, 4, 1, fp); // 4B 사이즈의 데이터를 1개 쓰기
    • 구조체 데이터를 읽고 쓰기에 적합함
      • C에서는 연관있는 데이터를 구조체로 엮어서 사용하는 경우가 많고,
      • Binary I/O 는 Text I/O 보다 성능 및 용량 면에서 효율적으로 I/O가 가능함

image

예제

예제 1) fread(), fwrite()

 1#include <stdlib.h>
 2#include <stdio.h>
 3
 4int main(int argc, char* argv[]) {
 5        FILE *rfp, *wfp;
 6        char buf[80];
 7        int i=0, count;
 8
 9        if(argc != 3) {
10                printf("< Usage: ./file4 file_for_read file_for_write >\n");
11                return 1;
12        }
13        rfp = fopen(argv[1], "r");
14        if (rfp == NULL) {
15                perror("Open file for read");
16                exit(1);
17        }
18        wfp = fopen(argv[2], "w");
19        if (wfp == NULL) {
20                perror("Open file for write");
21                exit(1);
22        }
23        printf("%s and %s are opened! rfp = %p wfp = %p\n", argv[1], argv[2], rfp, wfp);
24
25        while((count = fread(buf, 1, 10, rfp)) > 0 ) {
26                printf("%d: count=%d\n", i++, count);
27                fwrite(buf, 1, count, wfp);
28        }
29        fclose(rfp);
30        fclose(wfp);
31        return 0;

예제 2) fseek(), ftell()

 1#include <unistd.h>
 2#include <stdlib.h>
 3#include <stdio.h>
 4
 5char buf[80];
 6int  count;
 7FILE* fp;
 8
 9void read_five_bytes() {
10        if ((count = fread(buf, 1, 5, fp)) <= 0) {
11                perror("Read Error");
12                exit(1);
13        }
14}
15
16int main(int argc, char* argv[]) {
17        if(argc != 2) {
18                printf ("< Usage: ./high2 filename >\n");
19                return 1;
20        }
21
22        fp = fopen(argv[1], "r");
23        if (fp == NULL) {
24                perror("Open");
25                exit(1);
26        }
27        printf("%s is opened! fp = %p\n", argv[1], fp);
28        read_five_bytes();
29        printf("\n%d: %s\n", count, buf);
30        printf("Current position: %ld\n", ftell(fp));
31
32        fseek(fp, 1, SEEK_SET);
33        read_five_bytes();
34        printf("\n%d: %s\n", count, buf);
35        printf("Current position: %ld\n", ftell(fp));
36
37        fseek(fp, 2, SEEK_SET);
38        read_five_bytes();
39        printf("\n%d: %s\n", count, buf);
40        printf("Current position: %ld\n", ftell(fp));
41
42        fclose(fp);
43
44        return 0;
45}

예제 3) fprintf()

 1#include <unistd.h>
 2#include <stdlib.h>
 3#include <stdio.h>
 4
 5int main(int argc, char* argv[]) {
 6        FILE *fp;
 7
 8        if(argc != 2) {
 9                printf("< Usage: ./high3 filename >\n");
10                return 1;
11        }
12          
13        fp = fopen(argv[1], "w"); 
14        if(fp == NULL) {
15                perror("Open");
16                exit(1);
17        }
18
19        fprintf(fp, "%s %d %d %d %2.2f\n", "2015123", 80, 90, 100, 90.00);
20        fprintf(fp, "%s %d %d %d %2.2f\n", "2016123", 50, 80, 60, 66.22);
21        fprintf(fp, "%s %d %d %d %2.2f\n", "2017123", 70, 20, 70, 55.55);
22
23        fclose(fp);
24        return 0;
25}    

예제 4) fscanf()

 1#include <unistd.h>
 2#include <stdlib.h>
 3#include <stdio.h>
 4
 5int main(int argc, char* argv[]) {
 6	FILE *fp;
 7	char id[10];
 8	int grade1, grade2, grade3;
 9	float avg;
10	if(argc != 2) {
11		printf("< Usage: ./high4 filename >\n");
12		return 1;
13	}
14
15	fp = fopen(argv[1], "r");
16	if(fp == NULL) {
17		perror("Open");
18		exit(1);
19	}
20	
21	fscanf(fp, "%s %d %d %d %f\n", id, &grade1, &grade2, &grade3, &avg);
22	fprintf(stdout, "%s %d %d %d %.3f\n", id, grade1, grade2, grade3, avg);
23
24	fscanf(fp, "%s %d %d %d %f\n", id, &grade1, &grade2, &grade3, &avg);
25	fprintf(stdout, "%s %d %d %d %.3f\n", id, grade1, grade2, grade3, avg);
26
27	fscanf(fp, "%s %d %d %d %f\n", id, &grade1, &grade2, &grade3, &avg);
28	fprintf(stdout, "%s %d %d %d %.3f\n", id, grade1, grade2, grade3, avg);
29
30	fclose(fp);
31	return 0;
32} 

예제 5) fscanf() with feof()

 1#include <unistd.h>
 2#include <stdlib.h>
 3#include <stdio.h>
 4
 5int main(int argc, char* argv[]) {
 6        FILE *fp;
 7        char id[10];
 8        int grade1, grade2, grade3;
 9        float avg;
10        if(argc != 2) {
11                printf("< Usage: ./high5 filename >\n");
12                return 1;
13        }
14
15        fp = fopen(argv[1], "r");
16        if(fp == NULL) {
17                perror("Open");
18                exit(1);
19        }
20
21        while(!feof(fp)) {
22                fscanf(fp, "%s %d %d %d %f\n", id, &grade1, &grade2, &grade3, &avg);
23                fprintf(stdout, "%s %d %d %d %.3f\n", id, grade1, grade2, grade3, avg);
24        }
25
26        fclose(fp);
27        return 0;
28}

예제 6-1) fgets() with feof()

 1#include <stdio.h>
 2#include <stdlib.h>
 3
 4#define MAX_BUF	80
 5
 6int main(int argc, char* argv[]) {
 7	FILE *fp;
 8	int line=0;
 9	char buf[MAX_BUF];
10
11	if(argc != 2) {
12		printf("< Usage: ./high6 filename >\n");
13		return 1;
14	}
15
16	if((fp = fopen(argv[1], "r")) == NULL) {
17		perror("Open");
18		exit(1);
19	}
20	
21	while(!feof(fp)) {
22		fgets(buf, MAX_BUF, fp);
23		fprintf(stdout, "%3d: %s", line++, buf);
24	}
25
26	fclose(fp);
27	return 0;
28}

예제 6-2) fgets()

 1#include <stdio.h>
 2#include <stdlib.h>
 3
 4#define MAX_BUF	80
 5
 6int main(int argc, char* argv[]) {
 7	FILE *fp;
 8	int line=0;
 9	char buf[MAX_BUF];
10
11	if(argc != 2) {
12		printf("< Usage: ./high6 filename >\n");
13		return 1;
14	}
15
16	if((fp = fopen(argv[1], "r")) == NULL) {
17		perror("Open");
18		exit(1);
19	}
20	
21	while(fgets(buf, MAX_BUF, fp) != NULL) {
22		fprintf(stdout, "%3d: %s", line++, buf);
23	}
24
25	fclose(fp);
26	return 0;
27} 

예제 7) 구조체 파일 쓰기

 1#include <stdio.h>
 2#include <stdlib.h>
 3
 4typedef struct {
 5        char id[10];
 6        int grade1, grade2, grade3;
 7        float avg;
 8} student;
 9
10int main(int argc, char* argv[]) {
11        FILE *fp;
12        int i;
13        student data[3];
14
15        if(argc != 2) {
16                printf("< Usage: ./high7 filename >\n");
17                return 1;
18        }
19
20        if((fp = fopen(argv[1], "w")) == NULL) {
21                perror("Open");
22                exit(1);
23        }
24
25        for (i=0; i<3; i++) {
26                if(fscanf(stdin, "%s %d %d %d", data[i].id, &data[i].grade1, &data[i].grade2, &data[i].grade3) > 0) {
27                        data[i].avg = (data[i].grade1+data[i].grade2+data[i].grade3)/3;
28                        fwrite(&data[i], sizeof(student), 1, fp);
29                }       else {
30                        break;
31                }
32        }
33
34        fclose(fp);
35        return 0;
36}

이러한 소스코드로 바이너리 파일을 생성한 뒤 vim 편집기로 :%!xxd로 hex 데이터로 확인하면 해당 값을 볼 수 있다. 이 때, 구조체 크기를 보면 원래는 10, 4, 4, 4,4 를 통해서 26이여야하나, 파일에서는 sizeof(student)는 28이다. 이 이유는 memory alignment로, 인텔 CPU는 데이터의 메모리 시작 주소를 항상 4의 배수가 되도록 정렬한다.

  • 이로 인해 10B 크기의 char id[10] 데이터가 저장된 다음, 2B 만큼의 padding space 가 생긴 것
  • 다음 int grade1 은 12B 위치부터 저장됨. 이후에는 모든 데이터가 memory aligned 된 위치이므로 padding 이 없음

댓글