책정리/혼자 연구하는 C,C++ 1

17장 파일 입출력

GONII 2015. 2. 22. 17:13

17.1 파일

17.1.1 정보의 저장

파일은 디스크에 정보가 저장되는 단위이며 고유의 이름을 가진다.

프로그램이 실행 중에 파일을 액세스 하는 경우가 많은데 디스크에 있는 파일을 읽거나 쓰고 관리하는 방법을 알아본다.

실행 파일의 크기에는 제약이 있기 때문에 모든 정보를 다 가질 수 없으며, 큰 정보는 외부의 파일에 두고 실행 중에 읽어서 사용하는 방법을 쓴다. (ex. 실행에 필요한 이미지, 사운드 파일)

프로그램이 작업 결과를 영구적으로 저장하기 위해서도 파일을 사용한다.

파일은 보조 기억장치에 기록되어 있다.

파일을 액세스 하는 방법에는 여러 가지 종류가 있다.

  1. 고수준 입출력 스트림 사용

    C 라이브러리가 제공하는 파일 입출력 방법이며 성능은 조금 떨어지지만 사용하기는 쉽다. 표준에 의해 삼수의 형태가 고정되어 있으므로 이식에 유리하다.

  2. 저수준 파일 핸들

    C 라이브러리가 제공하는 파일 입출력 방법이며 대규모의 데이터를 다룰 때 편리하다.

  3. C++의 스트림 객체

    ifstream, ofstream 등의 입출력 객체와 그 멤버 함수를 사용하여 파일을 액세스 한다.

  4. 운영체제가 제공하는 API 함수

    파일이 저장되는 디스크의 관리 주체는 운영체제이며 운영체제는 응용 프로그램을 위해 파일 관련 API 함수를 제공한다. 윈도우즈는 CreateFile, ReadFile, WriteFile 등의 함수를 제공하므로 이 함수를 사용하면 파일을 액세스 할 수 있다.

  5. 클래스 라이브러리가 제공하는 파일 액세스 객체 사용

    고수준 클래스 라이브러리들은 파일 액세스 기능을 캡슐화한 클래스를 제공하며 이 클래스를 사용하면 쉽게 파일을 다룰 수 있다. MFC의 경우 CFile 클래스를 제공한다.

17.1.2 C언어의 파일 지원

C언어가 제공하는 파일 입출력 방식은 고수준과 저수준 두 가지 이다.

고수준과 저수준을 나누는 분류 방법은 사람에게 얼마나 가까운가, 즉 쓰기 쉬운가를 기준으로 하는 것이지 기능의 좋고 나쁨을 의미하는 것은 아니다.

  

고수준

저수준

버퍼 사용

사용

메모리로 직접 읽어들임

입출력 대상

스트림

파일 핸들

속도

느리다.

빠르다.

문자 단위 입출력

가능

가능하지만 비효율적이다.

난이도

비교적 쉽다

조금 어렵다

세밀한 조작

어렵다.

가능하다.

두 방식의 가장 큰 차이점은 버퍼 사용 여부이다.

나머지 차이점은 버퍼의 사용 유무에 따라 파생되는 특성들이다.

버퍼는 파일로부터 입,출력되는 데이터를 잠시 저장하는 메모리 영역이다.

저수준과 고수준은 버퍼의 사용 유무에 따라 속도에 약간의 차이가 있지만 요즘은 저수준 파일 입출력이 큰 이점이 없는 셈이다.

17.2 고수준 파일 입출력

17.2.1 스트림

스트림(Stream)이라는 용어는 흐름을 의미한다.

바이트들이 순서대로 입,출력되는 논리적인 장치를 스트림이라고 한다.

파일에도 바이트들이 저장되어 있으며 읽을 때나 쓸 때 순서대로 바이트들이 입,출력되므로 스트림이라고 할 수 있다. 키보드, 화면, 프린터 등의 물리적인 장비들도 바이트들이 순서대로 흘러 다니므로 일종의 스트림이다.

대부분의 운영체제는 키보드와 화면(Console), 프린터 등을 스트림이라는 동질적인 장치로 다루며 파일과 같은 방법으로 입,출력한다.

스트림은 내부에 입출력 버퍼를 가지고 있으며 이 버퍼는 스트림에 의해 자동으로 관리되므로 프로그래머는 버퍼를 준비하거나 관리할 필요가 없다.

스트림의 현재 상태를 FILE 구조체에 기억된다. 이 구조체는 stdio.h에 정의되어 있다.

struct _iobuf

{

char *_ptr ;

int _cnt ;

char *_base ;

int _flag ;

int _file ;

int _charbuf ;

int _bufsiz ;

char *_tmpfname ;

} ;

typedef struct _iobuf FILE ;

스트림이 내부적으로 사용하는 버퍼

버퍼의 크기와 현재 위치

액세스 하고 있는 파일의 이름

등이 저장됨

   

파일 입출력을 하기 전에 이 구조체의 포인터를 하나 선언하고 입출력 함수에게 포인터만 넘겨주면 된다.

17.2.2 파일 열기

파일을 액세스 하려면 먼저 대상 파일을 열어야(Open) 한다.

파일을 오픈한다는 것은 파일 입출력을 하기 위한 준비를 한다는 뜻

   

파일 오픈 함수

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

   

  • 파일 이름

    액세스할 대상 파일의 이름이다.

    경로가 생략되고 파일 이름만 주어질 경우 현재 디렉토리에서 파일을 찾는다.

    경로 지정 : "C:\\data\\file.txt"

    도스, 윈도우 : 대소문자 구분 안함

    리눅스, 유닉스 : 대소문자 구분

  • 모드

    모드는 파일을 어떻게 열 것인지를 지정하며 파일을 열어서 어떤 작업을 할 것인가에 따라 진다.

모드

설명

r

읽기 전용으로 파일을 연다. 이 모드로 연 파일은 읽을 수만 있으며 데이터를 기록하지는 못한다. 만약 파일이 없을 경우 에러가 리턴된다.

w

쓰기 위해 파일을 연다. 이 모드로 연 파일은 쓰기만 가능하며 읽지는 못한다. 도스나 윈도우즈의 파일은 쓰기 전용 속성이 없지만 스트림은 쓰기 전용 상태로 열 수 있다. 파일이 없으면 새로 만들고 이미 존재한다면 기존의 파일은 지워진다.

a

추가를 위해 파일을 연다. 추가란 파일의 끝에 다른 정보를 더 써 넣는다는 뜻이다. 이 모드로 연 파일은 오픈 직후에 FP가 파일의 끝으로 이동한다. 파일이 없으면 새로 만든다.

r+

읽고 쓰기가 가능하도록 파일을 연다. 파일이 없을 경우 에러가 리턴된다.

w+

읽고 쓰기가 가능하도록 파일을 연다. 파일이 없을 경우 새로 만든다.

a+

읽기와 추가가 가능하도록 파일을 연다. 파일이 없으면 새로 만든다.

  • 파일 형태

    fopen의 두 번째 인수 mode에는 오픈 모드 외에도 파일의 형태를 지정하는 플래그를 추가로 지정할 수 있다.

    열고자 하는 파일이 텍스트 파일이면 t를 붙이고 이진 파일이면 b를 붙인다. 아무런 지정이 없으면 전역변수 _fnmode의 값이 사용된다.

       

    텍스트 파일 모드로 파일을 열면 다음 두 가지 변환을 한다.

  1. 개행 코드를 의미하는 CR/LF 조합은 LF로 변환되어 읽혀지며 LF를 기록하면 CR/LF가 출력된다. 이런 변환을 해 주는 이유는 C 문자열 출력 함수들은 개행을 위해 확장열 LF(\n)를 사용하기 때문이다.
  2. 파일의 끝을 나타내는 Ctrl+Z(0x1A)는 EOF(-1)로 변환되어 읽혀진다. 단 "a+" 모드로 열였을 때는 끝부분에 데이터를 추가할 수 있도록 Ctrl+Z를 제거한다.

       

    mode 인수의 예

    "rt" : 텍스트 파일을 읽기 전용으로 연다.

    "wb" : 이진 파일을 쓰기 전용으로 연다.

    "r+b" : 이진 파일을 읽기, 쓰기 가능하도록 연다. "rb+"로 쓸 수도 있다.

       

  • 리턴값

    fopen은 지정한 파일을 지정한 모드로 열고 입출력에 필요한 FILE 구조체를 생성한 후 그 포인터를 리턴한다. 에러가 발생하면 NULL을 리턴한다.

    여러가지 이유로 에러가 발생할 수 있으므로 이 함수의 리턴값은 반드시 점검해 보아야 함

       

    에러 발생 검사 코드

    FILE *f ;

    f = open("c:\\영화\\영구와땡칠이.avi", "rb") ;

    if ( f == NULL )

    {

    printf("파일이 안보임\n") ;

    }

       

    파일 닫기 함수

    int fclose(FILE *stream) ;

    다 사용한 파일은 이 함수로 반드시 닫아야 한다.

17.2.3 파일 액세스

파일을 열었으면 파일안의 내용을 읽고 쓴다.

모든 파일 입출력 함수는 대상 스트림을 전달하기 위해 FILE형의 구조체 포인터를 인수로 취한다는 점에서 공통적이다.

char *fgets(char *string, int n, FILE *stream) ;

int fputs(const char *string, FILE *stream) ;

   

fgets() : 파일에서 문자열을 읽어들이는 함수

읽어 들인 문자열을 저장할 버퍼를 첫 번째 인수로 주고 두 번째 인수로 이 버퍼의 크기를 알려준다. fgets는 최초의 개행 문자를 만날 때까지 또는 버퍼의 길이만큼 문자열을 읽어들이므로 이 함수를 반복적으로 호출하면 텍스트 파일을 줄 단위로 읽을 수 있다.

읽는 도중 에러가 발생했거나 파일 끝에 도달했으면 NULL을 리턴

   

  • 예제 fputs

#include <stdio.h>

   

void main ( void )

{

FILE *f ;

char *str = "이 파일은 c 표준 함수로 생성된 텍스트 파일입니다.\n"

"C드라이브의 루트 디렉토리에 test.txt라는 이름으로 생성됩니다.\n" ;

   

f = fopen ("c:\\text.txt","wt") ;

if ( f != NULL )

{

fputs (str, f) ;

fclose (f) ;

}

}

  • 예제 fgets

#include <stdio.h>

   

void main ( void )

{

FILE *f ;

char buf[256] ;

   

f = fopen("test.txt", "rt") ;

if ( f != NULL )

{

for ( ; ; )

{

if ( fgets(buf, 256, f) == NULL )

{

break ;

}

printf("%s", buf) ;

}

fclose (f) ;

}

}

   

feof 함수 : 인수로 주어진 스트림의 끝(EOF:End Of File)까지 읽었는지 조사한다.

int feof(FILE *stream) ;

이 함수가 TRUE를 리턴할 때까지 반복적으로 fgets 함수를 호출하면 파일의 끝까지 모든 내용을 읽을 수 있다.

   

문자 하나씩 입출력 하는 함수

int fgetc(FILE *stream) ;

int fputc(int c, FILE *stream) ;

   

블록 단위 입출력 함수

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) ;

대개의 경우 지정한 크기만큼 입출력하지만 파일의 끝 부분을 읽거나 디스크가 가득 찼을 때는 더 작은 크기만 입출력할 수도 있다.

이 두 함수를 사용하면 구조체의 배열이나 임의 타입의 집합을 한꺼번에 스트림으로 입출력 할 수 있다.

  • 예제 fread

#include <stdio.h>

   

void main ( void )

{

FILE *src, *dest ;

char buf[256] ;

size_t nRead ;

   

src = fopen ("test.txt", "rb") ;

if ( src != NULL )

{

dest = fopen("test2.txt", "wb") ;

if ( dest != NULL )

{

while (!feof(src))

{

nRead = fread(buf, 1, 256, src) ;

fwrite(buf, 1, nRead, dest) ;

}

fclose(dest) ;

}

fclose(src) ;

}

}

src에 복사할 원본을 읽기 모드(r)로 열고 dest는 쓰기 모드( w)로 연 후 src의 처음부터 256바이트씩 읽어 dest로 출력하기를 파일의 끝에 이를 때까지 반복

   

서식화된 스트림 입출력 함수

int fscanf(FILE *stream, const char *format [,argument ]...) ;

int fprintf(FILE *stream, const char *format [,argument ]...) ;

사용하는 방법은 scanf, printf와 동일하되 대상이 화면이나 키보드가 아니라 파일이라는 점만 다르다. 이 두 함수를 사용하면 정수나 실수 변수를 스트림으로 입출력 할 수 있다.

  • 예제 fprintf

#include <stdio.h>

   

void main ( void )

{

char str[128] = "string" ;

int i = 1234 ;

double d = 3.1416 ;

FILE *f ;

   

f = fopen("test.dat", "wb") ;

if ( f != NULL )

{

fprintf(f, "%d %f %s", i, d, str) ;

fclose(f) ;

}

   

i = 0 ;

d = 0.0 ;

f = fopen("test.dat", "rb") ;

if ( f != NULL )

{

fscanf(f, "%d %lf %s", &i, &d, &str) ;

printf("파일에서 읽은 정수값 = %d, 실수값 = %f, 문자열 = %s\n", i, d, str) ;

fclose(f) ;

}

}

17.2.4 임의 접금

스트림은 다음 입출력할 파일의 위치를 항상 기억하고 있는데 이 위치를 FP(File Position)라고 한다. 최초 파일을 열 때 FP는 선두를 가리키고 있으며 스트림에서 내용을 읽거나 쓸 때 FP는 액세스한 만큼 자동으로 뒤로 이동한다.

   

순차 접근(Suquential Access) : 처음부터 뒤쪽으로 순서대로 내용을 액세스 하는 것

임의 접근(Random Access) : 파일의 임의 위치로 이동하면서 원하는 내용을 읽는 방법

다음 액세스할 위치를 옮기고 싶을 때는 FP를 원하는 위치로 옮긴 후 액세스 함수를 호출하면 된다.

   

FP를 원하는 곳으로 옮기는 함수

int fseek(FILE *stream, long offset, int origin) ;

   

첫 번째 인수는 대상 스트림이며, 두번째 인수 offset은 FP를 어디로 옮길 것인가를 지정하며 세 번째 인수 origin은 어디를 기준으로 FP를 옮길 것인가를 지정

origin은 세가지 종류가 있다.

SEEK_SET : 스트림의 선두를 기준으로 FP를 이동

SEEK_CUR : 현재 위치

SEEK_END : 스트림의 끝을 기준

   

  • 예제 fseek

#include <stdio.h>

   

void main ( void )

{

FILE *f = NULL ;

char buf[256] ;

   

f = fopen("test.txt", "rt") ;

if ( f != NULL )

{

fseek(f, 24, SEEK_SET) ;

fgets(buf, 256, f) ;

printf("%s", buf) ;

fclose(f) ;

}

}

"생성된 텍스트 파일입니다." 출력

파일을 열었을 때 FP는 선두를 가리키고 있지만

fseek 함수로 선두 기준 24바이트 위치로 이동시켰기 때문에 fgets는 이 위치부터 개행 문자를 만날 때까지 읽어들임

   

현재 FP를 조사하거나 리셋하는 함수

long ftell(FILE *stream) ;

void rewind(FILE *stream) ;

   

ftell은 스트림의 현재 FP를 조사하는데 커서의 wherex, wherey 함수에 대응된다고 할 수 잇다.

rewind함수 : FP를 파일 선두로 보냄 == fseek(f, 0, SEEK_SET)

17.2.5 기정의 스트림

고수준 입출력 함수들은 스트림을 대상으로 입출력을 수행한다.

스트림이란 파일뿐만 아니라 키보드나 모니터처럼 바이트를 연속적으로 입출력하는 물리적인 장치까지를 포괄하는 개념이므로 고수준 입출력 함수로 이런 장치들을 다룰 수 있다.

키보드나 화면을 스트림으로 관리하고자 할 때는 미리 정의되어 있는 표준스트림을 사용

이름

설명

버퍼

stdin

표준 입력

사용

stdout

표준 출력

미사용

stderr

표준 에러

미사용

FILE *를 인수로 요구하는 함수에 표준 스트림을 대신 사용할 수 있으며 이 스트림들은 미리 정의되어 있기 때문에 별도로 오픈 할 필요없이 바로 사용할 수 있다. 사용한 후에 닫을 필요도 없다.

  • 예제 PredefinedStream

#include <stdio.h>

   

void main ( void )

{

char buf[256] ;

   

fputs("문자열을 입력해보시오 : ", stdout) ;

fgets(buf, 256, stdin) ;

fputs(buf, stderr) ;

}

fputs 함수는 스트림으로 문자열을 출력하는 함수인데 fopen으로 연 FILE 포인터 대신 표준 출력 스트림인 stdout을 주면 이 문자열이 화면으로 출력된다.

   

fgets함수의 마지막 인수로 stdin을 주면 키보드에서 문자열을 입력받아 buf에 채움 scanf, gets와 달리 버퍼의 길이를 지정할 수 있어 사용자가 버퍼 길이 이상을 입력하더라도 배열 범위를 넘어서지 않도록 한다.

   

stderr는 표준 출력 스트림인 stdout과 마찬가지로 화면을 가리키며 주로 에러 메시지를 출력하기 위해 사용한다.

   

표준 스트림 중에 stdin은 버퍼를 사용하기 대문에 stdin으로부터 입력을 받는 함수들은 키보드로부터 직접 입력을 받지 않고 버퍼에 입력되어 있는 값을 꺼내온다. 그러다 보니 미리 입력해 놓은 텍스틑가 다음 번 입력 함수가 호출될 때 읽혀지는 경우가 종종 있다.

  • 예제 fflush

#include <stdio.h>

#include <string.h>

   

void main ( void )

{

char buf[256] ;

   

for ( ; ; )

{

printf("문자열을 입력하시오(끝낼때 0) : ") ;

scanf("%s", buf) ;

   

if ( strcmp(buf,"0") == 0)

{

break ;

}

   

//< fflush(stdin) ;

printf("입력한 문자열은 \"%s\" 입니다.\n", buf) ;

}

}

aaa bbb ccc를 한꺼번에 입력하면 이상 동작을 한다.

scanf 함수는 문자열을 입력받을 때 공백으로 구분된 단어까지만 입력받고 나머지는 버퍼에 그대로 남겨두며 다음 번에 버퍼에 남아 있는 값을 읽어온다.

   

실시간으로 입력을 받아야 하는 프로그램의 경우 버퍼에 아직 읽지 않은 문자가 남아 있다면 입력 대기를 하지 않을 뿐만 아니라 버퍼의 문자를 바로 읽어 버리는 문제가 있다.

   

이때는 입력받은 버퍼에 남아 있는 불필요한 텍스트를 지운 후 다시 입력 받아야 한다.

   

버퍼에 남아있는 데이터 지우는 함수

int fflush(FILE *stream) ;

   

버퍼는 원래 운영체제가 관리하는 것이며 적당한 시간이 되면, 예를 들어 버퍼가 가득 차거나 스트림을 닫을 때 또는 한가할 때 백그라운드에서 비동기적으로 비워진다. 그러나 당장 사용해야 할 데이터라면 버퍼가 비워질 때까지 기다릴 수 없으며 즉시 버퍼를 비워야 하는데 이때 fflush 함수를 사용한다.

  • 예제 scanbuf

#include <stdio.h>

   

void main(void)

{

int i;

   

char ch;

   

scanf("%d", &i);

// fflush(stdin); // buffer에 남아있는 enter를 지운다

scanf("%c", &ch); // Enter가 문자로 읽혀져 버린다.

//gets(&ch); // gets는 문자열 입력을 완료하면서 버퍼의 enter를 지우는 특성을 가짐

   

printf("i = %d, ch = %c\n", i, ch);

}

17.2.6 정보의 저장

  • 예제 SaveRecord

#include <stdio.h>

#include <stdlib.h>

#include <conio.h>

#include <string.h>

#include <memory.h>

   

void WriteFriend();

void ReadFriend();

   

struct tag_friend

{

char name[10];

int age;

double height;

};

   

tag_friend Friends[256] =

{

{"ronaldo", 30, 182.2 },

{"messi", 30, 170.2 },

{"jason", 30, 178.2 },

{"cobi", 30, 123.2 },

{"", 0, 0 },

};

   

struct tag_header

{

char desc[32];

int ver;

int num;

};

   

int Num = 4;

   

void main(void)

{

int sel, i;

   

for(;;)

{

printf("선택(1:보기, 2:저장, 3:전부삭제, 4:읽기, 9:종료)" );

sel = getch();

   

switch(sel)

{

case '1' :

{

for ( i = 0 ; i < Num ; i++ )

{

printf("이름 : %s, 나이 : %d, 키 : %1f\n", Friends[i].name, Friends[i].age, Friends[i].height );

}

printf("\n출력완료");

}

break;

case '2' :

{

WriteFriend();

}

break;

case '3' :

{

memset(Friends, 0, sizeof(Friends));

Num = 0 ;

puts("\n\n 모든 레코드 삭제");

}

break;

case '4' :

{

ReadFriend();

}

break;

case '9' :

{

exit(0);

}

break;

}

}

}

   

// 파일 저장

void WriteFriend()

{

FILE *f;

tag_header h;

   

f = fopen("c:\\friend.data", "wb");

if( f == NULL )

{

printf("파일 생성할 수 업습니다.\n");

}

else

{

strcpy( h.desc, "친구목록");

h.ver = 100;

h.num = Num;

fwrite(&h, sizeof(tag_header), 1, f);

fwrite(Friends, sizeof(tag_friend), Num, f );

fclose(f);

printf("저장완료\n");

}

}

   

// 파일 불러오기

void ReadFriend()

{

FILE *f = NULL ;

tag_header h;

   

f = fopen("c:\\friend.data", "rb");

if( f == NULL )

{

printf("파일을 열 수 없습니다.\n");

}

else

{

memset(Friends, 0, sizeof(Friends));

fread(&h, sizeof(tag_header), 1, f );

   

if( strcmp( h.desc, "친구목록") != 0 )

{

puts("주소록 파일이 아닙니다.");

goto end;

}

   

if( h.ver != 100 )

{

puts("버전이 틀림");

goto end;

}

Num = h.num;

fread(Friends, sizeof(tag_friend), Num, f );

puts("파일 읽기 성공");

}

end:

if( f )

fclose(f);

}

  • 예제 SaveRecored2

#include <stdio.h>

#include <stdlib.h>

#include <conio.h>

#include <string.h>

#include <memory.h>

   

void WriteFriend();

void ReadFriend();

void DeleteAll();

   

struct tag_friend

{

char *name;

int age;

double height;

};

   

tag_friend Friends[256] =

{

{NULL, 30, 182.2 },

{NULL, 30, 170.2 },

{NULL, 30, 178.2 },

{NULL, 30, 123.2 },

{NULL, 0, 0 },

};

   

struct tag_header

{

char desc[32];

int ver;

int num;

};

   

int Num = 4;

   

void main(void)

{

int sel, i;

   

// 실행 중에 사용자로부터 입력 받았다고 가정한다.

Friends[0].name = (char*)malloc(7);

strcpy(Friends[0].name, "ㅂㅂㅂ");

Friends[1].name = (char*)malloc(7);

strcpy(Friends[1].name, "아ㅏㅇ");

Friends[2].name = (char*)malloc(7);

strcpy(Friends[2].name, "vlkcc");

Friends[3].name = (char*)malloc(7);

strcpy(Friends[3].name, "1234");

   

for(;;)

{

printf("선택(1:보기, 2:저장, 3:전부삭제, 4:읽기, 9:종료)" );

sel = getch();

   

switch(sel)

{

case '1' :

{

for ( i = 0 ; i < Num ; i++ )

{

printf("이름 : %s, 나이 : %d, 키 : %.1f\n", Friends[i].name, Friends[i].age, Friends[i].height );

}

printf("\n출력완료");

}

break;

case '2' :

{

WriteFriend();

}

break;

case '3' :

{

DeleteAll();

puts("\n\n 모든 레코드 삭제");

}

break;

case '4' :

{

ReadFriend();

}

break;

case '9' :

{

exit(0);

}

break;

}

}

   

DeleteAll();

}

   

// 사용중인 모든 메모리를 해제

void DeleteAll()

{

int i;

   

for ( i = 0 ; i < Num ; i++ )

{

if( Friends[i].name == NULL )

{

break;

}

free(Friends[i].name);                

}

memset(Friends, 0, sizeof(Friends));

Num = 0 ;

}

   

// 파일 저장

void WriteFriend()

{

FILE *f;

tag_header h;

int i, len;

   

f = fopen("c:\\friend.data", "wb");

if( f == NULL )

{

printf("파일 생성할 수 업습니다.\n");

}

else

{

strcpy( h.desc, "친구목록");

h.ver = 100;

h.num = Num;

fwrite(&h, sizeof(tag_header), 1, f);

// name의 길이, name, age, height

for ( i = 0 ; i < Num ; i++ )

{

len = strlen( Friends[i].name );

fwrite( &len, sizeof(int), 1, f );

fwrite( Friends[i].name, len, 1, f );

fwrite( &Friends[i].age, sizeof(int), 1, f );

fwrite( &Friends[i].height, sizeof(double), 1, f );

}

fclose(f);

printf("저장완료\n");

}

}

   

//

   

// 파이 불러오기

void ReadFriend()

{

FILE *f = NULL ;

tag_header h;

int i, len;

   

f = fopen("c:\\friend.data", "rb");

if( f == NULL )

{

printf("파일을 열 수 없습니다.\n");

}

else

{

// 사용중인 데이터를 먼저 지운다

DeleteAll();

fread(&h, sizeof(tag_header), 1, f );

   

if( strcmp( h.desc, "친구목록") != 0 )

{

puts("주소록 파일이 아닙니다.");

goto end;

}

   

if( h.ver != 100 )

{

puts("버전이 틀림");

goto end;

}

Num = h.num;

for( i = 0 ; i < Num ; i++ )

{

fread( &len, sizeof(int), 1, f );

Friends[i].name = (char*)calloc(len+1, 1 );

fread(Friends[i].name, len, 1, f);

fread(&Friends[i].age, sizeof(int), 1, f);

fread(&Friends[i].height, sizeof(double), 1, f);

}

puts("파일 읽기 성공");

}

end:

if( f )

fclose(f);

}

17.3 저수준 파일 입출력

17.3.1 파일 핸들

저수준 파일 입출력 방법은 운영체제(DOS, 윈도우즈)가 파일을 관리하는 방법과 동일하다.

고수준은 파일을 스트림이라는 논리적인 대상으로 취급할 수 있도록 하는데 비해 저수준은 파일을 핸들로 관리한다는 것이 특징이다. 스트림에 비해 핸들을 사용하는 방법이 조금 더 어렵지만 성능은 더 좋다.

저수준 파일 입출력 함수의 이름 앞에는 _문자가 있는데 _로 시작하는 함수들은 표준이 아니라 확장된 함수라는 뜻이다.

저수준 파일 입출력을 할 때는 먼저 대상 파일을 열어 핸들을 얻어야 한다. 핸들은 열려진 파일을 대표하는 값이며 핸들에는 파일을 액세스 하기 위한 모든 정보가 저장되어 있다.

  • 파일을 열고 닫을 때 사용하는 함수

    int _open( const char* finename, int oflag [,int pmode] );

    int _close( int fd );

    oflag 인수에는 파일을 어떤 모드로 열 것인지를 지정한다, OR 연산자로 여러 개의 플래그를 한꺼번에 지정할 수 있다.

플래그

설명

_O_BINARY

이진 모드로 연다

_O_TEXT

텍스트 모드로 연다

_O_CREAT

새로 파일을 만들며 파일이 이미 존재하면 아무 것도 하지 않는다

_O_RDONLY

읽기 전용으로 연다

_O_RDWR

읽기 쓰기가 가능하도록 연다

_O_WRONLY

쓰기 전용으로 연다

_O_TRUNC

파일을 열고 크기를 0으로 만든다. _O_CREAT와 함께 사용될 경우 새로 파일을 만든다.

_O_APPEND

FP를 파일 끝으로 보낸다

_O_RANDOM

캐시를 임의 접근 방식으로 최적화한다.

_O_SEQUENTIAL

캐시를 순차 접근 방식으로 최적화횐다

_O_SHORT_LIVED

_O_CREAT 플래그와 함께 사용되며 임시 파일을 만든다

_O_TEMPORARY

_O_CREAT 플래그와 함께 사용되며 파일을 닫을 때 삭제한다

_O_EXCL

_O_CREAT 플래그와 함께 사용되며 파일이 이미 존재할 경우 에러를 리턴한다

마지막 인수 pmode는 파일의 보안 속성을 지정하는데 _O_CREAT 플래그로 파일을 새로 만들 때만 적용된다. 나머지 플래그로 파일을 열 때는 이 인수를 생략한다.

파일을 읽기 가능하도록 하려면 _S_IREAD 플래그를 주고 쓰기 가능하도록 하려면 _S_IWRITE를 주며 두 플래그르 모두 다 줄 수 있따. _S_* 매크로 상수들은 sys/stat.h 헤더 파일에 정의되어 있으므로 이 매크로를 사용하려면 sys/stat.h를 인클루드해야 한다.

17.3.2 저수준 파일 액세스

저수준 파일을 읽고 쓸 때는 다음 두 함수를 사용한다. 두 함수 모두 첫 번째 인수로 파일 핸들을 요구하는데 _open으로 구한 핸들을 전달하면 된다.

int _read( int fd, void *buffer, unsigned int count );

int _write( int fd, const void *buffer, unsigned int count );

_read 함수는 파일로부터 count 바이트를 읽어 buffer에 채운다. buffer는 count 보다 크거나 최소한 같아야 하는데 통상 malloc으로 필요한 만큼 메모리를 할당한 후 사용한다.

파일에서 바이트를 읽은 후 FP는 읽은 만큼 뒤로 자동으로 이동한다. 만약 파일 끝이거나 파일 핸들이 무효하다면 -1이 리턴된다.

_write는 buffer에 저장된 count 바이트만큼을 파일로 출력한다.

  • 예제 readfile

#include <stdio.h>

#include <io.h>

#include <fcntl.h>

   

void main()

{

int file;

char buf[256] = {0, };

   

file = _open("c:\\test.txt", _O_RDONLY);

if( file != -1 )

{

_read(file, buf, 256 );

printf("%s", buf);

_close(file);

}

}

_open 함수가 사용하는 플래그들은 io.h, fcntl.h 파일에 정의되어 있으므로 이 헤더파일을 포함시켜야 한다.

  • 예제 cp

// 저수준 파일 입출력 함수를 사용하여 파일을 복사

// 도스의 copy 명령과 사용 방법이 동일하다

#include <stdio.h>

#include <stdlib.h>

#include <io.h>

#include <fcntl.h>

#include <sys/stat.h>

   

void main(int argc, char *argv[])

{

int src, dest;

int readnum;

void *buf;

   

if( argc < 3 )

{

printf("복사 원본과 목적 파일 이름을 지정해야합니다.\n");

exit(1);

}

   

src = open( argv[1], _O_RDONLY | _O_BINARY ) ;

if( src == -1 )

{

printf("원본 파일을 열 수 없습니다.\n");

exit(1);

}

   

dest = open(argv[2], _O_CREAT | _O_WRONLY | _O_BINARY | _O_TRUNC, _S_IWRITE );

if( dest == -1 )

{

printf("목적 파일을 생성할 수 없습니다.\n");

_close(src);

exit(1);

}

   

buf = malloc(60000);

   

for ( ;; )

{

readnum = read( src, buf, 60000 );

   

if( readnum == 0 )

{

break;

}

_write( dest, buf, readnum );

}

   

_close(src);

_close(dest);

free(buf);

printf("%s 파일을 %s로 복사했습니다.\n", argv[1], argv[2] );

}

저수준 입출력 함수도 고수준과 마찬가지로 다음 액세스 할 위치를 FP로 가리키는데 FP를 옮기면 임의의 위치를 액세스 할 수 있다. 다음 두 함수는 저수준 파일 입출력의 임의 접근 함수들이다.

long _lseek( int fd, long offset, int origin ) ;

long _tell( int fd );

17.4 파일 관리

17.4.1 기본적인 파일 관리

파일 관리 함수들은 저장된 데이터를 대상으로 하는 것이 아니라 파일 그 자체를 대상으로 한다

다음 함수는 파일 또는 디렉토리의 보안 허가 상태(Premission) 즉 쓰기가 가능한지, 읽기 전용인지 등을 조사하는데 주로 파일이 존재하는지를 조사하는 목적으로 많이 사용된다.

int _access(const char* path, int mode );

path에 조사할 파일의 경로를 주고 mode에 조사할 상태를 지정하는데 0은 존재, 2는 쓰기, 4는 읽기를 나타낸다. 요청한 허가 상태를 가지면 0을 리턴, 아니면 -1을 리턴한다.

   

다음 두 함수는 파일을 삭제한다.

int remove( const char *path );

int _unlink( const char *filename );

   

다음 함수는 파일의 속성을 변경한다.

int _chmod( const char *filename, int pmode );

대상 파일의 이름과 새로 지정할 속성을 지정하되 속성은 _S_IWRTIE, _S_IREAD 둘 중 하나를 주거나 둘 다 줄 수 있다.

17.4.2 파일 검색

파일을 검색하는 함수는 다음 세 함 수이다.

long _findfirst( char *filespec, struct _finddata_t *fileinfo );

int _findnext( long handle, struct _finddata_t *fileinfo );

int _findclose( long handle );

  • 예제 findfirst

#include <stdio.h>

#include <io.h>

   

void main()

{

_finddata_t fd;

long handle ;

int result = 1;

   

handle = _findfirst("c:\\*.*", &fd );

if ( handle == -1 ) return;

while( result != -1 )

{

printf("파일명 : %s, 크기 : %d\n", fd.name, fd.size );

result = _findnext(handle, &fd );

}

   

_findclose(handle);

}

17.4.3 디렉토리 관리

다음 세 함수는 가장 기본적인 디렉토리 관리 함수이다.

int _chdir( const char *dirname );

int _mkdir( cost char *dirname );

int _rmdir( const char *dirname );

_chdir함수는 현재 디렉토리를 변경한다. _mkdir은 디렉토리를 생성, _rmdir은 디렉토리를 제거하되 비어있지 않은 디렉토리는 삭제할 수 없다.

   

다음 함수는 현재 작업 디렉토리를 조사한다.

char * _getcwd( char *buffer, int maxlen );

   

다음 두 함수는 파일 경롤르 각각의 요소로 분리한다. 파일 경로는 드라이브, 디렉토리 파일명, 확장자로 구성되어 있는데 직접 문자열을 조사하는 것은 무척 번거로운 일이다.

경로 관리 함수는 파일 시스템의 이름 규칙대로 정확하게 경로 요소를 분리하고 합쳐 주므로 간편하게 쓸 수 있다.

void _splitpath( const char* path, char *drive, char *dir, char *fname, char *ext );

void _makepath( char* path, const hcar *drive, const char *dir, const char *fname, const char *ext );

  • 예제 splitpath

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

void main()

{

char path[_MAX_PATH];

char drive[_MAX_DRIVE];

char dir[_MAX_DIR];

char fname[_MAX_FNAME];

char ext[_MAX_EXT];

   

strcpy( path, "c:\\My Document\\test\\report 2.5.back");

_splitpath(path, drive, dir, fname, ext);

printf("파일명 = %s\n", fname);

}

  • 예제 ChangeExt

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

   

void changeExt( char *path, char *newext)

{

char drive[_MAX_DRIVE];

char dir[_MAX_DIR];

char fname[_MAX_FNAME];

char ext[_MAX_EXT];

   

_splitpath( path, drive, dir, fname, ext );

sprintf( path, "%s%s%s%s", drive, dir, fname, newext );

}

void main()

{

char doc[_MAX_PATH] = "c:\\My Doc\\Diary\\2005년 6월.txt";

   

changeExt( doc, ".bak");

printf("백업 파일 = %s\n", doc );

}

17.4.4 디스크 관리

다음 두 함수는 작업 드라이브를 조사하거나 변경한다. 드라이브는 번호로 표현되는데 1은 A, 2는 B, 3은 C순이다.

int _getdrive(void);

int _chdrive(int drive);

   

다음 함수는 디으크의 총 용량 및 남은 용량을 조사한다.

unsigned _getdiskfree(unsigned drive, struct _diskfree_t *driveinfo );

조사하고자 하는 드라이브의 번호를 주면 이 드라이브의 용량에 대한 정보를 다음 구조체에 채운다.

struct _diskfree_t {

unsigned total_clusters;

unsigned avail_clusters;

unsigned sectors_per_cluster;

unsigned bytes_per_sector;

};

  • 예제 diskfree

#include <stdio.h>

#include <windows.h>

#include <dos.h>

   

void main()

{

_diskfree_t df;

int bytes;

int total, avail;

   

_getdiskfree( 3, &df );

bytes = df.sectors_per_cluster * df.bytes_per_sector ;

total = MulDiv(df.total_clusters, bytes, 1048576);

avail = MulDiv(df.avail_clusters, bytes, 1048576);

printf("총 용량 : %dM, 사용한 용량 = %dM, 남은 용량 = %dM, 클러스터당 바이트 = %dB.\n", total, total-avail, avail, bytes );

}

대용량 하드 디스크를 바이트 단위로 조사하면 정수형의 범위를 넘어 버리기 때문에 메가바이트 단위로 값을 출력하도록 했다.

MulDiv는 Win32 API 함수인데 대단히 큰 수를 곱한 후 나누기를 할 때 내부적으로 64비트 계산하여 자리 넘침에 의한 오차를 제거해준다.

반응형

'책정리 > 혼자 연구하는 C,C++ 1' 카테고리의 다른 글

19장 자료구조  (0) 2015.02.25
18장 C 고급 문법  (0) 2015.02.22
16장 함수 고급  (0) 2015.02.20
15장 포인터 고급  (0) 2015.02.19
13장 구조체  (0) 2015.02.19