프로그래밍 언어/C언어

C언어 18. 파일 입출력-1

닉네임못짓는사람 2020. 7. 23. 23:13
반응형

이번 글에선 C언어의 파일 입출력에 대해서 알아보도록 하겠습니다.

먼저 파일 입출력이란 무엇인가에 대해서 한번 생각해봅시다.

직관적으로 생각하면 데이터를 파일로 출력하거나, 파일로부터 읽는 것이라 할 수 있습니다.

그런데, 이것은 사람의 관점에서 바라볼 때의 이야기입니다.

 

실제 프로그램 입장에서 파일 입출력은 더 포괄적인 의미를 가지고있습니다.

프로그램 입장에서 파일은 데이터를 입출력하는 모든 대상이란 의미를 가지고 있습니다.

예를 들어 키보드, 마우스, 모니터 등도 프로그램에겐 모두 파일이 되는 것입니다.

어떻게 이 모든 것을 파일이라는 하나의 단어로 말할 수 있는 것일까요?

스트림 파일


사실 프로그램이 입출력을 실행하는 대상은 모두 동일한 대상입니다.

이 대상을 스트림 파일이라고 말하는데, 프로그램은 스트림 파일에 데이터를 입출력하고

이것이 중간다리 역할을 하여 다른 대상에 실제로 입출력을 합니다.

왜 이런 중간다리가 필요한 것일까요?

가장 큰 이유는 프로그램이 일관된 입출력 작업을 수행하기 위해서입니다.

무슨 의미냐 하면, 컴파일러는 사용자의 편의를 위해서 입출력 함수를 제공합니다.

그런데 이 입출력 함수를 모든 입출력 대상에 직접적으로 연결하기란 사실상 불가능합니다.

입출력 대상의 종류는 매우 다양하며, 계속해서 변화하기 때문이죠.

따라서 스트림 파일을 사용해 컴파일러가 일관된 입출력 작업을 수행하게 해 주는 것입니다.

fopen으로 파일을 열기


이제 실제로 파일에 데이터를 입출력하는 방법을 알아보도록 하겠습니다.

이를 위해선 먼저 스트림 파일을 생성해서 데이터를 입출력할 곳과 연결해야 합니다.

이 작업을 파일 개방이라고 말하며, fopen함수를 사용해 작업을 수행합니다.

fopen함수의 원형은 다음과 같습니다.

FILE* fopen(char*, char*);

함수를 좀 더 자세히 알아보도록 합시다.

먼저 첫 번째 전달 인자는 연결해줄 파일의 이름을 적어주고, 두번째 전달인자는 개방 모드를 적어줍니다.

개방 모드란 대상 파일을 입력용으로 쓸지, 출력용으로 쓸지 결정하는 것입니다.

개방 모드의 종류는 조금 있다가 보도록 하겠습니다.

프로그램 기준으로 읽는 것은 데이터를 입력받는 것이고, 쓰는 것은 데이터를 출력하는 것입니다.

먼저 r모드의 경우 데이터를 입력받는데, 데이터를 읽어올 파일이 없으면 널 포인터를 리턴합니다.

w와 a의 경우 데이터를 출력하는데, 파일이 없을 경우 새로 생성합니다.

둘의 차이점은 w의 경우 이미 파일이 있으면 파일의 내용을 모두 지우지만,

a의 경우 파일의 끝에서부터 데이터를 추가할 수 있습니다.

또한 위의 모드에 +를 붙여주면 읽기, 쓰기가 동시에 가능하도록 개방할 수 있습니다.

하지만 별도로 버퍼를 관리해야 하는 부담이 있기 때문에,

일단 파일을 닫고 다른 모드로 다시 개방하시는 걸 추천합니다.

fopen("ex.txt", "w");

위와 같이 작성해주시면 ex.txt파일이 있으면 내용을 모두 지우고 다시 쓰기를 실행하며,

파일이 없으면 새로 생성해서 쓰기를 실행합니다.

그럼 이 파일의 경로는 어디일까요?

위처럼 파일의 이름만 써주게 되면 현재의 작업 디렉토리, 즉 소스파일이 저장되는 곳이 경로가 됩니다.

다른 위치에서 파일을 개방하고 싶다면 구체적인 경로를 적어주어야 합니다.

fopen("C:\\asd\\ex.txt", "w");

이렇게 적어주시면 c드라이브 asd폴더 내의 ex.txt파일을 출력용으로 개방합니다.

주의할 점은 \를 두 번 써주셔야 정상적으로 \가 인식된다는 점입니다.

 

이번엔 fopen의 반환값에 대해 알아보도록 하겠습니다.

fopen함수가 파일 개방에 성공하면 실제 파일과 연결되는 스트림 파일을 메모리에 생성합니다.

그리고 이 스트림 파일에 접근할 수 있도록 포인터를 반환해주는데,

이 값이 바로 fopen의 반환값이며 이것을 파일 포인터라고 말합니다.

 

그럼 이 파일 포인터에 대해서 자세히 알아보도록 합시다.

먼저 스트림 파일은 데이터를 저장하는 버퍼를 가지고 있습니다.

이 버퍼에 데이터를 모아서 입출력을 하게 되는데, 단순히 데이터만 저장한다면 문제가 생깁니다.

 

예를 들어 printf를 사용해 두 개의 문자열을 출력한다고 생각해봅시다.

이때 버퍼에 두 개의 문자열이 입력될 텐데, 첫 번째 문자열이 버퍼의 첫 부분부터 입력되면

두 번째 문자열은 그다음에 이어서 버퍼에 저장되어야 할 것입니다.

따라서 이 위치를 알려주는 지시자가 필요합니다.

또한 버퍼가 메모리의 어디에 위치하는지도 알아야 하기 때문에 이 주소값 또한 필요합니다.

 

따라서 스트림 파일은 이와 같은 정보들을 구조체 변수에 저장하게 됩니다.

이 구조체의 형태 이름이 FILE이며 결국 fopen함수는 이 구조체 변수의 포인터를 반환하는 것입니다.

이 구조체의 형태는 다음과 같습니다.

struct _iobuf {
	char* _ptr;
	int _cnt;
	char* _base;
	int _flag;
	int _file;
	int _charbuf;
	int _bufsiz;
	char *_tmpfname
};

따로 설명은 하지않고, 위와 같은 형태라는 것만 한번 보시면 될 것 같습니다.

이 반환값을 다음과 같이 구조체 포인터 변수에 저장해서 실제로 사용할 수 있습니다.

FILE *fp;
	fp = fopen("ex.txt", "w");

그럼 이제 실제로 이 함수를 사용해 파일에 데이터를 입출력해보도록 합시다.

#include <stdio.h>

int main() {
	FILE *ifp, *ofp;
	ofp = fopen("C:\\asd\\ex.txt", "w");
	if (ofp == NULL) {
		printf("출력 파일을 개방하지 못했습니다.\n");
		return 1;
	}
	printf("출력 파일을 개방했습니다.\n");
    	fclose(ofp);

	ofp = fopen("C:\\asd\\ex.txt", "r");
	if (ofp == NULL) {
		printf("입력 파일을 개방하지 못했습니다.\n");
		return 1;
	}
	printf("입력 파일을 개방했습니다.\n");

	fclose(ifp);
    	return 0;
}

위의 코드는 동일한 파일을 출력용으로 개방하고, 파일을 닫은 뒤에

다시 입력용으로 개방해 이것이 정상적으로 작동하는지 확인해보는 코드입니다.

 

fopen은 파일을 정상적으로 개방하지 못하면 NULL포인터를 반환합니다.

따라서 이 값이 NULL포인터이면 오류가 발생했음을 알려주고, 프로그램을 종료해줍니다.

여기서 프로그램이 정상적으로 수행되지 못했기 때문에 main함수의 반환 값은 1이 됩니다.

또한 개방한 파일 포인터를 닫기 위해선 fclose함수를 사용하는데,

함수 원형은 다음과 같습니다.

int fclose(FILE*);

전달 인자로는 파일 포인터를 넘겨주고, 반환 값은 int값을 받아옵니다.

이 반환값은 파일을 정상적으로 닫았는지 확인하는 용도로 사용되는데,

값이 0이면 정상적인 실행이고, -1이면 오류가 발생한 것이라는 것을 기억해두시기 바랍니다.

 

이번 글은 여기서 마치도록 하고, 다음 글에서 실제로 데이터를 읽고, 쓰는 방법에 대해 알아보겠습니다.

반응형

'프로그래밍 언어 > C언어' 카테고리의 다른 글

C언어 20. 전처리와 분할 컴파일-1  (0) 2020.07.27
C언어 19. 파일 입출력-2  (0) 2020.07.24
C언어 17. 응용자료형  (0) 2020.07.23
C언어 16. 변수의 영역  (0) 2020.07.22
C언어 15. 메모리 동적 할당  (0) 2020.07.20