[System Programming] 7-1장. C 표준 파일 입출력
7.1 파일 및 파일 포인터
저수준 파일 입출력과 고수준 파일 입출력
- 저수준 파일 입출력: 유닉스 커널의 시스템 호출을 사용하여 파일 입출력을 실행하며, 특수 파일도 읽고 쓸 수 있다.
- int fd = open (const char *path, int oflag, [ mode_t mode ]);
- file descriptor 리턴
- int fd = open (const char *path, int oflag, [ mode_t mode ]);
- 고수준 파일 입출력: 표준 입출력 라이브러리로 다양한 형태의 파일 입출력 함수를 제공한다.
- FILE *fopen(const char *name, const char *mode)
- 파일 구조체의 시작 주소 리턴
- FILE *fopen(const char *name, const char *mode)
시스템 호출과 C 라이브러리 함수
- 시스템 호출(System Calls)
- Unix 커널에 서비스 요청하는 호출
- UNIX man의 Section 2에 설명되어 있음
- C 함수처럼 호출될 수 있음.
- OS가 제공하는 user interface.
- Efficiency: 자원을 효과적으로 관리해야 throughput이 증가함
- Convenience: Hardware로 바꾸어 줌
- trap: user to kernal mode change
- C 라이브러리 함수(Library Functions)
- C 라이브러리 함수는 보통 시스템 호출을 포장해 놓은 함수
- 보통 내부에서 시스템 호출을 함
- 1975년에 Dennis Ritchie에 의해 작성
- ANSI C Standard Library
Unix man
Section # Topic
1 Commands available to users
실행 가능한 프로그램이나 쉘 명령어
2 Unix and C system calls
시스템 콜(커널에서 제공하는 기능)
3 C library routines for C programs
라이브러리 콜(프로그램 라이브러리 기능)
4 Special file names
특별한 파일들(일반적 /dev 디렉토리에 있는 파일 )
5 File formats and conventions for files used by Unix
파일 포맷과 /etc/passwd와 같은 파일 명명 규칙
6 Games
게임
7 Word processing packages
그 외의 여러가지 것들(매크로 패키지와 명명규칙 등을 포함
8 System administration commands and procedures
시스템 관리 명령어(일반적으로 root 유저를 위한 명령어)
9 커널 루틴[ 비 표준 ]
모르는 명령어 -> Unix man page
시스템 호출
- 시스템 호출은 커널에 서비스 요청을 위한 프로그래밍 인터페이스
- 응용 프로그램은 시스템 호출을 통해서 커널에 서비스를 요청한다.
파일
- C 프로그램에서 파일은 왜 필요할까?
- 변수에 저장된 정보들은 실행이 끝나면 모두 사라진다.
- 정보를 영속적으로 저장하기 위해서는 파일에 저장해야 한다.
- 유닉스 파일
- 모든 데이터를 연속된 바이트 형태로 저장한다.
- 바이트들을 어떻게 해석하느냐는 전적으로 프로그래머의 책임
- 파일에 4개의 바이트가 들어 있을 때 이것을 int형의 정수 데이터로도 해석할 수 있고 아니면 float형 실수 데이터로도 해석할 수 있다.
C 언어의 파일 종류
-
텍스트 파일(text file)
-
사람들이 읽을 수 있는 한글, 영문, 숫자 등의 문자들을 저장하고 있는 파일
-
여러 줄로 구성, 매 줄마다 줄의 끝을 나타내는 개행문자(‘\n’) 포함
-
텍스트 파일에서 “한 줄의 끝”을 나타내는 표현은 파일이 읽어 들여질 때, C 내부의 방식으로 변환된다.
-
-
이진 파일(binary file)
- 모든 데이터를 컴퓨터 메모리에 저장된 변수 값의 2진수 표현을 그대로 저장할 파일
- 이진 파일을 이용하여 메모리에 저장된 변수 값 형태 그대로 파일에 저장할 수 있다.
- 여전히 연속된 바이트의 저장물
- 이진 파일은 텍스트 파일과는 달리 라인들로 분리되지 않는다.
- 라인의 구분이 없다.
- 모든 데이터들은 문자열로 변환되지 않고 입출력
파일 저장 방식
- 텍스트 방식에서는 데이터를 파일에 저장하거나 파일로부터 읽을 때 ASCII 코드에 대응되는 byte 단위로 처리
- 그래서 사람이 읽을 수 있음
- 이진 방식에서는 파일에 저장하거나 파일로부터 읽을 때 데이터 형(type)에 기준 하여 처리.
- 이진 방식으로 저장된 내용을 텍스트 방식의 메모장 프로그램으로 읽으면 1 byte 단위로 읽은 데이터를 ASCII 코드의 문자로 해석하기 때문에 이상한 문자들로 출력 됨
파일 입출력
- C 언어의 파일 입출력 과정
- 파일 열기: fopen( ) 함수 사용
- 파일 입출력 : 다양한 파일 입출력 함수 사용
- 파일 닫기: fclose( ) 함수 사용
파일 열기
- 파일을 사용하기 위해서는
- 반드시 먼저 파일 열기(fopen)를 해야 한다.
- 파일 열기를 하면 FILE 구조체에 대한 포인터(주소)가 리턴되며
- FILE 포인터는 열린 파일을 나타낸다.
-
함수 fopen()
-
FILE *fopen(const char *filename, const char *mode); const char *filename//: 파일명에 대한 포인터 const char *mode//: 파일 열기 모드
-
-
예
-
FILE *fp; fp = fopen(“~/sp/text.txt", "r"); if (fp == NULL) { printf("파일 열기 오류\n"); }
fp = fopen("outdata.txt", "w"); fp = fopen("outdata.txt", "a");
-
fopen (): 텍스트 파일 열기 모드
FILE 구조체
- 파일 관련 시스템 호출
- 파일 디스크립터 (file descriptor) : file table의 index
- C 표준 입출력 함수
- fopen( ) 함수로 파일을 열면 FILE 포인터(FILE *)가 리턴됨
- 열린 파일을 가리키는 FILE 구조체에 대한 포인터
- FILE 포인터를 표준 입출력 함수들의 인수로 전달해야 함
#include <stdio.h>
- FILE 구조체
- 하나의 스트림에 대한 정보를 포함하는 구조체
- 버퍼에 대한 포인터, 버퍼 크기 …
- 파일 디스크립터, 파일 입출력 모드
- FILE 구조체: 열린 파일의 현재 상태를 나타내는 필드 변수들 특히 파일 입출력에 사용되는 버퍼 관련 변수들
typedef struct {
int cnt; // 버퍼의 남은 문자 수
unsigned char*base; // 버퍼 시작
unsigned char*ptr; // 버퍼의 현재 포인터
unsinged flag; // 파일 입출력 모드( _IOFBF, _IOLBF, _IONBUF,
// _IOEOF, _IOERR _IOREAD, _IOWRT)
int fd; // 열린 파일 디스크립터
} FILE;// FILE 구조체
파일 입출력 모드
-
100% 반드시 버퍼를 통해서만 read/write이 일어나야 한다.
-
버퍼가 line 단위로
- _IOFBF
- An abbreviation(약어) for “input/output fully buffered“
- _IOLBF
- An abbreviation for “input/output line buffered”
- _IONBUF
- An abbreviation for “input/output not buffered”;
File Open 성공 여부 체크
- File Open 성공 여부 체크 필요(read 모드의 경우 반드시 해야 함)
FILE* fp = fopen("myfile", "r");
if (fp == NULL) {
perror("Failed to open file \"myfile\"");
}
- File Open 성공 여부 체크 불필요
FILE* fp = fopen("myfile", “w");
FILE* fp = fopen("myfile", “a");
이미 열려있으니까 w 혹은 a로 열었을 것임
파일 스트림(stream)
- fopen()에 의해 파일이 열리면 이를 스트림이라고 함.
- 파일 열기를 하면 스트림이 생성됨
- 스트림에 대한 정보는 FILE 구조체에 저장되고
- FILE 구조체에 대한 포인터 (파일 포인터)가 리턴 됨
- 파일 포인터가 열린 파일(스트림)을 나타내게 됨
- 스트림은 버퍼형 파일 입출력을 위한 논리적 인터페이스
- C 언어는 키보드나 모니터를 포함한 모든 주변 장치들을 파일처럼 취급.
- 모든 입력과 출력은 스트림(stream)이라고 하는 공통된 인터페이스를 사용하여 파일로 취급되는 주변장치들과 연결되어 이루어짐
- 입력과 출력을 바이트(byte)들의 흐름으로 간주
표준 입력/출력/에러
File pointer를 통해 파일에 접근할 때도 결국은 file descriptor를 사용하여 접근하게 된다.
- 표준 I/O 스트림 (stream)
- 프로그램이 시작되면 자동으로 open되는 스트림
- stdin, stdout, stderr
- FILE*
- 스트림은 구체적으로 FILE 구조체를 통하여 구현
- FILE은 stdio.h에 정의되어 있다.
#include <stdio.h>
파일 닫기
- 파일을 열어서 사용한 후에는 파일을 닫아야 한다.
- int fclose(FILE *fp );
- fp는 fopen 함수에서 받았던 포인터
- 닫기에 성공하면 0, 오류일 때는 EOF( -1)를 리턴한다.
- 예
- fclose(fp);
7.2 텍스트 파일
텍스트 파일 (text file)
- 텍스트 파일은 사람이 읽을 수 있는 텍스트가 들어 있는 파일
- (예) C 프로그램 소스 파일이나 메모장 파일
- 텍스트 파일은 연속된 문자를 ASCII 코드를 이용하여 저장
- 텍스트 파일은 연속적인 라인들로 구성
파일 입출력 함수
문자 단위 입출력
- fgetc() 함수와 fputc() 함수
- 파일에 문자 단위 입출력을 할 수 있다.
int fgetc(FILE *fp); //getc()
- getc 함수는 fp가 지정한 파일에서 한 문자를 읽어서 리턴한다.
- 파일 끝에 도달했을 경우에는 EOF(-1)를 리턴한다.
- int fputc(int c, FILE *fp); //put(c)
- putc 함수는 파일에 한 문자씩 출력하는 함수
- 리턴값으로 출력하는 문자 리턴
- 출력시 오류가 발생하면 EOF(-1) 리턴
- #define getchar () getc(stdin)
- #define putchar (x) putc(x, stdout)
cat.c
#include <stdio.h>
/* 텍스트 파일 내용을 표준출력에 프린트 */
int main(int argc, char *argv[])
{
FILE *fp;
int c;
if (argc < 2)
fp = stdin; // 명령줄 인수가 없으면 표준입력 사용
else fp = fopen(argv[1],"r"); // 읽기 전용으로 파일 열기
c = getc(fp); // 파일로부터 문자 읽기
while (c != EOF) { // 파일끝이 아니면
putc(c, stdout); // 읽은 문자를 표준출력에 출력
c = getc(fp); // 파일로부터 문자 읽기
}
fclose(fp);
return 0;
}
copy.c
#include <stdio.h>
/* 파일 복사 프로그램 */
int main(int argc, char *argv[])
{
char c;
FILE *fp1, *fp2;
if (argc !=3) {
fprintf(stderr, "사용법: %s 파일1 파일2\n", argv[0]);
return 1;
}
fp1 = fopen(argv[1], "r");
if (fp1 == NULL) {
fprintf(stderr, "파일 %s 열기 오류\n", argv[1]);
return 2;
}
fp2 = fopen(argv[2], "w");
while ((c = fgetc(fp1)) != EOF)
fputc(c, fp2);
fclose(fp1);
fclose(fp2);
return 0;
}
기타 파일 관련 함수
- int feof(FILE *fp)
- 파일 포인터 fp가 파일의 끝을 탐지하면 0이 아닌 값을 리턴하고
- 파일 끝이면 0을 리턴 한다.
- int ungetc(int c, FILE *p)
- c에 저장된 문자를 입력 스트림에 반납한다.
- 마치 문자를 읽지 않은 것처럼 파일 위치 지정자를 1 감소시킨다.
- int fflush(FILE *fp)
- 아직 기록되지 않고 버퍼에 남아 있는 데이터를 fp가 가리키는 출력 파일 에 보낸다. (하드디스크에 있는 파일에다가)
- 버퍼 비우기 기능을 수행하는 함수이다.
줄 단위 입출력
- fgets() 함수와 fputs() 함수 // gets() 함수와 puts() 함수 참조
- 텍스트 파일에서 한 줄씩 읽거나 쓸 수 있다.
- char* fgets(char *s, int n, FILE *fp);
- 파일로부터 한 줄을 읽어서 문자열 포인터 s에 저장하고 s를 리턴
- 개행문자(‘\n’)나 EOF를 만날 때까지 파일로부터 최대 n-1 개의 문자를 읽고 읽어온 데이터의 끝에는 NULL 문자를 붙여준다. (개행문자 포함)
- 파일을 읽는 중 파일 끝 혹은 오류가 발생하면 NULL 포인터 리턴.
- int fputs(const char *s, FILE *fp);
- 문자열 s를 파일 포인터 fp가 가리키는 파일에 출력
- NULL 문자는 출력하지 않음.
- 성공적으로 출력한 경우에는 출력한 바이트 수를 리턴
- 출력할 때 오류가 발생하면 EOF 값을 리턴
- 문자열 s를 파일 포인터 fp가 가리키는 파일에 출력
line.c
#include <stdio.h>
#define MAXLINE 80
/* 텍스트 파일에 줄 번호 붙여 프린트한다. */
int main(int argc, char *argv[])
{
FILE *fp;
int line = 0;
char buffer[MAXLINE];
if (argc != 2) {
fprintf(stderr, "사용법:line 파일이름\n");
exit(1);
}
if ( (fp = fopen(argv[1],"r")) == NULL)
{
fprintf(stderr, "파일 열기23 line++";
exit(2);
}
while (fgets(buffer, MAXLINE, fp) != NULL) { // 한 줄 읽기
line++;
printf("%3d %s", line, buffer); // 개행 문자 스트링에 포함
}
exit(0);
}
포맷 입출력
- fprintf() 함수
- printf() 함수와 같은 방법으로 파일에 데이터를 출력할 수 있다.
- fscanf() 함수
- scanf() 함수와 같은 방법으로 파일로부터 데이터를 읽어 들일 수 있다.
- int fprintf(FILE *fp, const char *format, …);
- fprintf 함수의 첫 번째 인수 fp는 츨력한 파일에 대한 FILE 포인터
- 두 번째부터의 인수는 printf 함수와 동일
- int fscanf(FILE *fp, const char *format, …);
- fscanf 함수의 첫 번째 인수 fp는 입력받을 파일에 대한 FILE 포인터
- 두 번째부터의 인수는 scanf 함수와 동일
printf()를 이용한 출력
형식지정자
- %d : 부호 있는 10진수로 출력
- %u : 부호 없는 10진수로 출력
- %x : 부호 없는 16진수로 출력, 소문자로 표기
- %f : 소수점 고정 방식으로 출력 (123.4)
- %e : 지수 표기 형식으로 출력 (1.23e+3)
scanf()를 이용한 입력
- 문자열 형태의 입력을 형식 지정자에 의해 변환한다.
- %d : 10진수로 입력 받음
- %o : 8진수로 입력 받음
- %x : 16진수로 입력 받음
- %c : char형으로 입력받음
- %s : 공백 문자가 아닌 문자부터 공백 문자가 나올 때 까지를 문자열로 변환하여 입력 받음
fprintf.c (텍스트 파일에 쓰기)
#include <stdio.h>
#include "student.h"
/* 학생 정보를 읽어 텍스트 파일에 저장핚다. */
int main(int argc, char* argv[]) {
struct student rec;
FILE *fp;
if (argc != 2) {
fprintf(stderr, "사용법: %s 파일이름\n", argv[0]);
return 1;
}
fp = fopen(argv[1], "w");
printf("%-9s %-7s %-4s\n", "학번", "이름", "점수");
/* -9s -> (-): 필드폭은 9자리, 출력값을 왼쪽 정렬 */
while (scanf("%d %s %d", &rec.id, rec.name, &rec.score)==3)
fprintf(fp, "%d %s %d ", rec.id, rec.name, rec.score);
fclose(fp);
return 0;
}
$ fprint stud.txt
학번 이름 점수
14010001 박연아 96
14010002 김연아 86
----
fscanf.c(텍스트 파일에서 읽기)
#include <stdio.h>
#include "student.h"
/* 텍스트 파일에서 학생 정보를 읽어 프린트핚다. */
int main(int argc, char* argv[]) {
struct student rec;
FILE *fp;
if (argc != 2) {
fprintf(stderr, "사용법: %s 파일이름\n", argv[0]);
return 1;
}
fp = fopen(argv[1], "r");
printf("%-9s %-7s %-4s\n", "학번", "이름", "점수");
while (fscanf(fp,"%d %s %d", &rec.id, rec.name, &rec.score)==3)
printf("%10d %6s %6d\n", rec.id, rec.name, rec.score); // 모니터에 프린트
fclose(fp);
return 0;
}
$ fscanf stud.txt
학번 이름 점수
14010001 박연아 96
14010002 김연아 86
----
7.3 이진 파일
이진 파일 특징
-
이진 데이터가 직접 저장되어 있는 파일
-
컴퓨터에서 데이터를 표현하는 방식 그대로 저장
-
이진 파일은 텍스트 파일과는 달리 라인들로 분리되지 않는다.
-
모든 데이터들은 문자열로 변환되지 않고 입출력
-
이진 파일은 특정 프로그램에 의해서만 판독이 가능
- (예) C 프로그램 실행 파일, 사운드 파일, 이미지 파일
-
chunk
-
line은 고정 사이즈가 아니다.(마구 입력한 다음에 엔터 쳐야 끝)
-
fread에서 buf로 받으면 고정된 사이즈 만을 받아야 한다.
-
fopen(): 이진 파일 열기
블록 단위 입출력
- fread()와 fwrite()
- 한 번에 일정한 크기의 데이터를 이진 파일에 읽거나 쓰기 위한 입출력 함수
- int fread(void *buf, int size, int n, FILE *fp);
- fp가 가리키는 파일에서 size 크기의 블록(연속된 바이트)을 n개 읽어서 버퍼 포인터 buf가 가리키는 곳에 저장
- 읽어온 블록의 개수를 리턴
- int fwrite(const void *buf, int size, int n, FILE *fp);
- 파일 포인터 fp가 지정한 파일에 버퍼 buf에 저장되어 있는 size 크기의 블록(연속된 바이트)을 n개 기록
- 성공적으로 출력한 블록 개수를 리턴
fwrite() 함수
줄이 바뀌는 것이 입출력의 단위
블록 입출력
- 기본 아이디어
- 어떤 자료형의 데이터이던지 그 데이터를 연속된 바이트로 해석해서 파일에 저장
- 파일에 저장된 데이터를 연속된 바이트 형태로 읽어서 원래 변수에 순서대로 저장하여 원래 데이터를 그대로 복원
- 예: rec 저장
struct student rec;
FILE *fp = fopen("stfile", "wb");
…
fwrite(&rec, sizeof(rec), 1, fp);
student.h
struct student {
int id;
char name[20];
short score;
};
stcreate1.c (이진 파일에 쓰기)
#include <stdio.h>
#include "student.h"
int main(int argc, char* argv[])
{
struct student rec;
FILE *fp;
if (argc != 2) {
fprintf(stderr, "사용법: %s 파일이름\n",argv[0]);
exit(1);
}
fp = fopen(argv[1], "wb");
printf("%-9s %-7s %-4s\n", "학번", "이름", "점수");
while (scanf("%d %s %d", &rec.id, rec.name, &rec.score) == 3)
fwrite(&rec, sizeof(rec), 1, fp);
fclose(fp);
exit(0);
}
$ stcreate1 stdb1
학번 이름 점수
14010001 박연아 96
14010002 김연아 86
----
stdb1 -> 이진 파일
fprint.c - text 파일로 저장
#include <stdio.h>
#include "student.h"
/* 학생 정보를 읽어 텍스트 파일에 저장핚다. */
int main(int argc, char* argv[]) {
struct student rec;
FILE *fp;
if (argc != 2) {
fprintf(stderr, "사용법: %s 파일이름\n", argv[0]);
return 1;
}
fp = fopen(argv[1], "w");
printf("%-9s %-7s %-4s\n", "학번", "이름", "점수");
/* -9s (-): 필드폭은 9자리, 출력값을 왼쪽 정렬 */
while (scanf("%d %s %d", &rec.id, rec.name, &rec.score)==3)
fprintf(fp, "%d %s %d ", rec.id, rec.name, rec.score);
fclose(fp);
return 0;
}
fread() 함수
stprint.c (이진 파일에서 읽기)
#include <stdio.h>
#include "student.h"
/* 파일에 저장된 모든 학생 정보를 읽어서 츨력핚다. */
int main(int argc, char* argv[])
{
struct student rec;
FILE *fp;
if (argc != 2) {
fprintf(stderr, "사용법: %s 파일이름\n", argv[0]);
return 1;
}
if ((fp = fopen(argv[1], "rb")) == NULL ) {
fprintf(stderr, "파일 열기 오류\n");
return 2;
}
printf("-----------------------------------\n");
printf("%10s %6s %6s\n", "학번", "이름", "점수");
printf("-----------------------------------\n");
while (fread(&rec, sizeof(rec), 1, fp) > 0)
if (rec.id != 0)
printf("%10d %6s %6d\n", rec.id, rec.name, rec.score);
printf("-----------------------------------\n");
fclose(fp);
return 0;
}
$ stprint stdb1
학번 이름 점수
14010001 박연아 96
14010002 김연아 86
----
댓글남기기