* 디스크 파일의 사용
 대부분의 프로그램에서는 데이터나 도는 사용 환경을 저장하는 등 여러 가지 목적으로 디스크 파이를 사용한다. 오늘은 다음과 같은 내용을 다룰 것이다.

·디스크 파일에 관련된 스트림
·C에서 사용되는 두 가지 형태의 디스크 파일
·파일에 데이터를 저장하는 방법
·파일에서 데이터를 읽어들이는 방법
·파일을 닫는 방법
·디스크 파일의 관리
·임시 파일의 사용

1. 스트림과 디스크 파일
: C는 디스크 파일을 포함하여 모든 입력과 출력을 스트림으로 수행한다. 앞에서는 키보드,화면, 그리고 DOS 시스템에서 프린터와 같이 특정 장치와 연결되어 있는 C의 미리 정의된 스트림을 사용하는 방법을 다루었다. 디스크 파일 스트림은 기본적으로 동일한 방법으로 사용된다. 이것은 스트림을 통한 입출력의 한 가지 장점이다. 한 가지 스트림을 사용하는 방법은 다른 스트림에서 변화가 없거나 약간만 다르게 해서 사용할 수 있다. 디스크 파일 스트림에서 중요한 차이점이 있다면, 프로그램이 특정 디스크 파일과 관련된 스트림을 반드시 생성해야 한다는 것이다.

2. 디스크 파일의 종류
: 14번째 강의에서 C의 스트림에 텍스트(text)와 이진(binary)의 두 가지 종류가 있다는 것을 설명했다. 이런 두 가지 형태의 스트림 중에서 어떤 것도 파일과 관련될 수 있는데, 파일을 적절한 모드로 사용하기 위해서는 각각의 특성을 이해할 필요가 있다.

텍스트 스트림(text stream)은 텍스트 모드 파일과 관련되어 있다. 텍스트 모드 파일은 일련의 문장들로 구성된다. 각각의 문장은 문자로 구성되고 문장의 마지막을 나타내는 하나 이상의 문자를 포함한다. 문장의 최대 길이는 255자이다. '문장'은 C의 문자열과 다르다는 것을 기억할 필요가 있다. 문장에는 널 문자(\0)가 포함되지 않는다. 텍스트 모드의 스트림을 사용할 때 C의 문장 진행 문자(\n)와 운영체제가 디스크 파일에서 문장의 마지막을 표시하기 위해서 사용하는 문자 사이에는 변환이 수행된다. DOS 환경에서는 개행 문자(CR : carriage return)와 다음 줄 문자(LF : line feed)로 변환된다. 데이터가 텍스트 모드의 파일에 저장될 때 각각의 \n은 CR-LF로 변환된다. 데이터가 디스크 파일에서 읽어들여질 때 DR-LF는 다시 \n으로 변환된다. UNIX 환경에서는 어떤 변환도 수행되지 않으므로 문장 진행 문자가 변경되지 않고 그대로 남는다.

이진 스트림(binary stream)은 이진 모드 파일과 관련되어 있다. 모든 데이터는 있는 그대로 저장되거나 읽어들여지고, 문장 내에서 구분이나 문장의 마지막을 표시하는 문자는 사용되지 않는다. NULL과 문장의 마지막을 표시하는 문자는 특별한 의미를 가지지 않으며 다른 어떤 데이터와 똑같이 취급된다.

어떤 파일 입출력 함수는 한 가지 파일 모드에서만 사용될 수 있고, 다른 어떤 함수는 두 가지 모드에서 사용될 수 있다. 이 장에서는 어떤 함수를 어떤 모드에서 사용해야 하는지 알려줄 것이다.

3. 파일 이름
: 모든 디스크 파일은 이름을 가지고 있고 디스크 파일을 다룰 때에는 반드시 파일 이름을 사용해야 한다. 파일 이름은 다른 텍스트 데이터와 마찬가지로 문자열에 저장된다. 파일 이름을 위해 적용되거나 제한되는 규칙은 운영체제마다 다른다. DOS와 윈도우 3.x에서 파일 이름은 1 ~ 8자까지의 이름, 선택적으로 사용되는 마침표(.), 3자까지의 확장자로 구성된다. 반면에, 윈도우 95(98)와 NT, 대부분의 UNIX 시스템에서는 256자까지의 파일 이름을 사용할 수 있다.

운영체제마다 다른 것으로는 파일명에 허용되는 문자도 포함된다. 예를 들어, 윈도우 95에서는 다음 문자들이 허용되지 않는다.

   / \ : * ? " < > |

여러분은 사용중인 운영체제에 따라 파일명 규칙을 지키고 주의해야 한다. 또한, C 프로그램에서의 파일명은 경로 정보를 가질 수 있다. 경로(path)는 파일이 위치된 드라이브와 디렉토리(또는 폴더)를 가리킨다. 만약 경로 없이 파일명을 지정하면 프로그램은 파일이 운영체제가 현재 기본적으로 사용중인 디렉토리에 있다고 가정할 것이다. 파일명의 일부분으로 경로 정보를 항상 지정하는 것은 좋은 프로그래밍 습관이다. PC에서 백슬래시 문자(\)는 경로의 디렉토리 이름을 구분하는 데 사용된다. 예를 들어, DOS와 윈도우에서 다음은

   c:\data\list.txt

드라이브 C의 디렉토리 \DATA에 저장되어 있는 LIST.TXT라는 이름의 파일을 뜻한다. 백슬래시 문자가 문자열에서 사용될 때에는 C에 대해서 특수한 의미를 가진다는 것을 알고 있을 것이다. 그래서 백슬래시 문자 자체를 표현하려면 하나의 백슬래시를 추가해야 한다. 앞에 나타난 파일 이름은 C 프로그램에서 다음과 같이 표현될 것이다.

   char *filename = "c:\\daa\\list.txt;

키보드에서 파일 이름을 입력한다면 하나의 백슬래시만을 사용할 수 있다. 모든 시스템에서 백슬래시가 디렉토리 구분자로 사용되는 것은 아니다. 예를 들어, UNIX에서 는 일반적인 슬래시(/)를 사용한다.

4. 파일 열기
: 디스크 파일에 관련된 스트림을 생성하는 과정을 파일 열기(opening)라고 한다. 파일을 열게 되면 파일에서 프로그램으로 데이터를 읽어들이는 읽기(reading) 동작이나 프로그램에서 파일로 데이터를 저장하는 쓰기(writing) 동작, 또는 두 가지 모두가 가능하게 된다. 파일의 사용을 마치게 되면 닫아야 한다. 파일을 닫는 것에 대해서는 이 장에서 나중에 설명할 것이다. 파일을 열기 위해서는 라이브러리 함수 fopen()을 사용한다. fopen()의 원형은 STDIO.H에 나타나 있으며 다음과 같다.

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

이 원형은 fopen()이 STDIO.H에 선언된 구조체인 FILE형에 대한 포인터를 돌려준다는 사실을 알려준다. FILE 구조체의 멤버는 프로그램에서 여러 가지 파일 관리를 수행하기 위해 사용되지만, 여기에서는 자세한 내용을 알 필요가 없다. 그러나 열기 원하는 각각의 파일에 대해서는 FILE형에 대한 포인터를 선언해야 한다. fopen()을 호출하면 함수는 FILE 구조체형 변수를 생성하고, 생성된 구조체에 대한 포인터를 돌려준다. 파일을 사용하는 이후의 모든 동작에서는 이 포인터가 사용된다. 만약 fopen() 함수에서 문제가 발생하면 NULL을 돌려준다. 예를 들어, 이런 실패는 하드웨어 에러나 또는 초기화되지 않은 디스크에서 파일을 열려고 할 때 발생할 수 있다.

인수 filename은 열리는 파일의 이름이다. 앞에서도 언급했듯이 filename에는 경로 정보가 포함될 수 있고, 포함되는 것이 좋다. 인수 filename은 큰 따옴표 내에 포함된 일반적인 문자열이나 도는 문자열 변수에 대한 포인터가 될 수 있다. 인수 mode는 열릴 파일의 사용 모드를 지정하는 것이다. mode는 이진(binary)모드나 텍스트(text) 모드, 그리고 읽기(reading)나 쓰기(writing), 또는 두 가지 모두 중에서 어떤 상태로 열려야 하는지 제어한다. mode에 사용할 수 있는 값은 <표 16.1>에 나타나 있다.

<표 16.1> fopen() 함수의 mode의 값

모드

의미

r

 읽기 상태로 파일을 연다. 지정된 이름의 파일이 존재하지 않으면 fopen()은

 NULL을 돌려준다.

w

 쓰기 상태로 파일을 연다. 지정된 이름의 파일이 존재하지 않으면 생성된다.

 지정된 이름의 파일이 존재하면 경고 없이 삭제되고 새롭고 비어 있는 파일

 이 생성된다.

a

 데이터 추가 상태로 파일을 연다. 지정된 이름의 파일이 존재하지 않으면

 생성된다. 파일이 이미 존재한다면 새로운 데이터는 파일의 마지막에

 추가된다.

r++

 읽기와 쓰기 상태로 파일을 연다. 지정된 이름의 파일이 존재하지 않으면

 생성된다. 파일이 이미 존재한다면 새로운 데이터는 이전의 데이터를

 덮어쓰며 파일의 시작 부분에 위치된다.

w+

 읽기와 쓰기 상태로 파일을 연다. 지정된 이름의 파일이 존재하지 않으면

 생성된다. 파일이 이미 존재한다면 덮어써진다.

a+

 읽기와 데이터 추가 상태로 파일을 연다. 지정된 이름의 파일이 존재하지

 않으면 생성된다. 파일이 이미 존재한다면 새로운 데이터는 파일의 마지막에

 추가된다.

기본적으로 설정되어 있는 파일의 모드는 텍스트(text)이다. 파일을 이진 모드로 열기 위해서는 인수 mode에 b를 추가해야 한다. 인수 mode에 a를 포함시키면 텍스트 모드의 파일을 데이터 추가가 가능한 상태로 열어주고, ab를 포함시키면 이진 모드의 파일을 데이터 추가가 가능한 상태로 열어줄 것이다. 만약 에러가 발생하면 fopen()은 NULL을 돌려준다는 사실을 기억하자. 다음과 같은 경우에는 NULL의 복귀값을 얻게 될 것이다.

·유효하지 않은 파일 이름을 사용한 경우

·준비되지 않은 디스크에서 파일을 열려고 할 때. 예를 들어, 드라이브가 닫히지 않았거나 디스크가 초기화되어 있지 않을 때

·존재하지 않는 디렉토리나 디스크 드라이브의 파일을 열려고 할 때

·존재하지 않는 파일을 'r' 모드로 열려고 할 때

fopen()을 사용할 때에는 에러가 발생했는지 확인할 필요가 있다. 구체적으로 어떤 에러가 발생했는지 정확하게 알 수 있는 방법은 없지만, 적절한 메시지를 출력하고 다시 파일을 열도록 해주거나 프로그램을 종료할 수 있다. 대부분의 C 컴파일러는 에러의 상태에 대한 정보를 알 수 있게 해주는 ANSI 비 호환 확장 기능을 포함하고 있다.

<리스트 16.1> 다양한 모드로 디스크 파일을 여는 fopen()의 사용

 /* fopen() 함수의 사용 예 */


 #include <stdio.h>


 main()

 {

    FILE *fp;

    char ch, filename[40], mode[4];


    while(1)

    {


       /* 파일명과 모드 입력 */


       printf("\nEnter a filename: ");

       gets(filename);

       printf("\nEnter a mode (max 3 characters): ");

       gets(mode);


       /* 파일 열기를 시도함 */


       if((fp = fopen(filename, mode)) != NULL)

       {

          printf("\nSuccessful opening %s in mode %s.\n,

                 filename, mode);

          fclose(fp);

          puts("Enter x to exit, any other to continue.");

          if((ch = getc(stdin)) == 'x')

             break;

          else

             continue;

       }

       else

       {

          fprintf(stderr, "\nError opening file %s in mode %s.\n",

                  filename, mode);

          puts("Enter x to exit, any other to try again.");

          if((ch = getc(stdin) == 'x')

             break;

          else

             continue;

       }

    }

 }

-> 입력 / 출력

  Enter a filename : junk.txt

  Enter a mode (max 3 characters): w

  Successful opening junk.txt in mode w.
  Enter x to exit, any other to continue.
  j

  Enter a filename: morejunk.txt

  Enter a mode (max 3 characters): r

  Error opening morejunk.txt in mode r.
  Enter x to exit, any other to try again.
  x

5. 파일에 데이터 기록하고 읽어들이기
: 디스크 파일을 사용하는 프로그램에서는 파일에 데이터를 기록하거나 파일에서 데이터를 읽어들이고 또는 두 가지를 모두 수행할 수 있다. 디스크 파일에 데이터를 저장하기 위해서 는 세가지 방법을 사용할 수 있다.

·형식화된 데이터를 파일에 저장하기 위해서 형식화된 출력을 사용할 수 있다. 형식화된 출력은 텍스트 모드의 파일에서만 사용해야 한다. 형식화된 출력의 기본적인 용도는 스프레드시트나 데이터베이스 등의 다른 프로그램에서 사용되는 텍스트와 숫자 데이터를 가지는 파일을 생성하는 것이다. 또한, 드물기는 하지만 C 프로그램에서 사용할 파일을 생성하기 위해서 형식화된 출력을 사용한다.

·한 문자나 문장을 파일에 저장하기 위해서 문자 출력을 수행할 수 있다. 기술적으로 이진 모드의 파일에 문자 출력을 수행하는 것은 불가능하지만 특수한 방법으로는 가능할 수도 있다. 문자 출력은 텍스트 파일에만 제한하여 사용해야 한다. 문자 출력은 문서 작성기와 같은 프로그램뿐 아니라 C 에서도 사용할 수 있는 형식으로, 숫자가 아닌 텍스트 데이터를 저장하기 위해서 주로 사용된다.

·메모리의 일부분에 저장된 내용을 디스크 파일에 저장하기 위해서 직접 출력을 사용할 수 있다. 이 방법은 이진 파일에서만 사용된다. 직접 출력은 나중에 C 프로그램에서 사용하기 위한 데이터를 저장하는 가장 좋은 방법이다. 파일에서 데이터를 읽어들이는 경우에도, 앞에서 설명한 것과 마찬가지로 형식화된 입력, 문자 입력, 또는 직접 입력의 세 가지 방법을 사용할 수 있다. 파일을 읽어들이는 경우에 사용하는 방법은 대개 파일의 특성에 따라 다르다. 일반적으로, 여러분은 파일을 저장했을 때와 같은 방법으로 데이터를 읽어들이게 되지만 반드시 지켜야 하는 규칙은 아니다. 그러나 파일을 저장했을 때와 다른 모드로 파일을 읽어들이려면 C와 파일 형식에 대한 충분한 지식이 필요하다.

앞에서 파일 입력과 출력의 세 가지 형태를 잘 이해했다면 각각의 형태가 어떤 경우에 가장 적합한지 알 수 있을 것이다. 그러나 이런 구분은 결코 사용을 제한하기 위한 것이 아니다. C 언어의 한 가지 장점은 융통성이므로, 숙련된 프로그래머는 파일 출력의 형태를 대부분 필요성에 맞추러 사용할 수 있다. 초보적인 프로그래머는 경우에는 다음에서 설명할 내용을 참고로 해서 프로그래밍에 적용할 수 있을 것이다.

5.1 형식화된 파일 입력과 출력
: 형식화된 파일 입/출력은 특정 방법으로 형식화된 텍스트와 숫자 데이터를 다룬다. 이것을 14번째 강의에서 설명한 printf()와 scanf() 함수를 통해서 키보드 입력과 화면 출력을 형식화하는 것에 비유할 수 있다. 우선, 형식화된 출력에 대해서 설명한 후에 입력을 다룰 것이다.

▶ 형식화된 파일 출력
: 형식화된 파일 출력은 라이브러리 함수 fprintf()를 통해서 수행된다. fprintf()의 원형은 헤더 파일 STDIO.H에 정의되어 있으며, 다음과 같다.

  int fprintf(FILE *fp, char *fmt, ...);

첫 번째 인수는 FILE형에 대한 포인터이다. 데이터를 특정 디스크 파일에 기록하기 위해서는 fopen()을 사용하여 파일을 열었을 때 구해지는 포인터를 전달해야 한다. 두 번째 인수는 형식화 문자열이다. 형식화 문자열에 대한 내용은 14번째 강의에서 printf()를 설명할 때 다루었다. fprintf()에서 사용되는 형식화 문자열은 printf()에서 사용되는 것과 똑같은 규칙을 따른다. 상세한 내용은 14번깨 강의를 참조하기 바람.....^^;; 마지막 인수는 말줄임표(...)이다. 이것은 무슨 뜻일까? 함수 원형에서 사용되는 말줄임표(...)는 변칙적인 개수의 인수를 뜻한다. 즉, fprintf()는 파일 포인터와 형식화 문자열을 인수로 가지며, 추가로 필요한 만큼 많은 인수를 받아들일 수 있다. 이것은 printf()와 비슷하다. 추가로 사용되는 인수는 지정된 스트림으로 출력되는 변수의 이름이다. fprintf()는 인수 목록에서 지정된 스트림으로 출력 내용을 전달한다는 것을 제외하면 printf() 와 비슷하게 동작한다는 것을 기억하기 바란다. 사실, fprintf()에서 stdout을 스트림 인수로 지정한다면 fprintf()는 printf()와 동일하다. <리스트 16.2>에 있는 프로그램은 fprintf()를 사용하고 있다.

<리스트 16.2> fprintf()가 파일과 stdout으로 동일한 내용의 형식화된 출력을 수행한다는 것을 보여주는 예

 /* fprintf() 함수의 사용 예 */


 #include <stdio.h>

 #include <stdlib.h>

 void clear_kb(void);


 main()

 {

    FILE *fp;

    float data[5];

    int count;

    char filename[20];


    puts("Enter 5 floating point numerical values.");


    for(count = 0; count < 5; count++)

       scanf("%f", &data[count]);


    /* 파일명을 구하고 파일을 연다. */

    /* 우선 stdin에서 나머지 문자를 지운다. */


    clear_kb();


    puts("Enter a name for the file.");

    gets(filename);


    if((fp = fopen(filename, "w")) == NULL)

    {

       fprintf(stderr, "Error opening file %s.", filename);

       exit(1);

    }


    /* 숫자 데이터를 파일과 stdout으로 기록한다. */


    for(count = 0; count < 5; count++)

    {

       fprintf(fp, "\ndata[%d] = %f", count, data[count]);

       fprintf(stdout, "\ndata[%d] = %f", count, data[count]);

    }

    fclose(fp);

    printf("\n");

    return(0);

 }


 void clear_kb(void)

 /* stdin에서 나머지 문자를 지운다. */

 {

    char junk[80];

    gets(junk);

 }


▶ 형식화된 파일 입력
: 형식화된 파일 입력에서는 입력 동작이 stdin 대신에 지정된 스트림을 통해서 수행된다는 것을 제외하면 scanf()와 비슷하게 동작하는 라이브러리 함수 fscanf()를 사용한다. scanf()는 14번째 강의에서 설명했다. fcsanf()의 원형은 다음과 같다.

   int fscanf(FILE *fp, const char *fmt, ...);

인수 fp는 fopen()이 돌려주는 FILE형에 대한 포인터이고, fmt는 fscanf()가 입력을 받아들이 는 방법을 지정하는 형식화 문자열에 대한 포인터이다. 형식화 문자열의 구성 요소는 scanf()에서 사용되는 것과 동일하다. 마지막으로, 말줄임표(...)는 fscanf()가 입력된 값을 할당하는 변수의 주소인 하나 이상의 추가적인 인수를 뜻한다. fscanf()를 사용하기 전에 14번째 강의에서 설명한 내용을 한 번 더 읽어볼 필요가 있을 것이다. 함수 fscanf()는 stdin 대신에 지정된 스트림에서 문자를 읽어들인다는 것을 제외하면 scanf()와 똑같은 방법으로 사용된다. fscanf()를 사용해보기 위해서는 함수가 읽어들일 수 있도록 형식화된 약간의 숫자나 문자열 이 저장된 텍스트 파일이 필요하다. 에디터를  사용하여 공백(빈칸이나 문장 진행 문자)으로 구분되는 5개의 부동 소수형 숫자를 가지는 INPUT.TXT라는 이름의 파일을 생성하자. 예를 들어, 다음과 같은 파일을 생성할 수 있다.

   123.45      87.001
   100.02
   0.00456     1.0005

이제 <리스트 16.3>에 있는 프로그램을 컴파일하고 실행하자.]

<리스트 16.3> 디스크파일에서 형식화된 데이터를 읽어들이기 위해 fscanf()를 사용하는 예제

 /* fscanf()로 형식화된 파일 데이터 읽어들이기 */

 #include <stdlib.h>

 #include <stdio.h>


 main()

 {

    float f1, f2, f3, f4, f5;

    FILE *fp;


    if((fp = fopen("INPUT.TXT", "r")) == NULL)

    {

       fprintf(stderr, "Error opening file.");

       exit(1);

    }


    fscanf(fp, "%f %f %f %f %f", &f1, &f2, &f3, &f4, &f5);

    printf("The values are %f, %f, %f, %f, and %f.", f1, f2, f3, f4, f5);


    fclose(fp);

    return(0);

 }

5.2 문자 입력과 출력
: 디스크 파일에서 수행되는 문자 입출력(character I/O)은 한 문자뿐 아니라 문자들로 구성되는 문장을 대상으로 한다. 문장은 문장 진행(newline) 문자로 종료되는 일련의 문자들이라는 것을 기억하자. 문자 입출력은 텍스트 모드파일에서 수행해야 한다. 문자 입출력 함수에 대한 설명과 예제 프로그램을 살펴보도록 하자.
 
▶ 문자 입력
: 한 문자를 읽어들이기 위한 getc(), fgetc()와 문장을 읽어들이기 위한 fgets()의 세 가지 문자 입력 함수가 있다.

▶ getc()와 fgetc() 함수
: 함수 getc()와 fgetc()는 동일하므로 원하는 함수를 사용하면 된다. 두 함수는 지정된 스트림에서 한 문자를 읽어들인다. getc()의 원형은 STDIO.H에 정의되어 있다.

   int getc(FILE *fp);

인수 fp는 파일이 열릴 때 fopen()이 돌려주는 포인터이다. 함수는 입력된 문자를 돌려주거나 또는 에러가 발생하면 EOF를 돌려준다.

여러분은 키보드에서 문자를 입력하기 위해 앞의 프로그램에서 getc()를 사용했다. 이 함수는 C의 스트림의 융툥성을 확인할 수 있는 또다른 예이다. 키보드나 파일 입력을 위해 이 함수를 사용할 수 있기 때문이다. 만약 getc()와 fgetc()가 한 문자를 돌려준다면 원형에 왜 int형의 복귀값을 가지는 것일까? 파일을 읽을 때 파일의 마지막을 표시하는 문자를 읽을 필요가 있는데, 시스템에 따라서 이 문자가 char형이 아닌 int형이 될 수 있으므로 int형의 복귀값을 가지는 것이다. <리스트 16.10>에서 getc()를 사용하는 예를 볼 것이다.

▶ fgets() 함수
: 파일에서 문장을 읽어들이기 위해서는 fgets() 라이브러리 함수를 사용하자. 원형은 다음과 같다.

   char *fgets(char *str, int n, FILE *fp);

인수 str은 입력 내용이 저장되는 버퍼에 대한 포인터이고, n은 입력되는 문자의 최대 개수이며, fp는 파일이 열릴 때 fopen()이 돌려주는 FILE형에 대한 포인터이다.

fgets() 함수를 호출하면 fp에서 문자를 읽어들이고 str이 지적하는 메모리 영역에 읽어들인 문자를 저장한다. 함수는 문장 진행 문자가 나타나거나 또는 n-1자를 읽어들일 때까지 계속해서 문자를 읽어들인다. n의 값을 str에 할당된 메모리의 바이트 수와 같은 값으로 설정하면 입력 내용이 할당된 영역을 벗어나서 메모리를 겹쳐쓰지 않도록 방지할 수 있다. n-1은 fgets()가 문자열의 마지막에 추가하는 널 종료 문자(\0)을 위한 공간은 제외한 값이다. 입력이 성공적으로 끝나면 fgets()는 str을 돌려준다. 또한, 다음과 같이 두 가지 경우에 NULL값을 돌려주고 에러가 발생한다.

·str에 어떤 문자를 할당하기 전에 읽기 에러가 발생하거나 EOF 문자가 나타나면 함수는 NULL을 돌려주고, str이 지적하는 메모리의 내용은 변경되지 않는다.

·str에 하나 이상의 문자를 할당한 후에 읽기 에러가 발생하거나 EOF 문자가 나타나면 함수는 NULL을 돌려주고, str이 지적하는 메모리에는 쓸모없는 데이터가 저장된다. 여기서 fgets() 함수가 반드시 한 줄의 문장을 읽어들이지는 않는다는 것을 알 수 있을 것이다. 즉, 함수는 문장 진행 문자가 나타날 때까지 계속해서 문자를 읽어들이지 않는다. 만약 문장 진행 문자가 나타나기 전에 n-1자를 읽어들였다면 fgets()는 동작을 중단한다. 프로그램은 다시 입력 동작을 수행할 때 마지막으로 입력 동작을 중단한 곳에서부터 시작한다. fgets()가 문장 진행문자에서 중단할 때까지 전체 문자열을 읽어들이기 위해서는 입력 버퍼의 크기는 물론이고 fgets()에 전달되는 n의 값을 충분한 크기로 설정하자.

▶ 문자 출력
: 문자 출력 함수에는 putc()와 fputs()가 있다.
 
▶ putc() 함수
: 라이브러리 함수 putc()는 지정된 스트림에 한 문자를 출력한다. 함수의 원형은 STDIO.H에 정의되어 있으며, 다음과 같다.

  int putc(int ch, FILE *fp);

인수 ch는 출력되는 문자이다. 다른 문자 함수에서와 마찬가지로 이 함수에서도 int형이 사용되고 있지만 실제로는 하위 바이트만 사용된다. 인수 fp는 파일에 대한 포인터이다. 즉, 파일을 열 때 fopen()이 돌려주는 포인터이다. 함수 putc()의 동작이 성공적이었다면 출력된 문자를 돌려주고 에러가 발생한 경우에는 EOF를 돌려준다. 기호 상수 EOf는 STDIO.H에 정의되어 있으며 -1의 값을 가진다. 실제로 '어떤' 문자도 -1이라는 값을 가지고 있지 않으므로 텍스트 모드의 파일에서는 에러를 표현하는 문자로 EOF를 사용할 수 있다.

▶ fputs() 함수
: 지정된 스트림에 문장을 출력하기 위해서는 라이브러리 함수 fputs()를 사용하지. 이 함수는 14번째 강의에서 다루어진 puts()와 같은 방법으로 사용된다. 유일한 차이는 fputs()를 사용할 경우 출력 스트림을 지정할 수 있다는 것이다. 또한, fputs()는 문자열의 마지막에 문장 진행 문자를 추가하지 않는다. 원한다면 문장 진행 문자를 직접 포함시키도록 하자. STDIO.H에 정의되어 있는 함수의 원형은 다음과 같다.

   char fputs(char *str, FILE *fp);

인수 str은 스트림으로 출력되고 널 문자로 종료되는 문자열에 대한 포인터이고, fp는 파일을 열 때 fopen()이 돌려주는 FILE형에 대한 포인터이다. str이 지적하는 문자열은 마지막의 \0을 제거한 상태로 파일에 기록된다. 함수 fputs()의 동작이 성공적이라면 음수가 아닌 값을 돌려주고, 에러가 발생하면 EOF를 돌려준다.

5.3 직접 파일 입력과 출력
: 현재 사용 중인 C 프로그램이나 또는 다른 어떤 C 프로그램에서 나중에 사용하기 위한 데이터를 저장할 때에는 직접 파일 입출력을 가장 많이 사용한다. 직접 입출력은 이진 모드의 파일에서만 사용된다. 직접 출력을 수행할 때에는 데이터가 블록 단위로 메모리에서 디스크 파일로 저장된다. 직접 입력의 경우에는 이와 반대로 블록 단위의 데이터를 디스크 파일에서 메모리로 읽어들인다. 예를 들어, 직접 출력 함수를 한 번 호출하여 double형의 배열 전체를 디스크에 저장할 수 있고, 직접 입력 함수를 한 번 호출하여 다시 디스크에서 메모리로 전체 배열을 읽어들일 수 있다. 직접 입출력 함수는 fread()와 fwrite()이다.

▶ fwrite() 함수
: 라이브러리 함수 fwrite()는 메모리의 데이터를 블록 단위로 이진 모드의 파일에 기록한다. STDIO.H에 정의되어 있는 함수의 원형은 다음과 같다.

  int fwrite(void *buf, int size, int count, FILE *fp);

인수 buf는 파일에 기록할 데이터가 저장되어 있는 메모리 영역에 대한 포인터이다. 포인터의 형은 void이므로 어떤 데이터형에 대한 포인터가 될 수 있다. 인수 size는 개별적인 데이터 항목의 크기를 바이트 단위로 지정하는 것이고, count는 기록할 항목의 수를 지정한다. 예를 들어, 100개의 요소를 가지는 정수형 배열을 저장하기 원한다면 각각의 int형은 2바이트를 차지하므로 size는 2가 될 것이고, 배열은 100개의 요소를 가지므로 count는 100이 될 것이다. size 인수를 계산하기 위해서 sizeof() 연산자를 사용할 수 있다. 인수 fp는 물론 파일을 열 때 fopen()이 돌려주는 FILE형에 대한 포인터이다. gwrite() 함수의 동작이 성공적이면 기록한 항목의 개수를 돌려준다. 만약 함수가 돌려주는 값이 count보다 작다면 어던 에러가 발생했다는 것을 알 수 있다. 에러를 확인하기 위해서는 대개 다음과 같이 fwrite()를 사용한다.

  if((fwrite(buf, size, count, fp) != count)
      fprintf(stderr, "Error writing to file.");

fwrite()를 사용하는 몇 가지 예를 살펴보자. 하나의 double형 변수 X를 파일에 기록하기 위해서 다음과 같이 한다.

  fwrite(&x, sizeof(double), 1, fp);

50개의 address형 구조체를 가지는 배열 data[]를 파일에 기록하기 위해서는 두 가지 방법을 사용할 수 있다.

  fwrite(data, sizeof(address), 50, fp);
  fwrite(data, sizeof(data), 1, fp);

첫 번째 방법에서는 address형의 크기를 가지는 50개의 요소가 배열 포함되어 있는 것으로 계산하여 배열을 저장한다. 두 번째 방법에서는 배열을 하나의 '요소'로 취급한다. 두 가지 방법의 실행 결과는 동일하다. 다음 단원에서는 fread()를 설명하고 나서 fread()와 fwrite()를 사용하는 프로그램을 분석할 것이다.

▶ fread() 함수
: fread() 라이브러리 함수는 이진 모드의 파일에서 블록 단위의 데이터를 메모리로 읽어들인 다. STDIO.H에 정의되어 있는 함수의 원형은 다음과 같다.

  int fread(void *buf, int size, int count, FILE *fp);

인수 buf는 파일에서 읽어들인 데이터를 저장할 메모리 영역에 대한 포인터이다. fwrite()에서와 마찬가지로 포인터형은 void이다. 인수 size는 읽어들일 개별적인 데이터 항목의 크기를 바이트 단위로 지정하는 것이고, count는 읽어들일 항목의 개수를 지정한다. 이런 인수들이 fwrite()에서 사용되는 인수들과 같다는 것에 주목하지 바란다. 여기에서도 size 인수를 계산하기 위해서 sizeof() 연산자를 자주 사용한다. 인수 fp는 항상 그렇듯이 파일을 열 때 fopen()이 돌려주는 FILE형에 대한 포인터이다. fread() 함수는 읽어들인 항목의 개수를 돌려준다. 그러나 파일의 마지막에 도달하거나 또는 에러가 발생한다면 이 값은 count보다 작을 수 있다. <리스트 16.4>에 있는 프로그램은 fwrite()와 fread()의 사용 예를 보여준다.

<리스트 16.4> 직접 파일 입출력을 위한 fwrite()와 fread()의 사용

 /* fwrite()와 fread()를 사용한 직접 파일 입출력 */

 #include <stdlib.h>

 #include <stdio.h>


 #define SIZE 20


 main(0

 {

    int count, array1[SIZE], array2[SIZE];

    FILE *fp;


    /* array1[] 초기화 */


    for(count = 0; count < SIZE; count++)

       array1[SIZE] = 2 * count;


    /* 이진 모드 파일 열기 */


    if((fp = fopen("direct.txt", "wb")) == NULL)

    {

       fprintf(stderr, "Error opening file.");

       exit(1);

    }

    /* array1[]을 파일에 저장 */


    if(fwrite(array1, sizeof(int), SIZE, fp) != SIZE)

    {

       fprintf(stderr, "Error writing to file.");

       exit(1);

    }


    fclose(fp);


    /* 같은 파일을 이진 모드 읽기 상태로 연다. */


    if((fp = fopen("direct.txt", "rb")) == NULL)

    {

       fprintf(stderr, "Error epening file.");

       exit(1);

    }


    /* 데이터를 array2[]로 읽어들인다. */


    if(fread(array2, sizeof(int), SIZE, fp) != SIZE)

    {

       fprintf(stderr, "Error reading file.");

       exit(1);

    }


    fclose(fp);


    /* 두 배열이 같다는 것을 보여주기 위해 출력 */


    for(count = 0; count < SIZE; count++)

       printf("%d\t%d\n", array1[count], array2[count]);

    return(0);

 }

6. 파일 버퍼링 : 파일 닫기와 플러시
: 파일의 사용을 마칠 때에는 fclose() 함수로 파일을 닫아야 한다. 이 장에 나타난 프로그램 에서는 이미 fclose()를 사용했었다. 함수의 원형은 다음과 같다.

   int fclose(FILE *fp);

인수 fp는 스트림에 관련된 FILE형 포인터이다. fclose()의 동작이 성공적으로 수행되면 함수는 0을 돌려주고 에러가 발생하면 -1을 돌려준다. 파일을 닫을 때에는 파일 버퍼가 플러시(flush)된다. 즉, 파일에 기록된다. 또한, fcloseall() 함수를 사용하여 표준으로 정의되어 있는 stdin, stdout, stdprn, stderr, stdaux를 제외하고 열려 있는 모드 스트림을 닫을 수도 있다. 이 함수의 원형은 다음과 같다.

   int fcloseall(void);

이 함수는 모든 스트림의 버퍼를 플러시하고 나서 닫힌 스트림의 개수를 돌려준다.

main()의 마지막에 도달하거나 또는 exit() 함수를 실행하여 프로그램을 마칠 때에는 모든 스트림이 자동으로 플러시되고 닫힌다. 그러나 프로그래머가 스트림을 직접 닫는 것이 좋다. 특히, 디스크 파일과 관련된 스트림에서는 파일의 사용이 끝나는 즉시 닫아야 한다. 이것은 스트림 버퍼와 관련된 문제이다.

 디스크 파일에 관련된 스트림을 생성할 때에는 자동으로 버퍼가 생성되고 스트림과 연결된다 버퍼(buffer)는 파일에 기록되거나 파일에서 읽어들여지는 데이터를 임시로 저장하기 위해서 사용되는 메모리 영역이다. 버퍼는 디스크 드라이브가 블록 단위를 기본으로 하는 장치이므 로 필요하다. 이런 장치들은 데이터를 블록 단위로 읽어들이거나 기록할 때 효과적이므로 블록 단위 장치라고 한다. 이상적인 블록의 크기는 사용중인 하드웨어에 따라 다르다. 대개 수백에서 수천 바이트 사이의 크기이다. 그러나 정확한 블록의 크기에 대해서는 신경 쓸 필요가 없다.

 파일 스트림과 관련된 버퍼는 문자를 기본으로 하는 스트림과 블록을 기본으로 하는 디스크 장치 간의 중간 매체 역할을 한다. 프로그램에서 데이터를 스트림으로 기록할 때 데이터는 버퍼가 가득찰 대가지 저장되었다가 블록 단위로 디스크에 기록된다. 이것은 디스크 파일에서 데이터를 읽어들이는 경우에도 적용된다. 버퍼의 생성과 사용은 모두 운영체제에 의해서 자동으로 처리된다. 프로그래머가 신경 쓸 일은 없다. C는 버퍼를 관리하기 위한 몇 가지 함수를 제공하지만 자세한 내용은 생략한다. 그래서 실제로는 이런 버퍼의 동작이 이론과 다르게 이루어진다. 프로그램이 실행되는 동안 디스크에 '저장할' 데이터가 디스크에 기록되는 것이 아니라 버퍼 내에 존재한다는 뜻이다. 만약 전원이 차단되거나 다른 어떤 문제가 발생하여 프로그램이 '중단'되면 버퍼 내의 데이터는 사라질 것이고, 디스크 파일에 저장된 것을 정확히 파악할 수 없게 된다.

 파일을 닫지 않고 스트림에 관련된 버퍼를 플러시(flush)하기 위해서는 fflush()나 flushall() 라이브러리 함수를 사용한다. 파일을 계속해서 사용하는 동안 버퍼를 디스크에 기록하기 원한다면 fflush()를 사용하자. 열려 있는 모든 스트림의 버퍼를 플러시 하기 위해서는 flushall()을 사용하자. 두 함수의 원형은 다음과 같다.

   int fflush(FILE *fp);
   int flushall(void);

 인수 fp는 파일을 열 때 fopen()이 돌려주는 FILE 포인터이다. 만약 쓰기 가능한 상태로 파일을 열었다면 fflush()는 버퍼를 디스크에 저장한다. 그러나 읽기 가능한 상태로 파일을 열었다면 버퍼는 제거된다. 함수 fflush()의 동작이 성공적으로 수행되면 0을 돌려주고, 에러가 발생하면 EOF를 돌려준다. 함수 flushall()은 열린 스트림의 개수를 돌려준다.

7. 파일의 순차적인 사용과 무작위 사용
 : 열려 있는 모든 파일은 관련된 파일 위치 표시(file position indicator)를 가지고 있다. 위치 표시는 파일에서 읽기와 쓰기 동작이 수행되는 위치를 가리킨다. 위치는 항상 파일의 시작을 기준으로 해서 바이트 단위로 표현된다. 새로운 파일을 열 때 위치 표시는 항상 파일의 시작 부분인 위치 0을 가리킨다. 새로운 파일의 길이는 0이므로 다른 곳을 지적할 수 없다. 이미 존재하는 파일을 열 때 파일이 추가 가능한 상태로 열리면 위치 표시는 파일의 마지막을 지적하고, 파일이 다른 어떤 모드로 열리면 파일의 시작 부분을 지적한다.

 여기서의 범위를 벗어나는 내용이기는 하지만, 이 장의 앞 부분에서 설명한 파일 입출력 함수는 위치 표시를 사용한다. 쓰기와 읽기 동작을 현재의 위치 표시에서 수행하고 나서 위치 표시를 변경하는 것이다. 예를 들어, 읽기 가능한 상태로 파일을 열고 10바이트를 읽어 들이면 위치 0부터 9가지에 해당하는 10바이트를 파일의 처음부터 읽어들이는 것이다. 읽기 동작을 수행하고 나면 위치 표시는 위치 10을 지적하게 되고 다음 읽기 동작은 위치 10부터 시작된다. 그래서 파일의 모든 데이터를 순차적으로(sequentially) 읽어들이거나 또는 파일에 데이터를 순차적으로 기록하는 경우에는 스트림 입출력 함수가 자동으로 다루어 주므로 위치 표시에 대해서 신경 쓸 필요가 없다. 그러나 파일을 다른 방법으로 사용할 필요가 있을 때에는 위치 표시의 값을 결정하거나 변경하게 해주는 C 라이브러리 함수를 사용해야 한다. 위치 표시의 값을 제어하면 파일을 무작위(random) 상태로 사용할 수 있다. 여기서 '무작위'라는 것은 앞 부분의 모든 데이터를 읽어들이거나 또는 앞 부분에 기록하지 않고 파일에서 임의의 위치에 있는 데이터를 읽어들이거나 데이터를 기록할 수 있다는 것을 뜻한다.

7.1 ftell()과 rewind() 함수
 : 위치 표시가 파일의 시작 부분을 지적하도록 설정하기 위해서 라이브러리 함수 rewind()를 사용하자. STDIO.H에 정의되어 있는 함수의 원형은 다음과 같다.

    void rewind(FILE *fp);

인수 fp는 스트림과 관련된 FILE 포인터이다. rewind()를 호출한 후에 파일의 위치 표시는 파일의 시작 부분인 바이트 0을 지적하게 된다. 파일에서 약간의 데이터를 일어들인 후에 파일을 닫고나서 다시 열지 않고도 파일의 시작부터 읽어들이기 원한다면 rewind()를 사용함. 파일의 위치 표시 값을 설정하기 위해서는 ftell()을 사용하자. STDIO.H에 정의되어 있는 이 함수의 원형은 다음과 같다.

   long ftell(FILE *fp);

인수 fp는 파일을 열 때 fopen()이 돌려주는 FILE 포인터이다. 함수 ftell()은 파일의 시작부터 현재 파일의 위치까지를 바이트 단위로 나타내는 long형 값을 돌려준다. 첫 번째 바이트는 위치 0이다. 만약 에러가 발생하면 ftell()은 -1의 long형인 -1L을 돌려준다. rewind()와 ftell()의 동작을 이해하기 위해서 <리스트 16.5>에 있는 프로그램을 살펴보자.

<리스트 16.5> ftell()과 rewind()의 사용

 /* ftell()과 rewind()의 사용 예 */

 #include <stdlib.h>

 #include <stdio.h>


 #define VUFLEN 6


 char msg[] = "abcdefghijklmnopqrstuvwxyz";


 main()

 {

    FILE *fp;

    char buf[BUFLEN];


    if((fp = fopen("TEXT.TXT", "w")) == NULL)

    {

       fprintf(stderr, "Error opening file.");

       exit(1);

    }


    if(fputs(msg, fp) == EOF)

    {

       fprintf(stderr, "Error writing to file.");

       exit(1);

    }


    fclose(fp);


    /* 읽기 상태로 파일을 연다 */


    if((fp = fopen("TEXT.TXT", "r")) == NULL)

    {

       fprintf(stderr, "Error opening file.");

       exit(1);

    }

    printf("\nImmediately after opening, position = %ld", ftell(fp));


    /* 5 문자를 읽어들인다. */


    fgets(buf, BUFLEN, fp);

    printf("\nAfter reading in %s, position = %ld", buf, ftell(fp));


    /* 다음 5문자를 읽어들인다. */


    fgets(buf, BUFLEN, fp);

    printf("\n\nThe next 5 characters are %s, and position now = %ld",

           buf, ftell(fp));


    /* 스트림을 시작 부분으로 설정한다. */


    rewind(fp);


    printf("\n\nAfter rewinding, the position is back at %ld",

           ftell(fp));


    /* 5 문자를 읽어들인다. */


    fgets(buf, BUFLEN, fp);

    printf("\nand reading starts at the beginning again: %s", buf);

    fclose(fp);

    return(0);

 }  

7.2 fseek() 함수
: 스트림의 위치 표시(position indicator)를 더욱 정확하게 제어하기 위해서는 라이브러리 함수 fseek()를 사용할 수 있다. fseek()를 사용하면 위치 표시가 파일 내의 임의의 위치를 지적하도록 설정할 수 있다. STDIO.H에 정의되어 있는 함수 원형은 다음과 같다.

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

인수 fp는 파일과 관련된 FILE 포인터이다. 위치 표시가 이동되는 거리는 offset에 바이트 단위로 지정된다. 인수 origin은 이동이 시작되는 위치를 지정한다. origin에 사용할 수 있는 기호 상수는 IO.H에 정의되어 있는데, <표 16.2>에 나타나 있듯이 세 가지 값이 있다.

<표 16.2> fseek()에서 사용되는 origin의 값

 상수 이름

 값

 의미

SEEK_SET

0

 위치 표시를 파일의 시작부터 offset 바이트 뒤로 이동

SEEK_CUR

1

 위치 표시를 현재 위치에서 offset 바이트 뒤로 이동

SEEK_END

2

 위치 표시를 파일의 마지막부터 offset 바이트 앞으로 이동

함수 fseek()는 위치 표시를 성공적으로 이동시키면 0을 돌려주고, 에러가 발생하면 0이 아닌 값을 돌려준다. <리스트 16.6>에 있는 프로글매은 파일을 무작위 상태로 사용하기 위해서 fseek()를 사용하고 있다.

<리스트 16.6> fseek()를 사용하여 무작위 상태로 파일을 사용하는 예

 /* fseek()를 사용한 무작위 사용 */

 #include <stdlib.h>

 #include <stdio.h>

 #include <io.h>


 #define MAX 50


 main()

 {

    FILE *fp;

    int data, count, array[MAX];

    long offset;


    /* 배열의 초기화 */


    for(count = 0; count < MAX; count++)

       array[count] = count * 10;


    /* 쓰기 상태로 이진 파일 열기 */


    if((fp = fopen("RANDOM.DAT", "wb")) == NULL)

    {

       fprintf(stderr, "\nError opening file.");

       exit(1);

    }


    /* 배열을 파일에 기록하고 나서 닫는다. */


    if((fwrite(array, sizeof(int), MAX, fp)) != MAX)

    {

       fprintf(stderr, "\nError writing data to file.");

       exit(1);

    }


    fclose(fp);


    /* 읽기 상태로 파일 열기 */


    if((fp = fopen("RANDOM.DAT", "rb")) == NULL)

    {

       fprintf(stderr, "\nError opening file.");

       exit(1);

    }


    /* 읽어들일 요소를 요구한다. */

    /* 요소를 입력하면 출력해주며, -1을 입력하면 마친다. */


    while(1)

    {

       printf("\nError element to read, 0-%d, -1 to quit: ", MAX - 1);

       scanf("%ld", &offset);


       if(offset < 0)

          break;

       else if (offset > MAX-1)

          continue;


       /* 위치 표시를 지정된 요소로 이동시킨다. */


       if((fseek(fp, (offset*sizeof(int)), SEEK_SET)) != 0)

       {

          fprintf(stderr, "\nError using fseek().");

          exit(1);

       }


       /* 하나의 정수를 읽어들인다. */


       fread(&data, sizeof(int), 1, fp);


       printf("\nElement %ld has value %d.", offset, data);

    }


    fclose(fp);

    return(0);

 }

=> 14번째 줄부터 35번째 줄까지는 <리스트 16.5>와 비슷하다. 16번째 줄과 17번째 줄은 50개의 int형 값을 가지는 data라는 배열을 초기화한다. 각 요소에 저장되는 값은 색인을 10배한 것이다. 그리고 나서 배열은 RANDOM.DAT라는 이진 파일에 저장된다. 21번째 줄에서 'wb'를 사용하여 파일을 열었으므로 이진 모드라는 것을 알 수 있다. 39번째 줄에서는 무한 루프인 while문을 실행하기 전에 파일을 읽기 가능한 이진 모드로 다시 열고 있다. while문에서는 값을 읽어들이기 원하는 배열 요소의 번호를 입력하도록 요구한다. 53번째 줄부터 56번째 줄까지는 입력된 요소가 파일 내에 포함되는 것인지 확인해본다는 것에 주의하자. 그렇다면 파일의 마지막을 벗어난 요소를 읽어들인다는 뜻일까? 실제로 그렇다. 배열의 마지막을 지난 곳에 값이 저장될 수 있는 것과 마찬가지로, C는 파일의 마지막을 지난 곳에서 값을 읽어들이게 해준다. 만약 파일의 마지막을 지난 곳이나 시작 이전에서 값을 읽어들인다면 결과는 예상할 수 없을 것이다. 이 프로그램의 53번째 줄부터 56번째 줄까지에 나타나 있는 것처럼 수행하는 동작의 결과를 항상 확인해 보는 것이 좋다. 읽어들이기 원하는 요소의 번호를 확인하고 나면 60번째 줄에서는 fseek()를 사용하여 적절한 위치로 이동한다. SEEK_SET이 사용되므로 이동은 파일의 시작을 기준으로 수행된다. 파일 내에서 이동되는 거리는 offset의 값이 아니라 offset의 값에 요소의 크기를 곱한 만큼이라는 것을 기억하자. 그리고 나서 68번째 줄에서는 값을 읽고, 70번째 줄에서는 값을 출력한다.

8. 파일의 마지막을 찾는 방법
: 파일의 길이를 정확하게 알고 있는 경우에는 파일의 마지막을 찾을 필요가 없을 것이다. 예를 들어, 100개의 요소를 가지는 정수형 배열을 저장하기 위해서 fwrite()를 사용한다면 전체적인 파일은 200바이트의 길이(2바이트 정수라고 가정할 때)가 된다는 것을 알 수 있다. 그러나 파일의 정확한 길이를 모르는 상태에서 파일의 처음부터 마지막까지를 읽어들이기 원하는 경우에는 어떻게 할 것인가? 파일의 마지막을 찾는 두 가지 방법이 있다. 텍스트 모드의 파일에서 문자 단위로 값을 읽어들일 때에는 EOF 문자를 찾을 수 있다. 기호 상수 EOF는 STDIO.H에서 -1로 정의되어 있고 '실제' 문자에서 사용되지 않는 값이다. 그래서 문자 입력 함수가 텍스트 모드의 스트림에서 EOF를 읽어들일 때 파일의 마지막에 도달했다는 것을 알 수 있다. 예를 들어, 다음과 같은 문장을 작성할 수 있을 것이다.

    while((c = fgetc(fp)) != EOF)

이진 모드의 스트림에서는 -1의 값을 가지는 데이터가 사용될 수도 있으므로 EOF가 파일의 마지막을 뜻하지는 않는다. -1의 값을 파일의 마지막을 뜻하지 않을 것이다. 대신에, 이진 모드에서는 라이브러리 함수 feof()를 사용할 수 있다. 이 함수는 이진 모드와 텍스트 모드의 파일에서 사용할 수 있다.

   int feof(FILE *fp);

인수 fp는 파일을 열 때 fopen()이 돌려주는 FILE 포인터이다. 함수 feof()는 파일 fp의 마지막에 도달하지 않았다면 0을 돌려주고, 파일의 마짐가에 도달하면 0이 아닌 값을 돌려준다. feof() 함수를 사용했을 때 파일의 마지막에 도달했다는 것을 확인하면 rewind()를 수행하거나, fseek()를 사용하거나, 또는 파일을 닫고 다시 열 때가지 더 이상의 일기 동작이 허용되지 않는다. <리스트 16.7>에 있는 프로그램은 feof()의 사용 예를 보여준다. 프로그램이 파일의 이름을 입력하도록 요구할 때 텍스트 파일의 이름을 입력하자. 예를 들어, C 소스 파일이나 STDIO.H와 같은 헤더 파일의 이름을 입력할 수 있다. 파일이 현재 디렉토리에 있다는 것을 확인하거나 또는 파일명의 일부로 경로를 입력하기 바란다. 프로그램은 feof()가 파일의 마지막을 발견할 때까지 파일을 한 번에 한 줄씩 읽어들이고 stdout으로 출력한다.

<리스트 16.7> 파일의 마지막을 찾기 위한 feof()의 사용 예

 /* 파일의 마지막 찾기 */

 #include <stdlib.h>

 #include <stdio.h>


 #define BUFSIZE 100


 main()

 {

    char buf[BUFSIZE];

    char filename[60];

    FILE *fp;


    puts("Error name of text file to display: ");

    gets(filename);


    /* 읽기 상태로 파일 열기 */

    if((fp = fopen(filename, "r")) == NULL)

    {

       fprintf(stderr, "Error opening file.");

       exit(1);

    }


    /* 파일의 마지막에 도달하지 않았다면 한 줄을 읽고 출력한다. */


    while(!feof(fp))

    {

       fgets(buf, BUFSIZE, fp);

       printf("%s", buf);

    }


    fclose(fp);

    return(0);

 }

-> 입력 / 출력

  Enter name of text file to display:
  hello.c
  #include <stdio.h>
  main()
  {
     printf("Hello, world.");
     return(0);
  }

9. 파일 처리 함수
 : 파일 처리(file management)는 디스크에 존재하는 파일을 다루는 동작을 뜻한다. 즉, 파일에서 데이터를 읽어들이거나 파이에 저장하는 것이 아니라 파일 자체를 삭제하거나 파일의 이름을 변경하고 복사하는 것을 말한다. C 표준 라이브러리에서는 파일을 삭제하고, 이름을 변경하기 위한 함수가 제공되며, 자신만의 파일 복사 함수를 작성할 수 있다.

9.1 파일 삭제하기
 : 파일을 삭제하기 위해서는 라이브러리 함수 remove()를 사용한다. 함수 원형은 다음과 같이 STDIO.H에 정의되어 있다.

   int remove(const char *filename);

변수 *filename은 삭제되는 파일의 이름에 대한 포인터이다. 파일의 이름에 대해서는 이 장의 앞 부분을 참고하기 바란다. 지정된 파일이 존재한다면 DOS 프롬프트에서 DEL 명령이나 UNIX에서 rm 명령을 사용한 것과 마찬가지로 삭제되고, remove() 함수는 0을 돌려준다. 파일이 존재하지 않거나 또는 읽기 전용 상태로 되어 있거나, 사용자가 적절한 이용 권한을 가지지 않거나, 다른 어떤 에러가 발생하면 remove()는 -1을 돌려준다. <리스트 16.8>에 있는 간단한 프로그램은 remove()의 사용 예를 보여준다. 주의하자. 파일을 '삭제'하면 완전히 사라지는 것이다.

<리스트 16.8> 디스크 파일을 삭제하기 위한 remove()함수의 사용

 /* remove() 함수의 사용 예 */


 #include <stdio.h>


 main()

 {

    char filename[80];


    printf("Enter the filename to delete: ");

    gets(filename);


    if(remove(filename) == 0)

       printf("The file %s has been deleted.", filename);

    else

       fprintf(stderr, "Error deleting the file %s.", filename);

    return(0);

 }

9.2 파일의 이름 변경하기
: rename() 함수는 이미 존재하는 디스크 파일의 이름을 변경한다. 함수 원형은 다음과 같이 STDIO.H에 정의되어 있다.

   int rename(const char *oldname, const char *newname);

oldname과 newname이 지적하는 파일의 이름은 이 장의 앞부분에서 설명한 규칙을 따른다. 한 가지 제한이 있다면 두 이름은 동일한 디스크 드라이브를 지정해야 한다는 것이다. 서로 다른 디스크 드라이브에 존재하는 파일의 이름을 '변경'할 수는 없다. 함수 rename()의 동작이 성공적이라면 0을 돌려주고, 에러가 발생하면 -1을 돌려준다. 에러는 다음과 같은 경우에 발생할 수 있다.

·oldname이라는 파일이 존재하지 않는다.
·newname이라는 이름의 파일이 이미 존재한다.
·서로 다른 디스크에서 이름을 변경하려고 한다.

<리스트 16.9>에 있는 프로그램은 rename()의 사용 예를 보여준다.

<리스트 16.9> 디스크 파일의 이름을 변경하기 위한 rename()의 사용

 /* 파일명을 변경하기 위한 rename()의 사용 예 */


 #include <stdio.h>


 main()

 {

    char oldname[80], newname[80];


    printf("Enter current filename: ");

    gets(oldname);

    printf("Enter new name for file: ");

    gets(newname);


    if(rename(oldname, newname) == 0)

       printf("%s has been renamed %s.", oldname, newname);

    else

       fprintf(stderr, "An error has occurred renaming %s.", oldname);

    return(0);

 }

9.3 파일 복사하기
: 프로그램에서는 가끔 파일을 복사할 필요가 있다. 파일을 복사한다는 것은 다른 이름이나 또는 동일한 이름이지만 다른 드라이브나 디렉토리에 동일한 내용의 복사본을 만드는 것을 뜻한다. DOS에서는 COPY 명령을 수행하여 파일을 복사할 수 있고, 다른 운영체제에서는 같은 기능의 명령이 있다. C 프로그램에서는 어떻게 파일을 복사할 수 있을까? C 에서는 파일을 복사는 라이브러리 함수를 제공하지 않으므로 직접 자신만의 함수를 작성할 필요가 있다. 이것은 어렵게 생각될 수 있지만, C의 입출력용 스트림을 사용한다면 아주 간단하게 수행할 수 있다. 다음은 함수에서 수행할 필요가 있는 단계들이다.

① 원본 파일을 읽기 가능한 상태의 이진 모드로 연다. 이진 모드를 사용하는 이유는 텍스트 파일뿐 아니라 모든 종류의 파일을 복사할 수 있도록 하기 위해서이다.

② 목적 파일을 쓰기 가능한 상태의 이진 모드로 연다.

③ 원본 파일에서 문자를 읽어들인다. 파일이 처음 열릴 때 위치 표시는 파일의 사직 부분을 지적하고 있으므로 파일 포인터를 다시 위치시킬 필요는 없다는 사실을 기억하자.

④ 함수 feof()가 원본 파일의 마지막에 도달했다는 것을 알려주면 복사가 완료된 것이므로 두 개의 파일을 닫고 함수를 호출한 프로그램으로 돌아갈 수 있다.

⑤ 파일의 마지막에 도달하지 않았다면 읽어들인 문자를 목적 파일에 기록하고 나서 단계 3으로 돌아간다.

<리스트 16.10>에 있는 프로그램은 앞에서 설명한 순서대로 원본과 목적 파일의 이름을 전달받아서 복사 동작을 수행하는 함수 copy_file()을 사용하고 있다. 어떤 파일을 열 때 에러가 발생하면 이 함수는 복사를 수행하지 않고 함수를 호출했던 프로그램에 -1을 돌려줌. 복사 과정이 완료되면 프로그램은 두 개의 파일을 닫고 0을 돌려준다.

<리스트 16.10> 파일을 복사하는 함수

 /* 파일 복사 */


 #include <stdio.h>


 int file_copy(char *oldname, char *newname);


 main()

 {

    char source[80], destination[80];


    /* 원본과 목적 파일의 이름을 구한다. */


    printf("\nEnter source file: ");

    gets(source);

    printf("\nEnter destination file: ");

    gets(destination);


    if(file_copy(source, destination) == 0)

       puts("Copy operation successful");

    else

       fprintf(stderr, "Error during copy operation");

    return(0);

 }

 int file_copy(char *oldname, char *newname)

 {

    FILE *fold, *fnew;

    int c;


    /* 원본 파일을 읽기 상태의 이진 모드로 연다. */


    if((fold = fopen(oldname, "rb")) == NULL)

       return -1;


    /* 목적 파일을 쓰기 상태의 이진 모드로 연다. */


    if((fnew = fopen(newname, "wb")) == NULL)

    {

       fclose(fold);

       return -1;

    }


    /* 원본에서 한 번에 한 바이트를 읽는다. */

    /* 만약 파일의 마지막에 도달하지 않았다면 */

    /* 목적 파일에 바이트를 기록한다. */


    while(1)

    {

       c = fgetc(foid);


       if(!feof(foid))

          fputc(c, fnew);

       else

          break;

    }


    fclose(fnew);

    fclose(fold);


    return(0);

 }

10. 임시 파일 사용하기
: 어떤 프로그램은 실행되는 동안 하나이상의 임시 파일을 사용한다. 임시 파일(temporary file)은 프로그램에 의해서 생성되고 프로그램이 실행되는 동안 다른 목적으로 사용되다가 프로그램이 종료되기 전에 삭제되는 파일이다. 임시 파일을 생성할 때에는 나중에 삭제할 것이므로 파일의 이름에 대해서 신경 쓰지 않는다. 그러나 이미 사용중이 아닌 파일의 이름을 사용해야 한다. C 표준 라이브러리에서는 어떤 존재하는 파일과 충돌하지 않는 파일의 이름을 생성하는 함수 tmpnam()이 제공된다. STDIO.H에 정의되어 있는 함수의 원형은 다음과 같다.

   char *tmpnam(char *s);

인수 s는 파일 이름을 저장하기에 충분한 버퍼에 대한 포인터가 되어야 한다. 또한, 파일의 임시 이름을 tmpnam의 내장 버퍼에 저장하는 경우에는 널(NULL) 포인터를 전달할 수 있고, 함수는 버퍼에 대한 포인터를 돌려준다. <리스트 16.11>에 있는 프로그램은 파일의 임시 이름을 생성하기 위해서 tmpnam()을 사용하는 두 가지 방법을 보여준다.

<리스트 16.11> 파일의 임시 이름을 생성하기 위한 tmpnam()의 사용

 /* 임시 파일명의 사용 예 */


 #include <stdio.h>


 main()

 {

    char buffer[10], *c;


    /* 정의된 버퍼에 임시 이름을 저장한다. */


    tmpnam(buffer);


    /* 다른 이름을 구한다. */

    /* 이번에는 함수의 내장 버퍼에 저장한다. */


    c = tmpnam(NULL);


    /* 이름을 출력한다. */


    printf("Temporary name 1: %s", buffer);

    printf("\nTemporary name 2: %s", c);

 }


'C 언어' 카테고리의 다른 글

15장 포인터 : 고급 기능들  (0) 2019.06.02
16장 링크리스트  (0) 2019.06.02
API 윈도우 창 띄우기  (0) 2019.05.25
CreateWindow() - 10개의 인수  (0) 2019.05.25
1. 배열  (0) 2019.05.25

+ Recent posts