22.4 Character I/O
글자 한개를 읽고 쓰는 함수들이다. 이 함수들은 text stream, binary stream 모두에서 잘 작동한다.
Output Functions
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
putchar는 하나의 문자를 stdout 스트림에 쓴다.
putchar(ch); /* writes ch to stdout */
fputc와 putc는 putchar의 더 일반적인 버전으로 하나의 문자를 임의의 스트림에 쓴다.
fputc(ch, fP); /* writes ch to fp */
putc(ch, fp); /* writes ch to fp */
putc와 fputc는 동일한 작업을 하지만, putc는 보통 매크로로 구현되어 있다(함수로도 구현되어 있다). fputc는 오직 함수로만 구현되어 있다. 따라서 putc가 fputc보다 빠르고 더 선호된다.
putchar 자체가, 보통 putc를 이용한 매크로로 정의되기도 한다.
#define putchar(c) putc((c), stdout)
C 표준에서는 putc 매크로가 stream 인자를 두 번 이상 측정하는 것을 허용하고 있다. 하지만 fputc에서는 그렇지 않다. putc는 더 빠르지만, fputc는 매크로가 가지고 있는 잠재적인 문제점으로부터 자유롭다.
http://stackoverflow.com/questions/14008907/fputc-vs-putc-in-c
읽기 에러가 발생하면, 세 함수 모두 스트림의 error indicator를 set하고 EOF를 리턴한다. 그 외에는 모두 쓰인 글자를 리턴한다.
Input Functions
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);
int ungetc(int c, FILE *stream);
getchar는 stdin 스트림으로부터 문자 한개를 읽는다.
ch = getchar(); /* reads a character from stdin */
fgetc와 getc는 임의의 스트림으로부터 문자 한개를 읽는다.
ch = fgetc(fp); /* reads a character from fp */
ch = getc(fp); /* reads a character from fp */
세 함수는 모두 문자를 unsigned char 형식의 값으로 간주한다(그리고 리턴하기 전에 int 형식으로 변환해 리턴한다). 그 결과, 리턴 값은 EOF를 제외하면 절대로 음의 값을 리턴하지 않는다.
getc와 fgetc의 차이는 putc와 fputc의 차이와 유사하다. getc는 보통 매크로로 구현되어 있다(함수로도 구현되어 있다). fgetc는 항상 함수로만 구현되어 있다. getchar도 보통 매크로로 구현되어 있다.
#define getchar() getc(stdin)
보통은 getc를 fgetc보다 선호한다. 하지만 getc가
fgetc, getc, getchar 함수도 에러가 발생했을 때 똑같은 행동을 한다. 파일의 끝이 되면, 스트림의 end-of-file indicator를 set하고 EOF를 리턴한다. 만약 읽기 에러가 발생한 경우, 스트림의 error indicator를 set하고 EOF를 리턴한다. 둘 중 어떤 문제가 발생했는지 알기 위해, feof와 ferror를 호출해야 한다.
fgetc, getc, getchar의 가장 흔한 사용법은 파일에서 end-of-file이 나타날 때까지 문자를 한개씩 읽는 것이다. 다음 while loop가 그런 목적을 위해 사용된다.
while ((ch = getc(fp)) != EOF) {
...
}
fgetc, getc, getchar의 리턴 값을 언제나 char 변수가 아닌 int 형식 변수에 저장해야 한다. char 변수에 EOF를 테스트하는 것은 틀린 결과를 낳을 수 있다.
ungetc 함수는 스트림에서 읽은 문자를 되돌리고 스트림의 end-of-file indicator를 clear한다. 이것은 우리가 입력 도중 "미리보기" 문자를 필요로 할 때 유용하다. 예를 들어, 연속된 숫자를 읽기 위해, 첫번째 nondigit에서 멈추려면 다음과 같이 쓴다.
while (isdigit(ch = getc(fp))) {
...
}
ungetc(ch, fp); /* pushes back last character read */
읽기 작업을 방해하지 않으면서 ungetc를 연속 호출해서 되돌릴 수 있는 문자의 갯수는 implementation과 스트림의 종류에 따라 다르다. 첫번째로 호출한 ungetc만 성공이 보장된다. file-positioning function(fseek, fsetpos, rewind) 호출시 pushed-back character를 잃어버린다.
/*
fcopy.c
to copy the file from f1.c to f2.c, use the command
fcopy f1.c f2.c
*/
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
FILE *source_fp, *dest_fp;
int ch;
if (argc != 3) {
fprintf(stderr, "usage: fcopy source dest\n");
exit(EXIT_FAILURE);
}
if ((source_fp = fopen(argv[1], "rb")) == NULL) {
fprintf(stderr, "Can't open %s\n", argv[1]);
exit(EXIT_FAILURE);
}
if ((dest_fp = fopen(argv[2], "wb")) == NULL) {
fprintf(stderr, "Can't open %s\n", argv[2]);
fclose(source_fp);
exit(EXIT_FAILURE);
}
while ((ch = getc(source_fp)) != EOF)
putc(ch, dest_fp);
fclose(source_fp);
fclose(dest_fp);
return 0;
}
22.5 Line I/O
여기서 다를 함수들은 'lines'를 읽고 쓰는 함수들이다. 대부분 바이너리 스트림에서 사용하는것도 legal하지만, 대부분 텍스트 스트림에서 쓰인다.
Output Functions
int fputs(const char * restrict s,
FILE * restrict stream);
int puts(const char *s);
puts 함수는 13.3에서 다룬 바 있다. string of characters를 stdout에 쓴다.
puts("Hi, there!"); /* writes to stdout */
문자열의 문자들을 쓴 이후, puts는 언제나 개행 문자를 더한다.
fputs는 puts의 더 일반적인 버전이다. 두번째 인자는 output을 어떤 스트림에 쓸 지를 지정한다.
fputs("Hi, there!", fp); /* writes to fp */
puts와 달리, fputs 함수는 문자열의 끝에 추가로 개행문자를 더하지 않는다.
두 함수 모두 에러 발생시에는 EOF를, 다른 경우에는 음이 아닌 정수를 리턴한다.
Input Functions
char *fgets(char * restrict s, int n,
FILE * restrict stream);
char *gets(char *s);
gets 함수는 13.3에서 다룬 바 있다. stdin으로부터 한 줄의 라인을 읽는다.
gets(str); /* reads a line from stdin */
gets는 문자들을 하나씩, 개행 문자를 읽을 때까지 읽어서, str이 가리키는 배열에 저장한다. 개행 문자는 버려진다.
fgets 함수는 gets의 일반적인 버전으로 어떤 스트림에서든 읽을 수 있다. 또한 fgets는 gets보다 더 안전한데, 읽어들일 문자의 개수를 제한하기 때문이다. str이 문자의 배열이라 가정할 때 다음과 같이 fgets를 사용할 수 있다.
fgets(str, sizeof(str), fp); /* reads a line from fp */
fgets는 첫번째 개행문자를 읽거나 sizeof(str) - 1개의 문자를 읽으면(둘 중 한 조건을 만족하면) 문자 읽기를 멈춘다. 개행문자를 읽으면, fgets는 그것을 다른 문자들과 같이 저장한다. 따라서 gets는 개행문자를 절대 저장하지 않고, fgets는 somtimes does.)
gets와 fgets 함수는 에러가 발생하거나 문자를 저장하기 전 입력 스트림의 끝에 도달했을 때 null 포인터를 리턴한다. (이 때는 feof와 ferror 함수를 호출해 어떤 상황이 발생했는지를 판별할 수 있다) 다른 경우에는 두 함수는 두 함수 입력을 저장한 배열을 가리키는 첫번째 인자를 리턴한다. 두 함수 모두 문자열 끝에 null character를 추가한다.
이제 fgets를 알았기 때문에, 항상 언제나 gets보다 fgets를 사용하는 것이 좋다. gets의 경우, 항상 저장되는 배열의 범위를 넘을 수 있는 위험성이 존재하기 때문에, 읽히는 문자열이 배열의 크기를 초과하지 않는다는 것이 보장될 때에만 안전하다. 만약 그러한 보장이 없을 때에는 fgets를 사용하는 것이 훨씬 안전하다.
22.6 Block I/O
size_t fread(void * restrict ptr,
size_t size, size_t nmemb,
FILE * restrict stream);
size_t fwrite(const void * restrict ptr,
size_t size, size_t nmemb,
FILE * restrict stream);
fread와 fwrite 함수는 프로그램이 한번에 데이터의 큰 블럭을 읽고 쓰도록 해 준다. fread와 fwrite 함수는 주로 바이너리 스트림에서 사용되지만, 주의해서 다룬다면 텍스트 스트림에서도 사용할 수 있다.
fwrite 함수는 배열을 메모리에서 스트림으로 복사하도록 설계되었다. 첫번째 인자는 배열의 주소, 두번째 인자는 각 원소의 크기(in bytes), 세번째 인자는 쓸 원소의 개수이다. 네번째 인자는 파일 포인터로 데이터를 어디에 쓸 지를 지정한다. 배열 a의 모든 원소를 쓸 때 다음과 같이 fwrite를 사용한다.
fwrite(a, sizeof(a[0]), sizeof(a) / sizeof(a[0]), fp);
배열의 모든 원소를 써야 한다는 법은 없으며 그 일부만 복사해도 된다. fwrite는 쓰인 원소의 개수(not bytes)를 리턴한다. 에러가 발생했을 때 리턴 값은 세번째 인자보다 작은 값이 된다.
fread 함수는 스트림에서 배열의 원소들을 읽는다. 인자들은 fread와 비슷하다. 순서대로 배열의 주소, 각 원소의 크기(in bytes), 읽을 원소의 개수, 파일 포인터이다. 파일의 내용을 읽어서 배열 a에 저장하려면, 다음과 같이 fread를 호출한다.
n = fread(a, sizeof(a[0]), sizeof(a) / sizeof(a[0]), fp);
fread의 리턴 값을 체크하는 것이 중요하다. 이는 실제로 읽은 원소의 개수를 나타낸다. 입력 파일의 eof에 도달하거나 읽기 에러 발생한 경우가 아니라면 리턴 값은 세번째 인자와 같아야 한다. 세번째 인자보다 더 작은 값이 리턴된 경우 feof와 ferror 함수를 사용해서 이유를 알 수 있다.
n = fread(a, 1, 100, fp); /* return value will be between 0 and 100) */
n = fread(a, 100, 1, fp); /* return value will be either 0 or 1 */
fwrite는 종료 이전에 데이터를 파일에 저장해야 할 때 유용하다. 나중에 그 프로그램이나 또는 다른 프록그램에서 fread로 데이터를 메모리로 다시 읽을 수 있다. fread와 fwrite가 다루는 데이터는 꼭 배열 형태일 필요가 없다. 어떤 종류의 변수에 대해서도 잘 작동한다. 특히 구조체도 읽고 쓸 수 있다. 구조체 변수 s를 파일에 쓰고자 할 때, 다음과 같이 fwrite를 호출할 수 있다.
fwrite(&s, sizeof(s), 1, fp);
fwrite로 포인터 변수가 들어있는 구조체를 쓸 때는 조심해야 한다. 이 값들은 다시 읽었을 때 유효하리라는 보장이 없다.
22.7 File Positioning
int fgetpos(FILE * restrict stream,
fpos_t * restrict pos);
int fseek(FILE *stream, long int offset, int whence);
int fsetpos(FILE *stream, const fpos_t *pos);
long int ftell(FILE *stream);
void rewind(FILE *stream);
모든 스트림은 file position과 관련되어 있다. 파일이 열렸을 때, 파일의 포지션은 파일 가장 처음으로 set된다. (만약 파일이 "append" 모드로 열렸을 경우, 처음 파일 포지션은 파일의 가장 처음일 수도, 마지막일 수도 있다. implementation-dependent) 그리고 읽기나 쓰기 작업이 이뤄지면, 파일 내에서 자동적으로 이동해서 우리가 파일 내에서 순차적으로 움직일 수 있게 된다.
많은 프로그램에서 순차적인 진행으로 좋지만, 어떤 프로그램에서는 파일 내에서 점프해서 이곳저곳의 데이터에 접근해야 한다. <stdio.h>는 5가지의 함수를 제공해서 파일의 현재 포지션을 알아내거나 바꿀 수 있게 해 준다.
fseek 함수는 첫번째 인자(파일 포인터)와 관련된 파일 포지션을 변경한다. 세번째 인자는 파일의 시작위치, 현재 위치, 또는 파일의 마지막 위치로부터 상대적 거리로 표시된 새 위치를 지정한다. <stdio.h>에서는 다음 세가지 매크로를 제공한다.
SEEK_SET Beginning of file
SEEK_CUR Current file position
SEEK_END End of file
두번째 인자는 byte count이고 음수가 될 수도 있다.
fseek(fp, 0L, SEEK_SET); /* moves to beginning of file */
fseek(fp, 0L, SEEK_END); /* moves to end of file */
fseek(fp, -10L, SEEK_CUR); /* moves back 10 bytes */
숫자 뒤 L은 long을 의미한다. L을 붙이지 않더라도 인자들은 자동적으로 변환된다.
보통 fseek은 0을 리턴한다. 만약 에러 발생시(예를 들어 요청한 포지션이 존재하지 않는 경우), fseek은 0이 아닌 값을 리턴한다. file-positioning 함수는 바이너리 스트림에 사용되는 것이 가장 좋다. 텍스트 스트림에 사용하는 것이 가능하지만, 운영 체제간의 차이 때문에 조심해서 사용해야 한다. 텍스트 스트림의 경우, (1) offset(fseek의 두번째 인자)이 0이거나, (2) whence(세번째 인자)가 SEEK_SET이고 offset이 이전에 ftell 호출해서 얻은 값이어야 한다. (다시 말해 fseek으로는 텍스트 스트림의 가장 처음, 끝 또는 이전에 간 적이 있었던 지점으로만 갈 수 있다) 바이너리 스트림의 경우, whence가 SEEK_END인 경우의 호출을 지원하지 않아도 된다.
ftell 함수는 현재 파일 위치를 long integer로 리턴한다. 만약 에러 발생시, ftell은 -1L을 리턴하고 에러 코드를 errno에 저장한다. ftell의 리턴값을 저장했다가 이후에 fseek 호출시 인자로 사용해서 이전 파일 위치로 돌아가도록 사용할 수 있다.
long file_pos;
...
file_pos = ftell(fp); /* saves current position */
...
fseek(fp, file_pos, SEEK_SET); /* returns to old position */
fp가 바이너리 스트림인 경우, ftell(fp) 호출시 byte count로 측정된 현재 위치를 리턴한다(0은 파일의 시작을 나타냄). fp가 텍스트 스트림인 경우, ftell(fp)는 꼭 byte count이지 않아도 된다. 따라서, ftell의 리턴값에 산술 연산을 수행하지 않는 것이 바람직하다. 예를 들어, 파일의 서로 다른 위치 간의 거리를 알기 위해 두 위치에서 얻은 ftell의 값을 빼는 것은 좋은 생각이 아니다.
rewind 함수는 파일 포지션을 처음으로 설정한다. rewind(fp)와 fseek(fp, 0L, SEEK_SET)은 거의 동일하다. 차이점은 rewind는 리턴 값이 없지만 fp의 error indicator를 clear한다. fseek 호출시에는 end-of-file indicator만 clear된다.
fseek와 ftell 함수가 가지는 문제점: 위치를 long integer로 저장할 수 있는 파일들에만 사용할 수 있다. 매우 큰 파일을 다룰 때를 위해, 두개의 함수가 추가로 제공된다. fgetpos, fsetpos. 이 함수들은 fpos_t 형식의 값을 사용해 파일 위치를 나타낸다. fpos_t 값은 꼭 정수이지 않아도 되며 예를 들어 구조체일 수도 있다.
fgetpos(fp, &file_pos) 를 호출하면 fp와 관련된 파일 포지션을 file_pos 변수에 저장한다. fsetpos(fp, &file_pos) 호출시 fp와 관련된 파일의 포지션을 file_pos에 저장된 값으로 설정한다. (이 값은 file_pos 변수에 의해 저장된 값이어야 한다) 만약 fgetpos나 fsetpo가 실패했을 때에는 에러 코드를 errno에 저장한다. 두 함수 모두 성공했을 때에는 0을, 실패했을 때에는 0이 아닌 값을 리턴한다.
fpos_t file_pos;
...
fgetpos(fp, &file_pos); /* saves current position */
...
fsetpos(fp, &file_pos); /* returns to old position */
// invclear.c
/* Modifies a file of part records by setting the quantity
on hand to zero for all records */
#include <stdio.h>
#include <stdlib.h>
#define NAME_LEN 25
#define MAX_PART 100
struct part {
int number;
char name[NAME_LEN+1];
int on_hand;
} inventory[MAX_PARTS];
int num_parts;
int main(void)
{
FILE *fp;
int i;
if ((fp = fopen("inventory.dat", "rb+")) == NULL) {
fprintf(stderr, "Can't open inventory file\n");
exit(EXIT_FAILURE);
}
num_parts = fread(inventory, sizeof(struct part),
MAX_PARTS, fp);
for (i = 0; i <num_parts; i++)
inventory[i].on_hand = 0;
rewind(fp);
fwrite(inventory, sizeof(struct part), num_parts, fp);
fclose(fp);
return 0;
}
rewind를 호출하는 것은 매우 중요하다. fread를 호출한 후에, 파일의 위치는 eof이다. 만약 rewind를 호출하지 않고 fwrite함수를 호출하면, 원래 파일 내용에 덮어쓰는 것이 아니라 기존 파일에 이어서 쓰게 된다.
22.8 String I/O
이 섹션에서 설명하는 함수들은 다소 생소한데, 스트림이나 파일과 아무 관련이 없기 때문이다. 대신 이 함수들을 통해 마치 문자열이 스트림인 것처럼 데이터를 읽고 쓸 수 있다. sprintf와 snprintf 함수는 스트림에 쓸 때와 같은 방식으로 문자열에 문자들을 쓴다. sscanf 함수는 스트림에서 읽는 것인 양 문자열에서 문자들을 읽는다. 이 함수들을 쓸 때 ...printf의 포맷 스트링 능력과 ...scanf의 강력한 패턴 매칭 능력을 실제 스트림을 사용하지 않고서도 이용할 수 있다. 이 섹션에서 spirntf, snprintf, sscanf의 디테일을 다룬다.
세가지 비슷한 함수(vsprintf, vsnprintf, vsscanf)도 <stdio.h>에 속해 있다. 하지만 이 함수들은 <stdarg.h>에 선언된 va_list 형식에 의존하기 때문에 그 헤더를 다루는 26.1에서 다룬다.
Output Functions
int sprintf(char * restrict s,
const char * restrict format, ...);
int snprintf(char * restrict s, size_t n, const char * restrict format, ...); // (C99)
sprintf 함수는 printf, fprintf 함수와 비슷하다. 차이점은 이 함수는 출력을 첫번째 인자가 가리키는 문자 배열에 쓴다는 점이다. sprintf의 두번째 인자는 printf에서 사용되는 포맷 스트링과 동일하다.
sprintf(date, "%d/%d/%d", 9, 20, 2010);
은 date에 "9/20/2010"이라고 쓴다. 문자열에 쓰기를 끝내면 끝에 null character를 더하고 쓰인 문자의 개수(null character 제외)를 리턴한다. 만약 인코딩 에러가 발생하면(와이드 문자가 적합한 멀티바이트 문자로 translate될 수 없었음), sprintf는 음의 값을 리턴한다.
snprintf 함수는 추가로 매개변수 n이 있는 것을 제외하면 sprintf 함수와 동일하다. null character를 제외하고 최대 n-1개의 문자만 문자열에 쓰일 수 있다. n이 0이 아닌 이상 null 문자는 항상 쓰이게 된다.
snprintf(name, 13, "%s, %s", "Einstein", "Albert");
는 name 변수에 "Einstein, Al"을 쓰게 된다.
snprintf는 만약 길이 제한이 없었다면 쓰였을 글자의 개수(null character 제외)를 리턴한다. 인코딩 에러가 발생하면, snprintf 함수는 음의 값을 리턴한다.
Input Functions
int sscanf(const char * restrict s,
const char * restrict format, ...);
sscanf 함수는 fscanf, scanf함수와 비슷하다. 차이는 sscanf 함수는 스트림 대신 문자열(첫번째 인자가 가리키는)로부터 읽는다는 점이다. sscanf의 두번째 인자는 scanf와 fscanf 함수에서 쓰이는 포맷 스트링과 동일하다.
sscanf는 다른 입력 함수에서 읽은 문자열로부터 데이터를 추출할 때 유용하다. 예를 들어 fgets로 입력 한 라인을 얻고, sscanf로 그 라인을 전달할 수 있다.
fgets(str, sizeof(str), stdin); /* reads a line of input */
sscanf(str, "%d%d", &i, &j); /* extracts two integers */
scanf나 fscanf 대신 sscanf를 사용하는 것의 장점은 입력 라인을 원하는 만큼 포맷을 바꿔가면서 읽을 수 있다는 점이다.
scanf와 fscanf와 마찬가지로 sscanf는 성공적으로 읽고 저장한 데이터의 개수를 리턴한다. 첫번째 아이템을 찾기 전에 null character를 만난 경우, EOF를 리턴한다.