본문 바로가기
프로그래밍/UNIX 고급 프로그래밍

제5장 표준 입출력 라이브러리

by Ohdumak 2017. 10. 18.






표준 입ㅇ러ㅣㅏㅇㄹ4.18 기호 링크의 생성과 읽기


기호 링크는 symlink 함수나 symlinkat 함수로 생성할 수 있다.

링크 자체를 열어서 링크 안의 이름을 읽으려면 readlink 함수와 readlinkat 함수를 사용하면



출처: http://ohdumak.tistory.com/category/프로그래밍/UNIX 고급 프로그래밍 [오두막]

4.18 기호 링크의 생성과 읽기


기호 링크는 symlink 함수나 symlinkat 함수로 생성할 수 있다.



출처: http://ohdumak.tistory.com/category/프로그래밍/UNIX 고급 프로그래밍 [오두막]

4.18 기호 링크의 생성과 읽기


기호 링크는 symlink 함수나 symlinkat 함수로 생성할 수 있다.



출처: http://ohdumak.tistory.com/category/프로그래밍/UNIX 고급 프로그래밍 [오두막]

4.18 기호 링크의 생성과 읽기


기호 링크는 symlink 함수나 symlinkat 함수로 생성할 수 있다.

링크 자체를 열어서 링크 안의 이름을 읽으려면 readlink 함



출처: http://ohdumak.tistory.com/category/프로그래밍/UNIX 고급 프로그래밍 [오두막]

2017/09/20 - [프로그래밍/UNIX 고급 프로그래밍] - 제4장 파일과 디렉터리


5.1 소개


표준 입출력 라이브러리(statandard I/O library)를 설명한다.




5.2 스트림과 FILE 객체


표준 입출력 라이브러리에 대한 논의의 중심은 스트림(stream)이다. 표준 입출력 라이브러리에서는 파일을 열거나 생성했을 때 "파일 서술자를 얻었다"가 아니라 "파일에 스트림을 연관시켰다"라고 말한다. 표준 입출력 라이브러리의 파일 스트림은 단일 바이트 문자 집합과 함께 사용할 수도 있고 다중 바이트 문자 집합('넓은' 문자 집합)과 함께 사용할 수도 있다. 문자들을 단일 바이트로 읽고 쓸 것인지 아니면 다중 바이트로 읽고 쓸 것인지는 스트림의 지향(orientation)에 의해 결정된다. 스트림 지향을 설정하는 fwide 함수가 있다. 스트림에 지향이 이미 설정되어 있으면 fwide는 그것을 바꾸지 않는다는 점을 주의하기 바란다.


이번 장 전반에서는 표준 입출력 라이브러리를 유닉스 시스템의 맥락에서 서술한다.



5.3 표준 입력, 표준 출력, 표준 오류


모든 프로세스에는 미리 정의된 세가지 스트림이 자동으로 제공된다. 파일 서술자 STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO와 동일한 파일을 가리킨다. 이 세 표준 입출력 스트림들은 미리 정의된 파일 포인터 stdin과 stdout, stderr를 통해서 지칭한다. 이 파일 포인터들은 <stdio.h> 헤더에 정의되어 있다.



5.4 버퍼링


표준 입출력 라이브러리가 제공하는 버퍼링(buffering)의 목표는 read 호출과 write 호출을 최소한으로만 사용하는 것이다.

표준 입출력 라이브러리는 다음 세 종류의 버퍼링을 제공한다.

1. 전체 버퍼링(full buffering). 이 경우 실제 입출력은 표준 입출력 버퍼가 꽉 차면 일어난다.

방출(flush)이라는 용어는 표준 입출력 버퍼의 내용을 실제 출력 대상에 기록하는 작업을 뜻한다. 버퍼는 표준 입출력 함수에 의해 자동으로 방출될 수도 있고(버퍼가 꽉 찼을 때 등), 프로그램의 fflush 함수 호출에 의해 방출될 수도 있다.

2. 줄 단위 버퍼링(line buffering). 이 경우 표준 입출력 라이브러리는 입력이나 출력에서 새 줄(newline) 문자를 만났을 때 입출력 연산을 수행한다. 줄단위 버퍼링은 터미널을 가리키는 스트림에 주로 쓰인다.

3. 버퍼링 없음(unbuffered; 비버퍼링). 이 경우 표준 입출력 라이브러리는 문자들을 버퍼링하지 않는다. 예를 들어 표준 오류 스트림은 일반적으로 버퍼링이 없다.


- 표준 오류에는 항상 버퍼링을 적용하지 않는다.

- 그 외의 모든 스트림은 터미널 장치를 지칭하면 줄 단위 버퍼링, 그렇지 않으면 전체 버퍼링이다.


임의의 주어진 스트림에 대해 이러한 기본 방식이 마음에 들지 않는다면 setbuf 함수나 setvbuf 함수를 호출해서 버퍼링 방식을 변경할 수 있다. 이 함수들은 반드시 스트림을 연 후에, 그리고 스트림에 대해 다른 연산을 수행하기 전에 호출해야 한다.


도해 5.1 setbuf 함수와 setbuf 함수 요약

함수

mode

buf

버퍼 및 길이

버퍼링 종류

setbuf

 

널 아님

사용자가 제공한 buf, 길이는 BUFSIZ

전체 버퍼링 또는 줄 단위 버퍼링

NULL

(버퍼 없음)

버퍼링 없음

setvbuf

_IOFBF

널 아님

사용자가 제공한 buf, 길이는 size

전체 버퍼링

NULL

적절한 길이의 시스템 버퍼

_IOLBF

널 아님 

사용자가 제공한 buf, 길이는 size

줄 단위 버퍼링

NULL

적절한 길이의 시스템 버퍼

_IONBF

(무시됨)

(버퍼 없음)

버퍼링 없음


fflush 함수는 스트림에 대해 아직 기록되지 않은 모든 자료를 커널에게 넘겨준다. 특별한 경우로, 만일 fp가 NULL이면 fflush는 모든 출력 스트림을 방출한다.



5.5 스트림 열기


표준 입출력 스트림을 열 때에는 fopen 함수나 freopen, fdopen 함수를 사용한다.

1. fopen 함수는 지정된 파일을 연다.

2. freopen 함수는 지정된 파일을 지정된 스트림에서 연다. 만일 스트림이 이미 열려있으면 먼저 닫은 후 다시 연다.

3. fdopen 함수는 open이나 dup, dup2, fcntl, pipe, socket, socketpair, accept 함수로 얻은 기존의 파일 서술자를 받아서 표준 입출력 스트림을 그 서술자에 연관시킨다. 이 함수는 파이프나 네트워크 통신 채널을 생성하는 함수가 돌려준 서술자에 흔히 쓰인다.


도해 5.2 표준 입출력 스트림을 여는 함수들의 type 인수

type 

서술

open(2) 플래그들 

r 또는 rb

읽기용으로 연다 

O_RDONLY 

w 또는 wb 

길이가 0이 되도록 자르거나 쓰기를 위해 생성 

O_WRONLY|O_CREATE|O_TRUNC 

a 또는 ab 

추가, 즉 파일 끝에서 쓰도록 열거나 생성 

O_WRONLY|O_CREATE|O_APPEND

r+ 또는 r+b  또는 rb+

읽기와 쓰기용으로 연다 

O_RDWR 

w+ 또는 w+b 또는 wb+

길이가 0이 되도록 자르거나 읽기 및 쓰기용으로 연다 

O_RDWR||O_CREATE|O_TRUNC

a+ 또는 a+b 또는 ab+ 

파일의 끝에서 읽기나 쓰도록 열거나 생성

O_RDWR||O_CREATE|O_APPEND


 문자 b를 type의 일부로 사용하면 표준 입출력 시스템은 텍스트 파일과 이진 파일을 구별한다. 그러나 UNIX커널은 그 두 종류의 파일을 구별하지 않으므로, type 인수에 b를 포함시켜도 아무런 효과도 없다.

 열린 스트림을 닫을 때에는 fclose를 호출한다.



5.6 스트림 읽고 쓰기


스트림을 연 후에는 스트림에 대해 다음과 같은 세 종류의 서식 없는(unformatted) 입출력 연산을 수행할 수 있다.

1. 문자 단위 입출력. 

2. 줄 단위 입출력.

3. 직접 입출력. 


입력 함수들

문자를 한 번에 한 자씩 읽어 들일 때 사용하는 세 가지 함수는 getc, fgetc, getchar 이다. getchar 함수는 getc(stdin)과 동등하게 정의된다. getc와 fgetc의 차이는, getc는 매크로로 구현될 수 있지만 fgetc는 매크로로 구현될 수 없다는 것이다.

1. 부수 효과(side effect)를 가진 표현식을 getc에 대한 인수로 지정하지는 말아야 한다. 매트로 확장 시 인수가 여러 번 평가될 수 있기 때문이다.

2. fgetc는 반드시 함수임이 보장되므로, fgetc의 주소를 취할 수 있다. 따라서 fgetc의 주소를 다른 함수의 인수로 넘겨주는 것이 가능하다.

3. 함수 호출에 따른 추가 부담 때문에, fgetc 호출이 getc 호출보다 더 많은 시간을 소비할 수 있다.

이 함수들에서 오류가 발생했을 때의 반환값과 파일의 끝에 도달했을 때의 반환값이 동일하다는 점을 주의하기 바란다. 그 두 경우를 구분하기 위해서는 반드시 ferror 함수나 feof 함수를 호출해야 한다.

 스트림을 읽은 후에 ungetc함수를 이용해서 그 문자들을 다시 스트림에 밀어 넣을 수 있다.

출력 함수들

앞에서 살펴본 입력 함수들에 대응되는 세 가지 출력 함수는 putc, fputc, putchar이다. 입력 함수에서처럼, putchar(c)는 putc(c, stdout)과 동등한 것으로 정의되며, putc는 매크로로 구현될 수 있는 반면 fputc는 매크로로 구현될 수 없다.



5.7 줄 단위 입출력


줄 단위 입출 기능을 제공하는 함수는 fgets와 gets이다. fgets를 호출할 때에는 n인수에 버퍼 크기를 지정해야 한다. gets 함수는 사용하지 말아야 한다.

줄 단위 출력 기능은 fputs 함수와 puts 함수가 제공한다. 항상 fgets와 fputs만 사용한다면 각 줄의 끝에서 새 줄 문자를 직접 처리해야 하는지의 여부를 혼동할 여지가 없다.



5.8 표준 입출력의 효율성


도해 5.4 getc와 putc를 이용해서 표준 입력을 표준 출력으로 복사하는 프로그램


#include "apue.h"

int main(void) {
	int c;
	while ((c = getc(stdin)) != EOF) {
		if (putc(c, stdout) == EOF) {
			err_sys("output error");
		}
	}
	if (ferror(stdin)) {
		err_sys("input error");
	}
	exit(0);
}

도해 5.5 fgetc와 fputc를 이용해서 표준 입력을 표준 출력으로 복사하는 프로그램


#include "apue.h"

int main(void) {
	char buf[MAXLINE];
	while (fgets(buf, MAXLINE, stdin) != NULL) {
		if (fputs(buf, MAXLINE, stdout) == EOF) {
			err_sys("output error");
		}
	}
	if (ferror(stdin)) {
		err_sys("input error");
	}
	exit(0);
}

도해 5.4나 도해 5.5에서 표준 입출력 스트림을 명시적으로 닫지는 않는다는 점을 주목하기 바란다. 대신 exit 함수가 아직 기록되지 않은 모든 자료를 방출하고 모든 열린 스트림을 닫는다는 점에 의존한다. 


도해 5.6 표준 입출력 함수들을 이용한 프로그램들의 시간 측정 결과

함수 

사용자 CPU

(초) 

시스템 CPU
(초)

클록 시간

(초)

프로그램 텍스트

바이트 수 

도해 3.6의 최적시간

0.05 

0.29 

3.18 

 

fgets,fputs 

2.27 

0.30 

3.49 

143 

getc, putc 

8.45

0.29 

10.33 

114 

fgetc, fputc 

8.16 

0.40 

10.18 

114 

도해 3.6의 단일 바이트 시간 

134.61

249.94 

394.95 

 

한가지 명심할 점은, 이러한 시간 측정 결과는 오직 실제로 시간을 측정한 해당 시스템에서만 유효하다는 것이다. 모든 유닉스 시스템에서 동일하게 구현되지 않은 기능들이 많으며, 그런 기능들이 구체적인 측정 결과에 영향을 미칠 수 있다. 그렇긴 하지만 이런 일단의 수치들을 얻고 여러 버전이 다른 결과를 내는 이유를 설명해 보는 것은 시스템을 더 잘 이해하는 데 도움이 된다. 이번 절과 3.9에서 우리는 표준 입출력 라이브러리가 read, write 함수를 직접 호출하는 것에 비해 아주 느리지 않음을 알 수 있었다. 대부분의 본격적인 응용 프로그램에서 사용자 CPU 시간을 가장 많이 차지하는 것은 표준 입출력 루틴들이 아니라 응용 프로그램 자체이다.



5.9 이진 입출력


 이진 입출력에서는 한 번에 하나의 구조체 전체를 읽거나 쓰는 경우가 많다. 이를 getc나 putc로 수행 한다면 구조체 전체를 루프를 통해서 한 번에 한 바이트씩 훑어 나가야 한다. fputs나 fgets는 널 바이트때문에 구조체를 그대로 읽어 드리지 못한다. 그런 이유로 이진 입출력을 위한 fread, fwrite 두 가지 함수를 제공된다.

#include 

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);

 이 함수들은 주로 다음 두 가지 용도로 쓰인다.

  1. 이진 배열을 읽거나 쓴다. 다음은 어떤 부동소수점 배열의 2번 원소에서 5번 원소까지 기록하는 예이다.
    	float data[10];
    
    	if (fwrite(&data[2], sizeof(float), 4, fp) != 4)
    		err_sys("fwrite error");
    
    이런 경우 배열의 한 원소의 크기를 size 인수에 지정하고 배열 개수를 nobj 인수에 지정한다.
  2. 구조체 하나를 통째로 읽거나 쓴다. 다음이 그러한 예이다.
    	struct {
    		short count;
    		long total;
    		char name[NAMESIZE];
    	} item;
    
    	if (fwrite(&item, sizeof(item), 1, fp) != 1)
    		err_sys("fwrite error");
    
    이런 경우 size 인수에는 구조체의 크기를, nobj에는 1(기록할 객체의 개수)을 지정한다.

fread와 fwrite는 읽거나 쓴 객체들의 개수를 돌려준다. 읽기의 경우 오류가 발생했거나 파일의 끝에 도달했다면 읽은 객체 개수가 nobj보다 작을 수 있다. 그런 경우에는 반드시 ferror나 feof를 호출해서 오류인지 파일 끝인지를 구분해야 한다. 쓰기의 경우, 반환된 개수가 요청한 개수 nobj보다 작다면 오류가 발생한 것이다.

 이진 입출력의 근본적인 문제점 하나는, 같은 시스템에서 기록한 자료를 읽어 들이는 데에만 사용해야 한다는 것이다. 오늘날 이질적인 시스템들이 네트워크로 연결되어 있기 때문에 문제가 된다.




5.10 스트림 위치 조회 및 설정


 표준 입출력 스트림의 읽기, 쓰기 위치를 조회하거나 설정하는 방법

  1. ftell 함수와 fseek 함수 사용. 파일 위치를 하나의 긴 정수에 저장할 수 있다는 가정을 깔고 있다.
  2. ftello 함수와 fseeko 함수 사용. 긴 정수 대신 off_t 자료 형식을 사용한다.
  3. fgetpos 함수와 fsetpos 함수 사용. 파일 위치의 기록에 fpos_t라는 추상 자료 형식을 사용하는데, 구현은 이 자료 형식을 파일 위치를 기록하기에 충분한 크기로 정의할 수 있다.

응용 프로그램을 비 유닉스 시스템으로 이식할 때에는 fgetpos와 fsetpos를 사용해야 한다.



5.11 서식화된 입출력


서식화된 출력

서식화된 출력(formatted output)은 다음과 같은 다섯 가지 printf류 함수들이 담당한다.

 #include 

int printf(const char *restrict format, ...);

int fprintf(FILE *restrict fp, const char *restrict format, ...);

int dprintf(int fd, const char *restrict format, ...);
// 반환값(셋 다): 성공 시 출력된 문자 개수, 출력 오류 시 음수

int sprintf(char *restrict buf, const char *restrict format, ...);
// 반환값: 성공 시 배열에 저장된 문자 개수, 부호화 오류 시 음수

int snprintf(char *restrict buf, size_t n, const char *restrict format, ...);
// 반환값: 만일 버퍼가 충분히 크다면 버퍼에 저장되었을 문자 개수, 부호화 오류 시 음수

printf 함수는 서식화된 문자들을 표준 출력에 기록하고 fprintf는 지정된 스트림에, dprintf는 지정된 파일 서술자에 기록한다. 그리고 sprintf는 서식화된 문자들을 배열 buf에 집어넣는다. sprintf 함수는 배열 끝에 자동으로 널 문자를 추가하나, 이 널 바이트는 반환값(문자 개수)에 포함되지 않는다.

 dprintf는 파일 서술자를 직접 받는다. 따라서 파일 서술자를 fdopen을 이용해서 파일 포인터로 변환한 후 fprintf를 호출할 필요가 없다.

 format 인수로 지정하는 서식 명세(format specification) 문자열은 그 인수 이후의 추가 인수들을 부호화하고 표시하는 방식을 결정한다. 추가 인수들은 퍼센트 기호(%)로 시작하는 특별한 변환 명세(conversion specification)에 따라 부호화된다. 서식 명세에서 변환 명세 이외의 문자들은 수정 없이 그대로 복사된다.


%[플래그][필드너비][정밀도][길이수정자]변환형식


도해 5.7 변환 명세의 플래그 성분

플래그

설명

`

(아포스트로피) 정수를 천 단위로 구분 문자와 함께 서식화

-

필드 안에서 출력을 왼쪽으로 정렬

+

부호 있는 변환의 부호를 항상 표시

(빈칸)

부호가 만들어지지 않은 경우 빈칸을 앞에 붙임

#

다른 형태의 서식을 이용해서 변환(이를테면 16진 서식에 접두사 0x를 붙이는 것을 포함해서)

0

필드 앞 부분을 빈칸 대신 0들로 채움


필드너비 성분은 변환의 최소 필드 너비를 결정한다. 변환 결과의 문자 개수가 그 너비보다 적으면 나머지 공간에 빈칸이 채워진다. 이 성분에는 음이 아닌 십진 정수 또는 별표(*)를 지정할 수 있다.

정밀도 성분은 정수 변환의 경우에는 표시될 최소 숫자 개수이고 부동소수점 변환의 경우에는 소수점 오른쪽에 표시될 최소 숫자 개수, 문자열 변환의 경우에는 최대 바이트 개수를  뜻한다.

길이수정자 성분은 인수의 크기를 결정한다.

변환형식 성분은 생략할 수 없다. 이 성분은 인수의 해석 방식을 결정한다.


도해 5.8 번환 명세의 길이 수정자 성분

길이 수정자

설명

hh

부호 있는 또는 없는 char

h

부호 있는 또는 없는 short

l

부호 있는 또는 없는 long 또는 넓은 문자

ll

부호 있는 또는 없는 long long

j

intmax_t 또는 uintmax_t

z

size_t

t

ptrdiff_t

L

long double


도해 5.9 변환 명세의 변환 형식 성분

변환 형식

설명

d, i

부호 있는 십진수

o

부호 없는 팔진수

u

부호 없는 십진수

x,X

부호 없는 16진수

f, F

배정도(double) 부동소수점 수

e, E

지수 형식으로 표현되는 배정도(double) 부동소수점 수

g, G

변환되는 값에 따라 f나 F, e, E로 해석됨.

a, A

16진 지수 형식으로 표현되는 배정도(double) 부동소수점 수

c

문자(길이 수정자 l이 있으면 넓은 문자)

s

문자열(길이 수정자 l이 있으면 넓은 문자열)

p

void를 가리키는 포인터

n

부호 잇는 정수르 가리키는 포인터; 그 정수는 지금까지 기록된 문자 개수를 뜻함

%

% 문자 자체

C

넓은 문자(XSI 옵션; lc와 동등)

S

넓은 문자열(XSI옵션; ls와 동등)


서식화된 입력

서식화된 입력을 처리하는 세 가지 scanf류 함수들이다.

#include 

int scanf(const char *restrict format, ...);

int fscanf(FILE *restrict fp, const char *restrict format, ...);

int sscanf(const char *restrict buf, const char *restrict format, ...);
// 반환값(셋 다): 변환 성공 시 배정된 입력 항목 개수, 입력 오류이거나 아무 변환도 일어나지 않고 파일 끝에 도달했으면 EOF

 인수들의 변환 및 배정 방식은 format으로 지정된 서식 문자열의 변환 명세들에 의해 결정된다. 하나의 변환 명세는 퍼센트 기호(%)로 시작한다.

 변환 명세는 생략 가능한 세 성분과 반드시 지정해야하는 성분 하나로 구성된다.


%[ * ][필드너비][m][길이수정자]변환형식


제일 앞의 생략 가능한 별표는 배정이 일어나지 않게 만드는 역할을 한다. 별표를 지정하면 입력이 변환 명세의 나머지 부분에 따라 변환되긴 하지만 그 결과가 변수에 배정되지는 않는다.

 필드너비 성분은 필드의 최대 문자 개수를 결정한다. 길이수정자 성분은 변환의 결과를 배정할 인수의 크기를 뜻한다. 이 성분에 사용할 수 있는 값들은 printf류 함수들의 길이 수정자에 쓰이는 값들과 동일하다.

 변환형식 필드는 printf류 함수들에 쓰이는 변환 형식 필드와 비슷하되 몇 가지 차이점이 있다. 한가지는, 입력의 부호 있는 형식의 값이 부호 없는 형식의 변수에 저장될 수도 있다.

 필드 너비와 길이 수정자 사이에 생략 가능한 문자 m을 배정 할당 문자(assignment-allocation character)라고 부른다. 이 문자를 %c나 %s, %[와 함께 사용 하면 변환된 문자열을 담을 메모리 버퍼를 함수가 직접 할당해준다. 버퍼가 더 이상 필요하지 않게 되면 반드시 free함수를 이용해서 버퍼를 해제해야 한다.


도해 5.10 변환 명세의 변환 형식 성분

변환 형식

설명

d

부호 있는 십진수

i

부호 있는 정수, 밑(진수)은 입력의 서식에 의해 결정됨 

o

부호 없는 팔진수(입력은 부호 있는 형식일 수 있음)

u

부호 없는 십진수(입력은 부호 있는 형식일 수 있음)

x,X 

부호 없는 16진수(입력은 부호 있는 형식일 수 있음)

a,A,e,E,f,F,g,G

부동소수점 수

c 

문자(길이 수정자 l이 있으면 넓은 문자)

s 

문자열(길이 수정자 l이 있으면 넓은 문자열)

[

]까지의 문자들 중 하나와 부합 

[^ 

]까지의 문자들 이외의 모든 문자와 부합 

p 

void를 가리키는 포인터 

n 

부호 잇는 정수르 가리키는 포인터; 그 정수는 지금까지 기록된 문자 개수를 뜻함

% 

문자 자체 

C 

넓은 문자(XSI 옵션; lc와 동등)

S 

넓은 문자열(XSI옵션; ls와 동등)



5.12 구현 세부사항


UNIX 시스템에서 표준 입출력 라이브러리의 어떤 함수를 호출하면 결국에는 입출력 함수들이 호출된다. 각각의 표준 입출력 스트림에는 파일 서술자가 연관되며, 특정 스트림에 연관된 파일 서술자는 fileno 함수로 얻을 수 있다.




5.13 임시 파일


ISO C 표준은 임시 파일의 생성을 돕는 표준 입출력 라이브러리 tmpnam, tmpfile을 정의 한다.

tmpnam 함수는 유효한, 그리고 그 어떤 기존 파일의 이름과도 일치하지 않는 문자열을 생성한다. 이 함수는 최대 TMP_MAX번 호출될 때까지 매번 서로 다른 경로이름을 생성하는데, TMP_MAX는 <stdio.h>에 정의되어 있다. (mint18.2에서는 <bits/stdio_lim.h>에 # define TMP_MAX 238328로 정의됨). ptr 인수에 NULL를 지정하면 함수는 생성한 경로이름을 정적 영역에 저장하고 그영역을 가리키는 포인터를 함수의 값으로서 돌려준다.

 tmpfile 함수는 임시 이진 파일(wb+ 형식)을 생성한다. 그 파일은 자신이 닫히거나 응용프로그램이 종료되면 자동으로 제거된다.

 단일 UNIX 규격은 임시 파일을 다루는 추가적인 함수 두 개를 XSI 옵션의 이부로서 제공한다. 바로 mkdtemp 함수와 mkstem 함수이다.

 mkdtemp 함수는 고유한 이름을 가진 디렉터리를 생성하고 mkstemp 함수는 고유한 이름을 가진 정규 파일을 생성한다.

 tmpfile 함수와 달리 mkstemp가 생성한 임시 파일은 자동으로 제거되지 않는다. 파일 시스템 이름공간에서 임시 파일을 제거하려면 해당 링크를 프로그램에서 직접 해제해야 한다.

 tmpnam 함수와 tempnam 함수는 응용 프로그램이 고유한 경로이름을 얻는 시점과 그 이름으로 파일을 생성하는 시점 사이에서 다른 프로세스가 같은 이름의 파일을 생성할 소도 있다. 따라서 그런 문제점이 없는 tmpfile 함수와 mkstemp 함수를 사용하는 것이 바람직하다.

	char	good_template[] = "/tmp/dirXXXXXX";	/* 올바른 방법 */
	char	*bad_template = "/tmp/dirXXXXXX";	/* 잘못된 방법 */


두 템플릿의 작동 방식의 차이는 문자열의 선언 방식에서 비롯된다. 첫 템플릿은 배열 변수로 선언되었으며 해당 문자열이 스택에 할당된다. 반면 둘째 템플릿은 포인터로 선언되었다. 이 경우 스택에 할당되는 것은 포인터 자체에 대한 메모리뿐이다. 컴파일러는 그 포인터가 가리키는 문자열을 실행 파일의 읽기 전용 구역에 저장한다. 따라서 mkstemp 함수가 그 문자열을 수정하려고 하면 구역 위봔 오류 (segmentation fault)가 발생한다.



5.14 메모리 스트림


UNIX 규격 버전 4에서는 메모리 스트림의 지원이 추가되었다. 메모리 스트림은 연관된 파일이 없는 표준 입출력 스트림으로, 파일이 없지만 여전히 FILE 포인터를 통해서 접근한다. 메모리 스트림에 대한 모든 입출력 연산은 주 메모리 안의 버퍼를 통해서 바이트들을 주고받는 식으로 일어난다. 겉보기에는 파일 스트림과 별 차이가 없지만, 이런 스트림들이 문자열을 다루는 데 좀 더 적합한 여러 기능을 갖추고 있음을 잠시 후 에 알게 될 것이다.

 메모리 스트림을 생성하는 함수는 세 가지이다.

fmemopen 함수로는 메모리 스트림에 사용할 버퍼를 호출자가 직접 지정할 수 있다.

도해 5.14 메모리 스트림을 여는 fmemopen 함수의 type 인수

type 

설명 

r 또는 rb 

읽기용으로 연다 

w 또는 wb 

쓰기용으로 연다 

a 또는 ab 

추가; 첫 널 바이트에서 쓰기용으로 연다 

r+ 또는 r+b 또는 rb+ 

읽기 및 쓰기용으로 연다 

w+ 또는 w+b 또는 wb+ 

길이가 0이되도록 자르거나 읽기 및 쓰기용으로 연다 

a+ 또는 a+b ab+ 

추가; 첫 널 바이트에서 읽기 및 쓰기용으로 연다 


이 값들이 파일 기반 표준 입출력 스트림에 쓰이는 것들에 대응되긴 하지만 미묘한 차이가 있음을 주의해야 한다. 첫째로, 추가용으로 메모리 스트림을 열 때에는 현재 파일 위치가 버퍼의 처 번째 널 바이트가 있는 곳으로 설정된다. 버퍼에 널 바이트가 하나도 없으면 현재 파일 위치는 버퍼의 끝을 하나 바이트 지나친 곳으로 설정된다. 추가 모드는 첫 번째 널 바이트를 기준으로 자료의 끝을 결정하므로, 메모리 스트림은 이전 자료의 저장에 적합하지 않다(이진 자료에서는 자료의 끝 이전의 위치에 널 바이트들이 있을 수 있으므로).

 둘째로, 만일 buf인수가 널 포인터이면, 읽기 전용이나 쓰기 전용으로 스트림을 여는 것이 별로 의미가 없다. 그런 경우 버퍼를 fmemopen이 할당하므로 호출자로서는 버퍼의 주소를 알아낼 수 없다. 따라서 쓰기전용으로 스트림을 연다는 것은 그 스트림에 기록한 자료를 나중에 다시 읽을 수가 없다는 뜻이다. 마찬가지로, 읽기 전용으로 스트림을 연다고 해도 그 스트림을 읽기 전에 스트림에 뭔가를 기록하는 것이 불가능하므로 메모리에 원래 있던 쓰레기 값을 읽 수 있을뿐이다.

 셋째로, 스트림에 뭔가를 기록해서 버퍼의 자료가 늘어난 후 fclose나 fflush, fseek, fseeko, fsetpos를 호출하면 스트림의 현재 위치에 널 바이트가 기록된다.

메모리 스트림을 지원하는 것은 Linux 3.2.0뿐이다. 이는 구현들이 최신 표준을 아직 따라잡지 못한 예인데, 시간이 지나면 상황이 바뀔 것이다. (Ubuntu에서 사용할 수 없었음 2017.10.18)

메모리 스트림을 생성하는 데 사용할 수 있는 다른 두 함수는 open_memstream과 open_wmemstream이다.

open_memstream함수는 바이트 지향 스트림을 생성하고 open_wmemstream함수는 넓은 문자 지향 스트림을 생성한다. 

fmemopen과 다른점

  • 스트림이 쓰기용으로만 생성된다.
  • 호출자가 버퍼를 직접 지정할 수 없다. 주소와 크기에 접근하는 것은 가능
  • 스트림을 닫은 후 호출자가 버퍼를 직접 해제해야 한다.
  • 스트림에 바이트를 추가하면 그에 따라 버퍼도 더 커진다.
버퍼 주소와 그 길이를 사용할 때 지켜야 할 규칙이 있다. 첫째로, 버퍼 주소와 길이는 오직 fclose나 fflush를 호출한 후에만 유효하다. 둘째로, 이 값들은 오직 스트림에 대한 다음 번 쓰기나 fclose 호출 이전까지만 유효하다. 버퍼가 더 커질 수 있으므로, 버퍼를 재할당해야 하는 경우도 생긴다.
 메모리 스트림은 문자열 생성에 아주 적합하다. 버퍼 넘침이 방지되기 때문이다. 또한, 메모리 스트림은 디스크상의 파일에 저자오디는 것이 아니라 오직 주 메모리에만 존재하므로, 임시 파일로 사용하기 위해 표준 입출력 스트림을 인수로 받는 함수에 메모리 스트림을 사용하면 성능이 크게 향상된다.


5.15 표준 입출력 라이브러리의 대안들


표준 입출력 라이브러리가 완벽하지는 않다. 표준 입출력 라이브러리 자체에 내제된 비효율성 하나는 자료 복사의 양이 많다는 것이다. 줄단위 함수 fgets나 fputs를 사용할 때에는 보통의 경우 자료가 두 번 복사된다. 커널과 표준 입출력 버퍼 사이에서 한 번 복사되고(해당 read나 write가 호출될 때) 표준 입출력 버퍼와 프로그램의 줄 버퍼 상이에서 또 한 번 복사된다.

 Fast I/O 라이브러리([AT&T 199a]의 fio(3)), [Korn, Vo 1991]의 sfio, [KLieger, Stumm, Unrau 1992]의 ASI(Alloc Stream Interface)등이 있다. sfio 패키지처럼 ASI는 자료 복사의 양을 최소화하기 위해 포인터를 활용한다.



5.16 요약


표준 입출력 라이브러리는 대부분의 UNIX 응용 프로그램에 쓰인다. 이 라이브러리에서 발생하는 버퍼링을 주의하기 바란다. 표준 입출력 라이브러리 사용 시 대부분의 문제와 혼란은 바로 이 버퍼링 부분에서 비롯된다.

728x90

댓글