본문 바로가기
휴지통/C 언어

파일 입출력1

by 신재권 2021. 2. 6.

파일의 이동 경로가 되는 다리를 가리켜 '스트림(Stream)'이라 한다.

즉 프로그램과 파일 사이에 스트림을 형성해야 데이터를 주고 받을 수 있다.

 

프로그램과 파일사에이 스트림이 형성된 것은 파일로부터 데이터를 읽어들일 준비가 됬다는 것이다.

 

#include <stdio.h>

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

->성공시 해당 파일의 FILE구조체 변수의 주소 값, 실패시 NULL반환

 

함수의 첫번째 인자로는 스트림을 형성할 파일의 이름을,. 두 번째 인자로는 형성할 스트림의 종류에 대한 정보를 문자열의 형태로 전달한다. 그러면 이 함수는 해당 파일과의 스트림을 형성하고 스트림정보를 FILE구조체 변수에 담아서 그 변수의 주소 값을 반환한다.

 

FILE이라는 이름의 기본자료형이 존재하지 않으나, 이는 분명 구조체의 이름이다 .

FILE구조체가 어떻게 정의되어있는지는 알필요가 없다. FILE구조체 변수의 멤버에 직접 접근할 일이 없기 때문이다. 

즉 위 함수가 반환하는 FILE 구조체의 포인터는 파일을 가리키기 위한 용도로 사용이된다. 즉 이 포인터를 이용해서 파일에 데이터를 저장하거나 ,  파일에 저장된 데이터를 읽게된다. 따라서 FILE구조체가 어떻게 정의되어 있는지 알 필요가 없다.

 

fopen 함수가 호출되면 FILE 구조체 변수가 생성된다.

생성된 FILE 구조체 변수에는 파일에 대한 정보가 담긴다.

FILE 구조체의 포인터는 사실상 파일을 가리키는 '지시자'의 역할을 한다.

 

fopen의 전달인자를 살펴보면

첫 번째 전달인자 : 스트림을 형성할 파일의 이름

두 번째 전달인자 : 형성하고자 하는 스트림의 종류

 

스트림의 종류에는 출력스트림과 입력 스트림이 있다. 

데이터를 파일로부터 읽어들이기 위한 '입력 스트림' 과 데이터를 파일에 쓰기 위한 '출력 스트림'으로 구분이 된다. 

 

FILE *fp = fopen("data.txt", "wt");  //출력 스트림의 생성

"파일 data.txt와 스트림을 형성하되 wt모드로 스트림을 형성해라" 라는 듰이다.

wt모드의 스트림은 텍스트 데이터를 쓰기 위한 출력 스트림을 뜻한다. 

 

위에 선언으로 생성되는 스트림은 출력 스트림 이기 때문에 파일에- 데이터를 쓸 수 있어도 읽지는 못한다. 만약에 파일로부터 데이터를 읽기 원한다면 별도로 입력 스트림을 형성해야 한다.

FILE *fp = fopen("data.txt", "rt");  //입력 스트림의 형성

"파일 data.txt와 스트림을 형성하되 rt모드로 스트림을 형성해라"

rt모드의 스트림은 텍스트 데이터를 읽기 위한 입력 스트림을 뜻한다.

 

fopen함수의 호출을 통해 파일과의 스트림을 형성되었을 때 '파일이 개방(오픈)되었다'라고 표현하는 것이 일반적이다.

 

"rt"를 전달함으로써 텍스트 데이터 입력용 스트림이, "wt"를 전달함으로써 텍스트 데이터 출력용 스트림이 형성되었다.

 

#include <stdio.h>

int main()
{
	FILE * fp = fopen("data.txt", "wt");
    if(fp==NULL){
    	puts("파일 오픈 실패!");
        return -1;
  	}
    
    fputc('A', fp);
    fputc('B', fp);
    fputc('C', fp);
    fclose(fp); 	//스트림의 종료
    return 0;
}

 위 예제에서는  data.txt와의 출력 스트림을 형성하고 있다. 출력 스트림을 형성하는 경우에는 해당 파일이 생성된다.

경로를 직접 설정이 가능하고, VC++을 사용시 프로젝트 디렉터리에 생성이 된다.

 

이렇게 해서 스트림이 형성되면 이제부터는 fp는 파일 data.txt.를 지칭하는 포인터가 된다.

즉 다음 문장이 실행이 되면

 fputc('A', fp);

fp가 지칭하는 파일 data.txt에 문자 A가 저장된다. 

data.txt파일을 살펴보면 ABC가 입력됬다는 것을 확인 할 수있다.

 


위 예제에서 호출한 fclose함수를  자세히 살펴보면 fclose함수는 fopen함수의 반대 기능을 제공한다. 

fopen함수가 스트림을 형성하는 함수라면, fclose함수는 스트림을 해제하는 함수이다 .

즉 fopen함수는 파일을 개방하는 함수이면, fclose함수는 파일을 닫는 함수이다.

 

#include <stdio.h>

int fclose(FILE *stream);

-> 성공시 0, 실패시 EOF를 반환

 

fclose함수의 호출을 통해서 개방되었던 파일을 닫아줘야 하는 이유는

1. 운영체제가 할당한 자원의 반환

2. 버퍼링 되었던 데이터의 출력

 

파일을 닫아 주지 않으면 할당된 채로 남아있게 되어 , 그만큼의 자원손실을 초래하기 때문에 파일의 사용이 끝나는 즉시 fclose 함수를 호출해서 자원을 반환해줄 필요가 있다.

스트림의 중간에 존재하는 입력 버퍼와 출력버퍼가 파일 스트림에도 존재를 한다.

즉 fputc와 같은 함수의 호출로 데이터를 파일로 전송한다고해서 파일에 바로 저장이 되는 것은 아니다. 일단은 출력 버퍼에 저장되었다가 버퍼링 방식에 따라 뒤늦게 파일에 저장이 된다.

즉 fclose를 호출하지않으면 버퍼가 비워지지 않기 때문에 파일에 저장이 되지 않는다.


출력 버퍼를 비우는 함수

#include <stdio.h>

int fflush(FILE *stream);

->함수 호출 성공시 0, 실패시 EOF반환

 

출력 버퍼를 비운다는 것은 출력 버퍼에 저장된 데이터를 목적지로 전송한다는 의미

입력 버퍼를 비운다는 것은 입력 버퍼에 저장된 데이터를 소멸시킨다는 의미

fflush함수는 출력버퍼를 비우는 함수이다.

fflush함수는 입력버퍼를 대상으로 호출할 수 없다.

 

파일의 출력버퍼를 비울수 있으면, 데이터가 파일에 저장이된다.

 

파일 스트림의 입력버퍼를 비우는 함수는 필요가 없다. 파일에 저장된 데이터는 원할 때 언제든지 읽을 수 있을 뿐만 아니라(읽혀진 데이터는 입력 버퍼에서 지워진다), 파일 대상의 입력 버퍼를 비워야만 하는 상황이라는 것이 특별히 존재하지 않는다.

 

int ch = fgetc(fp);

fgetc함수는 파일에 저장된 문자 하나를 반환하는 함수이다. 

위의 함수호출로 인하여 FILE구조체의 포인터 fp가 지칭하는 파일에 저장된 문자 하나가 반환되어 변수 ch에 저장된다는 사실이다.

#include <stdio.h>

int main()
{
	int ch, i;
    FILE *fp - fopen("data.txt", "rt");
    if(fp ==NULL){
    	puts("파일 오픈 실패!");
        return -1;
   	}
    
    for(i=0; i<3; i++)
    {
    	ch =fgetc(fp);
        printf("%c \n", ch);
  	}
    fclose(fp);
    return 0;
}
====================실행결과===============
A
B
C

fopen함수의 두번째 인자로 "wt"와 "rt"를  전달하여 스트림을 형성하였는데 , 형성할 수 있는 스트림의 종류는 다양하다.

 

기준 1: 읽기 위한 스트림인가? 쓰기 위한 스트림인가?

기준 2: 텍스트 데이터를 위한 스트림인가? 바이너리 데이터를 위한 스트림인가?

 

스트림을 구분하는 기준 1

데이터 READ스트림 : 읽기만 가능

데이터 WRITE 스트림 : 쓰기만 가능

데이터 APPEND 스트림 : 쓰되 덧붙여 쓰기만 가능

데이터 READ/WRITE 스트림 : 읽기, 쓰기 모두 가능

모드(mode) 스트림의 성격 파일이 없으면?
r 읽기 가능 에러
w 쓰기 가능 생성
a 파일의 끝에 덧붙여 쓰기 가능 생성
r+ 읽기/쓰기 가능 에러
w+ 읽기/쓰기 가능 생성
a+ 읽기/덧붙여 쓰기 가능 생성

위 표를 참조하여 필요료 하는 스트림의 특성과 일치하는 '파일의 개방(mode)'를 선택하면 된다.

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

모드의 +는 읽기,쓰기가 모두 가능한 스트림의 형성을 의미한다.

모드 a는 쓰기가 가능한 스트림을 형성하는데. 여기서 말하는 쓰기는 덧붙이기이다.

 

+를 붙이면 읽기/쓰기가 모두 가능하다 해서 좋은 모드라 할 수 없다.

이러한 모드를 기반으로 작업하는 경우에, 읽기에서 쓰기 , 그리고 쓰기에서 읽기로 작업을 변경할 때마다 메모리 버퍼를 비워줘야 하는 등의 불편함과 더불어 잘못된 사용의 위험성도 따른다. 

그러므로 r, w , a 중에서 하나를 선택하여 스트림을 형성하는 것이 좋다.

 


텍스트 파일과 바이너리 파일을 구분하는 방법

개인이 소유하는 도서의 목록  : 문자 데이터

슈퍼마켓의 물품 가격 : 문자 데이터

영상파일 : 바이너리 데이터

음악 파일 : 바이너리 데이터

 

결론은 사람이 인삭할 수 있는 문자를 담고 있는 파일을 가리켜 '텍스트파일(text file)'이라 하며, 그 이외에 컴퓨터가 인식할 수 있는 데이터를 담고 있는 파일을 가리켜 '바이너리 파일(binary file)'이라 한다.

 

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

 

개행은 일반적인 문자 데이터와 성격이 다르다. 개행은 줄이 바뀌었다는 일종의 현상이지 그 자체가 하나의 데이터로 존재하는 대상은 아이다. 

C언어에서는 개행을 \n이라 약속했다.  이는 C언어 에서만의 약속이다.

 

Windows의 파일 내 개행 \r\n

mac의 파일 내 개행 \r

Unix계열의 파일 내 개행 \n

 

따라서 windows 기반의 편집기는 \r과 \n이 나란히 등장할 때 개행으로 인식하고, mac 기반은 \r, unix는 \n이다.

즉 C언어에서 개행을 의미하는 문자 \n을 파일에 저장하고, 이 파일을 windows나 mac의 편집기로 열어보면 \n이 개행으로 표시되지 않음을 확인할 수 있다. 

Unix에서는 확인이 된다. 

 

이렇게 직접 변환을 해줘야되는데 귀찮을 것이다 .

그렇다면 파일을 텍스트 모드로 개방하면 자동으로 변환이 된다.

 


스트림을 구분하는 기준2

파일을 텍스트 모드로 개방하면 바로 위에서 말한 형태의 변환이 자동으로 이뤄진다. 

windows 기반이라고 가정하면

C프로그램에서 \n을 파일에 저장하면 \r\n으로 변환되어 저장됨.

파일에 저장된 \r\n을 C프로그램상에서 읽으면 \n으로 변환되어 읽혀짐 

 

때문에 우리가 직접 개행 문자의 변환을 신경 쓸 필요가 없다. 그저 텍스트 모드로 파일을 개방만하면된다. 

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

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

 

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

 

반대로 바이너리 데이터를 저장하고 있는 파일의 경우에는 이러한 형태의 변환이 일어나면 안되기 때문에(아무런 변환도 일어나면 안되기 때문에) 바이너리 모드로 파일을 개방해야 한다. 

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

 

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

 

t나 b를 붙여주지 않으면 자동으로 텍스트 모드로 개방이 된다. 

 

바이너리 모드로 파일을 개방하면 아무런 변환도 발생하지 않는다. 그러나 텍스트 모드로 파일을 개방하면  운영체제에 따른 표현 차로 인한 변환이 발생한다. 그러나 텍스트 모드로 파일을 개방하면 운영체제에 따른 표현 차로 인한 변환이 발생한다.

 

 

 

 

'휴지통 > C 언어' 카테고리의 다른 글

메모리 관리와 동적할당  (0) 2021.02.10
파일입출력2  (0) 2021.02.07
구조체  (0) 2021.02.04
문자열 입력 함수  (0) 2021.02.02
문자와 문자열  (0) 2021.02.01