728x90

시스템 프로그래밍 3주차는 2주차의 내용에 연장으로 표준 입출력에 대해 공부한다.

 

  • Standard IO

Standard IO는 platform에 독립적인, user-buffering solution이다.

 

File pointer

File operation을 관리하는 구조체를 가리키는 포인터

내부적으로 file descriptor와 연동됨

 

Stream

프로그램과 file을 연결한 통로

 

파일 IO에 대한 일의 순서는

File open -> File access -> File close로

파일을 열고 사용을 한 후 파일을 닫는 순서이다.

 

  • File open & close

Opening a file/stream

#include <stdio.h>

FILE *fopen(const char *path, const char *mode);

-path(file path)

열려는 파일의 경로

-mode(file open mode)

파일 열기 모드

return: file pointer (NULL: error)

 

파일 열기 모드(mode)

  모드 파일이 존재하지 않을 때 파일이 존재 할 때
r 읽기 열기 실패 열기 성공
w 쓰기 파일이 생성 덮어 씀(기존 내용은 지워 짐)
a 덧붙이기 파일이 생성 기존 파일 뒤에 기록
r+ 읽기+ 모드 / 쓰기 모드로 전환 가능 읽기 <-> 쓰기 모드 전환 시,
반드시 fflush(), fseek(), fstepos(), rewind() 중 하나를 호출해야 함
w+ 쓰기+ 모드/  읽기 모드로 전환 가능
a+ 덧붙이기+ / 읽기 모드로 전환 가능
b 이진 파일 모드 이진 파일 형식으로 파일을 연다
읽기/쓰기 모드는 위의 지정자들로 지정

이진 파일은 사람이 읽을 수 있는 문자로 채워진 Ascii 파일과 다르게 이진 데이터가 직접 저장된 파일을 말한다.

 

Closing a files/streams

#include <stdio.h>

int fclose(FILE *stream);

-stream

닫으려는 stream

return: 0(-1: error)

 

열고 닫는 방법을 배웠으니 바로 한 번 해보도록 하자.

#include <stdio.h>
#include <stdlib.h>

int main(void){
    FILE *fp;

    if ((fp = fopen("hello.txt", "w")) == NULL){
        perror("fopen: hello.txt");
        exit(1);
    }

    fclose(fp);

    return 0;
}

딱 파일만 열고 바로 닫는 예제이다.

실행을 해보면 w 모드이기 때문에 

없던 hello.txt 파일이 생성되는 것을 볼 수 있다.

 

  • File read & write

파일을 열고 닫는 방법을 공부했으니 이제 순서의 중간인 파일의 사용에 대해 공부해보자

 

Character-based

Chararcter-based reading

#include <stdio.h>

int fgetc(FILE *stream);
int getc(FILE *stream); //macro 형태로 구현되어 있어, 인자에서 계산은 X
int getchar(void); //getc(stdin), 표준입력에서 입력을 받음

-stream

File operation을 수행할 stream

return : 읽은 문자 (-1: error)

 

파일로부터 문자 하나씩 읽는 방법이다.

 

Character-based writing

#include <stdio.h>

int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c); //putc(c, stdout)

-stream

File operation을 수행할 stream

-c

쓰려는 문자

return: 기록한 문자(-1:error)

 

Characted-based로 실습을 해보자

#include <stdlib.h>
#include <stdio.h>

int main(void){
    FILE *rfp, *wfp;
    int c;

    if((rfp = fopen("hello.txt", "r")) == NULL){
        perror("fopen: hello.txt");
        exit(1);
    }

    if((wfp = fopen("hello.out", "w")) == NULL){
        perror("fopen: hello.out");
        exit(1);
    }

    while((c = fgetc(rfp)) != EOF){ //하나씩 계속 읽어서 hello.out에 작성
        fputc(c, wfp);
    }

    fclose(rfp);
    fclose(wfp);

    return 0;
}

이렇게 파일을 작성하면 hello.txt에서 문자를 하나씩 읽어서 hello.out에 저장하게 된다.

String-based

String-based reading

#include <stdio.h>

char *gets(char *s); //get from stdin
char fgets(char *s, int n, FILE *stream);

-s

읽은 문자열을 저장할 buffer

-n

buffer의 크기

-stream

File operation을 수행할 stream

return: Buffer의 시작 주소(NULL: error)

 

String-based writing

#include <stdio.h>

int puts(char *s); //put to stdout
int fputs(char *s, FILE *stream);

-s

기록할 문자열을 저장한 buffer

-stream

File operation을 수행할 stream

return: 양수(음수: error)

 

문자열 단위로 실습을 해보자

#include <stdio.h>
#include <stdlib.h>

int main(void){
    FILE *rfp, *wfp;
    char buf[BUFSIZ];
    printf("BUFSIZ = %d\n", BUFSIZ);

    if((rfp = fopen("hello.txt", "r")) == NULL){
        perror("fopen: hello.txt");
        exit(1);
    }

    if((wfp = fopen("hello.out", "a")) == NULL){
        perror("fopen: hello.txt");
        exit(1);
    }

    while(fgets(buf, BUFSIZ, rfp) != NULL) fputs(buf, wfp);

    fclose(rfp);
    fclose(wfp);

    return 0;
}

문자열 단위로 읽어와서 hello.out에 저장한다.

이렇게 문자열 단위로 잘 가져오는 것을 볼 수 있다.

 

Binary IO

#include <stdio.h>

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

-ptr

Pointer to buffer

-size

size of an item

-nmemb

number of items to read/write

-stream

File operation을 수행 할 stream

return: read/write 한 item의 수(EOF: 파일의 끝)

 

이진 파일을 작성하는 예제이다.

#include <stdio.h>
#include <stdlib.h>

int main(void){
    char *fileName = "binary.bin";
    int data[5] = {10, 20, 30, 40, 50};
    FILE *fp;

    if(!(fp = fopen(fileName, "wb"))){
        fprintf(stderr, "Fail to open the file - %s\n", fileName);
        exit(1);
    }

    size_t i = fwrite(data, sizeof(int), 5, fp);
    printf("Success to write %d objects.\n", i);

    fclose(fp);
    return 0;
}

그러고 cat으로 파일을 확인해보면 이진파일이기 때문에 알 수 없는 내용이 출력된다.

해당 이진파일의 내용을 그대로 보고 싶다면

xxd binary.bin을 사용하면 된다.

이제 이진파일을 읽어보자

#include <stdio.h>
#include <stdlib.h>

int main(void){
    int buf[5] = {0};
    FILE *fp = fopen("binary.bin", "rb");

    if(!fp){
        fprintf(stderr, "Fail to open the file - %s\n", "binary bin");
        exit(1);
    }

    size_t i = fread(buf, sizeof(int), 5, fp);
    printf("Success to read %d object\n", i);
    for(int i = 0; i < 5; i++) printf("%d ", buf[i]);

    fclose(fp);
    return 0;
}

이렇게 이진파일을 정수로 읽어오는 것을 볼 수 있다.

이진 파일은 사람이 바로 읽을 수 없지만 동일한 데이터를 저장하는 데 Ascii 파일보다 적은 공간을 요구한다는 장점이 있다.

 

Formatted IO

#include <stdio.h>

int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);

int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);

-format

입출력 형식

-stream

File operation을 수행 할 stream

return: 입출력 한 문자의 수(음수: error)

 

자세히 볼 필요도 없을만큼 많이 사용한 함수들이다.

바로 작성을 하는 예제이다.

#include <stdio.h>
#include <stdlib.h>

typedef struct{
    int ID;
    char name[8];
    float score;
}Student;

int fileOpen(FILE **fp, char *fileName, char *mode){
    *fp = fopen(fileName, mode);
    if(!*fp){
        printf("Fail to open - %s\n", fileName);
        return -1;
    }
    return 0;
}

int main(void){
    Student info = {0};
    char *fileName = "StudentList.txt";
    FILE *fp = NULL;

    if(fileOpen(&fp, fileName, "a") < 0) exit(1);

    while(1){
        printf("Enter Id Name Score (Exit: -1): ");
        scanf("%d", &info.ID);
        if(info.ID < 0) break;
        scanf("%s %f", &info.name, &info.score); getchar();
        fprintf(fp, "%d %s %.1f\n", info.ID, info.name, info.score);
    }

    fclose(fp);

    return 0;
}

-1을 입력할 때까지 입력을 받고 StudentList.txt에 작성을 하는 예제이다.

이렇게 StudentList.txt를 만들어 그곳에 작성을 하는 것을 볼 수 있다.

 

이제 이 파일을 읽어보자

#include <stdio.h>
#include <stdlib.h>

typedef struct{
    int ID;
    char name[8];
    float score;
}Student;

int fileOpen(FILE **fp, char *fileName, char *mode){
    *fp = fopen(fileName, mode);
    if(!*fp){
        printf("Fail to open - %s\n", fileName);
        return -1;
    }
    return 0;
}

int main(void){
    Student info = {0};
    char *fileName = "StudentList.txt";
    FILE *fp = NULL;
    
    if(fileOpen(&fp, fileName, "r") < 0) exit(1);

    int numStudent = 0;
    float sum = 0;
    while(!feof(fp)){
        fscanf(fp, "%d %s %f\n", &info.ID, &info.name, &info.score);
        sum += info.score;
        numStudent++;
    }

    printf("%d students, Average = %2.f\n", numStudent, sum / numStudent);

    fclose(fp);
}

해당 파일을 잘 읽는 것을 볼 수 있다.

 

Synchronizing with the disk

#include <stdio.h>

int fflush(FILE *stream);

-stream

File operation을 수행 할 stream

return: 0(-1:error)

 

stream에 있는 내용들을 바로 디스크에 작성하는 방법

 

  • File offset & File pointer

Handling file offset

#include <stdio.h>

int fseek(FILE *stream, long offset, int whence);
int ftell(FILE *stream);
void rewind(FILE *stream);
int fsetpos(FILE *stream, const fpost_t *pos);
int fgetpos(FILE *stream, fpost_t *pos);

-stream

File operation을 수행 할 stream

-offset

이동시킬 byte 수(양수, 음수 모두 가능)

-whence

기준 위치(SEEK_SET, SEEK_CUR, SEEK_END)

-pos

offset을 저장 할 fpost_t 주소

 

File Pointer <-> File Descriptor

#include <stdio.h>

FILE *fdopen(int fd, const char *mode);

-fd

file descriptor

-mode

파일 열기 모드

return: File pointer(NULL: error)

 

#include <stdio.h>

int fileno(FILE *stream);

-stream

변환 할 stream

return: File descriptor(-1: error)

 

서로 pointer와 descriptor간의 변환하는 함수이다.

+ Recent posts