5.1 Introduction

이 챕터에서는 standard I/O library에 대해 배운다. 이 라이브러리는 ISO C standard 에 정의되어있다. 왜냐하면 이게 UNIX 시스템 뿐만 아닌 다른 operating system 위에서도 구현되었기 때문이다.

스탠다드 I/O는 우리가 버퍼크기를 얼마나 잡아야 할지 걱정할 필요없도록 optimal-sized chunks에서 buffer를 할당하고 동작하도록 핸들링함.



5.2 Streams and FILE Objects

챕터3에서 모든 I/O 루틴들이 fd 중심이었다. 파일이 열리면 fd가 리턴되며, 그 fd는 나중에  모든 subsequent I/O operating에서 사용된다. Standard I/O library에서는 stream에 중심으로 나갈것이다. 

Standard I/O의 stream은 singlebyte 와 multibyte character sets 모두 처리 가능.
stream's orientation을 보고 multibyte로 읽고 쓸것인지 singlebyte로 읽고쓸것인지가 결정된다.

처음 stream이 열리면 orientation이 설정되어있지않다.
만약 orientation이 설정되어있지 않은 스트림에 처음으로 multibyte I/O function을 사용하거나 singlebyte I/O function을 사용하면 그때 orientation이 결정됨.

오직 두 함수만 orientation을 변경가능.
-> freopen , fwide.

freopen은 orientation을 clear.
fwide는 orientation을 set.


#include <stdio.h>
#include <wchar.h>
int fwide(FILE* fp, int mode);

//returns : positive = stream is wide oriented.
//          negative = stream is byte oriented.
//             0     = stream has no orientation.


mode
      - negative -> byte oriented로 만들려고 시도.
      - positive -> wide oriented로 만들려고 시도. 
      - 0 -> 현재 orientation을 리턴.

stream에 orietnation이 이미 set 된 상태라면 fwide로 orientation을 변경할수 없음.

fopen같은 Standard I/O function으로 스ㅡㅌ림을 열경우 FILE opjects의 포인터를 리턴함.
이 오브젝트는 보통 standard I/O 라이브러리가 스트림을 매니지하는데 필요한 정보들을 담고있는 스트럭쳐다.
  • fd
  • 스트림의 버퍼 포인터
  • 버퍼 크기
  • 현재 버퍼에 있는 캐릭터 개수
  • error flag
  • 기타등등



5.3 Standard Input, Standard Output, and Standard Error

모든 프로세스에는 세가지의 미리정의된 stream이 자동으로 존재한다.
  • standard input
  • standard output 
  • standard error
이 스트림들은 STDIN_FILENO, STDOUT_FILENO, STDERROR_FILENO fd가 가리키는 파일을 똑같이 나타낸다.
stdin, stdout, stderr의 stdio.h에 미리 정의된 포인터로 접근가능하다.



5.4 Buffering

스탠다드 I/O 라이브러리가 제공하는 버퍼링의 목적은 read 와 write call을 최소화 하는것이다.
또한 어플리케이션이 그것에 대해 걱정하지 않고, 라이브러리가 자동으로 각각의 i/o stream에 버퍼링을 하도록 노력함.
불행하게도 스탠다드 I/O 라이브러리가 가장 혼란스러운 것 중 하나가 버퍼링 때문이다.

버퍼링은 세가지 타입으로 제공됨.
  1. Fully buffered.
  2. Line buffered.
  3. Unbuffered.

Fully buffered
이 경우 ,실제 I/O는 스탠다드 I/O 버퍼가 가득 찼을 경우에 일어남.
file residing on disk는 보통 fully buffered이다.
사용하는 버퍼는 보통 스탠다드 I/O 라이브러리 함수가 malloc을 호출해서 얻어짐.
flush는 Standard I/O buffer를 write 하는 것을 의미한다. buffer가 가득찼을때 자동으로 flush되거나 fflush를 호출해서 stream을 flush 할수 있다.
(터미널 드라이버에서 사용할 때는 버퍼에 저장된 내용을 버리는 것을 의미한다. ex. tcflush. chapter18에 나옴).

Line buffered
이 경우, 스탠다드 I/O 라이브러리는 input이나 output에서 newline character를 만났을 경우 I/O를 실행함.
이 타입을 쓰면 한번에 한글자씩 아웃풋에 보낼 수 있음.
terminal 같은 곳에서 line buffered를 주로 사용한다.
두가지 주의점이 있다.
첫째는 스탠다드 I/O 라이브러리가 라인별로 모을때 사용하는 버퍼의 크기가 fixed임. 그래서 newline이 들어오기 전에 버퍼가 가득차서 I/O가 발생할 수 있다.
둘째는 unbuffered stream이나 line buffered stream에서 input 요청이 오면 모든 line buffered output stream은 flush된다.
그리고 뭐라 써져있는데 잘 모르겠음.

Unbuffered
버퍼링하지않음.

ISO C는 다음의 버퍼링 특성을 필요로한다.
  • standard input and standard output는 fully buffered이다. (얘들이 interactive device를 나타내지 않을 경우에)
  • standard error 는 never fully buffered.

그러나 이게 standard input, standard output, standard error의 버퍼링 타입을 딱 정해주진 않는다. 가장 디폴트로 사용되는타입은 다음과 같다
  • Standard error는 unbuffered.
  • Standard input/output은 line buffered. (terminal device가 아닐경우 fully buffered).

setbuf나 setvbuf를 호출해서 버퍼링 타입을 변경할 수 있다.


#include <stdio.h>
void setbuf(FILE *restrict fp, char *restrict buf);
int setvbuf(FILE *restrict fp, char * restrict buf, int mode, size_t size);

 //return : 0 if OK, nonzero on error.

setbuf를 통해 버퍼링을 켜고 끌수 있음.
버퍼링을 켜기 위해서는 buf는 stdio.h에 정의된 BUFSIZ의 length를 가진 버퍼를 가리키는 pointer를 인자로 넣어줘야함.
보통, 스트림은 full buffered이지만, 몇몇 시스템은 스트림이 터미널 디바이스와 관련되어있다면 line buffering을 쓰기도 한다.
NULL을 넣어주면 unbuffered로 설정됨.

setvbuf는 mode에 값을 넘겨줌으로써 buffering의 타입을 정해줄수 있다
  • _IOFBF : fully buffered
  • _IOLBF : line buffered
  • _IONBF : unbuffered

unbuffered 스트림을 설정했다면, size 파라미터는무시됨.
버퍼 타입이 unbuffered가 아닌 다른것을 선택하고, buf에 NULL을 넘겨주면 함수는 자동으로 buf를 생성한다. (size BUFSIZ)


#include <stdio.h>
int fflush(FILE *fp);
//return : 0 if OK, EOF on error

fflush 는 스트림의 unwritten data가 kernel에 넘어가도록 한다.
fp가 NULL인경우, 모든 output stream이 flush되도록 함.




5.5 Opening a Stream


#include <stdio.h>

FILE *fopen(const char *restrict pathname, const char *restrict type);
FILE freopen(const char *restrict pathname, const char *restrict type, FILE *restrict fp);
FILE *fdopen(int fd, const char *type);

//return : file pointer if OK, NULL on error

세 함수의 다른 점은 다음과 같다.
  1. fopen은 특정한 파일을 여는 것이다.
  2. freopen은 특정한 파일을 특정한 스트림에다 여는 것이다. 기존에 열려있던 것은 닫음. 스트림이 orientation을 갖고있었다면 clear시킴. 이 함수는 standard input, standard output, standard error 로 미리 정의된 스트림으로 파일을 열때 사용됨.
  3. fdopen은 이미 존재하는 fd를 이용한다. 주로 파이프나 네트워크 커뮤니케이션을 위해 열어놓은 fd를 사용한다. 왜냐하면 이런 특수한 file들은 스탠다드 I/O 의 fopen으로는 열수 없기 때문에 fd를 얻어온 다음, fdopen으로 그 fd와 연결시켜 스트림을 만듬.

fopen에서 사용되는 type과 open에서 사용되는 flags의 관계가 표에 나타남.

<표>

스탠다드 I/O의 type을 보면 text file 과 binary file을 구분함. UNIX 커널은 그 둘을 구분하지않음. 따라서 type의 b옵션은 no effect.
fdopen에서 type의 의미는 조금 다르다. fd는 이미 open되어있으며, open for writing은 파일을 truncate 하지 않는다. 또한 스탠다드 I/O의 append type도 file을 create하지 못한다. 
파일이 append 옵션으로 열린다면, 각각의 write는 파일의 current end 뒤에 적용됨. 따라서 여러 프로세스가 같은 파일에 append모드로 접근해도 올바르게 쓰여짐.


파일이 읽기, 쓰기 권한을 갖고 열렸을 경우(plus 기호가 붙은 type들), 두가지 제약사항이 있다.
  • input 다음에 fflush나 fseek, fsetpos, rewind없이 output이 바로 오지 못한다.
  • output 이후 fseek, fsetpos, rewind, 파일 끝에서 만나는 input operation 없이 input이 바로 오지 못한다.

open이나 create가 아닌 stream함수의 w or a 타입으로 새로운 파일을 만들경우 permission bit을 설정할 수 없다. 그러나 umask를 이용해서는 set 할수 있다(?).
이런식으로 열린 stream들은 기본적으로 fully buffered 방식이 적용된다. 단, terminal 장치에 대한 stream은 line buffered가 적용된다. 원한다면, stream을 연후 어떠한 연산도 수행하지 않은 상태에선, buffering 방식을 변경할 수 있다.


#include <stdio.h>

int fclose(FILE *fp);

//return : 0 if OK, EOF on error.

output buffered data들은 closed 되기전에 전부 flush 된다. input data들은 전부 버려진다. 자동으로 buf를 allocation한 경우, close 될때 release도 자동으로 시켜준다. 
프로세스가 일반적으로 terminate되면, (exit를 콜하거나 main이 끝나서) 모든 스탠다드 I/O 스트림들은 unwrittened buffering data들은 flush되고, 스트림은 close 된다.




5.6 Reading and Writing a Stream

스트림을 열면 세가지 unformatted I/O 중에서 선택해서 사용 가능.

1. Character-at-a-time I/O : 한번에 한글자씩 읽거나 쓰기 가능. stream이 buffered라면 스탠다드 I/O functions들이 알아서 버퍼링을 관리해준다.
2. Line-at-a-time I/O : 한번에 한라인씩 읽거나 쓰기 가능. (fgets / fputs). newline character를 보고 line이 terminated되며, fgets를 콜할때는 maximum line length를 정해줘야함.
3. Direct I/O : fread, fwrite. 각각의 I/O operation마다, 정해진 크기를 가진 object들을 읽을 수 있다.

Input Functions

#include <stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);
//return : next char if OK, EOF on end of file or error.

getchar는 getc(stdin)와 동등하게 정의되어있음. getc와 fgetc의 차이점은 getc는 매크로로 구현되어있고, fgetc는 아님.
(getchar == getc != fgetc)
이는 다음의 3가지를 의미한다.
  1. getc의 argument는 expression이 되면 안됨. 한번이상 evaluate 될수있기때문.
  2. fgetc는 함수이므로, 주소를 얻을 수있다. 즉, 다른함수의 argument로 fgetc의 주소를 넘길 수 있음.
  3. getc를 호출하는것보다 fgetc를 호출하는게 아마 더 오래걸릴것이다. (매크로보다 function을 콜하는게 더오래걸림)

이세가지 함수는 unsigned char를 int로 convert해서 리턴한다. unsigned는 high-order bit이 set되더라도 음수로 표현되지 않음.
가능한 모든 캐릭터들을 표현하고 에러가 발생하거나 file의 끝을 만났을 때 값을 처리하기 위해 int를 사용함. 
(stdio.h에서 EOF는 -1이다)

주의할 점은 이 함수들은 error나 파일의 마지막일때 모두 같은 값을 반환한다.
이 두가지를 구분하기 위해서 ferror, feof를 사용한다.

#include <stdio.h>
int ferror(FILE *fp);
int feof(FILE *fp);

void clearerr(FILE *fp);

파일 오브젝트에는 각각의 stream을 위한 flag들이 있다.
  • an error flag.
  • an end-of-file flag.

clearerr를 통해 플래그가 clear됨.

stream으로부터 read한 후, ungetc를 통해 다시 push back 할 수 있다.

#include <stdio.h>
int ungetc( int c, FILE *fp);
    //return : c if OK, EOF on error

ISO C에서는 여러글자 push back 도 허용하지만, 구현은 한 문자에 대해서만 되어 있다. 
EOF의 pushback은 불가능하지만 문서의 끝에서는 EOF를 넣을 수 있다.

c는 이전에 읽어들인 문자와 같을 필요 없음.

우리가 ungetc를 통해 pushback한다고해서 그게 underlying file or device에 written되지 않음.
스탠다드 I/O 라이브러리의 스트림의 버퍼에 저장되어있게됨.

Output Functions

#include <stdio.h>
int putc (int c, FILE *fp);
int fputc(int c, FILFE *fp);
int putchar(int c);

위 입력함수와 비슷한 동작을 함. ( putchar == putc )



5.7 Line-at-a-Time I/O


#include <stdio.h>
char *fgets(char *restric buf, int n, FILE *restrict fp);
char *gets(char *buf);
//return : buf if OK, NULL end of file or error

fgets는 특정 스트림으로부터 읽는반면, gets는 standard input으로부터 읽음.

fgets를 쓸때 buf의 사이즈 n을 정해줘야함. buf가 항상 null terminated 될 수 있도록 최대 n-1 byte만 읽는다.

gets는 쓰면 안된다. gets는 buffersize를 받지 않음. 이는 buffer가 overflow될수도있게 만든다.

ISO C가 gets도 구현하도록 요구하지만, 사용할때는 fgets만 쓰도록해라.


 #include <stdio.h>
int fputs(const char *restrict str, FILE *restrict fp);
int puts(const char *str);
//return : non-negative value if OK, EOF on error

fputs는 stream에 null terminateed string을 write함.
str의 끝의 null byte는 쓰여지지 않음. str이 newline을 포함하고 있지 않아도 됨.

puts는 null-terminated string을 standard output에 write한다. 역시 null byte는출력하지않음.
puts는 마지막에 newline을 쓴다.

puts는 gets처럼 위험하지는 않지만 마지막에 newline이 오는것을 항상 기억해야하기 때문에 잘 쓰지 않는다.

fgets, fputs 사용을 권장하고, 각 line의 끝에서 newline을 직접 처리하도록 하자. 




5.8 Standard I/O Efficiency

Standard I/O의 효율성을 알아보자

  • 300만 line의 98.5MB의 파일을 대상으로 읽고 쓰기를 하는 프로그램을 만들었다. 각 프로그램은 (fgets, fputs), (getc, putc), (fgetc, fputc) 그리고 3장에서 본 (read, write)를 사용하였다.
  • 각 프로그램은 stdin으로 입력을 받고, stdout으로 출력하는 프로그램이다.
  • line at a time이라 최적의 버퍼가 할당이된 read, write 함수에 비해 더 많은 루프를 돌아야 하므로 user CPU의 시간이 더 길다.
  • kernel request는 비슷하게 불려서 시간상으로 비슷하다.
  • 마지막 줄은 각 메인함수에서 text가 사용된 공간 ( C 컴파일러에 의해 생성된 machine instructions )
  • fgetc와 fputs를 사용한 경우와 read, write 버퍼 1인경우에서 차이가 나는 것은 전자는 200million번의 함수 호출 및 25,224의 시스템 호출에 비해 후자는 200million번의 함수 호출 및 200million의 시스템 호출을 한다.

결과적으로 standard I/O library는 read, write를 사용하는것에 비해 그렇게 느리지 않다.




5.9 Binary I/O

#include <stdio.h>
size_t fread( void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp);
size_t fwrite(const void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp);

//return : number of objects read or write.


//write binary array
//float array에서 2부터 5까지 write 하기.
float data[10];

if (fwrite(&data[2], sizeof(float), 4, fp) != 4)
     err_sys("fwrite error");


//write structure.
struct {
     short count;
     long total;
     char name[NAMESIZE];
} item;

if (fwrite(&item, sizeof(item), 1, fp) != 1)
     error("fwrite error");

읽기의 경우 리턴값 < nobj 이면, EOF 또는 에러이므로 이를 확인하기위해 ferror 혹은 feof를 사용해야함.
쓰기의 경우 nobj < return value이면 에러.
binary I/O의 문제점은 같은 시스템에서 쓰여진 데이터만 읽어서 쓸수 있다는 것이다. 몇년전까지 UNIX가 PDP-11s이었을땐 괜찮았지만, 현재는 heterogeneous system이 많아져서 문제가 됨.
function이 제대로 동작하지 않을때, 2가지 원인이 있다.
  1. 스트럭쳐 member의 오프셋이 컴파일러와 시스템마다 다를수 있다. (다른 alignment requirement를 가지기때문에). 그래서 몇몇 컴파일러들은 structure가 tight하게 pack 될수 있도록 해놨는데, 이것은 하나의 시스템에서도, 컴파일러 옵션에따라 structure의 binary layout이 달라질수 있음을 나타낸다.
  2. multibyte integer와 floating-point values를 저장하는 바이너리 포맷이 머신 아키텍쳐마다 다르다.



5.10 Positioning a Stream

Standard I/O Stream의 position에 대한 세가지 방법.
  1. ftell, fseek. file's position이 long integer로 저장될수 있다고 가정함.
  2. ftello, fseeko. Single UNIX Specification에 소개됨. 파일 오프셋이 long integer에 맞지않더라도 동작함. long integer를 off_t로 대체. 
  3. fgetpos, fsetpos. ISO C에서 소개됨. 파일의 포지션을 기록하기위해, abstract data type인 fpos_t를 사용한다. fpos_t 타입은 fileposition을 기록할수 있을만큼 커질 수 있음.

application을 non-UNIX 시스템으로 포팅해올때에는 fgetpos와 fsetpos를 써라.


#include <stdio.h>
long ftell(FILE *fp);
    //return : current file position indicator if OK, -1L on ERR
int fseek(FILE *fp, long offset, int whence);
     //return  : 0 if OK, -1 on ERR.
void rewind(FILE *fp);

ftell은 바이너리 파일의 파일 포지션을 알려줌.
fseek : whence는 lseek과 같다. 기준을 나타냄 (SEEK_END, SEEK_CUR, ....)
rewind: 위치를 파일의 시작으로 해줌.


#include <stdio.h>
off_t ftello(FILE *fp);
     //return : current file position indicator if OK, (off_t)-1 on error.
int fseeko(FILE *fp, off_t offset, int whence);
     //return : 0 if OK, -1 on error.
타입만 바뀜.
off_t는 3.6에서 나왔엇음. off_t의 타입은 32bit보다 클수 있다.


#include <stdio.h>
int fgetpos(FILE *restrict fp, fpos_t *restrict pos);
int fsetpos(FILE *fp, const fpos_t *pos);
     //return : 0 if OK, nonzero on error.

fgetpos는 current value of file's position indicator를 pos에 저장한다. 이 값은 이후 fsetpos에 사용될수 있다.



5.11 Formatted I/O

Formatted Output

Formatted Output 은 5가지 함수로 핸들링함.

#include <stdio.h>

int printf(const char *restrict format, ...);
int fprintf(FILE *restrict fp, const char *restrict format, ...);
int dprintf(int fd, const char*restrict format, ...);
     //all three return : number of chars output if OK, negative value if output error.

int sprintf(char restrict buf, const char* restrict format, ...);
     //return : number of chars sotred in array if OK, negative value if output error.

int snprintf(char *restrict buf, size_t n, const char *restrict format, ...);
     //return : number of chars the would been stored in array if buffer was large enough, negative value if encoding error

dprintf는 fdopen으로 연 fd를 fprintf를 쓰기위해 file pointer로 변경할 필요가 없도록해줌.
format spectification은 아규먼트들이 어떻게 인코딩되고 display될지를 나타냄.
conversion specification은 %로 시작됨.
  • %[flags][fldwidth][precision][lenmodifier]convtype 
flags : <표참고>
fldwidth : conversion을 위한 minimum field width. conversion 결과가 width보다 작으면 공백이 들어감. non-negative integer 혹은 *가 올수있음.
precision : '.'으로 시작한다. 정수부와 소수부의 최소자리수를 정한다. 혹은 스트링 conversion의 최대 바이트 수를 나타내기도 한다.
lenmodifier : size of argument. <Figure 5.8 참조> (ex. %ld에서 l 이런거. argument의 size가 long임을 나타냄)

... 대신 var_args를 쓰는 vprintf, vfprintf, ... 등등도 있다. variable length argument list는 나중에나옴. 

Formatted Input

#include <stdio.h>
int scanf(const char* restrict format, ...);
int fscanf(FILE restrict fp, const char *restrict foramt, ...);
int sscanf(const char *restrict buf, const char *restrict format, ...);

scanf : standard input으로부터 입력.
fscanf : 파일스트림으로부터 입력
sscanf : 데이터 형식의 문자열 (buf)에서 입력.

conversion specification
%[*][fldwidth][m][lenmodifier]convtype

[*] : 뒤의 스펙에 따라 input을 변환시켜주지만 그 값을 argument에 저장하지 않는다.
(ex.  scanf("%*d %d", &i); input이 12 34일때 i에 34만 들어가게됨. )
[fldwidth] : 읽어들일 문자수(폭)
[m] : assignment-allocation character. 메모리 버퍼가 변환된 스트링을 담기 위해



5.12 Implementation Details

스탠다드 I/O 라이브러리는 내부적으로 3장에서 나온 I/O 루틴들을 호출하도록 되어있다. 그래서 각각의 스탠다드 I/O 스트림들은 연결된 fd를 갖고있고, fileno라는 콜을 이용해 그 fd를 얻어올수있다.

#include <stdio.h>

int fileno(FILE *fp);
     //return : the file descriptor associated with the stream



5.13 Temporary Files

ISO C 표준은 임시파일을 만드는걸 돕기위한 두가지 함수를 정의한다. 그리고 두 함수는 스탠다드 I/O 라이브러리에서 제공됨.

#include <stdio.h>

char *tmpnam(char *ptr);
     //return : pointer to unique pathname
FILE *tmpfile(void);
     //return : file ppinter if OK, NULL on error.

tmpnam은 유효하면서도 유니크한 pathname을 만들어낸다.
불릴대마다 다른 pathname을 만들어낸다. 
ptr이 NULL이면, 만들어진 pathname은 static area에 저장되고 리턴값으로 나옴. 다음에 부르는 tmpnam이 이 static area를 덮어쓰게됨. 따라서 여러번 부르려면 pathname을 copy해줘야함.
ptr이 non-NULL이면, ptr은 l_tmpnam보다 큰 어레이 포인터여야한다. 만들어진 pathname이 여기에 저장되고 ptr은 리턴값으로 나옴.

tmpfile은 프로그램이 terminated될때 자동으로 close되는 temporary binary file을 만들어낸다. (type wb+).

tmpfile은 임시파일을 tmpnam으로 pathname을 만들고, 파일을 생성하고, 즉시 unlink함으로써  만들어냄.

UNIX 스펙은 XSI 옵션의 일부분으로써 임시파일을 다루는 부가적인 함수 2개를 제공함.

#include <stdlib.h>
char mkdtemp(char *template);
     //return : pointer to directory name if OK, NULL on error.
char mkstemp(char*template);
     //return : file descriptor if OK, -1 on error.
mkdtemp는 유니크한 이름으로 디렉토리를 만든다. (S_IRUSR | S_IWUSR | S_IXUSR)
mkstemp는 유니크한 이름으로 regular 파일을 만든다. (regular file 은 -rw-r--r--).
이름은 template 을 이용해 만들어진다. template의 마지막 여섯문자는 XXXXXX 이렇게 입력되고 이부분에 유니크한 이름이추가된다.
(ex. char tm[] = "/tmp/dirXXXXXX"; )

mkstemp로 만들어진 파일은 tmpfile로 만든것과 다르게 auto remove 되지 않음.

tmpnam과 tempnam은 다른 프로세스가 같은 이름으로 파일을 만드는걸 막지는 못함. ( tmpnam으로 path를 받아온 상태에서, 그 이름으로 file을 만들려고 하는 사이에 다른 프로세스가 그 이름으로 파일을 만들수도 있음.)  주의해야함.

5.14 Memory Streams
UNIX 버전 4에서 메모리스트림 지원이 추가됨. 어떤 파일이 없을지라도 FILE 포인터로 접근이 가능. 모든 I/O는 바이트를 메모리에 있는 버퍼에 전송하거나 수신함으로써 이루어진다.
메모리 스트림은 목표지점 자체가 메모리이며, 메모리 바이트 입출력 스트림은 바이트 어레이를 입출력 스트림으로 사용하고, 메모리 문자 입출력 스트림은 문자배열(입출력용)과 문자열(입력용) 또는 문자열 버퍼(출력용)을 입출력 스트림으로 사용한다. 메모리 스트림을 만드는데 다음 세가지 함수가 유용하다.

#include <stdio.h>
FILE *fmemopen(void *restrict buf, size_t size, const char *restrict type);
//return : stream pointer if OK, NULL on error.

fmemopen은 호출자가 메모리 스트림에서 사용할 버퍼를 제공하도록 함.
buf가 NULL이면, size 만큼의 버퍼를 함수가 스스로 allocate.
type 은 스트림이 어떻게 사용될것인지를 나나탬.


메모리스트림과 file-based standard IO stream은 거의 같지만, 차이점은 다음과 같다.
  1. append type으로 열리게 되면, current file position은 파일의 첫 null byte로 감. null byte가 없다면 버퍼의 끝 1byte 앞을 가리키게됨. (..is set to one byte past the end of buffer).
    1. append mode에서 null byte를 찾기 때문에 binary data를 저장하는 용도에는 맞지않음.
  2. buf 인자가 null이면 fmemopen 내부에서 allocation을 해서 buf를 들고있기때문에 버퍼의 address를 찾을 방법이 없다. 따라서 read only로 스트림을 열면 읽기 불가능. write only로 열면 읽기 불가능. (일반 스트림에 setbuf setvbuf로 한거랑 차이점인듯?)
  3.  우리가 스트림 버퍼의 데이터의 크기를 늘리려고할때나 fclose, fflush, fseek, fseeko, fsetpos를 호출하면 current file position에 null이 write 됨. 








#include <stdio.h>
FILE *open_memstream(char **bufp, size_t *sizep);
#include <wchar.h>
FILE open_wmemstream(wchar_t **bufp, size_t *sizep);
//return : stream pointer if OK, NULL on error.

open_memstream은 byte oriented스트림을 만드는 것이고,  open_wmemstream은 wide oriented로 스트림을 만드는 것임.
이 두함수와 fmemopen과의 차이점은 다음과 같다.
  1. 쓰기전용 스트림.
  2. 사용자 자신의 버퍼를 지정할수 없다. 그러나 bufp와 sizep의 주소에 접근 가능.
  3. 스트림을 닫기전에 버퍼를 free 해줘야함.
  4. 스트림에 바이트를 넣을수록 버퍼가 커짐.

그러나 우리는 몇가지 룰을 지켜야함.
  1. buffer addresss와 length는 fclose 혹은 fflush를 콜을 하고나서부터 유효함.
  2. 그리고 버퍼의 값들은 다음번 스트림에 write를 하기전 혹은 fclose가 호출되기 전까지만 유효함.
이러한 이유는 버퍼가 커질 수 있기 때문이다. 버퍼가 커지려면 rellocate해야 하기 때문에 address가 바뀔 수 있음.



5.15 Alternative to Standard I/O

Standard I/O library는 완벽하지 못하다. 많은 데이터 복사가 그 중 하나이다.

  • 대체 라이브러리
    1. fio(3) :함수가 라인을 버퍼로의 복사 대신 그 라인을 읽은 함수가 그 라인에 대한 포인터를 반환하게 하여 세배 이상의 속도를 내게 하였다.
    2. sfio : 다른 페키지에서는 제공하지 않는 새로운 feature를 가진 패키지를 제공한다. (파일과 메모리 지역 모두 표현하는 일반화된 I/O stream, 프로세싱 모듈, 그리고 더 나은 예외 처리)
    3. ASI(Allooc Stream Interface) : mapped file을 이용하였다. 데이터 복사를 최소화 하려고 시도하였다.
    4. uClibc C library & Newlib C library : embedded 시스템과 같은 작은 메모리에서 사용할 수 있는 라이브러리.


5.16 Summary
Standard I/O 라이브러리에서 제공되는 함수들에 대해 알아봤음.
많은 confusion과 problem의 원인이 되는 버퍼링에 대해서도 알아봤다.

'스터디 > APUE' 카테고리의 다른 글

15. Interprocess Communication  (0) 2017.12.28
9. Process RelationShips  (0) 2017.12.28
Posted by outshine90
,