C & C++/디딤돌 C언어

Part 27. 파일 입출력 - fread, fwrite, fseek, ftell 함수

언휴 2024. 1. 18. 13:40

Part 27. 파일 입출력 - fread, fwrite, fseek, ftell 함수

97. 바이너리로 파일 입출력

C언어 파일 입출력, fread, fwrite

이번에는 메모리를 덤프하는 입출력 함수를 살펴볼게요.

size_t fread(void * buf, size_t element_size, size_t count, FILE * fp);
size_t fwrite(void * buf, size_t element_size, size_t count, FILE * fp);

메모리를 덤프하는 입출력 함수는 내용을 그대로 파일 입출력하는 함수들이예요.
메모리 덤프하는 입출력 함수는 fread와 fwirte 함수가 있어요.
두 함수는 입출력 방향이 메모리에서 파일스트림과 파일스트림에서 메모리라는 점이 다를 뿐이며 함수 원형이 같아요.

두 함수의 입력 인자는 data를 읽거나 쓸 buf의 주소, 요소 크기, 요소 개수, 파일 스트림이예요.
그리고 반환 값은 입출력한 요소 개수를 반환해요.
만약 파일에 있는 내용이 요청한 개수보다 작으면 요청한 개수보다 반환 값이 작을 수 있어요.

회원 관리 프로그램을 예로 들어볼게요.
프로세스가 종료하기 전에 회원 데이터를 저장하고 시작할 때 저장한 회원 데이터를 로딩한다고 가정할게요.
회원 데이터를 배열로 관리한다면 fwrite 한 번 호출로 배열에 있는 전체 회원 정보를 파일에 저장할 수 있어요.
그리고 로딩도 fread 한 번 호출로 가능해요.

#define MAX_MEMBER    50
...중략...
Member members[MAX_MEMBER];
...중략...
void Save()
{
    ...중략...
    fwrite(members,sizeof(Member),MAX_MEMBER,fp);
    ...중략...
};
...중략...
void Save()
{
    ...중략...
    fread(members,sizeof(Member),MAX_MEMBER,fp);
    ...중략...
};

다음은 회원 구조체를 원소로 하는 배열을 파일에 쓰고 읽어오는 것을 테스트 하는 코드예요.

◈ 회원 구조체를 원소로 하는 배열을 파일에 쓰기 및 읽기 테스트

#include <stdio.h>
#define FNAME   "data.txt"
#define MAX_MEMBER    4
#define MAX_NAME_LEN 20
typedef struct _Member Member;
struct _Member
{
    char name[MAX_NAME_LEN+1];
    int num;
};
 
Member src[MAX_MEMBER]=
{
    {"홍길동",1},{"강감찬",4},{"을지문덕",2},{"김구",3}
};
 
Member dest[MAX_MEMBER];
void Save();//src의 회원 정보 파일에 쓰기
void Load();//파일에 회원 정보를 dest로 읽기
void List();//dest 내용 출력
int main()
{
    Save();
    Load();
    List();
    return 0;
}
 
void Save()
{
    FILE *fp = 0;
    fopen_s(&fp, FNAME,"w");
 
    if(fp)
    {
        fwrite(src,sizeof(Member),MAX_MEMBER,fp);
        fclose(fp);
    }
    else
    {
        printf("데이터 저장 실패\n");
    }
}
 
void Load()
{
    FILE *fp = 0;
    fopen_s(&fp, FNAME,"r");
    if(fp)
    {
        fread(dest,sizeof(Member),MAX_MEMBER,fp);
        fclose(fp);
    }
    else
    {
        printf("처음으로 회원 관리 프로그램을 사용을 환영합니다.\n");
    }
}
 

void List()
{
    int i = 0;
    for(i=0;i<MAX_MEMBER; i++)
    {
        printf("이름:%s 번호:%d\n",dest[i].name,dest[i].num);
    }
}

◈ 실행 화면

이름:홍길동 번호:1
이름:강감찬 번호:4
이름:을지문덕 번호:2
이름:김구 번호:3

fwrite 함수를 이용하여 출력한 후에 메모장 프로그램으로 내용을 확인하면 이상한 값이 쓰여진 것처럼 보여요.
메모장 프로그램은 ASCII 문자로 읽어와서 출력하는 프로그램이여서 그렇게 보이는 거예요.
만약 출력한 내용을 다른 프로그램으로 확인하길 원한다면 ASCII 문자로 출력하는 함수를 사용하세요.
하지만 프로그램의 수행 중의 내용을 파일에 보관하고 다음 실행 시에 로드하여 사용할 목적이라면 메모리 덤프로 입출력하는 것이 보다 효과적이예요.

98. 파일 입출력 – ftell, fseek

C언어 파일 입출력, ftell 함수, fseek 함수

이 외에도 입출력 작업의 위치를 확인하는 ftell 함수와 작업 위치를 변경하는 fseek 함수 등이 있어요.

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

ftell 함수는 입력 인자로 입출력 작업 FILE *를 전달하면 작업 위치를 반환하는 간단한 함수예요.
fseek함수는 FILE *외에도 세번째 인자로 기준이 되는 매크로 상수와 두번째 인자로 기준에서의 상대적 위치(음수도 가능)를 전달하여 작업 위치를 설정할 수 있어요.
세번째 인자로 들어가는 인자는 시작 위치를 의미하는 매크로 상수 SEEK_SET, 현재 위치를 의미하는 SEEK_CUR, 끝을 의미하는 SEEK_END가 올 수 있죠.

#define SEEK_CUR    1
#define SEEK_END    2
#define SEEK_SET    0

다음 코드는 먼저 회원 데이터를 파일 스트림에 출력한 후에 닫아요.
그리고 찾고자 하는 순서 번호를 입력받아 회원 정보를 출력하는 코드예요.
간단하게 주석을 달아놓았으니 확인해 보세요.

//회원 데이터를 파일에 출력한 후 닫고 찾고자 하는 순서 번호를 입력받아 회원 정보를 출력  
#include <stdio.h>
 
#define MAX_NAME_LEN    20
typedef struct{
    char name[MAX_NAME_LEN+1];
    int age;
}Member;
 
#define MAX_MEMBERS 10
int main (void)
{
    FILE * fp;
    int n;
    Member members[MAX_MEMBERS]=
    {
        {"홍길동",20},{"강감찬",15},{"을지문덕",22},{"이순신",19},{"김구",30},
        {"안중근",30},{"박찬호",35},{"김연아",17},{"아이유",20},{"주니엘",20}        
    };
    Member member;     
 
    fopen_s (&fp,"data.txt","wb");//쓰기 모드로 파일 열기
    if (fp==NULL) 
    {
        perror ("error fopen");
        return 0;
    }
    //출력 파일 스트림에 회원 정보 출력
    if(fwrite(members,sizeof(Member),MAX_MEMBERS,fp) != MAX_MEMBERS)
    {
        printf("출력 오류\n");
        return 0;
    }
    fclose (fp);//출력 파일 스트림 닫기
 
    printf("회원 순서 번호 입력(1~%d)  : ",MAX_MEMBERS);
    scanf_s("%d",&n);//조회할 회원 순서 번호 입력
    if((n<1)||(n>MAX_MEMBERS))//순서 번호가 범위를 벗어날 때
    {
        printf("입력 오류\n");
        return 0;
    }
 
    fopen_s (&fp,"data.txt","rb");//읽기 모드로 파일 열기
    if (fp==NULL)
    {
        perror ("error fopen");
        return 0;
    }
 
    fseek(fp,sizeof(Member)*(n-1),SEEK_SET);//파일 position 이동
    fread(&member,sizeof(Member),1,fp);  //회원 데이터 읽기
    printf("이름: %s 번호: %d\n",member.name,member.age);//회원 데이터 출력
    
    fclose (fp);//입력 파일 스트림 닫기
    return 0;
}

출력

회원 순서 번호 입력(1~10) : 10
이름: 주니엘 번호: 20

파일 입출력 관련 함수에 대해 보다 깊이있는 학습을 원하는 분들은 유닉스 시스템 프로그래밍에 관한 서적을 보세요.
그리고 파일 입출력 기능을 제공하는 다양한 형태의 프로그램을 작성해 보세요.
프로그램에서 관리하는 데이터의 논리적 메모리 구조를 그려본 후에 입출력 기능을 구현하는 습관을 갖어보세요.

99. 정리하기 – 파일 입출력 (95~98)

회원 관리 프로그램 실습한 프로그램에 파일 입출력 기능을 추가해 보세요.