C언어 정리하기 - 파일 입출력 실습 (2024)

기본(개정)

C언어 정리하기 - 파일 입출력 실습

루벤딕스 2017. 1. 10. 0:36

URL 복사 이웃추가

본문 기타 기능

신고하기

이번 내용을 시작하기 전에

안녕하세요 루벤딕스입니다.
저번 포스팅에서는 파일 입출력의 기본 개념을 알아봤어요. 파일의 종류와 상대 경로, 절대 경로를 알아봤죠.
이번 포스팅에서는 파일 입출력을 실습해볼 건데 딱 4개의 함수만 사용해볼 겁니다.
fopen(), fclose(), fprintf(), fscanf() 이렇게 4개만 사용해볼 거예요. 함수를 설명하는 포스팅이 아니니까요.

파일 스트림과 입출력 모드

저번 포스팅에서 파일 입출력을 하려면 파일 스트림을 열어야 한다고 설명했죠?
여는 작업이 있다면 당연히 닫는 작업도 있습니다. 파일 스트림을 여는 함수는 fopen(), 파일 스트림을 닫는 함수는 fclose()입니다.
저번 포스팅에서 fopen()과 fclose()를 사용해봤는데 동적 할당으로 비유하면 malloc()과 free()의 관계와 같습니다.

fopen()의 원형을 보면 char const * 2개를 매개변수로 갖고 있는데 _FileName은 이름만 봐도 알겠네요.
_FileName은 확장자를 포함한 파일의 이름을 뜻합니다. _Mode는잘 모르겠는데... 저번 포스팅을 잘 생각해보시면 답이 나와요.
파일은 문서 파일과 2진 파일로 분류한다고 설명했습니다. _Mode는 파일의 분류를 포함해 어떻게 작업할 건지를 설정하는 겁니다.
C언어 기본서를 보면 텍스트 모드와 바이너리 모드라고 해서 쭈욱 표로 제공해주는데 저도 그걸 보여드리겠습니다.

뭐... 저런 것들이 있는데 저걸 다 사용하지는 않고 필요한 것 위주로만 사용하게 됩니다.
주로 r, w를 사용하죠. 나머지는 그다지써먹을 일이 없더군요.
r은 읽기, w는 쓰기를 뜻하는데 문서 모드(텍스트 모드)와 2진 모드(바이너리 모드)를 뜻하는 t와 b를 결합해서 사용합니다.
조합하면 rt, wt, rb, wb 이렇게 나오겠네요. 여기서 t는 생략할 수 있습니다. 기본값이 t라는 얘기죠.
이제 파일 입출력을 실습해볼 건데 비주얼 스튜디오 2010 위주로 진행하겠습니다.
2015 같은 경우는 fopen_s()를 사용하라고 하는데 이건 ANSI의 표준 함수가 아니므로 설명을 생략할게요.
C 레퍼런스 포스팅에서는 저런 함수들도 다뤘으니까 fopen_s()를 사용하실 분들은 C 레퍼런스 포스팅을 참고해주세요.

파일 스트림을 열어서 파일 생성하고 닫기

#include <stdio.h>int main(void){char *pFileName = "Test.txt";FILE *pFile = fopen(pFileName, "wt");// 오류가 발생하면 반환 값은 NULLif (pFile == NULL){printf("파일 스트림을 열지 못했습니다!\n");}printf("%s를 생성했습니다!\n", pFileName);// 오류가 발생하면 반환 값은 EOF// EOF는 -1if (fclose(pFile) == EOF){printf("파일 스트림을 닫지 못했습니다!\n");}return 0;}

프로그램을 실행하면 프로젝트 폴더가 있는 곳에 Test.txt가 생성되는 걸 확인하실 수 있을 겁니다.
필요한 정보는 주석으로 달아놨는데... wt는 문서 모드를 쓰기 작업으로 설정하겠다는 거예요.
w는 파일이 없으면 새로 만들고, 파일이 존재하면 기존에 있던 파일에 덮어씌우는 모드입니다.
아직은 아무것도 쓴 게 없어서 잘 모르겠네요... 이것저것 써보기 전에 알아둬야 할 개념이 하나 있습니다. 그걸 알아볼게요.

파일 포인터

fopen()으로 문서 파일을 생성해봤는데 아직은 뭐... 아무것도 쓴 게 없어서 썰렁하네요...
빨리 이것저것 작업해보고 싶지만 그전에 파일 포인터라는 걸 알아봐야 합니다. 파일 입출력을 어렵게 만드는녀석이기도 하죠.
사실 파일 포인터는 어떤 새로운 개념이 아니라 콘솔창의 커서 같은 겁니다. 즉, 이런 걸 파일 포인터라고 하죠.

파일을 열면 파일이 시작되는 부분이 있고 파일이 끝나는 부분이있습니다.
파일의 시작 지점을 따로 뜻하는 표현은 없지만 파일의 끝은 EOF(End Of File)이라고 표현합니다.
EOF의 값은 IDE나 컴파일러마다 달라질 수 있는데 비주얼 스튜디오와 Dev C++에서는 -1로 사용하고 있습니다.
EOF를 이용하면 파일이 끝나는 부분을 알 수 있기 때문에 파일 입출력을 할 때는 EOF를 많이 사용합니다.
지금부터 실습해볼 파일 입출력도 EOF를 이용하게 되죠. 파일 입출력은 EOF를 잘 알고 있어야 해요.

fprintf()

#include <stdio.h>int main(void){char *pFileName = "Test.txt";FILE *pFile = fopen(pFileName, "wt");// 오류가 발생하면 반환 값은 NULLif (pFile == NULL){printf("파일 스트림을 열지 못했습니다!\n");}fprintf(pFile, "이것이 바로 파일 입출력!\n");printf("%s에 정보를 입력했습니다.\n", pFileName);// 오류가 발생하면 반환 값은 EOF// EOF는 -1if (fclose(pFile) == EOF){printf("파일 스트림을 닫지 못했습니다!\n");}return 0;}

오... 아까랑 별로 달라진 것도 없는데 fprintf() 이거 하나 사용했더니 결과가 달라졌네요?
fprintf()는 printf()의 파일 입출력 버전입니다. printf()랑 똑같은데 첫 번째 매개변수가 파일 포인터에요.
이것만 주의하시면 별로 어려울 게 없습니다. printf()는 그동안지겹도록 사용해왔으니까요.

fprintf()는 정보를 파일에 출력해주는데 파일 입출력은 비트가 아니라 바이트 단위로 이루어집니다.
이것에 대해서 별로 생각해보시는 분들은 없으시겠지만 자료형의 최하위 단위가 char이고...
ASCII도 1바이트로 표현되므로 파일 입출력은 바이트 단위로 이루어진다고 볼 수 있어요.
이것과 관련된 내용이 있는데 그건 이따가 설명하겠습니다.

fscanf()

#include <stdio.h>int main(void){char String[50];char *pFileName = "Test.txt";FILE *pFile = fopen(pFileName, "rt");// 오류가 발생하면 반환 값은 NULLif (pFile == NULL){printf("파일 스트림을 열지 못했습니다!\n");}while (fscanf(pFile, "\n%[^\n]s", String) != EOF){printf("%s\n", String);}// 오류가 발생하면 반환 값은 EOF// EOF는 -1if (fclose(pFile) == EOF){printf("파일 스트림을 닫지 못했습니다!\n");}return 0;}

fscanf()를 사용하는 건 좀 어려울 수 있어요. scanf()를 잘 알아야 활용할 수 있거든요...
파일에서 프로그램으로 정보를 읽어오려면 일단 정보를 저장할 공간이 필요합니다. 동적 할당을 이용해도 괜찮아요.
저장할 공간을 만들었으면 문서 파일을 rt로 열고 fscanf()를 사용하면 됩니다.
fscanf() 같은 파일 입출력 함수들은 반환 값으로 파일 포인터로 작업한결과를반환합니다.
pFile 안에는 파일 포인터가 있는데 2015에서는 _Placeholder를 이용하더군요. 뭐... 구성원이 저거 하나니까...

여튼... 함수 내부에서 파일 포인터를 읽고 파일 입출력 작업을 할 때 파일의 끝을 만나게 되면 EOF를 반환합니다.
그게 아니라면 EOF가 아닌 값을 반환하는데 위에 있는fscanf()는 1을 반환하더군요. 별로 중요한 건 아닙니다.
fscanf()는 scanf()의 확장형이라고 생각하시면 됩니다. 즉, 공백 처리도 scanf()처럼 합니다.
그래서 %[^\n]s를 사용했고 개행 문자 처리를 위해 \n%[^\n]s 이렇게 작성했습니다.
이게 무슨 의미냐면 파일에서 개행 문자를 읽고 문자열을 읽으라는 의미입니다.
개행 문자가 없으면 그냥 문자열을 읽게 되는 것이고, 개행 문자가 있으면 개행 문자를 읽고 문자열을 읽습니다.
하지만 fscanf()는 scanf()처럼 공백 문자들을 읽고 버립니다. 그래서 결과적으로는 문자열만 읽게 되는 것이죠.

그렇게 쭈욱 파일에서 문자열을 출력하다가 파일의 끝을 만나게 되면 fscanf()는 EOF를 반환하게 됩니다.
while문은 EOF가 나오기 전까지만 실행되므로 EOF가 나오면 while문을 빠져나가게 됩니다.
원래는 feof()라는 걸 이용하는데 여기서는 fscanf()로만 해결하기 위해서 feof()를 생략했습니다.

파일 입출력 함수의 반환값과 EOF

지금까지 파일 입출력을 실습해봤는데 간단하게 진행해서 별로 어렵지는 않았을 거예요.
이번에는 좀 더 깊숙히 들어가볼 건데파일 입출력 함수의 반환값과 EOF를 알아볼 겁니다.
위에서 파일 입출력을 할 때는 EOF를 이용한다고 설명했었어요. 정확히 말하자면 파일의 끝을 이용하는 거죠.
비주얼 스튜디오와 Dev C++에서는 EOF는 -1로 표현하고 있습니다. 이걸 비트로 표현하면 11111111이 되죠.

그런데 이건 signed일 때의 얘기입니다. unsigned에서는 11111111이 255를 뜻해요.
이게 무슨 문제가 되느냐? 함수의 반환값이 255면 -1로 인식될 수 있다는 겁니다!
ANSI 표준에서는 signed와 unsigned의 값이 같은 경우 어떻게 표현하는지는 모르겠지만... (관련 내용이 있으면 제보 부탁해요)
비주얼 스튜디오와 Dev C++에서는 비트가 같아도 signed가 음수로 되면 다르다고 합니다. 이걸 직접 보여드릴게요.

#include <stdio.h>int main(void){// 둘의 비트는 같음// 0xFF는 11111111signed char scNum = 0xFF;unsigned char ucNum = 0xFF;printf("결과 : %d\n", scNum == ucNum);ucNum = scNum = 10;printf("결과 : %d\n", scNum == ucNum);return 0;}

scNum과 ucNum에 0xFF를 할당했더니 값이 다르다고 합니다...
프로그램이 signed와 unsigned를 구분한다는 건데 scNum과 ucNum에 10을 넣었을 때는 값이 같다고 나옵니다.
이걸로 봐서는 signed가 -1이 되고, unsigned가 255가 된다 해도 별로 문제가 없어 보이는데... 혹시 모르니까요...

ANSI의 표준 함수를 보면 unsigned로 반환하는 경우는 거의 없습니다. 웬만해서는 signed를 반환하죠.
signed와 unsigned의 비트값이 같다고 둘을 같다고 인식하게 되면 EOF의 의미가 사라집니다. 255와 EOF가 같다고 되니까요.
그래서 특정 범위에서는 signed와 unsigned가 다르다고 인식하는 IDE나 컴파일러가 많습니다.
위에서 말한 대로 ANSI의 표준인지 아니면 IDE나 컴파일러의 확장인지는 잘 모르겠네요... 전에 봤을 때는 없었던 것 같던데...

만약에 signed와 unsigned의 비트값이 같다고 둘을 같은 값으로 인식해버리면...
파일의 끝이 아닌데도 반환값이 255가 되면 EOF와 같다고 인식해서 의도하지 않은 결과가 발생할 수도 있습니다.
결론을 말하자면 파일 입출력 함수 중에 unsigned char형식을 반환하는 함수는 없습니다.

하지만 새로운 문제가 있어요! 0xFF를 반환하게 되면 EOF와 값이 같아지게 됩니다.
파일의 끝이 아닌데도 파일의 끝이라고 인식해버리는 결과가 나올 수 있다는 거죠.
getchar()이라는 함수가 있는데 이 함수는 버퍼에서 읽어온 문자를 반환하는 함수입니다.
전에 한글을 파헤칠 때 한글은 2바이트로 표현한다고 설명했었죠? 한글은 0xFF20 이렇게 2바이트로 표현합니다.
getchar()은 반환 형식이 int형이지만 내부적으로는 char형만큼만 버퍼에서 값을 읽어옵니다. ANSI 표준을 보여드릴게요.

스트림이 가리키는 입력 스트림에 대한 파일의 끝 표시자가 설정되어있지 않고 문자가 존재한다면,
fgetc()는 unsigned char에서 int로 변환된 문자를 얻고 스트림에 대한 파일의 위치를 옮긴다.
스트림에 대한 파일의 끝 표시자가 설정되어있거나,
스트림이 파일의 끝인 경우, fgetc()는 EOF를 반환한다. 그렇지 않으면,
fgetc()는 스트림이 가리키는 입력 스트림에서 문자를 반환한다.
읽기 오류가 발생하면, 스트림에 대한 오류 표시자가 설정되고 fgetc()는 EOF를 반환한다.

하하... 이게 참 뭔 설명인지...
일단 getchar()은 fgetc()와 비슷하다고 합니다. 그래서 fgetc()를 알아봤는데 중요한 정보를 하나 얻었어요.
ANSI 표준에 따르면 입력 스트림에 존재하는 문자는 unsigned char입니다. 그걸 int로 변환하는 거죠.
이걸 다시 0xFF20에 적용하면... 0xFF20이 입력 스트림에 있을 때 0xFF20은 unsigned char로 존재하게 됩니다.

그런데 getchar()을 호출하면 입력 스트림에 있는 0xFF20을 int로 변환하게 됩니다. 아마 signed int겠죠.
unsigned char에서 int로 변환한다고 했으니까 0xFF20은 0x000000FF와 0x00000020 이렇게 변환됩니다.
결과적으로는 음수가 나올 수 없는 형식이네요... int에서 -1은 0xFFFFFFFF니까요... 이런 걸 노린 거였군요.
문자 부호는 8비트로 표현되기 때문에 이걸 int로 변환하게 되면 -1이 나올 수가 없습니다.

따라서 0xFF20이라는 값을 getchar()이 읽게 되면 0x000000FF와 0x00000020 이렇게 읽게 되므로
EOF의 값인 -1과 충돌될 확률이 없습니다. 바이트를 확장해서 EOF와의 충돌을 피할 줄이야... 정수 승격과 비슷하네요.
getchar()의 반환 형식은 int형이므로 이걸 char형에 넣게 되면 정보가 손실되지만 어차피 0만 있으므로 전혀 문제가 없습니다.
위에서 설명한 내용을 정리하면 파일 입출력 함수의 반환값은 EOF와의 충돌을 피하기 위해 int형식을 반환한다는 것!

파일 입출력의 단위

위에서 파일 입출력 함수의 반환값과 EOF의 관계를 알아봤는데 그러면 파일 입출력은 int형으로 이루어지는 걸까요?
그건 아닙니다. 그렇게 되면 char형이 int형으로 변하면서의미없는 용량이 3바이트씩 늘어나게 되니까요.
위에서 설명한 내용은 특정 함수에 관한 내용입니다. getchar(), fgetc() 등 이런 함수들 말이죠.
아까fgetc()에 대한 설명을 잘 보셨다면 파일 입출력의 단위는 이미 알고 계실 겁니다. 파일 입출력의 단위는 unsigned char입니다.
어떻게 보면 1바이트라고 할 수 있는데 위에서 알아본 것처럼 signed와 unsigned는 다르게 인식할 수도 있으니까 구분해줘야 해요.

0xC0CC는 CP949에서 "이"를 의미합니다. 즉, 2바이트가 파일에 출력된 거죠.
파일 입출력의 단위가 int였다면 0x000000C0 0x000000CC 이렇게 되어야 합니다.
하지만 파일 입출력의 단위는 unsigned char이므로 0xC0 0xCC 이런 식으로 출력됩니다.
위에서 파일 입출력 함수의 반환값과 EOF의 관계를 설명했는데 이거랑 헷갈리시면 안됩니다.

파일 입출력 응용하기

Code Test.c내 컴퓨터 저장네이버 클라우드 저장

전에 동적 할당을 응용했던 프로그램에 파일 입출력 기능을 추가했습니다.
fopen(), fclose(), fprintf(), fscanf() 이렇게 4개의 함수만 사용해서 구현해봤어요.
이 부분은 응용이라 설명은 생략하고 여러분들에게 맡기겠습니다. 잘 모르시면 소스 코드를 참고하시면 됩니다.
핵심은 이중 연결 리스트의 정보 개수를 저장하는 것과 fscanf()로 공백 문자를 처리해주는 겁니다.
그리고 파일을 불러올 때 이중 연결 리스트를 초기화하고 정보의개수만큼 동적 할당해주는 작업을 처리해줘야 합니다.
이게 좀 어려울 수 있는데... 동적 할당을 응용하실 수 있으면 충분히 가능하실 거예요.
소스 코드에 관해서 궁금하신 점이 있다면 댓글로 알려주세요~

이번 내용 마무리

이번 포스팅에서는 파일 입출력 실습과 관련 내용을 알아봤습니다. 마지막 응용은 쉽지 않으실 거예요 ㄷㄷ
파일 입출력은 이 정도로 끝내지만 이 정도만 하실 수 있으면 다른 파일 입출력 함수도 쉽게 사용하실 수 있을 겁니다.
다음 포스팅에서는 전처리기와 매크로에 대해서 알아볼게요.

저작자 명시 필수영리적 사용 불가내용 변경 불가

저작자 명시 필수- 영리적 사용 불가- 내용 변경 불가

공감이 글에 공감한 블로거 열고 닫기

댓글쓰기 이 글에 댓글 단 블로거 열고 닫기

인쇄

C언어 정리하기 - 파일 입출력 실습 (2024)
Top Articles
Latest Posts
Article information

Author: Pres. Lawanda Wiegand

Last Updated:

Views: 6716

Rating: 4 / 5 (71 voted)

Reviews: 94% of readers found this page helpful

Author information

Name: Pres. Lawanda Wiegand

Birthday: 1993-01-10

Address: Suite 391 6963 Ullrich Shore, Bellefort, WI 01350-7893

Phone: +6806610432415

Job: Dynamic Manufacturing Assistant

Hobby: amateur radio, Taekwondo, Wood carving, Parkour, Skateboarding, Running, Rafting

Introduction: My name is Pres. Lawanda Wiegand, I am a inquisitive, helpful, glamorous, cheerful, open, clever, innocent person who loves writing and wants to share my knowledge and understanding with you.