7 스트림에서의 입/출력

이 장은 스트림을 만들기 위한 함수와 그리고 그들에게 입력과 출력을 수행하기 위한 함수를 설명하고 있다. 6장[I/O Overview] 에서 논의했던 것처럼 스트림은 꽤 추상적이고, 파일, 디바이스, 또는 프로세스의 통신채널을 나타내는 고 수준의 개념이다.


7. 1 스트림

역사적인 이유로, 스트림으로 표현되는 C의 자료구조 타입은 "스트림"보다는 FILE로 불려졌다. FILE * 타입으로 object들을 다루는 라이브러리 함수들의 대부분은 term file pointer를 스트림의 의미로 또한 사용하고 있다. 이 전문어를 넘어선 적절하지 못한 혼란은 많은 C관련 책들이 선도하고 있다. 하지만 이 매뉴얼은 "file"과 "stream"의 사용에 주의를 기울일 것이다. 오직 기술적 분별 안에서. . ( 여기서 term file pointer는 어떤 단위를( 한줄, 혹은 한 레코드, 한 문자, 한 단어 등등. . ) 포인팅 하고 있는 포인터를 말하는 듯. . . )

FILE 타입은 헤더파일 'stdio. h'에 선언되어 있다.

데이터타입: FILE

이것은 스트림 object들을 나타내기 위해 사용되는 데이터형이다. FILE object는 파일 위치를 지적하고, 정보를 버퍼링 하는 것과 같은 일들을 포함한, 파일과 연관된 모든 내부적 정보를 갖고 있다. 각 스트림은 또한 ferror와 feof를 사용해서 검사할 수 있도록 에러와 파일끝에 대한 정보를 갖고 있다. 7. 13절 [EOF and Errors] 참조.
FILE object들은 입/출력 라이브러리 함수들에 의해 내부적으로 할당되고, 관리된다. 당신이 FILE이라는 형으로 object를 창조하려고 시도하지 마라. ; 그것을 라이브러리가 하도록 내버려 두라. 당신의 프로그램들은 오직 FILE objects를 직접 다루기보다는 포인터를 이용하여 다루도록 해야한다. ( 그것은 FILE * 과 같다. )


7. 2 표준 스트림

당신의 프로그램의 메인 함수가 불려질 때, 그곳에는 이미 열기도 하고 사용도 할 수 있는, 미리 선언된 3개의 스트림을 갖게된다. 이것은 프로세스를 위해 만들어놓은 "표준"의 입/출력을 표현하기 위함이다.

이 스트림들은 헤더파일 'stdio. h'에 선언되어 있다.

변수 : FILE * stdin

이 표준 입력 스트림은 프로그램의 입력을 위한 표준의 자원(source)이다.

변수 : FILE * stdout

이 표준 출력 스트림은 표준의 입력을 위해 사용된다.

변수 : FILE *stderr

이 표준 에러 스트림은 프로그램의 에러 발생에 대한 메시지와 진단에 대한 조언을 위해 사용된다.

GNU 시스템에서 당신이 정한 어떤 파일이나 프로세스도 쉘에서 제공하는 파이프나 리다이렉션을 사용해서 이 스트림들과(표준 입/출력, 에러 스트림) 통할 수 있다. ( 이 기능들을 도구로 사용하는 원시적 쉘은 9장[File System Interface] 에 설명되어 있다. ) 대부분의 다른 운영체제는 이와 비슷한 메커니즘을 제공하지만 이들을 어떻게 사용하는 지에 대한 세밀한 부분에서는 많은 차이가 있을 수 있다. GNU C 라이브러리에서는 stdin, dtdout과 stderr은 다른 어느 변수들처럼 당신이 정할 수 있는 보통의 변수이다. 예를 들어 표준출력에서 파일로 리다이렉션 하기 위해서는 다음과 같이 할 수 있다.

fclose (stdout);
stdout = fopen ("standard-output-file", "w");

뿐만 아니라, 다른 시스템에서는 stdin, stdout과 stderr은 보통의 방법으로 지정할 수 없는 매크로란 걸 기억하라. 그러나 당신은 하나를 닫고, 다시 여는 효과를 얻기 위해 freopen을 사용할 수 있다. 그것은 7. 3절 [Opening Streams] 참조.


7. 3 스트림 열기

하나의 파일을 fopen함수를 사용하여 여는 것은 새로운 스트림을 만들고 파일과 스트림사이의 연결을 만드는 것이다. 이것은 새로운 파일을 만드는 것도 포함된다.

이 절에서 설명된 모든 것은 헤더파일 'stdio. h'에 선언되어 있다.

함수: FILE * fopen (const char *filename, const char *opentype)

fopen함수는 입출력을 위해 파일이름으로 파일을 열고 스트림에게 포인터를 반환한다. 여기서 opentype 인수는 문자열로서 어떻게 열려진 파일을 제어하고 결과물로 얻어진 스트림의 속성을 지정하는 역할을 한다. 그것은 반드시 아래와 같은 문자들 중 하나로 시작해야한 만다.

'r'

오직 읽기 모드로 존재하는 파일을 열 때 사용

'w'

오직 쓰기 모드로 파일을 열 때, 만약 이미 그 파일이 존재한다면, 이미 존재하고 있는 것을 없애고 새로운 파일을 만드는 것과 같은 효과를 낸다.

'a'

덧붙임을 위해 파일을 열 때 사용하라. 그럴 때 오직 파일의 끝에만 쓰여 진다. 만약 파일이 이미 존재한다면 그 안에 이미 존재하고 있는 내용은 변하지 않고 스트림이 그 파일의 끝에 덧분여진 것 과 같은 결과를 낳는다. 그러나 파일이 없을 때는 새로운 파일이 만들어진다.

'r+'

이미 존재하는 파일을 읽거나 쓰는 작업을 동시에 하고자 할 때 사용한다. 그 파일 안에 이미 존재하고 있는 내용은 변하지 않고 파일 포인터는 파일의 처음에 존재하여 처음부터 파일을 읽고, 쓸 수 있도록 한다.

'w+'

쓰기와 읽기를 동시에 하고자 할 때 사용한다. 하지만 만약 파일이 이미 존재한다면 그 안에 있는 내용은 무시되고, 존재하는 파일이 없다면 새로운 파일을 만든다.

'a+'

읽고, 덧붙이는 일을 동시에 하는 파일을 열거나 만들 때 사용한다. 만약 그 파일이 이미 존재한다면 이미 있는 내용은 변화되지 않고, 존재하는 파일이 없으면 새로운 파일이 만들어진다. 읽기 위해 사용 할 때 파일의 처음 위치는 파일의 시작점이지만 출력은 항상 파일의 뒤에 덧붙여진다.

'+'기호

입력과 출력을 동시에 할 수 있도록 스트림에게 요구할 때 사용한다. ANSI에서는 그와 같은 스트림을 사용하여 읽기에서 쓰기 모드로 변할 때, 혹은 그 반대의 경우일 때, 반드시 fflush함수를 호출하거나, fseek처럼 파일의 위치를 정하는 함수를 사용하기를 권장한다. 왜냐하면 내부적인 버퍼가 아마도 비어있지 않을 것이므로. GNU C 라이브러리는 이러한 제한을 가지고 있지 않는다; 당신은 그 스트림에게 언제든지 쓰기나 읽기 명령을 마음대로 할 수 있다. (fflush에 대한 것은 7. 17절 [Stream Buffering]를 참조. fseek에 대한 것은 7. 15절 [File Positioning]를 참조하라. )

GNU C 라이브러리에서는 읽기 위한 모드로 하나를 더 선언해 놓고 있다. 그것은 문자'x'로써 이것은 만약 이미 존재하는 파일이 있다면 그 파일을 그냥 여는 것이 아니라 아예 fopen함수가 실패하는 것으로 끝남으로써 새로운 파일을 연다는 사실을 강조할 때 사용한다. 이것은 open 함수를 사용할 때 쓰이는 O_EXCL 옵션과 동일한 동작을 한다.

열기모드중의 하나인 문자 'b'는 표준의 c안에 있고, 텍스트 형태의 스트림이 아니라 바이너리 형태의 스트림을 요구할 때 사용한다. 그러나 이것은 POSIX 시스템들에게는 아무런 차이가 없다( GNU시스템을 포함하여 ) 만약 '+'와 'b'를 둘다 쓰면 어느 한쪽의 명령으로 나올 수 있다. 7. 14절 [Binary Streams] 참조.

어느 열기 모드안의 문자들을 간단히 무시된다. 그들을 다른 시스템에서만 의미가 있을 수 있다. 만약 열기가 실패한다면 fopen 함수는 널 포인터를 반환한다.

당신은 같은 파일상에 동시에 여러 개의 스트림( 혹은 파일기술자)을 정할 수 있다. 만일 당신이 오직 입력(읽기라고 하면 더 쉽겠네요. . )에만 이일을 한다면 이것은 올바르게 동작하지만 출력(역시 쓰기. . )에 이 동작을 가할 때는 주의를 기울여야 한다. 8. 5절 [Stream/Descriptor Precautions] 참조. 이것은 하나의 프로그램( 보통이 아닌)이나 여러 개의 프로그램( 쉽게 일어날 수 있다 )이던지 간에 실제로 사실로 나타난다. 그래서 동시에 억세스 하는 것을 피하기 위해 파일 잠금 도구를 사용하는 것이 유익할 것이다.

 

매크로 : int FOPNE__MAX

이 매크로의 값은 정수 상수 표현으로, 동시에 오픈할 수 있음을 보증하는 스트림의 최소개수를 나타낸다. 이 상수 값은 세 개의 표준 스트림, stdin, stdout, stderr에서는 적어도 8개이다.

함수 : FILE * freopen (const char *filename, cnost char *opentype, FILE *stream)

이 함수는 fclose와 fopen의 혼합과 같다. 이 함수는 첫째로 스트림에 의해 참조된 스트림을 닫는데, 이때 이 프로세스에서 검출된 어떤 에러도 무시한다. ( 에러가 무시되는 이유는 당신이 그 스트림을 사용하여 어느 출력을 했었다면 그 출력 스트림에 freopen을 사용할 수 없게되기 때문이다. ) 그러면 지정된 파일이름을 가진 파일이 fopen과 같은 파일 열기 모드를 가지고 열려진다.


7. 4 스트림 닫기

fclose함수를 사용하여 스트림을 닫을 때, 스트림과 파일사이의 연결은 취소된다. 당신이 스트림을 닫은 후에, 그것에 어떤 부가적인 명령을 수행하게 할 수 없다.

함수 : int fclose (FILE *stream)

이 함수는 스트림과 그것과 대응하는 파일과의 연결을 깨는데 사용된다. 출력버퍼안에 있는 내용은 쓰여지고, 입력버퍼안에 있는 내용은 버려진다. fclose함수는 성공적으로 파일이 닫혀진 경우에는 0을 반환하고, 에러가 검출된 경우에는 EOF를 반환한다.

당신이 fclose함수를 호출하여 출력 스트림을 닫을 때, 실제로 언제든지 에러들이 검출될 수 있기 때문에 에러를 체크하는 것은 중요하다. 예를 들어, fclose함수를 사용하여 버퍼에 남아있는 내용을 쓸 때에 혹시, 디스크가 다 찬 경우가 발생하여 그것으로 에러가 발생할 수 있을지 모른다. 심지어 당신이 버퍼가 비어있는 것을 안 경우라도, 만일 당신이 NFS(Network File System)을 사용하여 파일을 닫을 때 여전히 에러가 발생할 수 있다.

fclose함수는 'stdio. h'에 선언되어 있다.

만약 메인 함수가 당신의 프로그램에 반환하거나, 혹은 exit함수가 호출된다면 ( 22. 3. 1절[Normal Tremination] 참조), 모든 열려진 스트림 자동적으로 닫혀진다. 그러나 프로그램이 정상적이 아닌 다른 방법으로, 죽 abort함수를 호출하거나( 22. 3. 4절 [Aborting a Program] 참조), 어떤 심각한 신호( 21장 [Signal Handling] 참조) 가 있어서 프로그램이 종료된다면 열려진 스트림들은 완전히 닫혀지지 않을 것이다. 버퍼 안에 있는 내용들도 비워지지 않고, 파일들은 불완전해질 것이다. 좀더 많은 스트림의 버퍼화에 대한 정보를 보려면 7. 17절 [Stream Buffering] 를 참조하라.


7. 5 문자들이나 라인의 간단한 출력

이 절은 문자나 라인 단위의 출력을 수행하기 위한 함수들을 설명하고 있다.

이 함수들은 헤더파일 'stdio. h'에 선언되어 있다.

함수 : int fputc (int c, FILE *stream)

이 fputc함수는 문자 c 을 unsigned char형으로 변화시키고 그것을 스트림에 출력한다. 만약 에러가 발생하면 EOF를 반환하고, 그렇지 않으면 문자 c 가 출력된다.

함수 : int putc (int c, FILE *stream)

이 함수는 대부분의 시스템에서 속도를 빠르게 하기 위해 매크로로 실행된다는 것을 제외하고는 fputc와 같다. 여러 개의 인수보다는 하나의 인수로 이것을 평가할 때 그런 결과에 이른다. 그래서 putc는 보통 단일 문자를 출력하는데 사용하기에는 가장 좋은 함수이다.

함수 : int putchar (int c)

putchar 함수는 putc함수가 스트림의 인수로 stdout을 사용할 때는 동등하다.

함수 : int fputs( const char *s, FILE *stream)

이 함수는 파일 스트림에 문자열 s를 출력한다. 종료문자로 null문자가 쓰여지지 않는다. 이 함수는 newline character도 더하지 않고, 오직 문자열 안에 있는 문자들만 출력한다.
이 함수는 에러가 발생하면 EOF를 반환하고 그렇지 않으면 음이 아닌 값을 반환한다.
예를 들어:
fputs ("Are ", stdout);
fputs ("you ", stdout);
fputs ("hungry?\n", stdout);
이렇게 하면 출력은 새로운 줄이 하나 따르는 'Are you hungry?'가 된다.

함수 : int puts (const char *s)

puts함수는 표준스트림 stdout에 newline문자가 붙어있는 문자열 s를 출력한다. 스트링의 종료 널 문자는 출력되지 않는다. puts함수는 간단한 메시지를 출력하는데 가장 편리한 함수이다. 예를 들어:
puts ("This is a message. ");

함수 : int putw (int w, FILE *stream)

이 함수는 워드 w(워드란 바이트의 배가되는 개념이니 형으로 따지면 int가 되죠. . )를 스트림에 출력한다. 이 함수는 SVID와의 호환성을 위해서 제공되지만 우리는 대신에 fwrite를 사용한다. (7. 12절 [Block Input/Output] 참조)


7. 6 문자 입력

이 절은 문자와 라인 단위의 입력을 수행하기 위한 함수를 설명한다. 이 함수들은 헤더파일 'stdio. h'에 선언되어 있다.

함수 : int fgetc (FILE *stream)

이 함수는 스트림인 스트림에서 unsigned char 형으로 다음 문자를 읽고 그 값을 int 형으로 변화시켜 반환한다. 만약 파일에 끝인 이르거나 읽기 에러가 나면 EOF를 반환한다.

함수 : int getc (FILE *stream)

이 함수는 여러 개의 인수가 아닌 한 개의 인수에 적용할 때 더 빠르게 동작하도록 매크로로 동작하는걸 제외하고는 fgetc와 같다. getc함수는 종종 아주 최대한 활용되어서, 보통 단일 문자를 읽을 때 사용하면 가장 좋은 함수다.

함수 : int getchar (void)

getchar 함수는 getc함수가 stream의 인수 값으로 stdin을 사용하면 두 함수는 동일한 것이 된다.
이곳에 fgetc를 사용하여 입력을 행하는 함수를 보여주는 예가 있다. fgetc(stdin) 대신에 getchar()을 사용하거나 대신에 getc를 사용해서 같은 결과를 내고 있다.
int y_or_n_p (const char *question)
{
fputs (question, stdout);
while (1) {
int c, answer;
c = tolower (fgetc (stdin));
/* 줄의 첫 번째 문자를 읽어라. 이것은 원하는 문자일수도 있고, 아닐 수도 있다. */
answer = c;
while (c != '\n') /* 입력 라인의 나머지를 버려라 */
c = fgetc (stdin);
/* 만일 그것이 유용한 것이라면 그 답에 복종하라 */
if (answer == 'y')
return 1;
if (answer == 'n')
return 0;/
fputs ("Please answer y or n:", stdout);
/* 답이 쓸모 없는 것이라면: 유용한 답을 위해서 물어보아라 *
}
}

함수 : int getw (FILE *stream)

이 함수는 스트림으로 한 워드를( 아까. . 위에서 얘기했는데. . . 다시 찾아보셔요. . . ) 읽는다. 이것은 SVID와 호환성으로 위해 제공된다. 우리는 대신에 fread함수를 이용한다. ( 7. 12절 [Block Input/Output] 참조 )


7. 7 라인 단위 입력

많은 프로그램들이 라인을 기본으로 해서 입력을 받아들이므로 스트림으로부터 한 라인의 텍스트를 읽는 함수를 사용하기에 편리하다.

표준 C 는 이와 같은 일을 하는 함수를 가지고 있지만 그들의 사용에는 많은 위험이 도사리고 있다. 가령 널 문자와 심지어 (gets일 경우) 긴 라인을 혼동한다. 그래서 GNU 라이브러리는 신용할 수 있도록 라인을 읽기 위해 쉽게 만들었지만, 일반적이지 않은 getline 함수를 제공한다.

다른 GNU 확장은 getdelim으로 getline을 일반화한 것이다. 그 함수는 정해진 한계문자가 나타나기까지의 모든 것을 읽는, 즉 한계가 있는 레코드를 읽는다. 이 함수들 모두는 'stdio. h'에 선언되어 있다.

함수 : ssize_t getline (char **lineptr, size_t *n, FILE *stream)

이 함수는 스트림으로부터 전체의 라인을 읽어서, 버퍼 안에 텍스트를 저장하고( 새줄과 널 종료문자가 포함된) *lintptr안에 버퍼의 주소를 저장한다.
getline를 호출하기 전에, 당신은 malloc로 할당된 *n 바이트의 길이를 가진 버퍼의 주소를 *lineptr에 주어야한다. 만약 이 버퍼가 라인을 저장하기에 충분히 길면, getline은 이 버퍼에 라인을 저장한다. 그렇지 않으면, getline은 realloc을 사용해서 버퍼를 크게 만든 다음, *lineptr에 새로운 버퍼의 주소를 저장하고, *n안의 크기가 증가된다. 3. 3[Unconstrained Allocation] 를 참조.
만일 당신이 getline을 호출하기 전에 *lineptr을 널 포인터로 놓고, *n을 zero라고 하면 getline은 malloc을 호출하여 버퍼를 할당한다. 다른 경우, getline은 반환할 때 *lineptr은 라인 텍스트의 위치를 가리키고 있는 char * 이다. getline이 성공하면 읽은 문자들의 수를 반환한다( newline문자는 포함되지만, 널 종료문자는 포함되지 않은 ). 이 값은 종료를 표시하기 위해 삽입된 널 문자와 라인의 일부분의 하나인 널 문자들과는 구별하는 것이 가능하다. 이것은 GNU 확장이지만 스트림으로부터 라인을 읽는 방법으로 권장되고 있다. 표준 함수들은 믿을만 하지 않기 때문이다. 만약 에러가 발생하거나 파일의 끝에 도달하면 getline은 -1을 반환한다.

함수 : ssize_t getdelim (char **lineptr, size_t *n, int delimiter, FILE *stream)

이 함수는 줄의 끝임을 나타내는 newline 문자가 필요한 대신에 문자로 (그 문자가 나타날 때까지 모든걸 읽는) 종료를 나타내는걸 제외하고는 getline과 같다. 여기서 인수 delimiter에는 한계 문자를 정한다. getdelim은 정해진 그 문자가 보일 때까지 읽기를 지속한다. (아니면 파일의 끝까지) 텍스트는 lineptr안에 저장되고, 그 안에는 delimiter(한계) 문자와 종료 널 문자가 표현된다. getline처럼 getdelim은 만약 lineptr이 충분히 크지 않으면 그것을 크게 만든다.
getline은 getdelim을 이용하여 다음처럼 구현할 수 있다.
ssize_t getline (char **lineptr, size_t *n, FILE *stream)
{
return getdelim (lineptr, n, '\n', stream);
}
/* getlim의 delimiter인수를 newline 문자인 '\n'으로 주면 getline과 같게 되는 거죠. */

함수 : char * fgets (char *s, int count, FILE *stream)

fgets함수는 newline문자가 나올 때까지 스트림을 읽고, 스트링 s에 그들을 저장시키는데, 그때 스트링의 끝을 표시하기 위해 널 문자를 더한다. 당신은 문자열 s의 길이가 될 문자들의 개수를 알려줘야 하지만 읽혀지는 문자들의 수는 대부분 count-1이다. 나머지 하나의 문자는 스트링의 끝을 표시하기 위한 널 종료문자를 저장하는데 쓰인다. 이미 파일의 끝까지 읽혀졌는데 다시 당신이 fgets를 호출하면 문자열 s의 내용은 변하지 않고, 널 포인터가 반환된다. 에러가 발생됐을 때도 널 포인터는 반환된다. 그렇지 않으면 반환 값은 포인터 s가 된다.
주의 : 만약 입력 데이터가 널 문자라면, 당신은 아무 것도 할 수 없다. 그러므로 당신이 데이터에 널 이 포함되지 않았음을 확신할 수 없다면 fgets를 사용하지 말라. 사용자에 의해 편집된 파일을 읽을 때, fgets를 사용하는 것을 하지 말라고 하는 이유는 만약 사용자가 널 문자를 포함 시켰다면 당신이 에러 메시지를 프린트 해주거나, 아니면 그것이 가능하도록 처리를 해주어야 하기 때문이다. 우리는 fgets대신에 getline을 사용하도록 권장한다.

비난받는 함수: char * gets (char *s)

gets함수는 표준 스트림 stdin으로 부터 다음 newline문자가 나올 때까지 문자들을 읽고, 그들을 문자열 s에 저장한다. gets함수는 앞에서 보여준 fgets와는 다르게(fgets함수는 문자열에 newline문자를 복사한다. ) newline문자를 취하지 않고 버린다. 만약 gets함수는 에러가 발생하거나 파일의 끝에 이르면 널 포인터를 반환하고, 그렇지 않은 경우에는 문자열 s를 반환한다. gets함수는 문자열 s 가 오버플로우를 일으키는 경우에 대비한 아무런 보호책을 제공하지 않기 때문에 매우 위험한 함수이다. GNU 라이브러리 단지 호환성이라는 문제 때문에 이 함수를 제공하고 있을 뿐이다. 그러므로 당신은 gets대신에 fgets나 getline을 사용하라. 당시에게 이것을 상기시키기 위해 링커는( 당신이 GNU ld를 사용한다면 ) 당신이 gets를 사용할 때마다 경고를 내보낼 것이다.


7. 8 읽지 않기

구문분석 프로그램에서 스트림으로부터 문자를 지우지 않고 입력 스트림 안에서 다음 문자를 시험하기에 종종 유용하게 쓰인다. 당신의 프로그램에서 이것을 읽을 때 그냥 흘깃 보는 것과 같아서 이것을 "peeking ahead : 미리 엿보기"라고 부른다.

I/O 스트림을 사용할 때 당신은 문자를 일단 읽은 다음, 그것을 다시 읽지 않는 동작을 통해 입력을 미리 엿볼 수 있다. ( 또한 스트림에서 뒤로 밀기라고도 불린다. ) 문자 읽지 않기( Unreading a character)는 fgetc나 다른 입력 함수를 사용할 때, 스트림으로부터 전에 입력받았던 문자를 다시 입력받을 때 사용하기 유용한 것이다.

뭔 소린지 잘 모르시겠다구요. . . . ?

음. . 다음에 Unreading이 무슨 의미인지 나옵니다. 키키키. . .

 

7. 8. 1 Unreading이란 무슨 의미인가

이해를 돕기 위해 여기 그림 설명이 있다. 당신이 파일에서 읽어서 6개의 문자를 가진 한 개의 스트림을 갖고 있다고 가정하라. 그 문자는 'foobar'이다. 당신이 이미 3개의 문자를 읽었다고 가정하면 상황은 다음과 같다. :

f o o b a r
^
그러므로 다음 입력 문자는 'b'가 될 것이다.
만일 'b'를 읽는 대신에 'o'를 unread시키면 상황은 다음과 같다.
f o o b a r
|
o--
^
그래서 다음 입력 문자들은 'o'와 'b'가 될 것이다.
만일 당신이 'o'대신에 '9'를 unread 시키면, 상황은 다음과 같이 변한다.
f o o b a r
|
9--
^
그러면 다음 입력 문자들은 '9'와 'b'가 된다.

 

7. 8. 2 Unreading 하기 위해 ungetc를 이용하기

문자를 unread하기 위한 함수를 ungetc라고 부르는데, 그 이유는 getc의 동작을 되돌리기 때문이다.

함수 : int ungetc (int c, FILE *stream)

ungetc함수는 입력 스트림 상에서 문자 c의 뒤로 밀어놓는다. 그래서 다음 입력은 다른 어느 문자를 읽기 전에 c 가 읽혀질 것이다. 만일 c가 EOF이면, ungetc는 아무 일도 하지 않고 단지 EOF를 반환한다. 이것은 getc로 부터 에러를 체크하지 않고 getc의 반환 값으로 ungetc를 호출하도록 한다. unread할 문자들이 스트림으로부터 읽은 마지막 문자와 꼭 같을 필요는 없다. 실제로 ungetc로 그들을 unread하기 전에 스트림으로부터 실제로 읽은 어느 문자일 필요가 없다는 것이다. ( 아까 그림 설명에서 문자'9'를 unread한 것과 같은 것이죠. . . )그러나 이것은 프로그램을 출력을 위한 것치고는 좀 이상한 방법이다. ; 보통 ungetc는 동일한 스트림으로부터 읽었던 문자를 unread하는데 사용한다.

GNU C 라이브러리는 어떤 스트림에 unread할 때 오직 한 문자만을 지원하고, 새로운 입력을 받음이 없이 두 번 ungetc를 호출하는 것을 금한다. 다른 시스템들은 여러 개의 문자들을 unread하는 것이 허용된다. ; 그러면 역순으로 문자들을 읽을 수 있다는 것이 된다.

문자들을 뒤로 밀기( unread라 함이 더 이해하기 쉬운 것 같은데. . . )는 파일의 내용을 변화시키지 않는다. ; 오직 스트림을 위한 내부적 버퍼에 영향을 미친다. 만약 파일내부의 위치를 정하는( fssek나 rewind와 같은 7. 15절 [File Positioning] 를 참조) 것이 호출되었다면, 아직 쓰여지지 않은 pushed_back 문자들은 버려진다.

파일의 끝에 있는 스트림에서 문자를 unread 하는 것은 스트림의 파일의 끝임을 나타내는 지시 단어를 지우는데, 왜냐하면 그것을 유용한 입력 문자로 만들기 때문이다. 당신이 그 문자를 읽은 후에 다시 읽으려고 시도하면 파일의 끝과 다시 만날 수 있다.

여기에 공백문자를( whitespace characters) 건너뛰기 위해 getc와 ungetc를 사용하는 예제가 있다. 이 함수가 공백이 아닌 문자에 도달하면 그 문자를 unread하여서 다음 읽기 동작에서 다시 그 문자가 보여지도록 한다.

#include <stdio. h>
#include <ctype. h>
void skip_whitespace (FILE *stream)
{
int c;
do
/* EOF를 체크할 필요가 없다. 왜냐하면 isspace는 EOF를 무시하지 않고 ungetc는 EOF를 무시하기 때문이다. */
c = getc (stream);
while (isspace (c));
ungetc (c, stream);
}


7. 9 형식화된 출력

이 절에 기술된 함수들은(printf와 관련함수들) 형식화된 출력을 수행하는 편리한 방법을 제공한다. printf를 호출할 때는 나머지 인수들의 값을 형식화하는 방법을 지정하는 형식 문자열이나 템플리트 문자열을 사용한다.

당신의 프로그램이 행단위 또는 문자 단위의 처리만을 특별히 수행하는 필터가 아닌 이상, 이 절에 기술되어 있는 printf와 관련 함수들을 사용하는 것이 출력을 수행하는 가장 쉽고 상세한 방법이 될 것이다. 이 함수들은 에러 메시지, 데이터 테이블 등을 프린트하는 데에 특히 유용하다.

 

7. 9. 1 형식화된 출력의 기본사항

printf 함수는 여러 개의 인수를 프린트하는 데에 사용될 수 있다. 당신이 호출할 때에 지정한 템플리트 문자열 인수는 부가적인 인수의 개수에 대한 정보뿐만 아니라 인수들의 형태와 인수들을 프린트하는 데에 사용될 양식에 관한 정보도 제공한다.

템플리트 문자열 내의 통상적인 문자들은 단순히 출력 스트림 그대로 적으면 된다. 반면에 템플리트 내에서 '%'문자에 의해 소개되는 변환 지정자들은 후속 인수들이 출력 스트림으로 형식화되어서 쓰여지도록 만든다. 예를 들면,

int pct = 37;
char filename[] = "foo. txt";
printf (" `%s'의 처리는 %d%% 완료됨. \n ", filename, pct);
위 프로그램의 출력은 이렇다.
'foot. txt'의 처리는 37% 완료됨.

이 예는 '%d' 변환자의 사용은 int 인수가 십진수로 프린트되도록 지정하며, '%s' 변환자는 문자열 인수의 프린트를 지정하며, '%%' 지정자는 리터럴 '%' 문자의 프린트를 지정함을 보여준다. 정수 인수를 8진, 10진, 16진의 무부호 값으로 (각각 '%o', '%u', '%x') 프린트 되도록 하는 변환자도 있고, 문자 값('%c')으로 프린트되도록 하는 변환자도 있다.

부동소수점수는 '%f' 변환자를 사용하면 정상적인 고정소수점수로 프린트될 수 있으며, '%e'변환자를 사용하면 지수 표기로 프린트될 수 있다. '%g'변환자는 '%e'나 '%f' 형식을 사용하는데, 특정 숫자의 크기에 어떤 것이 더 적절한가에 달려있다.

당신은 '%'와 적용할 변환자를 나타내는 문자 사이에 수식어를 써넣음으로써 형식을 더 상세하게 통제할 수 있다. 이들은 변환자의 일상적인 행위를 쉽게 변경할 수 있다. 예를 들어, 대부분의 변환자 지정은 당신이 최소한의 필드 폭을 규정할 수 있도록 허용하며, 또한 필드내에서 그 결과를 왼편으로 정렬 또는 오른편으로 정렬할 것인가를 나타내는 플래그를 지정할 수 있도록 허용한다.

허용된 특정 플래그들, 수식어들과 그 해석은 특정 변환자에 따라 다르다. 이 점은 모두 다음 장에 상세하게 기술된다. 이것이 처음에 너무 복잡하게 보인다고 근심할 필요는 없다; 당신은 전혀 수식어들을 사용하지 않고서도 항상 자유로운 형식의 출력을 상당히 얻을 수 있다. 수식어들은 대체로 출력이 테이블에서 "더 예쁘게" 보이도록 하려고 사용된다.

 

7. 9. 2 출력 변환 문장

이 절은 printf 템플리트 문자열에서 표현될 수 있는 변환 지정의 상세한 문장에 관한 세부사항을 제공한다. 변환자 지정의 일부분이 아닌 템플리트 문자열 내의 문자들은 출력 스트림에 그대로 프린트된다. 수바이트의 문자 연속물들이(18장 [확장된 문자들] 참조) 템플리트 문자열 내에서 허용된다.

printf 템플리트 문자열 내에서의 변환자 지정은 일반적인 형태를 갖는다:

% flags width [ . precision ] type conversion

예를 들어, 변환지정자 '%-10. 8ld'에서는, '-'는 플래그이고, '10'은 필드 폭을 규정하며, 상세지정은 '8'이고, 문자 'l'은 형태변환자이며, 'd'는 변환자 양식을 규정한다. (이 특수한 형태지정자는 long int 인수를 십진수로 프린트함을 나타내며, 최소 10문자 넓이의 필드내에서 최소한 8개의 수를 왼편으로 정렬함을 나타낸다. )

더 상세히 설명하자면, 출력 변환 지정은 앞문자 '%'문자와 후속의 문자로 구성된다:

변환지정의 정상적인 행위를 변경하는 제로 또는 그 이상의 플래그 문자들.

최소한의 필드 폭을 지정하는 십진 정수 옵션. 정상적인 변환이 이 숫자보다 적은 문자를 생성하게 되면, 필드는 지정된 폭이 될 때까지 공백으로 채워진다. 이것은 최소 값이다; 정상적인 변환이 이 숫자보다 많은 문자를 생성하게 되면, 필드는 잘라지지 않는다. 정상적으로는, 출력이 필드내에서 오른편으로 정렬한다. 당신은 '*'의 필드 폭을 지정할 수 있다. 이것은 인수 목록내의 다음 인수(프린트되는 실제값 이전)가 필드 폭으로 사용됨을 의미한다. 그 값은 int이여야 한다. 만약 그 값이 음수이면, 이것은 '-'플래그(아래를 보라)를 설정해서 그 절대값을 필드 폭으로 사용함을 의미한다.

숫자 변환에 쓰이는 숫자의 개수를 지정하는 상세지정 옵션. 만약 상세지정이 지정되면, 그것은 십진정수(빠뜨리게 되면 제로로 내정된다)가 뒤따르는 하나의 마침표('. ')로 이루어진다. 당신은 '*'의 상세지정을 할 수 있다. 이것은 인수 목록내의 다음 인수(프린트되는 실제값 이전)가 상세지정으로 사용됨을 의미한다. 그 값은 int여야 하며, 만약 그 값이 음수이면 무시된다. 만약 당신이 필드폭과 상세규정 모두를 '*'로 지정하게 되면, 필드 폭 인수가 상세규정 인수에 앞선다. 다른 C 라이브러리 변환자들은 이 문장을 인식하지 못할 것이다.

형태 변경자 문자 옵션, 이것은 데이터 형태가 내정 형태와 다를 경우에 상응하는 인수의 데이터 형태를 지정하기 위하여 사용된다. (예를 들면, 정수 변환자는 int 형태를 가정하지만, 당신은 다른 정수 형태를 위해 'h', 'l'이나 'L'을 지정할 수도 있다. )

적용될 변환자를 지정하는 문자. 허용되는 정확한 옵션과 그것들이 해석되는 방식은 변환 지정자가 달라짐에 따라 변한다. 각각의 변환자가 사용하는 특정 옵션에 관한 정보는 각 변환자의 설명을 참조하라.

 

7. 9. 3 출력 변환자 테이블

각기 다른 변환자들이 하는 일을 요약해보자.

`%d', `%I'

정수를 부호 있는 십진수로 프린트한다. 7. 9. 4 [정수 변환자] 참조, 상세설명. '%d'와 '%i'는 출력에서는 동의어이지만, 입력에서 scanf를 사용할 때는 다르다. (7. 11. 3 [입력 변환자 테이블] 참조)

'%o' : 정수를 부호 없는 8진수로 프린트한다. 7. 9. 4 [정수 변환자] 상세설명 참조.

'%u' : 정수를 부호 없는 10진수로 프린트한다. 7. 9. 4[정수 변환자] 상세설명 참조.

'%Z'

정수를 부호 없는 십진수로 프린트하되, 정수가 size_t 형태로 전달된 것으로 가정한다.
7. 9. 4 [정수 변환자] 상세설명 참조. 이것은 GNU 확장이다.

`%x', `%X'

정수를 16진수로 프린트한다. '%x'는 소문자를 사용하고 '%X'는 대문자를 사용한다.
7. 9. 4[정수 변환자] 상세설명 참조.

'%f'

부동소수점수를 정상적인(고정소수점) 표기로 프린트한다.
7. 9. 5 [부동소수점 변환자] 상세설명 참조.

'%e', '%E'

부동소수점수를 지수 표기로 프린트한다. '%e'는 소문자를 사용하고 '%E'는 대문자를 사용한다.
7. 9. 5 [부동소수점 변환자] 상세설명 참조.

'%g', '%G'

부동소수점수를 정상적인 또는 지수 표기로 프린트하는데, 그 크기에 따라 적절한 것을 선택한다. '%g'는 소문자를 사용하고 '%G'는 대문자를 사용한다.
7. 9. 5 [부동소수점 변환자] 상세설명 참조.

'%c' : 단일문자를 프린트한다. 7. 9. 6 [여타의 출력 변환자] 참조.

'%s' : 문자열을 프린트한다. 7. 9. 6 [여타의 출력 변환자] 참조.

'%p' : 포인터의 값을 프린트한다. 7. 9. 6 [여타의 출력 변환자] 참조.

'%n'

프린트될 문자의 개수. 7. 9. 6 [여타의 출력 변환자] 참조. 이 변환자 지정은 어떤 출력도 생성할 수 없음에 주의하라.

'%m'

errno 값에 일치하는 문자열을 프린트한다. (이것은 GNU 확장이다. )
7. 9. 6 [여타의 출력 변환자] 참조.

'%%'

리터럴 '%' 문자를 프린트한다. 7. 9. 6 [여타의 출력 변환자] 참조. 변환자 지정 문장이 유효하지 않다면, 예상치 못한 결과가 일어날 것이다. 그러므로 그렇게 하지 말아야 한다. 만약 템플리트 문자열 내의 모든 변환자 지정을 위한 값들을 공급하는 함수의 인수들이 충분치 못하거나, 또는 인수들의 형태가 제대로 되지 못하였다면, 그 결과는 예측할 수 없다. 만약 당신이 변환자 지정보다도 많은 인수들을 주게 되면, 남는 인수 값들은 그대로 무시된다. 이것은 가끔 유용하다.

 

7. 9. 4 정수 변환자

이 절에서는 `%d', `%i', `%o', `%u', `%x', `%X' 와 `%Z' 변환자 지정을 위한 옵션에 대해 기술한다. 이들 변환자들은 정수들을 다양한 형식으로 프린트한다.

'%d'와 '%i' 변환자 지정은 모두 int 인수를 부호 있는 십진수로 프린트한다; 반면에 '%o', '%u'와 '%x'는 인수를 부호 없는 8진, 10진, 16진수로 프린트한다(각각). '%X'변환자 지정은 문자 'abcdef' 대신에 숫자로서 문자'ABCDEF'를 사용하는 점을 제외하고는 '%x'와 같다. '%Z'는 '%u'와 같으나, size_t 형태의 인수를 기대한다.

다음의 플래그들이 유의미하다:

'-' : 필드내에서(정상적인 오른편 정렬 대신에) 결과를 왼편 정렬한다.

'+' : 부호 있는 '%d'와 '%i' 변환자에서, 만약 그 값이 양수이면 플러스 부호를 프린트한다.

'` '

부호 있는 '%d'와 '%i' 변환자에서, 만약 그 결과치가 플러스 또는 마이너스 부호로 시작하지 않는다면, 그 대신에 공백문자로써 후미를 채운다. '+' 플래그는 그 결과가 부호를 포함하는 것을 보증하기 때문에, 만약 당신이 두 부호를 모두 공급하게 되면 이 플래그는 무시된다.

'#'

'%o' 변환자에서, 이것은 마치 상세지정을 증가시키는 것처럼 앞의 숫자를 '0'으로 채운다.
'%x' 나 '%X'에서는 이것이 결과 값에 앞의 숫자 '0x'나 '0X'를(각각) 붙여준다. 이것은 '%d', '%I'나 '%u' 변환자에 대해서는 아무런 소용도 없다. 이 플래그를 사용하게 되면 strtoul함수(14. 7. 1 [정수의 해부] 참조)와 '%i' 변환자를 갖는 scanf 함수(7. 11. 4 [수치 입력 변환자] 참조)에 의해 해부될 수 있는 출력이 생성된다.

'0'

필드를 공백 대신에 영(零)으로 채운다. 영은 어떤 부호나 진수의 지정 뒤에 위치한다. 이 플래그는 만약 '-' 플래그가 지정되거나 상세지정이 지정되면 무시된다.
만약 상세지정이 주어지면, 그것은 나타날 숫자의 최소개수를 지정한다; 앞에 오는 영들은 필요하면 생겨난다. 만약 당신이 상세지정을 하지 않는다면, 수치는 필요한 만큼의 숫자로 프린트된다. 만약 당신이 영의 명시적인 상세지정으로 영의 값을 변환시키게 되면, 어떤 문자도 만들어지지 않는다. 형태 변경자가 없을 때에는 상응하는 인수는 int (부호 있는 변환자 '%i'와 '%d')로 취급되거나 unsigned int(부호 없는 변환자 '%o', '%u', '%x'와 '%X')로 취급된다. printf와 관련함수들은 variadic해서 어떤 char와 짧은 인수들은 내정 인수 처리에 의해 자동적으로 int로 변환됨을 기억하자. 다른 정수 형태의 인수에 대해서 당신은 이들 변경자들을 사용할 수 있다.

'h'

인수가 short int 나 unsinged short int에 적합하도록 지정한다. 짧은 인수는 내정 인수 처리에 의해 어떻게든 int 나 unsigned int로 변환되지만, 'h' 변경자는 그것을 다시 짧은 정수로 변환한다.

'l'  : 인수가 long int나 unsigned long int에 적합하도록 지정한다.

'L'

인수가 long long int이도록 지정한다. (이 형태는 GNU C 컴파일러에 의해 지원되는 확장이다. extra-long 정수들을 지원하지 않는 체제에서는 이것은 long int와 같다. )

인수 형태 변경자는 '%Z'에는 적용할 수 없다. 왜냐하면, '%Z'의 유일한 목적은 데이터 형태 size_t를 지정하는 것이기 때문이다.

예를 들어 보자. 템플리트 문자열

"|%5d|%-5d|%+5d|%+-5d|% 5d|%05d|%5. 0d|%5. 2d|%d|\n"

을 사용하면, '%d'변환자에 대한 다른 옵션들을 사용하여 수치를 프린트해보면 다음과 같은 결과를 얻는다:

특히, 마지막의 경우는 수치가 지정된 최소 필드폭에 비해 너무 크기 때문에 발생하였음을 주의하라.

 

7. 9. 5 부동소수점 변환자

이 절에서는 부동소수점수에 대한 변환자 지정에 대해 논의한다:

`%f', `%e', `%E', `%g'와 `%G' 변환자.

'%f' 변환자는 그 인수를 고정소수점 표기로 프린트하며, [-]ddd. ddd 형태로 출력을 생성하는데, 여기에서 소수점 뒤에 따르는 숫자의 개수는 당신이 지정한 상세지정에 의해 조절된다.
'%e' 변환자는 그 인수를 지수 표기로 프린트하며, [-]d. ddde[+|-]dd 형태로 출력을 생성한다. 또한, 소수점 뒤에 따르는 숫자의 개수는 상세지정에 의해 조절된다. 지수는 최소한 두개의 숫자를 담게 된다. '%E' 변환자는 유사하지만 지수를 'e' 대신에 문자 'E'로 표기한다는 점만 다르다.
'%g''%G' 변환자는 만약 지수가 -4이하이거나 상세지정보다 같거나 크면 인수를 '%e'나 '%E' (각각) 형태로 프린트한다; 그렇지 않을 경우에는 이들은 '%f' 형태로 사용한다. 뒤에 붙는 영들은 결과치의 소수부분에서 제거되며, 소수점 문자는 숫자가 뒤따를 때만 나타난다.

다음 플래그들은 그 행위를 변경하는데 사용될 수 있다:

'-'  : 필드에서 결과를 왼편에 정렬한다. 정상적으로는 결과치를 오른편에 정렬한다.
'+'  : 결과치 에서 항상 플러스나 마이너스를 포함한다.
'` ' : 만약 결과치가 플러스나 마이너스 부호로 시작되지 않는다면, 그 대신에 공백으로 후미를 채운다. '+' 플래그는 결과치가 부호를 포함하도록 보증하기 때문에 만약 당신이 두 부호를 함께 공급하면 이 플래그는 무시된다.
'#' : 결과치가 비록 뒤따르는 숫자를 갖지 않더라도 항상 소수점을 포함하도록 지정한다. '%g'와 '%G' : 변환자에 대해서 이것은 소수점 뒤의 영을 다른 경우에는 제거되는 지점의 왼편에 두도록 강제한다.
'0'  : 공백대신에 0으로 필드를 채운다; 0은 어떤 부호 다음에 놓인다. 만약 '-' 플래그가 지정되면 이 플래그는 무시된다.

상세지정은 '%f', '%e'와 '%E' 변환자에서 소수점다음에 몇 개의 숫자가 올 것인가를 지정한다. 이들 변환자에 대해서 내정 상세지정은 6이다. 만약 상세지정이 명시적으로 0이면, 이것은 소수점 문자를 완전히 배제한다. '%g'와 '%G' 변환자에 대해서 상세지정은 몇 개의 유효숫자를 프린트할 것인가를 지정한다. 유효숫자는 소수점 앞의 처음 숫자와 소수점 다음의 모든 숫자다. 만약 '%g'나 '%G'에 대해 상세지정이 0이든가 지정되지 않으면, 상세지정은 1의 값을 갖는 것으로 간주된다. 만약 프린트될 값이 숫자의 지정된 개수에서 상세하게 나타낼 수 없는 경우라면, 값은 적당한 근사값으로 처리된다. 형태 변경자가 없으면, 부동소수점 변환자는 double 형태의 인수를 사용한다. (내정 인수 처리에 의해 어떠한 float 인수이든 간에 자동적으로 double로 변환된다. ) 다음 형태 변경자가 지원된다.

'L' : 대문자 'L'은 인수가 long double일 것을 지정한다.
다양한 부동소수점 변환자를 사용하여 어떤 수치들을 프린트할 수 있는가를 보여주는 몇 개의 예들이 있다. 이 수치들 모두는 다음의 템플리트 문자열을 사용하여 프린트되었다:
"|%12. 4f|%12. 4e|%12. 4g|\n"
출력은 이렇다:
헤더파일쓰여진
'%g' 변환자가 뒤따르는 0을 떨어뜨리는 법에 유의하라.

 

7. 9. 6 여타의 출력 변환자

이 절에서는 printf에 대한 각종의 변환자에 대해 기술한다.

'%c' 변환자

단일한 문자를 프린트한다. int 인수는 처음에 unsigned char로 변환된다. '-' 플래그는 필드내에서의 왼편 정렬을 지정하기 위해 사용될 수 있지만, 다른 플래그는 정의되지 않으며, 상세지정이나 형태 변경자도 주어질 수 없다.
 
예를 들어:
printf ("%c%c%c%c%c", 'h', 'e', 'l', 'l', 'o');
는 'hello'를 프린트한다.

'%s' 변환자

문자열을 프린트한다. 상응하는 인수는 char * (또는 const char *) 형태여야 한다. 상세지정은 기록할 문자의 최대 개수를 나타내기 위하여 지정될 수 있다; 그렇지 않을 경우에는 널 문자를 포함하지 않을 동안의 모든 문자가 출력 스트림에 기록된다. '-'플래그는 필드내에서의 왼편 정렬을 지정하기 위해 사용될 수 있지만, 다른 플래그나 형태 변경자는 이 변환자에 대해서 주어질 수 없다.
 
예를 들어:
printf ("%3s%-6s", "no", "where");
는 ' nowhere '를 프린트한다.
만약 당신이 우연히 '%s' 변환자에 대한 인수로서 널 포인터를 전달하게 되면, GNU 라이브러리는 그것을'(null)'로 프린트한다. 이것은 실패보다는 낫다고 생각할 수 있다. 그러나 의도적으로 널 인수를 전달하는 것은 좋은 습관이 아니다.

'%m' 변환자

errno내의 에러코드에 해당하는 문자열을 프린트한다. 2. 3 [에러 메시지] 참조.
즉,
fprintf(stderr, " `%s'를 열 수 없음: %m\n", filename); 은
fprintf(stderr, " `%s'를 열 수 없음: %s\n", filename, strerror (errno)); 과 같다.
'%m' 변환자는 GNU C 라이브러리 확장이다.

'%p' 변환자

포인터 값을 프린트한다. 상응하는 인수는 void * 형태여야 한다. 사실상, 당신은 어떠한 형태의 포인터도 사용할 수 있다.
GNU C 체제에서는, 널이 아닌 포인터들은 마치 '%#x' 변환자가 사용된 것처럼 무부호 정수로 프린트된다. 널 포인터들은 '(nil)'로 프린트된다. (포인터들은 체제마다 다르게 프린트될 것이다. )
 
예를 들어:
printf ( "%p", "연습" );
은 16진수가 뒤따르는 '0x'를 프린트한다_문자열 상수 "연습"의 주소. 그것은 '연습'이란 단어를 프린트하는 것이 아니다.
당신은 '%p' 변환자에서 왼편 정렬을 지정하기 위하여 '-' 플래그를 공급할 수는 있지만, 다른 플래그, 상세지정, 또는 형태 변경자를 정의할 수 없다.

'%n' 변환자

여타의 출력 변환자와는 다르다. 이것은 인수를 사용하는데, 그 인수는 int에 대한 포인트여야만 한다. 그러나 이 변환자는 어떤 것을 프린트하는 대신에 그 위치에서 호출되었을 때 프린트되는 문자의 개수를 저장한다. 'h'와 'l' 형태 변경자는 인수가 int * 대신에 short int *이든지 long int * 인가를 지정하도록 허용되어 있으며, 플래그, 필드 폭, 상세지정은 허용되지 않는다.
 
예를 들면,
int nchar;
printf ("%d %s%n\n", 3, "bears", &nchar);
는 3 bears를 프린트하고, '3 bears'는 7문자이기 때문에 nchar에 7을 넣는다.

'%%' 변환자

리터럴 '%' 문자를 프린트한 이 변환자는 인수를 사용하지 않으며 플래그, 필드 폭, 상세지정, 또는 형태 변경자가 허용되지 않는다.

 

7. 9. 7 형식화된 출력 함수

이 절에서는 printf와 관련함수들을 호출하는 방법을 기술한다. 이 함수들의 원형은 헤더파일 'stdio. h'에 있다. 이 함수들은 인수의 개수를 가변적으로 갖고있기 때문에, 당신은 그것들을 사용하기 전에 원형을 선언해야 한다. 물론 당신이 올바른 원형을 갖게 되는 확실하고 손쉬운 방법은 곧바로 'stdio. h'를 포함하는 것이다.

함수 int printf (const char *template, . . . )

printf 함수는 템플리트 문자열 template의 통제하에 선택적 인수들을 스트림 stdout로 프린트한다. 이 함수는 프린트되는 문자들의 개수를 반환하거나 만약 출력에러가 있으면 음수 값을 반환한다.

함수 int fprintf(FILE *stream, const char *template, . . . )

이 함수는 print 함수와 꼭 같으나, 출력이 stdout 대신에 스트림 stream에 쓰여 진다는 점만 다르다.
<역자주> 스트림 stream이 아니고 파일 stream이 아닌가?
** stream stream ---> FILE stream ????

함수 int sprintf (char *s, const char *template, . . . )

이것은 printf와 같으나, 출력이 스트림에 쓰여지는 대신에 문자 배열 s에 저장된다는 점만 다르다. 널 문자는 문자열의 끝을 표시하기 위해 쓰여 진다.
sprintf 함수는 배열 s에 저장된 문자의 개수를 반환하지만 종료 널 문자는 포함하지 않는다. 이 함수의 행위는 만약에 복사가 중첩되는 대상물사이에서 일어난다면__예를 들어, 만약에 s가 '%s' 변환자의 통제하에서 프린트되는 인수로서도 주어진다면__정의되지 않는다. 5. 4 [복사와 연결] 참조.
 
경고: sprintf 함수는 위험할 수가 있다, 왜냐하면그것은 잠재적으로 문자열 s의 할당크기에 적합 가능한 것보다 더 많은 수의 문자를 출력할 가능성이 있기 때문이다. 변환자 지정에서 주어진 필드폭은 최소값일 뿐임을 기억하라.
이 문제를 피하기 위하여, 당신은 다음에 기술되는 snprintf나 asprintf를 사용할 수 있다.

함수 int snprintf (char *s, size_t size, const char *template, . . . )

snprintf 함수는 sprintf 함수와 유사하지만, size 인수가 만들어낼 문자들의 최대개수를 지정한다는 점만 다르다. 따라붙는 널 문자는 이 한계치에 포함되어 계산되므로, 당신은 문자열 s에 대해 최소한 size 문자를 할당하여야만 한다. 반환 값은 저장된 문자의 개수인데, 종료 널을 포함하지 않는다. 만약 이 값이 size-1 이라면, s에는 모든 출력을 위한 공간이 불충분한 것이다. 당신은 더 큰 출력 문자열을 갖고서 다시 해야 한다.
이렇게 하는 예를 들어 보자:
/* 명칭이 name이고 값이 value인 변수의 값을 기술하는 메시지 만들기. */
char * make_message (char *name, char *value)
{
/* 정확히 100 char의 공간만 필요히디고 가정. */
int size = 100;
char *buffer = (char *) xmalloc (size);
while (1) {
/* 할당된 공간에 프린트 시도. */
int nchars = snprintf (buffer, size, "value of %s is %s", name, value);
/* 작동하면 문자열 반환함. */
if (nchars < size)
return buffer;
/* 그렇지 않을 경우 2배의 공간으로 다시 시도. */
size *= 2;
buffer = (char *) xrealloc (size, buffer);
}
}
사실상, 다음에 있는 asprintf를 바로 사용하는 편이 더 쉬울 때가 많다.

 

7. 9. 8 동적으로 할당하는 형식화된 출력

이 절에 있는 함수들은 출력물을 형식화시키고, 그 결과를 동적으로 할당된 메모리에 넣는 일을 한다.

함수 : int asprintf (char **ptr, const char *template, . . . )

이 함수는 sprintf함수가 미리 할당된 버퍼에 output을 저장하는 대신에 동적으로 할당된 곳에 저장한다는 점을 제외하고는 sprintf함수와 유사하다. ( malloc을 사용한다. 3. 3절 [Unconstrained Allocation] 참조) 여기서 ptr 인수는 char * object의 주소가 되어지고, asprintf는 그 위치에 새롭게 할당된 문자열을 가리키는 포인터를 저장한다.
여기에 snprintf와 같은 결과를 얻기 위해 asprintf를 사용하는 법을 보여주고 있다. 더 쉬운 방법이다.
/* 변수의 값, 즉, 이름은 뭐고, 값은 뭐라는 식으로 알리기 위해 구조화된 출력문이 필요하다. */
char *make_message (char *name, char *value)
{
char *result;
asprintf (&result, "value of %s is %s", name, value);
return result;
}

함수 : int obstack__printf (struct obstack *obstack, const char *template, . . . )

이 함수는 공간을 할당하기 위해서 obstack을 사용하는 것을 제외하면 asprintf 함수와 유사하다. 3. 4절 [Obstacks] 를 참조. 현재 object의 끝에 쓰여있는 문자들, 그들을 얻기 위하여, 당신은 obstack_finish로 object를 끝내야만한다. ( 3. 4. 6절 [Growing Objects] 참조)

 

7. 9. 9 다양한 인수들을 출력하는 함수들

vprintf 함수와 그 유사한 함수들은 printf 함수를 가지고 형식화된 출력을 할 수 있도록 내부적으로 사용되는 다양한 형식을 당신 자신의 것으로 정의할 수 있도록 해준다.

이런 함수들을 정의하는 가장 좋은 방법은 언어를 사용하여 "printf 함수를 호출하고, 첫 번째 다섯 개 후의 인수모두를 더한 템플릿을 넘겨라" 라고 말하는 것이다. 그러나 C로는 이렇게 할 수 있는 방법이 없다, 그리고 제공되는 방법은 매우 사용하기 어렵다. 그리고 C언어 수준에서는 당신의 함수에 많은 인수들이 어떻게 받아들여진건지 알 수 있는 방법이 없다.

그 방법이 불가능했기 때문에, 우리는 "첫 번째 다섯후의 모든 인수들"도 표현하여 va_list로 넘기도록 한 vprintf 시리즈인 선택함수를 제공한다.

vprintf나 이 절에도 보여주고 있는 다른 함수들은 호출하기 전에 당신은 먼저 va_start를 호출하여 다양한 인수들로 포인터를 초기화해줘야만 한다. ( A. 2절 Variadic Functions] 참조) 그리고나서 당신은 당신이 다루기 원하는 인수들을 가져오기 위해 va_arg를 호출할 수 있다.

일단 당신의 va_list 포인터가 당신이 선택한 인수를 가리키고 있으면 당신은 vprintf를 호출할 준비가 되어있는 것이다. 그 인수와 모든 부속된 인수들은 구분되게 정하여진 template와 함께 vprintf에 의해 사용되어진 당신의 함수에 넘겨진다.

어떤 다른 시스템에서는, va_list 포인터는 vprintf를 호출한 후에 유용하게 되어지는데, 당신은 vprintf를 호출한 후에 va_arg를 사용할 수는 없다. 대신에, 당신은 서비스로부터 포인터를 없애기 위하여 va_end를 호출해야한다. 그래야만 당신이 다른 포인터 변수에 va_start를 호출하고, 그 포인터를 통해 인수 추출을 시작하는것들을 안전하게 할 수 있다. vprintf를 호출하는 것은 당신의 함수의 인수 리스트를 파괴하지 않는다, 단지, 그것에게 넘겨진 포인터이다.

GNU C에서는 그와 같은 제한을 가지고 있지 않다. 당신은 vprintf에 그것을 넘겨준 이후에도 va_list 포인터로부터 인수들을 추출하는 것을 안전하게 계속할 수 있고 va_end is a no-op.

 
(참고 : 그렇지만 va_arg호출 부속 인수들은 미리 vprintf를 사용하여 같은 인수들을 추출할 것이다. )

이 함수들을 위한 프로토타입은 'stdio. h'에 선언되어 있다.

함수 : int vprintf (const char *template, va_list ap)

이 함수는 직접적으로 인수로써 한 변수의 수를 취하는 대신에 인수 리스트 포인터 ap를 취한다는 점을 제외하고는 printf와 유사하다.

함수 : int vfprintf (FILE *stream, const char *template, va_list ap)

이 함수는 vprintf로 직접적으로 정해진 변수 인수 리스트를 사용한 fprintf와 동등하다.

함수 : int vsprintf (char *s, const char *template, va_list ap)

이 함수는 변수 인수리스트를 인수로 사용한 sprintf와 동등하다.

함수 : int vsnprintf (char *s, size_t size, const char *template, va_list ap)

이 함수는 직접적으로 선정된 변수 인수리스트를 사용한 snprintf 함수와 동일하다.

함수 : int vasprintf (char **ptr, const char *template, va_list ap)

vasprintf함수는 직접적으로 선정된 변수 인수리스트를 사용한 asprintf와 동등하다.

함수 : int obstack__vprintf (struct obstack *obstack, const char *template, va_list ap)

obstack_vprintf 함수는 vprintf로 직접적으로 정해진 변수인수 리스트를 사용하는 obstack_printf와 동등하다.

여기에 vfprintf를 어떻게 사용했는지를 보여주는 예가 있다. 이것은 표준 스트림 sterr에 프로그램의 이름을 지적하는 접두사와 함께, 에러메시지를 프린트하는 함수이다.

(program_invocation_short_name의 기술을 위해서는 2. 3절 [Error Messages] 참조.
#include <stdio. h>
#include <stdarg. h>
void eprintf (char *template, . . . )
{
va_list ap;
extern char *program_invocation_short_name;
fprintf (stderr, "%s: ", program_invocation_short_name);
va_start (ap, count);
vfprintf (stderr, template, ap);
va_end (ap);
}
당신은 이처럼 eprintf를 호출할 수 있다.
eprintf ("file '%s' does not exist\n", filename);

 

7. 9. 10 템플릿 스트링 파싱

당신은 주어진 템플릿 스트링으로 예상되는 인수들의 타입과, 개수에 대한 정보를 얻기 위하여 parse_printf_format 함수를 사용할 수 있다. 이 함수는 파괴의 원인이 될 수도 있는, 사용자의 프로그램으로부터 유용하지 못한 인수들로 파싱하는 것을 피하기 위해 printf에게 인터페이스를 제공하는 즉각적 해석을 허용한다.

이 절에서 설명하고 있는 모든 심볼들은 헤더파일 'printf. h'에 선언되어 있다.

함수 : size_t parse__printf__format (const char *template, size_t n, int *argtypes)

이 함수는 printf 스트링 템플릿에 의해 예상되는 인수들의 개수와 타입에 대한 정보를 반환한다. 그 정보는 배열 argtypes에 저장되어 있다. ; 이 배열의 각 요소들은 한 개의 인수를 묘사하고 있다. 이 정보는 밑에 리스트된 다양한 'PA_'매크로를 사용해서 기호화되어졌다. n 인수는 배열 argtypes안에 요소들의 개수를 정한다. 이것은 parse_printf_format이 쓸려고(write) 시도하는 대부분의 요소들이다. parse_printf_format는 템플릿에 의해 요구되는 인수들의 총수를 반환한다. 만일 이 숫자가 n 보다 크다면 오직 첫 번째 인수인 n을 설명하여 정보를 반환한다. 만약 당신이 많은 인수들에게서 보다 더 많은 정보를 원한다면, 배열을 크게 할당하고 parse_printf_format를 다시 호출하라.

인수들의 타입은 기본 타입을 조합하고, 플래그 비트들을 수정하여 기호화 되었다.

매크로 : int PA__FLAG__MASK

이 매크로는 플래그 비트를 원하는 형태로 수정하기 위한 비트마스크이다. 당신은 인수를 위해 플래그 비트를 추출하거나 (argtypes[i] & PA_FLAG_MASK) 기본 타입 코드를 추출하기 위해(argtypes[i] & ~PA_FLAG_MASK) 그 표현을 쓸 수가 있다.

여기에 기본적 타입을 나타내기 위한 심볼 상수들이 있다. 그들은 정수 값들을 위한 표준이다.

 

PA_INT : 기본 타입인 int를 지정한다.

PA_CHAR : 기본 타입인 int형을 char로 캐스트 함을 지정한다.

PA_STRING : 이것은 널 종료문자를 갖는 스트링을 지정하는 기본 타입인 char*를 지정한다.

PA_POINTER : 이것은 형에 관계없는 포인터형인 void *를 지정한다.

PA_FLOAT : 이것은 float형 기본 형태를 지정한다.

PA_DOUBLE : 이것은 double형 기본 형태를 지정한다.

PA_LAST

당신은 PA_LAST로부터 offsets로 당신 자신의 프로그램에 부가적인 기본적인 타입들을 정의할 수 있다. 예를 들면, 만일 당신이 그들 자신의 특별 화된 printf 변환으로 'foo'와 'bar'의 데이터 타입들은 갖고 있다면, 당신은 이 타입들을 위해 다음과 같이 정의할 수 있다:
#define PA_FOO PA_LAST
#define PA_BAR (PA_LAST + 1)

여기엔 기본적인 타입들을 수정하기 위한 플래그 비트들이 있다. 그들은inclusive-or를 사용하여 기본적인 타입들과 조합되어진다.

PA_FLAG_PTR

만일 이 비트가 세트되면, 그것은 기본적인 타입을 가리키는 포인터를 기호화한 타입을 지정한다. 예를 들어, 'PA_INT|PA_FLAG_PTR' 이것은 'int *'를 나타낸다.

PA_FLAG_SHORT

만일 이 비트가 세트되면, short로 수정된 기본적인 타입을 지정한다.
( 이것은 타입 수정자 'h'에 해당한다)

PA_FLAG_LONG

만일 이 비트가 세트되면, long형으로 수정된 기본적인 타입을 지정한다.
( 이것은 타입수정자 'l'에 해당한다. )

PA_FLAG_LONG_LONG

만일 이 비트가 세트되면, long long으로 수정된 기본타입을 지정한다.
( 이것은 타입수정자 'L'에 해당한다. )

PA_FLAG_LONG_DOUBLE

이것은 PA_FLAG_LONG_LONG의 동의어로써, long double형을 지정하기 위해 PA_DOUBLE의 기본적인 타입과 함께 협약으로 사용됐다.

 

7. 9. 11 템플릿 스트링 파싱의 예

여기에 포맷 스트링을 가지고 인수 타입들을 해석하는 예가 있다. 우리는 이것을 NUMBER, CHAR, STRING과 STRUCTURE의 타입 인수들을 포함한 해석기의 일부분이라고 가정한다.

/* 벡터 args안에 정해진 objects인 nargs가 포맷 스트링 format으로 유용한지 테스트하라. 만약 그렇다면 1일 반환하고, 그렇지 않다면 0을 반환한 후 에러메시지를 출력한다. */

int validate_args (char *format, int nargs, OBJECT *args)
{
int *argtypes;
int nwanted;
/* 그 인수들에 대한 각 변환 지정은 적어도 두 개의 문자가 길어야 하고, 그것보다 더 길게 할 수는 없다. */
argtypes = (int *) alloca (strlen (format) / 2 * sizeof (int));
nwanted = parse_printf_format (string, nelts, argtypes);
/* 인수들의 수를 체크하라. */
if (nwanted > nargs) {
error ("too few arguments (at least %d required)", nwanted);
return 0;
}
/* 각 인수들의 타입을 체크하고, 그 objects들이 적당하게 주어졌는지 보라 */
for (i = 0; i < nwanted; I++) {
int wanted;
if (argtypes[i] & PA_FLAG_PTR) {
wanted = STRUCTURE;
} else {
switch (argtypes[i] & ~PA_FLAG_MASK)
{
case PA_INT:
case PA_FLOAT:
case PA_DOUBLE:
wanted = NUMBER;
break;
case PA_CHAR:
wanted = CHAR;
break;
case PA_STRING:
wanted = STRING;
break;
case PA_POINTER:
wanted = STRUCTURE;
break;
}
}
if (TYPE (args[i]) != wanted) {
error ("type mismatch for arg number %d", i);
return 0;
}
} /* for 의 끝 */
return 1;
} /* 함수의 끝 */


7. 10 printf 주문하기

GNU C라이브러리는 당신의 프로그램의 중요한 데이터 구조를 프린트하기 위해 printf에게 가르쳐주는 영리한 방법으로 printf 템플릿 스트링을 지정하여 당신이 주문한 방법으로 변환을 행할 수 있도록 정의하는 것이 허용된다.

당신이 이처럼 하는 방법은 register_printf_function함수로 그 변환을 등록하는 것이다. ; 7. 10. 1절 [Registering New Conbersions] 를 참조. 당신이 이 함수에 넘겨줄 인수증 하나는 출력을 만들어내는 핸들러 함수를 가리키는 포인터이다. 7. 10. 3절 [Defining the Output Handler] 를 참조하여 이 함수를 어떻게 쓰는지 정보를 얻어라. 당신은 변환 지시자에 의해 예상된 인수들의 수와 타입에 대한 정보를 반환하는 함수를 인스톨할 수 있다. 이것에 대한 정보는 7. 9. 10절 [Parsing a Template String] 를 참조하라.

이 절에서 설명하고 있는 도구들은 헤더파일 'printf. h'에 선언되어 있다.

 
호환성 노트: printf 템플릿 스트링의 구문을 연장하는 능력은 GNU확장이다. ANSI 표준 C는 이와 유사한 것이 아무 것도 없다.

 

7. 10. 1 새로운 변환 등록하기

register_printf_function은 새로운 출력 변환을 등록하는 함수로 'printf. h'에 선언되어 있다.

함수 : int register__printf__function (int spec, printf_function handler_function, printf_arginfo_function arginfo_function)

이 함수는 문자 spec 변환 지시자를 정의한다. 그러므로 만일 spec이 'q'라면 그것은 '%q'로 변환되어 정의된다. handler_function은 템플릿 스트링 안에 이러한 변환이 나타날 때 printf와 그 유사 함수들에 의해 호출되는 함수이다. 이것을 인수로 넘기기 위한 함수를 어떻게 정의하는가에 대한 정보는 7. 10. 3절 [Defining the Output Handler] 참조하라. 만일 당신이 널 포인터를 지정하면 spec을 위해 존재한 어느 handler function이 제거된다.

arginfo_function은 이 변환이 템플릿 스트링 안에 나타날 때 parse_printf_format 에 의해 호출되는 함수이다. 이것에 대한 정보는 7. 9. 10절[Parsing a Template String]를 참조하라. 일반적으로 당신은 변환을 위해서 두 개의 함수를 동시에 인스톨 해야하지만 만약 당신이 결코 parse_printf_foramt를 호출하지 않는다면, 당신은 arginfo함수를 정의할 필요가 없다. 성공하면 반환 값은 0이고, 실패하면 -1이다. (만약 spec이 범위를 벗어나는 경우)당신은 기본 출력 변환을 재정의 할 수도 있지만, 이것은 혼란의 가능성이 많기 때문에 좋은 생각이 아니다. 당신이 재정의 했다면 다른 사람에 의해 쓰여진 라이브러리 루틴은 파괴될 수 있다.

 

7. 10. 2 변환 지시자 옵션들

만일 당신이 '%q'의 의미를 정의했다면, 그것이 포함된 '%+23q'나 '%-#q'는 무슨 의미인가? 이 의미를 분별하기 위한 도구로 핸들러가 호출되는데 그것은 템플릿의 옵션의 명세를 얻도록 도와준다.

register_printf_function에게 주어지는 handler_function과 arginfo_function 인수는 변환 지시자의 사례 안에 나타나는 옵션들에 대한 정보를 포함하는 구조체 printf_info의 타입을 인수로써 받아들인다. 이 데이터 타입은 헤더파일 'printf. h'에 선언되어 있다.

타입 : struct printf__info

이 구조체는 printf 템플릿 스트링 안에 있는 변환 지시자의 인스탄스에 나타난 옵션에 대한 정보를 넘기기 위해 사용된다. 그것은 다음과 같다.

int prec

이것은 배정도에 대한 지시자이다. 이 값은 만약 아무 것도 지시되지 않으면 -1이다. 만약 배정도가 '*'로 주어졌다면, 핸들러 함수에 주어진 printf_info 구조체는 인수 리스트로부터 복구시킬 실제의 값을 포함하고 있다. 그러나 그 구조체가 INT_MIN의 값을 가지고 arginfo함수에 주어진다면 그 실제의 값은 알 수 없다.

int width

이것은 최소 필드 너비를 지정한다. 그 값이 0이면 아무 너비도 정해지지 않은 것이다. 만일 그 필드의 너비가 '*'로 주어지면, 핸들러 함수에 넘겨진 printf_info구조체는 인수 리스트로부터 복구될 실제의 값을 포함하고 있다. 그러나 arginfo함수에 주어진 그 구조체가 INI-MIN을 포함하고 있으면 그 실제의 값이 무엇인지 알 수 없다.

char spec

이것은 특별 화된 문자변환 지시자이다. 여러 개의 문자들을 위해 같은 핸들러 함수에 등록시킬 수 있는 구조체에 저장되어 있지만 여전히 핸들러 함수에 호출되어졌을 때 그들에게 말해줄 수 있는 방법을 가지고 있다.

unsigned int is_long_double

이것은 만약 'L'형의 수정자가 지정되면 참인 논리형이다.

unsigned int is_double

이것은 만약 'h'형의 수정자가 지정되면 참인 논리형이다.

unsigned int is_long

이것은 만약 'l'형의 수정자가 지정되면 참인 논리형이다.

unsigned int alt

이것은 만일 '#' 플래그가 지정되면 참인 논리형이다.

unsigned int space

이것은 만일 ' '플래그가 지정되면 참인 논리형이다.

unsigned it left

이것은 만일 '-' 플래그가 지정되면 참인 논리형이다.

unsigned int showsign

이것은 만일 '+' 플래그가 지정되면 참인 논리형이다.

char pad

이것은 최소 필드 너비의 출력에 덧붙여질 때 사용하는 문자이다. 그 플래그가 '0'이면 값이 '0'이지만 ' '이면 다르다.

 

7. 10. 3 출력 핸들러 정의하기

이제 register_printf_function에게 인수로써 주어지게 될 핸들러와 arginfo 함수들을 어떻게 정의하는지 살펴보자

당신은 프로토타입과 함께 당신의 핸들러 함수를 정의할 수 있다.

int function (FILE *stream, const struct printf_info *info, va_list *ap_pointer)

여기의 핸들러 함수에 넘겨진 stream 인수는 출력으로 쓰기 위한 스트림이다. 여기의 info 인수는 템플릿 스트링안의 변환을 포함하고 있는 다양한 옵션에 대한 정보를 포함하고 있는 구조체를 가리키는 포인터이다. 당신은 당신의 핸들러 함수 내부에서 이 구조체를 수정할 수는 없다. 이 데이터 구조를 나타내는 것은 7. 10. 2절 [Conversion Specifier Options]를 참조하라.

여기서 ap_pointer인수는 당신의 핸들러에 프린트되어질 값을 포함하고 있는 변수 인수 리스트의 꼬리를 넘기기 위해 사용되어진다. 다른 함수들이 명백하게 변수 인수 리스트를 넘겨질 수 있는 것과는 달리 여기서는 va_list 그 자체가 아니라 va_list를 가리키는 포인터이다. 그래서 당신은 va_arg의 방법으로 인수들을 추출할 수 있을 것이다. (포인터 넘기기가 당신의 핸들러 프로세스들인 인수들을 세기 위해서 자신의 va_list 변수를 갱신하기 위한 자신의 핸들러 함수를 호출하는 함수에 허용된다. A. 2절 [Variadic Functions] 참조. )

당신의 핸들러 함수는 단지 printf처럼 값을 반환시킬 것이다: 그것은 출력된 문자들의 수를 반환하거나 에러가 나면 음의 값을 반환할 것이다.

데이터 타입 : printf_function

이것은 핸들러 함수가 가질 수 있는 데이터 타입이다. 만일 당신이 당신의 응용 프로그램 안에 parse_printf_format를 사용하려하면, 당신은 당신이 register_printf_function으로 인스톨한 새로운 각각의 변환을 위해서 arginfo_function을 인수로 넘겨주기 위한 함수를 정의해야한다.
당신은 다음과 같이 프로토타입과 함께 이들 함수를 정의할 수 있다.
int function (const suruct printf_info *info, size_t n, int *argtypes)
그 함수로부터 반환되는 값은 변환 예상되는 인수들의 수가 되어질 것이다. 그 함수는 또한 이 인수들 각각의 타입에 대한 정보로써 argtypes 배열의 n개의 요소를 채운다. 이 정보는 다양한 'PA_' 매크로를 사용하여 기호화된다. ( 당신은 이것이 parse_printf_format에서 사용되는 동일한 호출 협약임을 기억할 것이다. )

데이터 타입 : printf_arginfo_function

이 타입은 변환 지시자에 의해 사용된 인수들의 개수와 타입에 대한 정보를 반환하는 함수를 설명하기 위해 사용되어진다.

7. 10. 4 printf 확장 예제

여기에 printf 핸들러 함수를 어떻게 정의하는지 보여주는 예제가 있다. 이 프로그램은 Widget이라고 불리는 데이터 구조를 정의하고 데이터 구조안에 포인터 값과 이름을 포함하고 있는 Widget * 인수들에 대해서는 '%W'라고 변환되도록 printf 정보를 정의한다. '%W'변환은 최소 필드 너비와 왼쪽 정렬 옵션을 지원하지만 그 외 모든 것은 거부된다.

#include <stdio. h>
#include <printf. h>
#include <stdarg. h>
typedef struct {
char *name;
} Widget;
int print_widget (FILE *stream, const struct printf_info *info, va_list *app)
{
Widget *w;
char *buffer;
int len;
/* 문자열 안에 출력을 포맷하라 */
w = va_arg (*app, Widget *);
len = asprintf (&buffer, "<Widget %p: %s>", w, w->name);
if (len == -1)
return -1;
/* 최소 필드 너비에 덧붙여서, 스트림을 프린트하라. */
len = fprintf (stream, "%*s", (info->left ? - info->width : info->width), buffer);
/* 지우고 반환하라 */
free (buffer);
return len;
}
int main (void)
{
/* 프린트하기 위하여 widget를 만들어라 */
Widget mywidget;
mywidget. name = "mywidget";
/* widgets를 print 함수에 등록하라. */
register_printf_function ('W', print_widget, NULL);
/* arginfo가 아니다. 이제 widget를 프린트하라. */
printf ("|%W|\n", &mywidget);
printf ("|%35W|\n", &mywidget);
printf ("|%-35W|\n", &mywidget);
return 0;
}
이 프로그램의 출력은 다음과 같다.
|<Widget 0xffeffb7c: mywidget> |
| <Widget 0xffeffb7c: mywidget>|
|<Widget 0xffeffb7c: mywidget> |


7. 11 형식화된 입력

이 절에 서술된 함수들(scanf와 관련함수들)은 형식화된 출력도구들과 유사한 형식화된 입력을 위한 도구들을 제공한다. 이 함수들은 포맷 문자열과 템플리트 문자열의 통제하에 임의의 값을 읽어오는 메커니즘을 제공한다.

 

7. 11. 1 형식화된 입력의 기초

scanf 함수에 대한 호출은 임의의 인수들이 템플리트 문자열의 통제하에서 읽힌다는 점에서 피상적으로는 printf 함수에 대한 호출과 유사하다. 템플리트에서의 변환자 지정 문장이 printf에서의 그것과 매우 유사한 반면에, 그 템플리트의 해석은 고정필드 형식화라기 보다는 자유형식 입력과 단순형식 어울리기라 볼 수 있다. 예를 들어, 대부분의 scanf 변환자들은 얼마만큼이든 "공백"(공백, 탭, 개행을 포함)을 뛰어넘으며, 해당 출력 변환자에 대한 상세지정의 개념은 있었던 반면에 수치입력 변환자에 대한 상세지정의 개념이 전혀 없다. 보통, 템플리트 내에서의 공백이 아닌 문자들은 출력 스트림 내의 문자들과 정확하게 일치한다. 그러나, 일치시키기 실패는 스트림 상에서의 입력 에러와는 다르다.

scanf가 printf와 다른 점의 또다른 하나는 scanf에 대한 선택적 인수로서는 직접적인 값 대신에 포인터를 공급해야 한다는 점이다; 읽혀지는 값은 포인터가 가리키는 대상물에 저장된다. 노련한 프로그래머들 조차도 종종 이 점을 잊어버리는 수가 있다. 그러므로 만약에 당신의 프로그램이 scanf와 관련된 것처럼 보이는 에러를 만날 때는 당신은 이것을 다시 체크하라. 일치 실패 현상이 일어나면 scanf는 첫 번째의 불일치 문자를 제쳐두고 스트림에서 읽히는 다음 문자를 반환한다.

scanf가 되돌리는 정상적인 반환 값은 할당된 값들의 개수이다. 그러므로 당신은 모든 기대값이 읽혀지기 전에 일치시키기 에러가 발생하였는지를 알아보기 위해 이것을 사용할 수 있다. scanf 함수는 전형적으로 테이블 내용 읽기와 같은 일들을 하기 위해 사용된다. 예를 들어, double형의 배열을 초기화하기 위해 scanf를 사용하는 함수가 있다.

void readarray (double *array, int n)
{
int i;
for (i=0; i<n; I++) {
if (scanf (" %lf", &(array[i])) != 1)
invalid_input_error ();
}
/*각 배열요소에 1개씩의 숫자만 들어 있으면, 별일이 없겠으나, 숫자가 안 들어있든지 2개(?)가 들어 있으면 에러보고??? */
}

형식화된 입력은 형식화된 출력만큼 자주 쓰이지는 않는다. 어느 면에서는 이것은 형식화된 입력을 적절히 사용하는 데에 주의를 요하기 때문이기도 하다. 또 다른 이유는 일치시키기 에러에서 벗어나기 어렵기 때문이다.

만약 당신이 단일한, 고정적인 형식에 들어맞지 않는 입력을 읽고자 한다면, 당신은 scanf를 사용하기 보다는 어휘 스캐너를 발생시키는 Flex라든가 분석자를 발생시키는 Bison과 같은 툴을 사용하는 편이 좋다. 이에 대한 상세한 정보는 Flex에 있는 "Flex"절을 참조하라: 어휘 스캐너 발생기와 Bison 레퍼런스 안내서에 있는 "Bison"절을 참조할 것.

 

7. 11. 2 입력 변환자 문장

scanf 템플리트 문자열은 '%'로 시작되는 변환 지정자들이 중간중간에 박혀있는 통상적인 수바이트 문자를 담고 있는 문자열이다. 템플리트에서의 공백문자(isspace 함수에 의해 정의된 것들; 4. 1 [문자 분류] 참조. )는 입력 스트림내에서 몇 개의 공백문자가 읽히거나 포기되게 하는 현상을 야기한다. 일치하는 공백문자들이 반드시 템플리트에서 나타나는 공백문자들과 꼭 같을 필요는 없다. 예를 들어, 콤마를 인식시키기 위해서 앞뒤에 선택적인 공백을 추가하여 템플리트 내에 ' , '라고 쓰는 경우. 템플리트 내에서 변환자 지정의 일부분이 아닌 다른 문자들은 입력스트림내의 문자들과 정확하게 일치한다; 만약 이러하지 못하다면, 일치 실패가 일어난다.

scanf 템플리트 문자열 내의 변환자 지정은 일반적 형식을 갖는다:

% 플래그 폭 형태 변환자

더 자세히 살펴보면, 입력 변환자 지정은 맨앞의 '%' 문자와 후속의 다음 문자들로 구성된다:

옵션 플래그 '*'는 이 지정에 대한 것으로 읽히는 본문을 무시하도록 한다. scanf가 이 플래그를 사용하는 변환 지정자 발견하였을 때는 나머지의 다른 변환 지정자가 가리키는 대로 입력을 읽어들인다. 그러나, 이 입력은 버려지며, 포인터 인수를 사용하지도 않을 뿐 아니라, 성공한 할당을 헤아리는 숫자도 증가하지 않는다.

옵션 플래그 문자 'a'(문자열 변환에서만 유효함)는 문자열을 담아두기에 충분한 버퍼의 할당을 요구한다. (이것은 GNU 확장이다. ) 7.11.6 [동적인 문자열 입력] 참조.

필드폭의 최대치를 규정하는 옵션 십진수. 입력 스트림에서의 문자 읽기가 중단되는 시점은 최대치에 도달하였을 때이거나 불일치 문자가 발견되었을 때인데, 어느 편이 먼저 발생하든지 간에 그렇게 된다. 대부분의 변환자는 맨앞의 공백문자들 ( 명시적으로 제공되지 않은 문자들 )을 버리게 되며, 이처럼 버려진 문자들은 최대 필드폭에 삽입되지 않는다. 문자열 입력 변환자들은 입력의 끝 부분을 표시하기 위하여 널 문자를 저장한다. 최대 필드폭은 이 종료자를 포함하지 않는다.

옵션 형태 변경자 문자. 예를 들어, 당신은 인수가 int에 대한 포인터이기 보다는 long int에 대한 포인터임을 지정하기 위하여 '%d'와 같은 정수 변환자에 형태 변경자 'l'을 지정할 수 있다.

적용될 변환자를 지정하는 문자. 허용되는 정확한 옵션들과 그것들이 해석되는 방식은 변환자 지정자들이 달라짐에 따라 변한다. 변환자 지정자들이 허용하는 특정 옵션들에 관한 정보를 얻으려면 개별적인 변환자에 대한 기술을 참조하라.

 

7. 11. 3 입력 변환자 테이블

다양한 변환자 지정을 요약해 놓은 테이블을 보기로 하자:

'%d' : 십진수로 된 선택적 부호 정수를 일치시킨다. 7.11.4 [수치 입력 변환자] 참조.

'%I'

C언어가 정수 상수를 지정하기 위해 정의하는 어떠한 형식의 선택적 부호 정수도 일치시킨다. 7.11.4 [수치 입력 변환자] 참조.

'%o' : 8진수로 된 무부호 정수를 일치시킨다. 7.11.4 [수치 입력 변환자] 참조.

'%u' : 10진수로 된 무부호 정수를 일치시킨다. 7.11.4 [수치 입력 변환자] 참조.

'%x', '%X' : 16진 정수로 된 무부호 정수를 일치시킨다. 7.11.4 [수치 입력 변환자] 참조.

`%e', `%f', `%g', `%E', `%G'

선택적 부호의 부동소수점수를 일치시킨다. 7.11.4 [수치 입력 변환자] 참조.

'%s' : 공백이 없는 문자들만을 담고있는 문자열을 일치시킨다. 7.11.5 [문자열 입력 변환 자] 참조.

'%['

지정된 세트에 속하는 문자들로 이루어진 문자열을 일치시킨다. 7.11.5 [문자열 입력 변환자] 참조.

'%c'

하나이상의 문자들로 이루어진 문자열을 일치시킨다. 읽히는 문자의 개수는 변환자에 대해 주어진 최대 필드폭에 의해 조절된다. 7.11.5 [문자열 입력변환자] 참조.

'%p'

printf에 대한 출력 변환자 '%p'에 의해 사용된 동일한 실행 정의 형식에 있는 포인터 값을 일치시킨다. 7.11.7 [다른 입력 변환자] 참조.

'%n'

이 변환자는 어떠한 문자도 읽지 않는다; 이것은 이것이 호출된 이후에 읽혀진 문자의 개수를 기록한다. 7.11.7 [여타의 입력 변환자] 참조.

'%%'

이것은 입력 스트림내의 리터럴 '%'문자를 일치시킨다. 상응하는 인수는 사용되지 않는다. 7.11.7 [다른 입력 변환자] 참조.

만약 변환자 지정 문장이 유효하지 않다면, 그 행위가 정의되지 않는다. 만약 할당을 수행하는 템플리트 문자열 내에서 모든 변환자 지정자에 대한 주소를 공급하기로 되어있는 함수 인수들이 충분하지 않거나, 인수들이 올바른 형태가 아니라면, 역시 그 행위가 정의되지 않는다. 한편, 남는 인수는 단순히 무시된다.

 

7. 11. 4 수치 입력 변환자

이 절에서는 숫자 값을 읽어들이는 scanf 변환자에 대해 기술한다.

'%d'

10진수에서 선택적인 부호 정수를 일치시킨다. 인식되는 문장은 기초인수의 값으로서 10을 갖고있는 strtol 함수(14.7. [정수 분석] 참조)에 대한 문장과 같다. '%i'변환자는 C언어가 정수 상수용으로 정의하는 어떤 형식에서나 선택적 부호 정수를 일치시킨다.
인식되는 문장은 기초인수의 값으로서 0을 갖고있는 strtol 함수(14.7 [정수 분석] 참조)에 대한 문장과 같다. (당신은 이 문장상의 정수들을 `%x', `%o'나 `%d' 변환자를 갖는 '#'플래그 문자를 사용하여 printf로 프린트할 수 있다. 7.9.4 [정수 변환자] 참조. )
예를 들어, '10', '0xa'나 '012'중의 어떤 문자들이든 '%i' 변환자가 관장하는 정수로 읽혀질 수 있다. 이들 각각은 십진 값 10을 갖고서 어떤 수를 지정한다.

'%o', '%u'와 '%x'

변환자는 각기 8진, 10진, 16진무부호 정수를 일치시킨다. 인식되는 문장은 기초인수의 값으로서 적절한 값(8, 10이나16)을 갖고있는 strtol 함수(14. 7. [정수 분석] 참조)에 대한 문장과 같다.

'%X' 변환자

'%x'변환자와 같다. 둘다 대문자나 소문자가 숫자로 사용되는 것을 허용한다. %d나 %i에 대해 상응하는 인수의 내정 형태는 다른 정수 변환자를 위해 int *와 unsigned int *로 되어있다. 당신은 다른 크기의 정수를 지정하려면 다음의 형태 변경자를 사용해야 한다:
'h' 인수가 short int * 또는 unsigned short int * 가 되도록 지정한다.
'l' 인수가 long int * 또는 unsigned long int * 가 되도록 지정한다.
'l' 인수가 long long int * 또는 unsigned long long int * 가 되도록 지정한다. (long long형태는 GNU C 컴파일러에 의해 지원되는 확장이다. ) extra-long 정수가 제공되지 않는 체제에서는 이것은 long int와 같다. )

'%e', `%f', `%g', `%E'와 `%G'

위 입력 변환자들은 모두 상호교환이 가능하다. 이들은 모두 strtod 함수에 대한 동일한 문장에서 선택적 부호 부동소수점수를 일치시킨다. (14.7.2 [부동소수점수 분석] 참조. ) 부동소수점 입력 변환자에 대해, 내정 인수 형태는 float *이다. (이것은 상응하는 출력 변환자와는 다른데, 출력 변환자의 내정 형태는 double이다; printf에 대한 float 인수는 내정 인수 처리에 의해double로 변환되지만, float * 인수는 double *로 처리되지 않는다. ) 당신은 이들 형태 변경자들을 사용하는 다른 크기의 float를 지정할 수 있다.
'l' 인수의 형태가 double *가 되도록 지정한다.
'L' 인수의 형태가 long double *가 되도록 지정한다.

 

7. 11. 5 문자열 입력 전환들

이 절은 문자열과 문자 값들을 읽어오기 위해 scanf입력 전환에 대해 설명하고 있다. :

'%s', '%[', 과 '%c'

당신에게는 이 전환들로부터 입력을 어떻게 받을 것인가에 대한 두 개의 옵션들이 있다.
그것을 저장하기 위한 버퍼를 제공하라. 이것은 디폴트로서. 당신이 char *형으로 인수를 주어야 할 것이다.
 
주의 : 튼튼한 프로그램을 만들려면, 당신은, 입력이 당신이 제공한 버퍼의 크기를 넘지 않는지( 종료문자 널을 더해서 ) 확인해야만 한다. 일반적으로, 이것을 하는 유일한 방법은 버퍼크기보다 하나 적은 최대 필드 너비로 정하는 것이다. 만일 당신이 버퍼를 만들려면, 오버플로우를 예방하기 위해 최대 필드 너비로 정하라.
버퍼의 크기를 크게 할당하려면 'a' 플래그 문자를 지정해서 scanf에게 요청하라. 이것은 GNU 확장이다. 당신은 버퍼의 주소가 저장되도록 char ** 타입의 인수를 주어야 할 것이다. 7.11.6절 [Dynamic String Input] 참조.

'%c' 전환

간단하다. 항상 정해진(사용자가) 개수의 문자를 출력한다. 많은 문자들을 읽을 때에는 최대 수를 정해줘야 하는데, 만약 그 개수를 정하지 않으면 디폴트값은 1이다. 이 전환은 읽은 텍스트의 끝에 널 문자를 덧붙이지 않는다. 또한 이 전환은 앞쪽의 공백(whitespace) 문자를 무시하지 않고, 정확하게 주어진 n개의 문자를 읽고, 만약 그만큼을 얻을 수 없다면 실패한다. 항상 '%c'와 함께 읽어올 최대개수를 주어라. (정해지지 않으면 디폴트는 1이다. ) 당신은 충분히 긴 버퍼를 만들어서 항상 오버플로우를 방지하라.

'%s'전환

비 공백(non_whitespace) 문자들과 대응된다. 이 전환은 앞쪽의 공백문자를 건너뛰고, 무시하지만, 공백이 아닌 다른 문자를 읽은 후에 또 다른 공백문자를 만나면 그곳에서 읽은걸 멈춘다. 이 전환은 읽은 텍스트의 끝에 널 문자를 저장한다.
예를 들어, 다른 입력을 읽으면;
hello, world
'%10c' 전환이 주어지면 그 결과는 " hello, wo"가 되지만, 같은 입력을 '%10s'로 읽으면 그 결과는 "hello, "가 된다.
 
주의 : 만약 당신이 '%s'와 함께 필드의 너비(읽어올 개수)를 정하지 않았다면 읽을 문자들의 개수는 오직 문자들에 나타난 다음 공백문자가 있는 곳으로 제한된다. 이것은 거의 확실하게, 올바르지 못한 입력으로는 당신의 프로그램이 버그로 파괴될 수 있음을 의미한다.

'%['전환

당신이 선택한 제멋대로인 문자열 셋에서 문자들을 읽으려면, '%[' 전환을 사용하라. 당신은 일반적인 표현에서 사용되는 같은 구문을 사용하여 '[' 문자와 ']' 문자 사이에 셋을 정한다.
다음 특별한 경우:
문자 ']'는 문자 셋의 첫 번째 문자로서 정해질 수 있다.
끼워진 '-' 문자( 그것은 문자 셋의 처음이거나 마지막이 아닌 것 )는 문자들의 범위를 정하는데 사용된다.
만일 처음 '[' 의 곧바로 다음에 부호 '^'가 따르면, 입력문자에서 '[' 과 ']' 안에 리스트된 문자들을 제외한 모든 것의 출력을 허용한다.
'%[' 전환은 처음의 공백문자(whitespace)를 건너뛰지 않는다. 이곳에 '%[' 전환과 그것이 무엇을 의미하는지에 대한 예가 몇 가지 있다.
`%25[1234567890]'
25개의 숫자로 문자열을 구성한다.
`%25[][]'
25개의 대괄호로 문자열을 구성한다.
`%25[^ \f\n\r\t\v]'
25개의 길이를 가진 문자열을 만드는데, 거기에는 어떠한 표준 공백문자들이 포함되지 않는다. 이것은 '%s'와는 약간 다른데 그 다른 점이란 만약 입력이 공백문자로 시작된다면 '%s'는 간단히 앞쪽의 공백문자를 무시해버리지만 '%['는 대응에 실패를 한다.
`%25[a-z]'
25개의 소문자로 구성된 문자열을 만든다.

한 번 더 기억 : '%s'와 '%[' 전환은 최대 너비를 정하지 않거나, 'a'플래그를 사용하지 않으면 위험하다. 왜냐하면 입력이 너무 길어서 당신이 제공한 버퍼가 오버플로우를 발생시킬 수 있기 때문이다. 당신이 제공한 버퍼가 주어진 입력보다 길다면 아무런 문제가 없다. 잘 만들어진 프로그램은 유용하지 못한 입력에 이해할 수 있는 에러메시지를 내서, 프로그램이 문제를 일으키지 않게 한다.

 

7. 11. 6 동적으로 할당하는 문자열 전환들.

GNU 확장은 최대 크기가 아닌 것으로도 문자열을 안전하게 읽는걸 허용한다. 이것을 사용하면 당신은 버퍼를 제공하지 않아도 되는데; 대신에 scanf는 당신이 준 주소에 데이터를 저장하기 위해 충분한 크기의 버퍼를 할당한다. 이것을 사용하려면, 플래그 문자로 'a'를 써서, '%as' 나 '%a[0-9a-z]로 쓴다.

입력을 저장하기 위해 당신이 제공한 포인터 인수는 char ** 형을 가진다. scanf함수는 버퍼를 할당하고 그 포인터 인수에 워드(word)로, 그 할당된 영역의 주소를 저장한다. 더 이상 그것이 필요치 않으면 free로 버퍼를 해제해야한다. 여기에 'varilble = value' 형태의 "variable assignment"를 읽기 위해 'a'플래그와 함께 '%['변환지정을 사용한 예가 있다.

{
char *variable, *value;
if (2 > scanf ("%a[a-zA-Z0-9] = %a[^\n]\n", &variable, &value))
{
invalid_input_error ();
return 0;
}
. . .
}

 

7. 11. 7 다른 입력 전환들

이 절에는 잡다한 입력 전환들에 대해 설명하고 있다.

'%p'전환

포인터 값을 읽기 위해 사용된다. printf를 위해 출력전환으로 사용되는 '%p'와 동일한 구문으로 인식되고( 7. 9. 6 [Other Output Conversions] 참조); 받아들여진 '%x' 전환은 단지 16진수를 위한 입력이다. 대응하는 인수로 void ** 형이 주어져야 하고; 그것은 포인터를 저장하기 위한 장소의 주소이다. 그 결과인 포인터 값은 만약 그 안에 읽는 동일한 프로그램 실행(execution)동안에 원래대로 써지지 않는다면 그것이 유용한 것인지는 보장할 수 없다.

'%n'전환

이 호출에 의해 읽혀진 문자들의 수를 만든다. 그 대응인수는 int *가 되어진다. 이 전환은 printf에서 사용하는 '%n'전환과 동일한 방법으로 작동한다; 7. 9. 6[Other Output Conversions] 에서 예제를 참조하라. '%n'전환은 문자의 작용(역주: 입력이나 출력등 어떤 문자열에 가해진 작용인 듯)의 성공여부를 결정하거나 은폐된 인수로의 전환을 위한 메커니즘이다. 만약 '%n'에 대응실패가 따르면 '%n'의 진행 전에 scanf가 반환한 그곳에는 아무 값도 저장되지 않은 것이다. 만일 당신이 scanf를 호출하기 전에 그 인수에 -1을 저장하면 scanf는 '%n'에 도달되기 전에 에러의 발생을 지적한다.

'%%' 전환

인수를 사용하지 않고 입력 스트림에 '%'문자를 넣는다. 이 전환은 어느 플래그, 필드의 너비나 타입수정자든, 어떤 것도 허용하지 않는다.

 

7. 11. 8 형식화된 입력 함수들.

이곳에서는 형식화된 입력을 수행하는 함수들을 설명하고 있다. 이 함수들을 위한 프로토타입은 헤더파일 'stdio. h'에 있다.

함수 : int scanf (const char *template, . . . )

scanf함수는 표준 스트림에서 템플릿 문자열의 제어에 따라서 형식화된 입력을 읽는다. 임의의 인수는(optional arguments) 반환된 값을 받기 위한 장소의 포인터이다. 그 반환 값은 보통 저장된 입력 필드의 수를 반환한다. 만일 어느 것도 읽혀지기 전에 파일의 끝인 상황이 검출되면, ( 템플리트에 공백문자와 다른 문자들을 읽는 것을 포함하여), 그러면 EOF가 반환된다.

함수 : int fscanf (FILE *stream, const char *template, . . . )

이 함수는 표준 스트림이 아닌 스트림에서 읽어오는걸 제외하면 scanf와 같다.

함수 : int sscanf (const char *s, cost char *template, . . . )

이 함수는 스트림대신에 널 종료문자를 가진 문자열로부터 문자들을 읽는다는걸 제외하고는 scanf와 같다. 스트링의 끝에 도달하면 파일의 끝인 상황처럼 취급한다. 이 함수의 동작이 만일 오버랩할 objects와 저장할 장소를 취하는 것 사이에 정의되지 않는다면, 만일 s가 스트링을 받기 위한 인수로서 주어진다면 '%s'의 제어하에 문자들을 읽는다.

 

7. 11. 9 변수 인수들의 입력 함수들

vscanf와 그와 유사한 함수들은 당신 자신만의 다양한 scanf와 같은 함수를 정의할 수 있도록 하는 기능을 제공한다. 이 함수들은 출력함수들인 vprintf 시리즈와 유사하다. 7.9.9 [Variable Argumints Output] 를 참조하여 그들은 어떻게 사용하는지에 대한 중요한 정보를 보라.

호환성 노트 : 이 절에 리스트된 함수들은 GNU확장이다.

함수 : int vscanf (const char *template, va_list ap)

이 함수는 직접적으로 인수들의 개수를 가진 변수를 취하는 대신에 인수 리스트로 va_list 형의 포인터 ap를 취하는걸 제외하고는 scanf와 유사하다.

함수 : int vfscanf (FILE *stream, const char *template, va_list ap)

이 함수는 직접적으로 인수 리스트를 변수로 취하면 fscanf와 동등하다.

함수 : int vsscnaf (const char *s, const char *template, va)list ap)

이 함수는 직접적으로 인수 리스트를 변수로 취하면 sscanf와 동등하다.


7. 12 블록 입력/출력

이 절은 데이터 블록의 입력과 출력 동작을 어떻게 하는지에 대해 설명하고 있다. 당신은 문자들이나 라인 대신에 정해진 크기의 블록에 텍스트를 읽고 쓰는 것은 물론, 바이너리 데이터를 읽고 쓸 수 있는 그러한 함수들을 사용할 수 있다.

바이너리 파일들은 실행되고있는 프로그램 안에서 데이터를 나타내기 위해 사용되어진것과 동일한 형식으로 데이터의 블록들을 읽고 쓰기 위해 사용된다. 다른 말로 하자면, 단지 문자나 스트링 오브젝트가 아닌 , 메모리의 블록을 바이너리 파일로 쓸 수도 있고, 동일한 프로그램에 의해 다시 의미있게 읽혀질 수도 있다.

바이너리 파일의 형식으로 데이터를 저장하는 것은 형식화된 입/출력 함수들 사용하는 것 보다 종종 상당히 더 효과적이다. 또한 플로팅 포인트 숫자들에 바이너리 형식의 사용은 변환작업에서 정밀도를(precision) 잃을 가능성을 피하게 한다. 하지만 한편으로, 바이너리 파일들은 많은 기본적인 파일 유틸리티( 텍스트 에디터 같은 )들을 사용하여 쉽게 시험하거나 수정할 수 없고, 다른 종류의 컴퓨터들이나 다른 언어에 적용시키는 것은 호환성이 없다.

이 함수들을 'stdio. h'에 선언되어 있다.

함수 : size_t fread (void *data, size_t size, size_t count, FILE *stream)

이 함수는 스트림 stream으로부터 배열 data안으로 size의 크기의 count개의 objects를 읽는다. 이것은 실제로 읽혀진 objects의 개수를 반환하고, 만약 에러가 발생하거나, 파일의 끝에 도달하면 count보다는 적은 수를 반환할 것이다. 만약 size나 count가 영이면 그 함수의 반환 값은 영이된다(아무 것도 읽지 않았다) 만약 object의 중간에 파일의 끝이 있다면 fread는 완전한 objects의 수를 반환하고, 나머지 object는 버린다. 그러므로, 그 스트림에는 실제 파일의 끝이 남는다.

함수: size_t fwrite (const void *data, size_t size, size_t count, FILE *stream)

이 함수는 배열 data에서 스트림 stream으로 size크기의 count개의 objects를 저장한다. 반환 값은 호출이 성공하면 일반적으로 쓰는데 성공한 count수이다. 다른값이 반환되면 에러나 실행이 실패하였음을 의미한다.


7. 13 파일의 끝과 에러들.

이장에 설명된 많은 함수들은 오퍼레이션의 불완전한 수행을 지적하기 위해 매크로 EOF의 값을 반환하는 함수이다. 파일의 끝이나 임의의 에러를 지적하는데 사용되고, 그것은 종종 명백하게 파일의 끝을 체크하기위해 feof, 그리고 에러들은 체크하는데 ferror를 사용하는 것이 좋다. 이 함수들은 스트림 오브젝트의 내부적 상황의 부분인 지시자, 스트림에서 앞에서 행해진 입/출력 오퍼레이션에 의해 검출된 적절한 상황을 가진 지시자 세트를 체크한다.

이 심볼들은 헤더파일 'stdio. h'에 선언되어 있다.

매크로 int EOF

이 매크로는 파일의 끝인 상황을 지적하거나 어떤 다른 에러 상황에서 사용된 함수들에 의해 반환되는 정수값이다. GNU라이브러리에서 EOF는 -1이고, 다른 라이브러리들에서는 어떤 다른 음의 값을 가질 것이다.

함수 : void clearerr (FILE *stream)

이 함수는 스트림 stream을 위해 파일의 끝과 에러 지시자를 클리어 한다. 이 파일 상태(positioning) 함수들은( 7. 15절 [File Positiong] 참조) 또한 스트림의 파일끝 지시자를 클리어 한다.

함수 : int feof (FILE *stream)

이 feof함수는 스트림에서 만일 단지 파일의 끝 지시자이면 영이아닌 값을 반환한다.

함수 : int ferror (FILE *stream)

이 ferror 함수는 스트림에서 에러지시자를 만나면 영이아닌 값을 반환하여 스트림에서 앞의 오퍼레이션에서 발생한 에러를 지적한다.

스트림과 연관하여 에러 지시자를 세팅하는 것 뿐만 아니라, 파일 기술자에 수행한 연관된 저수준 함수들과 같은 방법으로 에러번호(errno) 또한 세트 할 수 있다. 예를 들어. fputc, printf, 그리고 fflush등 스트림의 출력을 수행하는 모든 함수들에서 출력을 수행하는 동안에 에러의 상황에 따라 정의된 에러번호는 이 함수들에게 의미가 있다. 이 저수준 입/출력 함수 기술자에 대한 정보는 8장 [Low-Level I/O] 를 참조하라.

==> 지금까지 system을 "체제"로 번역하였으나, operating system은 "운영체제"로 번역하되, 다른 system 은 "시스템"으로 번역함. 왜냐하면, 이미 업계에서 "시스템"이라고 그냥 부르는 경우가 많기 때문임.


7. 14 텍스트와 바이너리 스트림.

GUU 시스템과 다른 POSIX 호환 운영체제들은 문자들의 열을 같은 형식으로 해서 모든 파일을 구성한다. 그렇지만, 다른 시스템들에서는 텍스트 파일과, 바이너리 파일사이에 다른점이 있고, ANSI C의 입력과 출력 도구들은 이 차이를 지원한다. 이 절은 다른 시스템들과 호환 되도록 어떻게 프로그램을 만들 것인가에 대한 정보가 있다.

스트림을 개방할 때, 당신은 텍스트 스트림인지 바이너리 스트림인지를 정할 수 있다. 당신이 바이너리 스트림을 원하면, fopen 의 opentype인수에 'b'라고 지정하면 원하는걸 얻을 수 있다. 7. 3절 [Opening Streams] 참조. 'b' 옵션이 없이 사용하면 텍스트 스트림으로 파일이 개방된다. 텍스트와 바이너리 스트림의 여러 가지 차이.

텍스트 스트림에서 읽어온 데이터는 새줄('\n')문자에 의해 종료된 라인들로 구분되지만, 바이너리 스트림은 간단히 문자들의 긴 연속이다. 어떤 시스템에서 텍스트는 254개의 문자보다 긴 문자를 취급할 수가 없다. (새줄 문자를 포함해서 )

어떤 시스템에서, 텍스트 파일들은 오직 프린트 가능한 문자와, 수평탭, 새줄기호 등을 포함할 수 있고, 그 외 다른 문자들은 지원하지 않는다. 하지만 바이너리 스트림들은 어떤 문자 값이라고 취급할 수 있다.

텍스트 스트림에서 새줄기호 앞에 쓰여진 공백문자는 다시 그 파일을 읽을 때는 사라질지도 모른다.

더 일반적으로, 실제 파일안의 문자들과, 텍스트 스트림에서 읽거나, 쓴 문자들 사이에 일대일로 대응되어질 필요가 없다.

바이너리 스트림이 텍스트 스트림보다 항상 더 유능하고, 더 예측 가능한데도 불구하고, 무슨 목적으로 텍스트 스트림이 제공되는지 당신은 의아해할 것이다. 바이너리 스트림의 사용은 왜 항상 간단하지 않은가? 이것에 대한 대답은 텍스트와 바이너리 파일의 형식을 다르게 사용하고, 텍스트를 기반으로한 프로그램으로 작업을 하는 "보통의 텍스트 파일"을 읽거나 쓰는 유일한 방법이 텍스트 스트림을 통해서만 가능한 그런 운영체제들 때문이다.

GNU 라이브러리와 모든 POSIX 시스템들에서는 텍스트 스트림과 바이너리 스트림 사이에 아무런 차이점이 없다. 당신이 스트림을 개방할 때, 바이너리 스트림으로 요청하는걸 신경을 쓰지 않아도 바이너리 스트림을 얻을 수 있다. 이 스트림은 텍스트 스트림이 가진 그런 제한없이 어떤 내용이라도 취급할 수 있다.


7. 15 파일 위치시키기

스트림의 파일 위치는 파일 스트림에서 현재 어느곳을 읽거나, 쓸것인지에 대한 것이다. 스트림의 입출력은 파일의 위치를 진보시킨다. GNU시스템에서는 파일의 위치는 파일의 시작점으로 부터 바이트의 수를 셈한 정수 값으로 나타낸다. 6.1.2절 [File Position] 참조.

 
역자주: 이곳에서 진보란 말은 파일의 위치가 앞으로 옮겨진다는 말.

일반적 디스크 파일에서 입/출력 동안에, 당신은 파일의 어느 부분을 읽거나, 쓰기 위하여, 당신이 원하면 언제든지 파일의 위치를 변경할 수 있다. 대부분의 파일들에서 이것이 허용될 것이다. 파일 위치의 변경을 지원하는 파일들은 때로는 랜덤-억세스 파일이라 불려진다. 당신은 스트림과 관련하여 파일 위치를 시험하거나, 수정하기 위해 이 절에서 설명한 함수들을 사용할 수 있다.

밑에 설명된 것들은 헤더파일 'stdio. h'에 설명되어 있다.

함수 : long int ftell (FILE *stream)

이 함수는 스트림 stream의 현재 파일 위치를 반환한다. 이 함수는 만일 스트림이 파일 위치 변경을 지원하지 않거나, 혹은 파일 위치를 long int로 표현할 수 없거나, 또는 다른 가능한 여러 가지 이유가 발생하면 실패할 수 있다. 만약 실패하면 -1의 값을 반환한다.

함수 : int fseek (FILE *stream, long int offset, int whence)

fseek함수는 스트림 stream의 파일 위치를 변경하는데 사용된다. whence값은 SEEK_SET, SEEK_CUR, 또는 SEEK_END 상수들 중 하나가 되어야 하는데, 이 상수 값들은 offset과 연관시켜서 바꾸기 원하는 파일의 위치를 지적하게 된다. 즉, SEEK_SET는 파일의 시작점과 offset를 연관시키고, SEEK_CUR은 파일의 현재위치와 offset를 연관시키고, SEEK_END는 파일의 끝과 offset를 연관시키는 것이다.
이 함수는 성공하면 0의 값을 반환하고, 실패하면 0이 아닌 값을 반환한다. 성공적인 호출은 또한 스트림의 파일끝(end-of-file) 지시자를 클리어 시켜, ungetc를 통한 "뒤로 밀림"동작이 무시되도록 한다. fseek는 파일 위치를 변경하기 전에 버퍼된 출력을 모두 플러쉬 시키고, 파일의 적당한 위치에 그것을 적는다.
호환성 노트 : 비-POSIX 시스템들에서, ftell 과 fseek만이 바이너리 스트림 상에서 믿음직하게 동작한다. 7. 14절 [Binary Streams] 참조하라. 다음에 설명된 심벌 상수들은 fseek함수의 whence 인수로 사용하기 위해 정의되었다. 그들은 또한 lseek함수에서도 사용되고, ( 8. 2절 [I/O Primiteves] 참조) 파일 잠금(locks)에서 offset을 정하기 위해( 8. 7절 [Control Operations] 참조) 사용된다.

매크로 int SEEK__SET

이것은 정수 상수로서 fseek함수에 whence인수로 사용되면, offset을 파일의 시작점과 연관시킴을 지정한다.

매크로 int SEEK__CUR

이것은 정수 상수로서 fseek 함수에서 whence인수에 사용되면 offset를 파일의 현재의 위치와 연관시킴을 지정한다.

매크로 int SEEK__END

이것은 정수 상수로서 fseek함수에서 whence인수에 사용되면 offset를 파일의 끝 위치와 연관시킴을 지정한다.

함수 : void rewind (FILE *stream)

rewind 함수는 파일의 시작점으로 스트림 stream의 위치를 정한다. 이 함수는 반환 값이 버려지고, 스트림의 에러 지시자가 다시 고쳐지는 것을 제외하면 SEEK_SET을 whence인수에서 사용하고, 0L이 offset 인수에서 사용된 fseek함수와 동등하다. 이들 SEEK_. . . '로 시작되는 세 가지 상수들은 예전의 BSD 시스템들과 호환을 목적으로 존재한다. 그들은 두 개의 다른 헤더파일 'fnctl. h' 와 'sys/file. h'에 정의되어 있다.

L_SET : SEEK_SET의 다른 이름.

L_INCR : SEEK_CUR의 다른 이름.

L_XTND : SEEK_END의 다른 이름.


7. 16 호환성 있는 파일-위치 함수들

GNU 시스템 상에서, 파일의 위치로 문자들을 세어서 할 수 있다. 당신은 fseek의 인수로 문자 개수의 값을 정할 수도 있고, 랜덤 억세스파일에서 이해 가능한 결과를 얻는다. 그렇지만 ANSI C시스템에서는 이 방법으로 파일 위치를 표현할 수 없다. 텍스트 스트림과 바이너리 스트림이 차이를 갖고 있는 시스템들에서는, 파일의 시작점에서 문자들을 세는 것으로 텍스트 스트림의 파일 위치를 표현하는 것은 불가능하다. 예를 들어, 어떤 시스템들에서 파일 위치는 파일안의 레코드 옵셋과, 레코드의 문자 옵셋이 둘을 다 해줘야한 한다. 결과적으로, 당신이, 당신 프로그램이 이 시스템들과 호환되기를 원한다면 어떤 규칙들을 준수해야하는 것이다.

텍스트 스트림 상에서 ftell로 부터 반환되는 값은 당신이 지금까지 읽은 문자들의 수와 아무런 연관이 없다. 당신이 의지할 수 있는 유일한 것은 같은 파일 위치에서 뒤로 옮기기 위해 fseek함수에 offset인수로 사용할 수 있다는 것이다.

텍스트 스트림 상에서 fseek를 호출하려면, offset이 0이 되거나아니면 whence는 SEEK_SET으로 주고 offset는 동일한 스트림 상에서 미리 ftell호출로 얻어진 값이 되어야 한다.

텍스트 스트림에서 파일 위치의 값은 ungetc를 통해서 뒤로 밀린(pushed back)읽혀지지 않았거나, 버려진 문자들에 대한 것은 정의 되어있지 않다. 7. 8절 [Unreading] 참조

그러나 당신이 이들 규칙을 준수한다고 하더라도, 당신은 여전히 긴 파일들을 다룰 때 문제가 있는데, 그것은 ftell과 fseek가 파일 위치를 나타내는 값으로 long int 값을 사용하기 때문이다. 그 형으로는 긴 파일안의 모든 파일위치를 표현해줄 수 없다. 그래서 만일 당신이 이러한 파일위치를 나타내기 위해서 특수한 시스템 지원을 원한다면 ftell과 fseek대신에 fgetpos와 fsetpos를 사용하는 편이 좋다. 이 함수들은 시스템에서 시스템으로 내부적 표현을 바꾸는 데이터 타입 fpos_t를 사용해서 파일 위치를 나타낸다.

이 심벌들은 헤더파일 'stdio. h'에 선언되어 있다.

데이터 타입 : fpos__t

이것은 fgetpos와 fsetpos함수에 의해 사용되는, 스트림의 파일 위치에 대한 정보를 암호화할 수 있는 object의 타입니다. GNU시스템에서는, fpos_t는 off_t나 long int와 동일하지만 다른 시스템에서는, 어떤 내부적 차이를 가질 것이다.

함수 : int fgetpos (FILE *stream, fpos_t *position)

이 함수는 스트림 stream을 위해서 파일 위치 지시자의 값을 fpos_t의 형을 가진 position이 지정하고 있는 곳에 저장하는 기능을 한다. 만일 성공하면 fgetpos는 0을 반환하고, 그렇지 않으면 영이아닌 값을 반환하고 에러번호 errno를 저장한다.

함수 : int fsetpos (FILE *stream, const fpos_t position)

이 함수는 스트림을 위한 파일지시자의 값을 동일한 스트림 상에서 position위치로 설정하는데, 그 새로운 위치는 이전에 호출한 fgetpos에 의해 얻어진 값이어야 한다. 만일 성공하면, fsetpos는 스트림상의 end-of-file 지시자를 클리어 시켜 ungetc를 통한 "pushed back"동작이 불가능하게 하고, 0의 값을 반환한다. 그렇지 않으면 fsetpos는 0이 아닌 값을 반환하고 에러번호 errno를 저장한다.


7. 17 스트림 버퍼링

문자들은 어플리케이션 프로그램에 의해 출력으로 바로 나타나는 대신에 일반적으로 문자들을 모아서 스트림으로 출력하거나, 하나의 블록으로 파일에게 비동기적으로 전송된다. 유사하게, 스트림들은 종종 문자 대 문자란 원리보다는 블록으로부터 입력을 검색한다. 이것을 버퍼링이라 부른다. 만약 당신이 스트림을 사용해서 입력과 출력을 행하는 프로그램을 만들려 한다면, 당신이 당신의 프로그램에서 유저 인터페이스를 구성할 때, 버퍼링이란 것이 어떻게 작동되는지 이해할 필요가 있다. 그렇지 않으면 당신은 당신이 의도한대로 나타나지 않은 출력을 찾거나, 유저에 의해 입력된(typed) 입력이 다른 예상하지 못한 동작이거나, 단일 문자인지를 체크하고, 그것이 유용하게 되도록 만들어야한다. 이 절은 반향(echoing), 제어흐름( flow control ), 그리고 디바이스들의 정해진 부류로 취급되어지는 것과 같은 것이 아닌 파일이나 디바이스들과, 스트림 사이에 문자들이 송신되어질 때 제어하는 것을 취급하고 있다. 터미널 디바이스의 일반적 제어 명령들에 대한 정보는 12장 [Low-Level Terminal Interface]를 참조. 당신은 파일기술자에서 동작하는 저수준 입력과 출력 함수들을 사용하면 스트림 버퍼링 도구들은 무시할 수 있다.

 

7. 17. 1 버퍼링 개념들

버퍼링 방법에 대한 세 가지 종류.

  • 비버퍼화된 스트림으로부터 읽거나 쓸 문자들은 가능한 한 빨리 파일로 또는, 파일로부터 개별적으로 전송되어진다.
  • 라인 버퍼 스트림으로 부터 읽거나 쓸 문자들은 새줄 문자를 만날 때 블록단위로 파일로, 또는 파일에서 전송되어진다.
  • 완전 버퍼 스트림으로부터 읽거나 쓸 문자들은 다양한 크기의 블록 형태로 파일로, 또는 파일로부터 전송되어진다.

새로이 개방된 스트림들은 한가지를 제외하고는, 일반적으로 완전한 버퍼이다; 터미널과 같은 대화식 디바이스와 연결된 스트림은 처음에 라인 버퍼로 되어진다.

대화식(interactive) 디바이스들을 위한 라인 버퍼의 사용에서 새로운 줄로( newline ) 끝나는 출력 메시지는 보통은 즉각적으로 출력될 것이고 이것은 보통 당신이 원하는 것이다. 새로운 줄로 끝나지 않는 출력은 즉시 출력되지 않을지도 모르는데, 그래서 만일 당신이 그들이 즉각적으로 출력되길 원한다면, 당신은 fflush를 사용해서 명시적으로 버퍼된 출력을 나오게 해야한다.

라인 버퍼링은 터미널 상에서 입력을 받기에 좋은데, 그 이유는 대부분의 대화식(interactive) 프로그램들이 보통 단일한 라인으로 된 명령을 읽기 때문이다. 프로그램은 즉시 각 라인을 실행할 수 있을 것이다. 라인 버퍼 스트림은 이것을 허용하지만, 완전한 버퍼 스트림은 그것을 읽도록 프로그램에 허용되기 전에 버퍼를 채워 항상 충분한 텍스트를 읽는다. 라인 버퍼링은 대부분의 운영체제에서 라인 단위의 입력에서 작동하는 보통의 입력-편집기로 알맞다.

어떤 프로그램들은 비버퍼화된 터미널 입력 스트림이 필요하다. 이 프로그램들은 단일-문자 명령들을 읽는 프로그램과 (Emacs와 같은)그들 자신이 입력을 편집하는 프로그램들( readline을 사용하는 것들처럼)이다. 한 번에 한 문자를 읽기 위해서, 입력 스트림의 버퍼링을 멈추게 하는 것만으로 충분하지 않고, 운영체제가 행하는 입력 편집의 작용도 멈추게 해야한다. 이것은 터미널 모드를 바꾸어야 한다. ( 12. 4절 [Terminal Modes] 참조). 만일 당신이 터미널 모드를 바꾸기 원한다면, 당신은 개별적으로-단지 모드들을 변화하지 않는 비버퍼화된 스트림을 사용하여 이것을 해야만 한다.

 

7. 17. 2 버퍼를 쏟아내기

역자주: 이곳에서 쏟아내기란 단어는 버퍼에 있는 내용을 강제적(?)으로 파일로 전송시키고 버퍼를 비우는 그런 의미를 내포하고 있다.

버퍼된 스트림 상에서 출력쏟아내기(Flushing)는 버퍼에 모아진 모든 문자들을 파일로 전송하는 것을 의미한다. 다음은 스트림 상에서 버퍼화된 출력이 자동적으로 쏟아질 때의 많은 상황이다.

  • 당신이 출력을 하려 시도하고, 출력 버퍼는 가득 차있을 때.
  • 스트림이 닫혀있을 때. 7. 4절 [Closing Streams] 참조.
  • 프로그램이 exit를 호출하여 종료될 때. 22. 3. 1절 [Normal Termination] 참조.
  • 만일 스트림이 라인 버퍼라면, 새줄기호를 만났을 때

파일로부터 데이터를 실제적으로 읽는 어느 스트림 상에서 입력 명령이 있을 때이다. 만일 당신이 다른 때(역자주 : 위의 상황이 아닌경우) 버퍼된 출력을 쏟아내길 원한다면, fflush를 호출하라,

그것은 헤더파일 'stdio. h'에 선언되어 있다.

함수 : int fflush (FILE *stream)

이 함수는 스트림 상에서 버퍼화된 어떤 출력을 파일로 배달하는 역할을 한다. 만약 스트림이 널 포인터라면 fflush는 열려진 모든 출력 스트림의 버퍼화된 출력을 강제로 관련 파일에 내보낸다. 이 함수는 만약 쓰기 에러가 발생하면 EOF를 반환하고, 아니면 0을 반환한다.
호환성 노트 : 어떤 머리가 모자라는( 음. . . 이 표현이 가장 적당한 것 같다. 키키키. . . ) 운영체제는 새줄기호를 만나면 라인 버퍼된 스트림을 쏟아내는 라인-지향 입력과 출력으로 완전히 고정시켜 놓은 것으로 알려져 있다! 하지만, 다행스럽게도 이 "특징"은 일반적인 현상은 아닌 것 같다. 당신은 이 GNU 시스템에 대해서는 아무런 걱정할 필요가 없다.

 

7. 17. 3 버퍼링의 종류 제어하기

스트림이 개방된 직후에( 그러나 그 스트림에 어떤 다른 명령도 수행되지 않았다. ), 당신은 setvbuf 함수를 사용해서 당신이 원하는 버퍼링의 종류를 명시적으로 지정할 수 있다. 이 절에 리스트된 도구들은 헤더파일 'stdio. h'에 선언되어 있다.

함수: int setvbuf (FILE *stream, char *buf, int mode, size_t size)

이 함수는 스트림인 stream을 버퍼링 모드인 mode를 갖도록 정하는데 사용한다. mode에는 _IOFBF(완전한 버퍼링), _IOLBF(라인 버퍼링), 이나 _IONBF( 비버퍼화된 입/출력을 위해 )들 중에서 사용할 수 있다. 만일 당신이 buf인수에 널 포인터를 정하면, setvbuf는 malloc를 사용하여 스스로 버퍼를 할당한다. 이 버퍼는 당신이 스트림을 닫을 때 해제된다. 그렇지 않으면, buf는 적어도 문자열을 저장할 수 있는 문자배열이 되어야 한다. 당신은 그 스트림이 열려진 상태로 있고, 이 배열이 버퍼 안에 남아있는 동안은 이 배열을 위해 할당된 공간을 해제할 수 없다. 당신은 버퍼를 정적으로 할당하거나, molloc을 사용해야 한다. ( 3. 3. 절 [Unconstrained Allocation] 참조].
자동 배열을 사용하는 것은 배열을 선언한 블록이 존재하기도 전에 그 파일이 닫힐 수도 있으므로 좋은 생각이 아니다. 배열이 스트림 버퍼에 남아있는 동안 스트림 입/출력 함수는 그들의 내부의 목적들로 버퍼를 사용할 것이다. 당신은 스트림이 버퍼링을 위해 사용되고 있는 동안에는 직접적으로 그 배열의 값을 억세스하려 시도할 수 없다. setvbuf함수는 성공하면 0을 반환하고, mode의 값이 유용하지 않거나, 그 요청이 받아들여질 수 없으면 0이 아닌 값을 반환한다.

매크로 : int __IOFBF

이 매크로의 값은 정수 상수 표현으로 스트림을 완전한 버퍼로 정하기 위해서, setvbuf 함수에서 mode 인수에서 사용되어질 수 있다.

매크로 : int __IOLBF

이 매크로 값은 스트림을 라인 버퍼로 정하기 위해서 setvbuf 함수에서 mode 인수로서 사용되어질 수 있는 정수 값의 상수 표현이다.

매크로 : int __IONBF

이 매크로 값은 스트림을 비버퍼화로 정하기 위해 setvbuf함수에서 mode 인수로 사용되어질 수 있는 정수 값의 상수 표현이다.

매크로 : int BUFSIZ

이 매크로 값은 setvbuf 함수에서 size 인수로 사용되기에 좋은 정수 값의 상수 표현이다. 이 값은 적어도 256을 보장한다. BUFSIZ의 값은 입/출력 스트림을 능률적으로 만들기 위해서 각 시스템에 따라 선택된다. 그래서 당신이 setvbuf를 호출할 때 버퍼를 위한 크기로 BUFSIZ을 사용하는 것이 좋다. 실제로, 당신은 fstat( 역자주: 앙~ 뭔지 모르겠다. ) 시스템 호출의 방법으로 버퍼 크기에 사용하기 위해 더 좋은 값을 얻을 수 있다. 이것은 파일 속성의 st_blksize 필드에서 발견되어진다.
9. 8. 1절 [Attribute Meanings] 참조. 때때로 사람들은 또한 fgets( 7. 6절 [Character Input] 참조)를 사용해서 입력의 한 라인을 받아들이는데 사용되는 문자열(strings)처럼, 연관된 목적을 위해 사용하는 버퍼들의 할당 크기로 BUFSIZ를 사용한다. 효과적인 덩어리로 입/출력을 행하기 위한 것을 제외하고는, 어느 다른 정수 값 대신에 BUFSIZ을 사용할 특별한 이유가 아무 것도 없다.

함수 : void setbuf (FILE *stream, chr *buf)

만일 buf가 널 포인터라면 이 함수를 사용한 효과는 _IONBF의 모드 인수를 사용해서 setvbuf를 호출한 것과 동등하다. 그렇지 않다면, _IOFBF의 모드 인수과 BUFSIZ의 크기 인수를 사용한 buf로 setvbuf를 호출한 것과 동등하다. 그 setbuf 함수는 오래된 코드와의 호환성을 위해 제공되고 있다. ; 새로운 프로그램들은 모두 setvbuf를 사용하라.

함수 : void setbuffer (FILE *stream, char *buf, size_t size)

만일 buf가 널 포인터라면, 이 함수는 비버퍼화된 스트림을 만든다. 그렇지 않다면 이 함수는 버퍼로서 완전한 버퍼화된 스트림을 만든다. size인수는 buf의 길이를 정한다. 이 함수는 오래된 BSD 코드와의 호환성 때문에 제공되고 있다. 대신에 setvbuf를 사용하라.

함수 : void setlinebuf (FILE *stream)

이 함수는 라인 버퍼된 스트림을 만들고, 버퍼를 할당한다. 이 함수는 오래된 BSD 코드와의 호환을 위해 제공되고 있다. setvbuf를 사용하라.


7. 18 다른 종류의 스트림

GNU 라이브러리는 개방된 파일과 교류하는 것이 필요하지는 않은 스트림의 부가적인 종류를 정의하는 방법을 제공하고 있다.

스트림의 어떤 타입은 한 문자열로부터 입력을 취하거나, 문자열에 출력을 쓴다. 스트림의 이러한 종류들은 sprintf와 sscanf 함수들을 위해 내부적으로 사용되어진다. 당신은 또한 7. 18. 1절 [String Streams] 88에서 설명하고 있는 함수들을 사용해서, 명시적으로 이러한 스트림을 만들 수 있다. 더 일반적으로, 당신은 당신의 프로그램에서 제공되는 함수들을 사용해서 다양한 objects에 입출력을 행하는 스트림들을 정의할 수 있다. 이 프로토콜은 7. 18. 3절 [Custom Streams] 100에서 논의되고 있다.

호환성 노트 : 이 절에서 설명하고 있는 도구들은 GNU로 정해진다. 다른 시스템이나 C 작동들은 동등한 함수들을 제공하거나, 혹은 제공하지 않을 수 있다.

 

7. 18. 1 문자열 스트림

fmemopen 과 open_memstream 함수들은 당신이 문자열이나 메모리 버퍼로 입출력을 행하는 것을 허용하는 함수이다. 이 도구들은 'stdio. h'에 선언되어 있다.

함수 : FILE * fmemopen (void *buf, size_t size, const char *opentype)

이 함수는 buf 인수에 의해 정해진 버퍼에서 읽거나 쓰기 위해, 정해진 opentype 인수로 억세스 하는걸 허용하는 스트림을 열어준다.
이 배열은 적어도 long 바이트의 크기를 가져야만 한다. 만일 당신이 buf 인수로 널 포인터를 정하면, fmemopen은 long 바이트 크기의 배열을 동적으로 할당한다. ( malloc을 사용해서; 3.3절 [Unconstrained Allocation] 참조). 이것은 버퍼에 어떤 것을 쓰고나서 다시 뒤로 가서 그들을 읽으려할 때 유용한 함수다, 왜냐하면 당신은 버퍼로부터 포인터를 얻을 수 있는 아무런 방법을 가지고 있지 않기 때문이다. (이것을 위해, open_memstream, 아래). 이 버퍼는 스트림을 열었을 때 freed된다.
opentype인수는 fopen과 같다( 7.3절 [Opening Streams] 참조) 만일 opentype이 append 모드로 정해진다면, 처음의 파일 위치는 파일에서 첫 번째 널 문자에 위치된다. 그렇지 않으면 파일 처음 위치는 버퍼의 시작점이다. 쓰기 위하여 개방된 스트림이 플러쉬(flushed-위에 이 개념이 있습니다. 히. . )되어지거나 닫혀질 때, 널 문자는( 0 바이트 ) 만일 그것이 적당하다면 버퍼의 끝에 쓰여 진다.
당신은 이것을 셈하기 위해서 size인수에 여분의 바이트를 더할 수 있다. 버퍼에 size보다 더 많이 쓰기를 시도하면 에러의 결과에 이른다. 읽기 위해 개방된 스트림에서, 버퍼안의 널 문자들은(0 바이트) "파일의 끝"으로 간주되지 않는다. 파일의 위치가 size를 넘겨 진행될 때, 파일의 끝임을 지적한다. 그래서 당신은 만일 당신이 널 종료문자로 끝나는 문자열을 읽기 원한다면, 당신은 size인수로 문자열의 길이를 공급해야한다.
이곳에 문자열을 읽기 위해 fmemopen을 사용해서 스트림을 만드는 예가 있다.
#include <stdio. h>
static char buffer[] = "foobar";
int main (void)
{
int ch;
FILE *stream;
stream = fmemopen (buffer, strlen (buffer), "r");
while ((ch = fgetc (stream)) != EOF)
printf ("Got %c\n", ch);
fclose (stream);
return 0;
}
이 프로그램은 다음과 같은 결과를 낸다.
Got f
Got o
Got o
Got b
Got a
Got r
/* 왜 이런 결과가 나왔는지 아시죠? */

함수 : FILE * open__memstream (char **ptr, size_t *sizeloc)

이 함수는 버퍼에 쓰기 위해 스트림을 연다. 그 버퍼는 동적으로 할당되고 ( malloc을 사용해서. 3. 3절 [Unconstrained Allocation] 참조)필요에 따라 늘린다. 스트림이 fclose를 사용하여 닫히거나, fflush를 사용하여 플러쉬될 때 ptr의 위치와 sizeloc는 버퍼의 포인터를 포함하고 있는 곳과, 그 크기로 갱신된다. 그 값은 스트림에 길이만큼 출력으로 저장된다. 만일 당신이 더 출력하기 원한다면, 당신은 그들을 다시 사용하기 전에 새로운 값을 저장하기 위해서 스트림을 플러쉬(flush)해야한다. 널 문자는 버퍼의 끝에 쓰여 진다. 이 널 문자는 sizeloc에 저장되어진 size값에 포함되지 않는다. 당신은 fseek( 7. 15절 [File Positioning] 참조)를 사용하여 스트림의 파일 위치(stream's file position)를 옮길 수 있다. 파일 위치를 파일의 끝을 지나서 옮기면 그 공백들은 영으로(zeros) 채워져 쓰여진.
이곳에 open_memstream을 사용한 예가 있다
#include <stdio. h>
int main (void)
{
char *bp;
size_t size;
FILE *stream;
stream = open_memstream (&bp, &size);
fprintf (stream, "hello");
fflush (stream);
printf ("buf = %s, size = %d\n", bp, size);
fprintf (stream, ", world");
fclose (stream);
printf ("buf = %s, size = %d\n", bp, size);
return 0;
}
이 프로그램은 다음과 같은 결과를 낸다.
buf = `hello', size = 5
buf = `hello, world', size = 12

 

7. 18. 2 Obstack 스트림

당신은 obstack에 데이터를 넣는 출력 스트림을 개방할수 있다. 3. 4절 [Obstacks] 를 참조하라.

함수 : FILE * open__obstack__stream (struct obstack *obstack)

이 함수는 obstack에 데이터를 쓰기 위하여 스트림을 개방한다. 이것은 obstack안에서 object를 시작하고, 쓰여진 데이터에 따라 늘려서 만든다. (3.4.6절 [Growing Objects] 참조) 이 스트림 상에서 fflush를 호출하면 쓰여진 데이터의 양에 맞추어서 object의 현재의 크기를 갱신한다. fflush를 호출한 후에, 당신은 일시적으로 object를 시험할 수 있다. 당신은 fseek(7.15절 [File Positioning] 참조)를 사용하여 obstack 스트림의 파일 위치를 옮길 수 있다. 파일의 끝을 지난곳으로 파일 위치를 옮기면 영(0)으로 공백들이 채워 저장된다.
변하지 않는 object를 만들려면, fflush를 사용하여 obstack을 갱신하고, 그 다음 object를 결말짓기 위해 obstack_finish를 사용하고, 그 주소를 얻어라. 그 스트림에 쓴 다음에 obstack에 새로운 object를 시작하면 당신이 다른 fflush와 obstack_finish를 사용하여 그 object에 더하여 쓸 수 있다. 그러나 당신이 object의 길이가 얼마인지 어떻게 알것인가? 당신은 obstack_object_size( 3.4.8절 [Status of an Obstack] 참조)를 호출함으로써 바이트 단위로 그 길이를 얻을 수 있거나 이와 같이 object를 널 종료시킬 수 있다.
obstack_1grow (obstack, 0);
당신이 무엇을 하던 지간에, obstack_finish를 호출하기 전에 그것을 해야 한다. ( 당신이 원하면 양쪽 다 할 수 있다. )
여기에 open_obstck_stream 함수의 예제가 있다.
char *make_message_string (const char *a, int b)
{
FILE *stream = open_obstack_stream (&message_obstack);
output_task (stream);
fprintf (stream, ": ");
fprintf (stream, a, b);
fprintf (stream, "\n");
fclose (stream);
obstack_1grow (&message_obstack, 0);
return obstack_finish (&message_obstack);
}

 

7. 18. 3 프로그래밍한 당신 자신만의 주문 스트림

이 절은 다양한 데이터 소스로부터 입력을 얻는 스트림이나, 당신에 의해 프로그램된 다양한 데이터 저장고(sink)에 출력을 쓰는 스트림을 어떻게 만들 수 있는지에 대한 설명이 있다. 우리는 이것을 주문(custom) 스트림이라고 부른다.

 

7. 18. 3. 1 주문 스트림과 Cookies

모든 주문 스트림 내부에는 cookie라고 불리는 특별한 object가 있다. 이것은 읽거나 쓴 데이터를 추출하거나 저장하기 위한 레코드들로 당신에 의해 제공되는 오브젝트이다. 당신에게 cookie에 사용될 데이터 타입을 정의하도록 한다. 라이브러리 안의 스트림 함수들은 결코 그 안의 내용을 직접적으로 참조하지 않고, 그들은 심지어 그것이 무슨 타입인지 알지 못한다; 그들 레코드의 주소는 void * 타입으로 저장되어 있다.

주문 스트림을 도구로 하려면, 당신은 정해진 위치에 데이터를 어떻게 저장하거나, 추출할 것인지 정해줘야만 한다. 당신은 읽거나 쓰거나, "파일 위치"를 변경하거나, 스트림을 닫거나 하는 후크(hook) 함수들을 정의함으로써 이것을 한다. 이 네 개의 함수들에 스트림 cookie가 전달되어지면 그들은 추출하거나 저장할 데이터의 위치를 알려줄 수 있다. 라이브러리 함수는 cookie안에 무엇이 있는지 알지 못하지만, 당신의 함수들은 알 것이다.

당신이 주문 스트림을 만들 때, 당신은 cookie 포인터를 정해 주어야만하고, 또한 네 개의 후크 함수들은 struct cookie_io_functions의 타입으로 구조체 안에 저장한다.

이 도구들은 'stdio. h'에 선언되어 있다.

데이터 타입 : struct cookie__io__functions

이것은 스트림과 cookie 사이의 통신 프로토콜을 정의하는 함수를 유지하기 위한 구조체 타입이다. 다음과 같은 멤버를 가지고 있다.

cookie_read_function *read

이것은 cookie로부터 데이터를 읽기 위한 함수이다. 만약 그 값이 함수 대신에 널 포인터라면, 스트림 상에서 읽기 동작들은 항상 EOF를 반환한다.

cookie_write_function *write

이것은 cookie에 데이터를 쓰기 위한 함수이다. 만약 그 값이 함수대신에 널 포인터라면, 스트림에 쓰여진 데이터는 버려진다.

cookie_seek_function *seek

이것은 cookie에 파일 위치시키기와 같은 동작들을 수행하는 함수이다. 만약 그 값이 함수대신에 널 포인터라면, 이 스트림에서 fseek를 호출하면, 오직 버퍼안의 위치만 찾을 수 있다; 버퍼의 밖에서 찾으려는 어떤 시도는 ESPIPE 에러를 반환할 것이다.

cookie_close_function *close

이 함수는 스트림이 닫힐 때 cookie에 적당한 정리작업을 수행한다. 만약 그 값이 함수대신에 널 포인터라면, 스트림이 닫힐 때 어떤 특별한 일을 행함이 없이 cookie를 닫는다.

함수 : FILE * fopencookie (void *cookie, const char *opentype, struct cookie_functions io_functions)

이 함수는 io_functions 인수 안에 있는 그 함수들을 사용해서 cookie와 통신하기 위한 스트림을 실제로 생성한다. opentype 인수는 fopen에서 사용되는 것과 같다. 7. 3절 [Opening Stream]를 참조하라. ( 하지만 "truncate on open" 옵션은 무시됨을 기억하라. 새로운 스트림은 완전히 버퍼 된다. fopencookie 함수는 새로이 생성된 스트림을 반환하거나, 아니면 에러가 발생한 경우에 널 포인터를 반환한다.

 

7. 18. 3. 2 주문 스트림 후크 함수들

주문 스트림이 필요로 하는 네 개의 후크 함수들을 정의하는 방법에 대해 자세히 설명한다.

당신은 cookie로부터 데이터를 읽기 위한 함수를 다음처럼 정의할 수 있다.

ssize_t reader (void *cookie, void *buffer, size_t size)

이것은 read 함수와 매우 유사하다; 8. 2절 [I/O Primitives] 참조하라. 당신의 함수는 버퍼 안에서 size 바이트를 참조하고, 읽은 바이트의 수를 반환하거나, 아니면 end-of-file을 지시하기 위해 0을 반환한다. 당신은 에러가 발생한 경우에 -1의 값을 반환할 수 있다. 당신은 cookie에 데이터를 쓰기 위한 함수를 다음처럼 정의할 수 있다.

ssize_t writer (void *cookie, const void *buffer, size_t size)

이것은 write함수와 매우 유사하다; 8. 2절 [I/O Primitives] 참조하라. 당신의 함수는 버퍼로부터 size 바이트를 참조하고, 씌어진 바이트의 수를 반환한다. 당신은 에러를 지적하기 위해 -1의 값을 반환할 수 있다.

당신은 cookie에 검색 명령을 수행하기 위한 함수를 다음처럼 정의할 수 있다.

int seeker (void *cookie, fpos_t *position, int whence)

이 함수에서, position과 whence 인수들은 fgetpos처럼 해석되어진다; 7. 16절 [Portable Positiong]참조하라. GNU 라이브러리에서는, fpos_t 타입은 off-t나 long int와 동등하고, 파일의 시작점으로 부터 바이트의 수를 표현한다. 검색 오퍼레이션이 실행된 이후, 당신의 함수는 파일의 시작점을 기준으로 한 파일의 위치를 결과로 하여 저장할 것이다. 당신의 함수는 성공하면 0을 반환하고, 아니면 실패일 경우 -1을 반환한다.

당신은 스트림을 닫을 경우 cookie에 적당한 명령을 주어서 정리 작업(cleanup)을 하기 위한 함수를 다음처럼 정의할 수 있다.

int cleaner (void *cookie)

당신의 함수는 성공하면 0, 그렇지 않으면 에러를 지적하기 위해 -1을 반환한다.

데이터 타입 : cookie __read__function

이것은 주문 스트림을 위한 읽기 함수의 데이터 타입이다. 만일 당신이 위에 보여준 것처럼 함수를 선언하면, 그때 그 함수가 갖는 데이터 타입이다.

데이터 타입 : cookie__write__function

주문 스트림을 위한 쓰기 함수의 데이터 타입

데이터 타입 : cookie__seek__function

주문 스트림을 위한 검색 함수의 데이터 타입.

데이터 타입 : cookie__close__function

주문 스트림을 위한 종료 함수의 데이터 타입