IT/C and C++

fseek(), ftell(), fread(), fwrite()

KSI 2005. 6. 21. 19:21

2> fseek(), ftell()

-----------------------------------------------------------------------

우리는 모니터 화면에 커서가 있는것을 안다. 이것은 텍스트 모드에

서 뿐만 아니라 그래픽 모드에서도 마찬가지 였다. 글자나 점 하나를 모니터

의 특정한 곳에 찍고 싶으면, 이 커서를 그 곳에다 갖다 놓고 그다음 찍는

행위를 해야한다.

이와같은 개념은 "파일"을 취급할 때도 마찬가지이다. 하지만 파일

은 하드디스크에 들어 있는 것이기 때문에, 모니터처럼 고정된 좌표를 가지

지 못한다는 것에 큰 차이가 있다.

언젠가 파일을 "두루마리 편지"라고 비유한 적이 있는데, 이제 현대

식으로 다시 비유를 해보면, 도트 프린터의 "연속용지"에 파일이 전부 인쇄

된 것과 유사하다고 생각해 볼 수 있다. 아시다시피 이 상태에서는 파일의

크기(프린트 용지의 길이)가 고정적일 수가 없다. 어떤 파일은 한장짜리가

될 수도 있고, 어떤 것은 베게로 베고 잘 수 있을 정도로 두꺼울 수도 있다.

 

파일을 "읽기 위해" fopen() 하면 이 "파일 커서"(사실 이런 용어는

안쓰지만 이해를 돕기위해서 씁니다)는 인쇄된 연속용지 "첫장의 제일 첫글

자"를 가리키고 있다. 이건 어디서나 상식이다.

그리고 이 파일 커서를 움직이려면 fseek()를 쓴다. 이 파일 커서는

돌아 다닐 수 있는 곳이 파일의 크기에 따라 가변적이므로 모니터에서 쓰듯

이 좌표를 줄 수 없다는 것은 당연하다. 그래서 돌아 다니는 방법이 조금 복

잡하다. 말로 하면, "현재 위치에서 5글자 뒤로 가"를 다음과 같이 쓴다.

 

fseek(fp,5L,SEEK_CUR) <--- "현재 위치"에서 5글자 뒤로 가

fseek(fp,5L,SEEK_SET) <--- "파일 제일 처음 글자"에서 5글자 뒤로 가

fseek(fp,-5L,SEEK_END) <--- "파일 제일 끝 글자"에서 5글자 앞으로 가

(뒤로는 더이상 못간다. 상식 !)

 

여기서 "뒤로 " 라는 얘기는 한글자씩 오른쪽 옆으로 움직이는걸 얘기

한다. 즉 우리가 책을 읽어나가는 방향이다. 이 때 물론, 한 줄 끝에 이르면

다음 줄 첫 글자로 이동한다.

이때 "SEEK_" 로 시작되는 이름들은 대문자로 쓴 걸 보아 벌써 어디선

가 정의 되있다는 것을 알것이다. 따라서 우리는 무조건 외어서 써야하는데

그 의미는 위에서 얘기한데로 다음과 같다.

--------------------------------------------------------

SEEK_CUR --> "현재(current) 위치에서"를 의미한다.

SEEK_SET --> ""파일 제일 첫글자 에서"를 의미한다.

SEEK_END --> "파일 제일 끝 글자 에서"를 의미한다.

---------------------------------------------------------

 

<숙제> 이런 상수들은 전부"헤더 파일"에 선언되 있다는 것을 알 수 있을 것

입니다. 어떤 헤더 파일에 정의되 있는지 찾아 보세요. (노턴 유틸

리티를 가지고 있는 분들은 TS( Text Search) 명령을 쓰세요.)

그리고 만일, 아직도 이런 툴(tool)을 안가지고 있는 분들은 빨리

구해서 활용을 해야 합니다. 프로그래머들에게는 이런 툴은 필수적

입니다.

 

실제로 파일을 열어놓고 이 파일 커서가 돌아다니기 시작하면 조금

정신이 없다. 왜냐하면 이건 모니터상에 깜박이는게 아니고 우리 머리속에서

만 움직이는 것이다. 따라서 누가 조금만 정신을 어지럽히면 현재 커서위치

를 놓치고 만다.

큰 파일을 취급할땐, 연속용지로 치면 길이가 무척 길어서 경부고속

도로를 쭉 따라가며 펼쳐 놔야한다. 그러다가 한 순간에 커서를 놓치면 이

놈이 천안에 가있는지 대전에 가있는지 알기 어렵다. 물론 그렇다고 전혀 못

찾는건 아니다.

이 때 "커서야 어디있니 !" 이렇게 외치는게 ftell() 이다. 쓰는법

을 정확히 알아보기 위해 [439쪽]을 참조하자. 이 사용법은

더이상 내가 설명을 안해도 잘 아실 줄로 믿는다. 예를 들면 다음과 같이 쓰

라는 얘기다.

 

#include <stdio.h>

main()

{

int i;

long ll;

FILE *from;

 

ll = ftell(from);

}

 

ftell() 의 대답(리턴 값)은 long형 이므로, long 타입 변수 ll 이

받은게 당연하다. 또 사용법에서 언급하는 ftell(FILE *fp)의 "FILE *fp"는

from 변수의 타입과 같음을 보라.

그러면 여기서, "왜 long형으로 대답하는지" 그 이유를 를 짚고 넘

어가기로 하자. "어디있니 ?"의 대답이 long 형 이면 4 바이트(32 비트) 크

기이다. 즉 4G 바이트 만큼 멀리갈 수 있다는 얘기다.

여러분의 하드디스크가 얼마나 큰지 모르겠는데, 나는 지금 420M(메

가) 짜리 하드 디스크를 쓰고 있다. 이것은 요즘 유행으로 봐서는 그다지 큰

것도 아닌 것 같다. 만일 이런 하드디스에 하나 짜리 파일이 통째로 들어가

있어도 ftell()은 가볍게 대답할 수 있는 거리다.

단 여기서 주의할 것은, 대답하는 거리는 무조건 "파일의 처음"에서

부터 떨어진 거리를 말한다. 여기에는 빨간 형광펜으로 밑줄을 "쫙" 그어두

기로 하자. 그러나 곰곰이 생각해보면 너무 당연한 얘기이기도 하다.

왠가하면 아마 지금도 교통방송에서는 어느 여자 아나운서의 낭랑한

목소리를 들을 수 있을테니까.

 

"경부 고속도로, "서울 기점 200Km 지점"에서 두 차가 장렬하게

닿았습니다."

 

아마도 그 여자 아나운서는 "C 언어"를 모를테지만 그래도 사고지점을 우리

에게 알려주는 방법은 ftell()의 "리턴값"과 같은 것이다.

"서울 기점 200Km" 면 아마 대전 근처가 아닐까 짐작하는데, 아무리

그래도 "대전 북방 2Km"따위로 얘기하지 않고, 기준점을 "서울"로 잡은 것이

다. 그게 "파일의 처음 위치"이다.

이제 다음의 예를 읽어보면서, "파일 커서"가 (개구리 처럼) 어디로

튀는지 잘 쫓아가 보기로 하자.

------------------------------------

#include <stdio.h>

 

main()

{

char c;

int i;

FILE *fp;

 

fp=fopen("a_to_z", "w+b");

 

for (c='A' ; c<='Z' ; c++)

fputc(c, fp);

 

fseek(fp, 0, SEEK_SET);

 

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

printf("\n%c : %d", fgetc(fp), ftell(fp) );

putchar('\n');

 

fseek(fp, -5, SEEK_END);

 

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

printf("\n%c : %d", fgetc(fp), ftell(fp) );

putchar('\n');

 

fseek(fp, -10, SEEK_CUR);

 

for (i=0 ; i<3 ; i++) {

printf("\n%c : %d", fgetc(fp), ftell(fp) );

fseek(fp, -2, SEEK_CUR);

}

fclose(fp);

}

<문제> 답을 미리 적어보고 실행해보세요.

 

-----------------------------------------------------------------------

3> fread(), fwrite()

-----------------------------------------------------------------------

이 함수들은 특정 자료형을 글자 단위로 입출력하지 않고 "덩어리

단위"로 읽고 쓴다. 다음의 예를 보자.

main()

{

FILE *fp;

char name[20] = "Lim chungha";

 

fp = fopen("xxx.xxx","w");

fwrite(name, 20,1,fp); <-- (요기)

}

(요기)를 말로 하면 "name[] 에 있는 데이타들을 파일 fp에 쓰는

데, 덩어리는 20 바이트가 한묶음이고 그런걸 한 덩어리 쓰라"는 얘기다.

즉, 이 경우에는 한 사람의 이름을 파일에 쓰라는 얘기이다.

여기서 name[]의 크기는 사람 이름을 위한 길이를 임의로 넉넉하게

[20] 바이트로 잡았다. fread() 도 마찬가지다.

main()

{

FILE *fp;

char name[20];

 

fp = fopen("xxx.xxx","r");

fread(name, 20,1,fp); <-- (요기)

}

fread()를 실행하기 전에 name[20]이라는 배열에는 아무 값도 안들

어가 있다. 그러나 위 fwrite()에서 임 청하의 이름을 xxx.xxx 에 성공적으

로 썼다면 위의 (요기)를 실행하고 나면 name[]에 임청하라는 이름이 들어

가 있을 것이다.

이것도 말로 하면 "파일 fp 에서 읽는데 쓸 곳은 name (즉 name[]

배열의 첫집)이다. 크기는 20 바이트짜리 덩어리 한 묶음이다"

이제 다음의 예제를 보자. 친구의 이름과 전화번호를 입력받아 파일에

저장하고 화면에 보여준다.

------------------------------

#include <stdio.h>

main(int argc, char *argv[])

{

int c;

FILE *fp;

struct {

char name[20];

char tel_no[20];

} entry;

 

char yn;

 

clrscr();

/*------------< write record to file >--------------*/

if((fp = fopen("friends.tel","w")) == NULL) {

printf(" can't open ..");

exit(1);

}

 

do {

printf("\n 이름 : ");

gets(entry.name);

printf(" 전화번호 : ");

gets(entry.tel_no);

fwrite(&entry, sizeof(entry),1,fp);

 

printf("\n 계속할까요 (y/n) ? ");

yn = getch();

} while ( yn == 'y' || yn == 'Y');

 

fclose(fp);

 

/*----------< display records >--------------*/

clrscr();

 

if((fp = fopen("friends.tel","r")) == NULL) {

printf(" Can't open file \n");

putch(0x07);

exit(1);

}

while(fread(&entry, sizeof(entry),1,fp) == 1) {

printf(" %s %s\n", entry.name, entry.tel_no);

}

fclose(fp);

getch();

 

}