5 문자열과 배열 유틸리티
문자열(또는 문자의 배열) 처리는 많은 프로그램에 있어 중요한 부분이다.GNU C 라이브러리는 많은 문자열 유틸리티 함수를 제공하는데, 여기에는 문자열의 복사, 연결, 비교, 검색을 위한 함수들이 포함되어 있다. 많은 문자열 처리함수들은 저장소의 임의의 구역을 처리할 수 있다; 예컨대, memcpy 함수는 어떠한 종류의 배열 내용도 복사해 낼 수 있다.
초보 C 프로그래머들이 자신의 코드로 이 기능을 중복되게 작성하여 "도구를 재발명"하는 일은 너무도 흔한 일이다. 그러나 그렇게 함으로써 라이브러리 함수들에 친숙해지고 그것들을 사용할 수 있게되므로, 이러한 일은 유지, 효율성, 운용에 이점을 제공하는 셈이다.
예컨대, 당신은 2행의 C 코드에서 하나의 문자열을 다른 문자열과 쉽게 비교할 수 있다. 만약 당신이 기존의 strcmp함수를 사용한다면 실수를 줄일 수 있다. 또한 이러한 라이브러리 함수들은 전형적으로 매우 최적화 되어 있으므로 당신의 프로그램은 빠르게 실행될 수도 있다.
5.1 문자열의 표현
이 절에서는 초보 C 프로그래머들을 위해 문자열의 개념을 간명하게 요약하였다.C에서 문자열이 어떻게 표현되며 흔히 있을 수 있는 함정은 무엇인가를 기술해 놓았다. 만약 당신이 이미 이러한 내용들을 잘 알고 있다면, 이 절을 건너뛰어도 좋다.
문자열은 char 대상물의 배열이다. 그러나 문자열 값을 가지는 변수들은 항상 문자포인터(char *) 형태로 선언된다. 그러한 포인터 변수들은 어떤 문자열의 내용물을 위한 공간을 포함하고 있지 않다; 그 내용물은 어떤 다른 곳에 저장된다_즉, 어떤 배열 변수, 문자열 상수, 또는 동적으로 할당된 메모리에 저장된다. 선택된 메모리 공간의 주소를 포인터 변수에 저장하는 일 당신의 몫이다. 선택적으로 당신은 포인터 변수에 널(null) 포인터를 담을 수도 있다. 이 널 포인터는 어떠한 곳도 가리키지 않는 것이므로 그것이 가리키는 문자열을 참조하려고 시도한다면 에러를 만날 것이다.
관행적으로 널 문자 '\0'은 문자열의 끝을 가리킨다. 예를 들어, char형 포인터 변수 p가 문자열의 끝을 가리키는 널 문자를 가리키는지 아닌지를 알아보고 싶으면, !*p 또는 *p == '\0'이라고 써보아라. 널 문자는 개념상으로 널 포인터와는 아주 다르다. 물론 그것들이 모두 정수 0으로 표현되기는 하지만.
C 프로그램 소스에서 문자열 리터럴은 이중 인용부호('"')사이에 있는 문자들의 문자열로서 나타난다.ANSI C에서는 문자열 리터럴은 문자열의 연결에 의해서 만들어질 수도 있다."a" "b"는 "ab"와 같다. 문자열 리터럴의 변경은 GNU C 컴파일러에서는 허용되지 않는다. 왜냐하면, 리터럴은 읽기 전용의 저장소에 위치하기 때문이다. <역자주석>리터럴:C 명령문 상에서 직접 입력하는 데이터 const로 선언된 문자 배열들 역시 변경이 금지된다. 변경금지의 문자열 포인터를 const char * 형태로 선언하는 것은 일반적으로 훌륭한 방식이다. 이렇게 하면, C 컴파일러는 당신의 프로그램이 그 문자열을 갖고서 무엇을 하고자 하는지에 관한 정보를 상당히 얻을 수 있으며, 또한 그 문자열의 우연한 변경에 대해 조사할 수 있게 된다.
문자 배열에 할당된 메모리의 양은 문자열의 끝 부분임을 통상적으로 표시하는 널 문자를 넘어서는 곳까지 확장될 수도 있다. 이 책에서는 할당크기(allocation size)라는 용어는 항상 문자열에 할당된 메모리의 총량을 가리키게 될 것이고, 반면에 길이(length)라는 용어는 종료시키는 널 문자까지의(포함하지는 않음) 문자의 개수를 가리키게 된다.
악명 높은 프로그램 소스의 버그는 문자열에서 그것의 적합한 크기보다 더 많은 문자를 집어넣으려고 하는 것이다. 문자열을 확장하고 이미 할당된 배열에 문자를 이동시키는 소스를 쓸 때, 당신은 항상 신중하게 텍스트의 길이를 추적해야 하며 배열이 넘치지 않는지를 분명하게 체크하여야한다. 많은 라이브러리 함수들이 알아서 이것을 해주지는 않는다! 그리고 당신은 문자열의 끝을 나타내는 널 문자를 담아둘 여분의 한 바이트를 할당해야함을 잊지 말아야 한다.
5.2 문자열과 배열 규정
이 장에서는 임의의 배열이나 메모리 블럭에서 작동되는 함수들과 널로 종료되는 문자의 배열에 한정되어 사용되는 함수들에 대해 설명한다. 임의의 배열이나 메모리 블럭을 처리하는 함수들은 'mem'(memcpy처럼)으로 시작되는 명칭을 가지며 항상 처리할 메모리 블럭의 크기를(바이트로) 지정하는 인수를 갖는다. 이 함수들의 배열 인수와 그 반환 값은 void * 형태를 가지며, 형식상 이러한 배열의 요소들은 "바이트"로서 참조된다. 당신은 이 함수들에게 어떤 종류의 포인터를 전달할 수 있으며, size 인수의 값을 계산하는데는 sizeof 연산자가 유용하다.
대조적으로, 특별히 문자열을 처리하는 함수들은 'str'로 시작하는(strcpy와 같이) 명칭을 가지며, 분명한 크기의 인수를 전달받기를 요구하는 대신에 문자열을 종료시키는 널 문자를 요구한다.(이러한 함수들 중에 어떤 것들은 특정한 최대 길이를 받아들이지만, 역시 널 문자를 써서 사전 종료를 체크하고 있다.) 이러한 함수들의 배열 인수와 반환 값은 char * 형태를 가지며, 배열 요소들은 "문자들"로서 참조된다.
많은 경우에 한 함수에는 'mem' 버전과 'str' 버전이 모두 갖춰져 있다. 그 중의 한편의 함수는 훨씬 더 적합하게 사용할 수 있으나 정확한 상황을 요구한다. 만약 당신의 프로그램이 임의의 배열이나 저장소의 블럭을 처리하려 한다면, 당신은 'mem'함수들을 쓰는 편이 좋다. 반면에, 당신이 사전에 문자열의 길이를 알지 못한 상태에서 널로 끝나는 문자열을 처리할 때는 str'함수들을 사용하는 편이 편리하다.
5.3 문자열 길이
당신은 strlen 함수를 사용하여 문자열의 길이를 구할 수 있다.
이 함수는 'string.h'에 선언되어 있다.
함수 size_t strlen (const char *s)
함수 strlen은 널로 종료되는 문자열 s의 길이를 반환한다.(다른 말로 표현하면, 그것은 배열내의 종료하는 널문자의 오프셋을 반환한다.) 예를 들면, strlen ("hello, world") 한 문자의 배열에 적용해보면, strlen 함수는 그곳에 저장된 문자열의 길이를 반환하는 것이지, 그것의 할당 크기를 반환하는 게 아니다. 문자의 할당 크기는 sizeof 연산자를 사용하여 구할 수 있다. char string[32] = "hello, world"; /*위의 배열에는 31개의 문자와 종료 널 문자 1개를 담을 수 있다*/ sizeof (string) ) 32 /*할당된 배열의 크기는 32*/ strlen (string) ) 12 /*실제의 문자열의 길이는 12*/
5.4 (문자열) 복사와 결합
이 절에서 서술해 놓은 함수들은 문자열과 배열을 복사하거나 한 문자열을 다른 문자열에 결합할 때 사용할 수 있다. 이 함수들은 'string.h'에 선언되어 있다.
이 함수들의 인수 기입 순서를 외우는 방법은 소스 배열의 왼편에 목적 배열을 두어서 할당 표현과 일치시키는 것이다. 이러한 함수들 전부가 목적 배열의 주소를 반환한다.
이 함수들의 대부분은 소스 배열과 목적 배열이 중첩될 경우에 제대로 작동하지 않는다. 예를 들면, 목적 배열의 시작 부분이 소스 배열의 끝 부분과 중첩되게 되면, 소스 배열 중의 그 부분의 내용이 복사되기 전에 덮이게 된다. 더욱 심각한 것은, 문자열 함수의 경우에, 문자열의 끝을 나타내는 널문자가 상실되고, 그 복사 함수는 당신의 프로그램에 할당된 모든 메모리를 돌아다니는 루프에 빠져버릴 것이다.
중첩된 배열사이에서 복사하는 문제를 갖는 모든 함수들이 이 안내서에서 분명하게 지적되어있다. 이 절의 함수들뿐만 아니라 sprintf나 scanf와 같은 몇몇 함수들이 있다.
함수 void * memcpy (void *to, const void *from, size_t size)
memcpy 함수는 from에서 시작되는 대상물로부터 to로 시작되는 대상물로 size 바이트를 복사한다. 만약 2개의 배열 from과 to가 중첩될 경우의 행위는 정의되어있지 않다; 중첩될 경우에는 이 함수 대신에 memmove를 사용하라. memcpy가 반환하는 값은 to의 값이다. 어떤 배열의 내용물을 복사하기 위해 memcpy 함수를 사용하는 예를 들어보자: struct foo *oldarray, *newarray; /*foo라는 명칭의 struct 가 2개의 포인터에 할당되고*/ int arraysize; . . . memcpy (new, old, arraysize * sizeof(struct foo)); /* old의 내용을 new에 <struct 크기 * 특정숫자>크기만큼 복사하라는 것*/
함수 void * memmove(void *to, const void *from, size_t size)
memmove는 2개의 블럭 공간이 중첩될 경우에조차도 from에서 size 바이트를 to의 size 바이트에 복사한다. 중첩이 있을 때, memmove는 주의 깊게 from블럭에 있는 바이트에서 최초의 값을 복사하는데, 거기에는 to블럭에 속하는 바이트도 포함되어 있기 때문이다.
함수 void * memccpy(void *to, const void *from, int c, size_t size) Function
이 함수는 from에서 to로 정확히 size 바이트만큼만 복사하고,c에 일치하는 한 바이트가 발견되면 중지한다. 반환 값은 c가 복사된 곳 바로 다음의 바이트에 대한 포인터이며, 만일 from의 최초의 size 바이트에 c에 일치하는 바이트가 없으면 널 포인터를 반환한다.
함수 void * memset (void *block, int c, size_t size)
이 함수는 c의 값을(unsigned char로 변환하여) block 에서 시작되는 대상물의 최초의 size 바이트의 각 부분에 복사한다. 이 함수는 블럭의 값을 반환한다.
함수 char * strcpy (char *to, const char *from)
이 함수는 문자열 from(종료하는 널 문자까지 포함하여)으로부터 문자열 to로 문자들을 복사한다.memcpy처럼 문자열이 중첩되게 되면 이 함수도 알 수 없는 결과를 초래한다. 반환 값은 to의 값이다.
함수 char * strncpy (char *to, const char *from, size_t size)
이 함수는 strcpy와 비슷하지만 항상 정확히 size 바이트만큼만 to에 복사한다. 만약 from의 길이가 size보다 크면, 이 함수는 앞부분의 size 바이트를 복사한다. 만약 from의 길이가 size보다 작으면, 이 함수는 from의 모든 것을 복사하고 그 크기만큼의 공백을 널 문자로 채운다. 이와 같은 행위는 별로 유용하지 않지만 ANSI C 표준에 의해 규정된 사항이다. 문자열이 중첩될 경우의 strncpy의 행위는 정의되어 있지 않다.strcpy와는 다르게 strncpy를 사용하는 것이 to의 할당된 공간의 끝 부분 이후에 널 문자를 채움으로써 버그를 피하는 수단이 된다. 그러나, 어떤 경우에는 프로그램의 실행을 느리게 하기도 한다: 큰 버퍼에 작은 문자열을 복사하는 경우이다. 이런 경우에는, size가 너무 커서 strncpy가 널 문자를 채우는 시간이 상당히 낭비되는 것이다.
함수 char * strdup (const char *s)
이 함수는 널로 종료되는 문자열 s를 새롭게 할당되는 문자열로 복사한다. 문자열은 malloc을 써서 할당된다. 만약 malloc이 새로운 문자열을 위한 공간을 할당할 수 없게되면,strdup는 널 포인터를 반환한다. 그렇지 않으면 이 함수는 새로운 문자열에 대한 포인터를 반환한다.
함수 char * stpcpy (char *to, const char *from)
이 함수는 strcpy와 같으나 한가지가 다르다. 이 함수는 시작부분의 포인터가 아니라 문자열 to의 끝 부분에 대한 포인터(즉, 종료하는 널문자의 주소)를 반환한다. 예를 들면, 다음 프로그램은 stpcpy 함수를 사용해서 'foobar'를 만들기 위하여 'foo'와 'bar'를 결합하였고, 그런 다음에 그것을 출력해 보았다. #include <string.h> #include <stdio.h> int main (void) { char buffer[10]; char *to = buffer; /*버퍼의 값을 to에 넣고?*/ to = stpcpy (to, "foo"); /*문자열 "foo"를 to에 복사*/ to = stpcpy (to, "bar"); /*문자열 "bar"를 "foo"가 담긴 to에 복사*/ puts (buffer); /*버퍼의 문자열을 꺼내보면, "foobar"가 되었겠지?*/ return 0; } 이 함수는 ANSI나 POSIX 표준의 일부분이 아니며, 유닉스 체제에서 일상적인 것도 아니지만, 우리가 이것을 만들어 낸 것도 아니다. 아마도 이것은 MS-DOS(?)에서 만들어진 것 같다. 문자열이 중첩될 경우의 행위도 정의되어 있지 않다.
함수 char * strcat (char *to, const char *from)
strcat 함수는 strcpy와 유사하지만 덮어쓰는 대신에 from의 문자들을 to의 끝에 결합하거나 추가하는 점만 다르다. 즉,from의 처음 문자가 to의 끝 부분을 나타내는 널 문자를 덮어쓰게 된다. strcat와 같은 정의는 아마 이렇게 될 것이다: char * strcat (char *to, const char *from) { strcpy (to + strlen (to), from); /*strcpy(to,from);은 from이 to의 기존내용을 덮어쓰지만, 위의 것은 to의 기존주소에다 to의 문자열 길이 만큼을 더한 주소에 from을 복사하므로,to의 널 문자를 제외한 기존 문자열이 그대로 살아남는다는 뜻?*/ return to; /*주소가 반환되남?*/ } 이 함수에도 문자열이 중첩될 경우가 정의되어 있지 않다.
함수 char * strncat (char *to, const char *from, size_t size)
이 함수는 strcat와 같으나,from의 size 문자만큼만 to의 끝 부분에 추가된다는 점에서 다르다. 이 함수 역시 하나의 널 문자를 to의 끝에 추가하며, 그리하여 to의 할당된 전체 크기는 그것의 최초의 길이보다 적어도 size+1 이상이 되어야 한다. strncpy 함수는 아래와 같이 만들어졌을 것이다. char *strncat(char *to, const char *from, size_t size) { strncpy (to + strlen (to), from, size); return to; } 이 함수 역시 문자열 중첩의 경우를 정의하지 않고 있다. 다음에 strncpy와 strncat의 예를 보자. strncat 함수를 호출할 때 문자 배열 버퍼가 중첩되는 것을 피하기 위해서 size 파라미터가 계산되는 방식에 주의하라. #include <string.h> #include <stdio.h> #define SIZE 10 static char buffer[SIZE]; /*음..정적 변수는 그 값이 불변으로 저장?*/ main () { strncpy (buffer, "hello", SIZE); /*버퍼에 "hello"를 10 바이트만 복사*/ /*그러면 buffer가 h e l l o \0 \0 \0 \0 \0 이렇게 되는 것?*/ puts (buffer); strncat (buffer, ", world", SIZE - strlen(buffer) - 1); /*음..10-5-1,즉,4만큼만 ", world"를 추가하면 결국, ", wo"만 결합되는군*/ puts (buffer); } 이 프로그램의 출력은 아래와 같다. hello hello, wo
함수 void * bcopy (void *from, const void *to, size_t size)
이 함수는 BSD에서 유래된 것으로서 memmove와 대체할 수 있어 쓸모 없게 된 함수다. 다만 인수를 배치하는 순서가 다른 점이 있어서 memmove와 완전히 같을 수 없음에 유의하라.
함수 void * bzero (void *block, size_t size)
이 함수는 BSD에서 유래된 것으로서 memset와 대체할 수 있어 쓸모 없게 된 함수이며, 이 함수가 저장할 수 있는 유일한 값은 영(0)이다. 어떤 장치들은 제로 화된 메모리를 위한 특별한 도구들을 갖추고 있는 경우가 있는데, 이때는 bzero가 memset보다 훨씬 효과적이다.
5.5 문자열/배열 비교
당신은 문자열과 배열의 내용을 비교하기 위해서 이 절에 있는 함수들을 사용할 수 있다. 비교뿐만 아니라 정렬을 할 때 순서관계를 결정하는 함수로서도 사용할 수 있다.
다른 대부분의 비교와 달리 C에서, 문자열 비교 함수는 그 비교의 결과가 동등하지 않을 경우 0이 아닌 값을 반환한다. 그 값의 의미는 비교한 문자열에서 동등하지 않은 첫 번째 문자의 순서관계를 가리킨다. 즉 음수 값은 첫 번째 문자열이 두 번째 문자열보다 작다는 걸 의미하고 그 값이 양수이면 첫 번째 문자열이 두 번째 문자열보다 크다는 걸 의미한다.
만약 당신이 단순하게 동등한지 아닌지 만을 체크하기 위해 이 함수들을 사용한다면 당신은 밑에 있는 것처럼 매크로 정의를 이용하여 좀더 깨끗한 프로그램을 만들 수도 있다.
#define str_eq(s1,s2) (!strcmp ((s1),(s2)))
이 함수들은 헤더파일 'string.h'에 정의되어 있다.
함수: int memcmp (const void *al, const void *a2, size_t size)
이 함수는 메모리의 a1로 시작하는 size만큼을 a2로 시작하는 같은 size만큼과 비교하는 함수이다. 바이트( unsigned char objects로 해석되고, int로 승격된)의 첫 번째 다른 쌍이 나타나면 다름을 나타내는 신호를 반환한다. 만약 두 블록의 내용이 동등하면 memcmp는 0을 반환한다. 정해지지 않은 배열에서 memcmp함수는 동등함을 비교하는데 대개는 유용하게 쓰인다. 그렇지만 어떤 배열에서 한 바이트별로 크기 비교를 할 때는 유용하지 않다. 예를 들어, 플로팅 포인트가 있는 여러 바이트에서 한 바이트 비교는 실수들값 사이의 관계에 대해서 아무 것도 말해주지 않는 것이다. 당신이 "holes(구멍)".. 즉.. unions의 끝에 덧붙여진 공백, 문자열의 끝에 덧붙여진 문자들처럼 그들이 실제로 저장된 공간보다 전체 길이를 더 차지하게 되고, 강제로 필요에(alignment) 의해 요구된 어떤 object들에 덧붙여진 것과 같은 "holes"를 포함할 수 있는 objects를 비교하기 위해 memcmp를 사용하는 것은 주의가 필요하다. 이들 "구멍들"은 바이트별 비교를 수행할 때 애매하고 이상한 작동의 원인이 될 수 있고 더 나아가서는 충돌 성분별 비교를 수행하는 것과 같은 결과를 낸다. 예를 들어 밑의 구조체형 정의처럼; struct foo{ unsigned char tag; union { double f; long i; char *p; } value; }; 당신은 구조체 foo 와 같은 object들에 비교함수를 쓸 때는 memcmp대신에 특별 화된 비교함수를 사용하는 것이 더 좋다.
함수: int strcmp (const char *s1, const char *s2)
strcmp함수는 문자열 s1과 s2를 비교해서 첫 번째 다른 문자의 ( unsigned char로 object를 해석하고 int로 승격된 ) 쌍이 나오면 그 다음에 해당하는 값을 반환한다. 만약 두 개의 문자열이 같으면strcmp는 0을 반환한다. strcmp를 사용했을 때 만약 s1이 s2와 앞쪽의 문자열이 같은 substring(부문자열)이라면 s1이 s2보다 작다고 간주한다.
함수: int strcasecmp (const char *s1, const char *s2)
이 함수는 무시되는 경우의 차이를 제외하면 strcmp와 같다.
함수: int strncasecmp (const char *s1, const char *s2)
이 함수는 무시되는 경우의 차이를 제외하고는 strncmp와 같다. strncasecamp는 GNU확장이다.
함수: int strncmp (const char *s1, cnost char *s2, size_t size)
이 함수는 비교될 문자들의 길이가 인수로 필요한걸 제외하면 strcmp와 비슷하다. 다른 말로 하면 만일 두 개의 문자열이 주어진 크기 안에서 그 안에 있는 문자들이 같다면 반환 값은 zero(0)이다. 여기에 strcmp와 strncmp의 사용을 보여주는 몇 가지 예가 있다. 이 예들은 ASCII 문자 셋을 사용한다고 가정하자. ( 만약 ASCII 대신에 EBCDIC과 같은 다른 문자 셋을 사용하면 glyphs(문자)를 다른 숫자 코드들로 연관시켜서 결과 값이나 순서관계가 우리의 예상과 다를 수 있다. strcmp ("hello", "hello") ) 0 /* 이 두 개의 문자열은 같다. */ strcmp ("hello", "Hello") ) 32 /* 비교는 case-sensitive 하다.( 즉 대, 소문자 구별을 한다..) */ strcmp ("hello", "world") ) -15 /* 문자 'h'는 문자 'w'보다 앞에 존재한다. 즉 h가 w보다 적다는 얘기겠지요. */ strcmp ("hello", "hello, world") ) -44 /* 컴마(,)와 널문자의 비교. 공백이 작다. */ strncmp ("hello", "hello, world"", 5) ) 0 /* 앞에서 5개 까지는 문자가 같다. */ strncmp ("hello, world", "hello, stupid world!!!", 5) ) 0 /* 앞에서 5개 까지는 문자들이 같다. */
함수: int bcmp (const void *a1, const void *a2, size_t size)
이 함수는 BSD확장으로 memcmp의 퇴화된 기능을 가졌다고 보면 된다.
5.6 대조 함수들
어떤 지역들에서는, 사전 편찬상의 순서가 문자 코드들의 엄격한 숫자적 순서와는 다르다. 예를 들어 스페인에서 대부분의 문자(glyphs)는 비교할 목적일 때 하나의 독립적인 문자로 간주되지 않는 악센트와 같은 발음 구별 기호가 있다. 한편으로는 두 문자의 문자열인 'll'이 'l'의 바로 다음순서를 갖는 단일 문자열로 취급되는 경우도 있다.
당신은 strcoll 과 strxfrm 함수( 헤더파일 'string.h'에 정의된.)를 사용하여 현재 지역에서 사용하는 대조순서를 사용하여 문자열을 비교할 수 있다. 이 함수들이 사용될 지역은 LC_COLLATE 분류에 속한 지역을 선택하여 지정할 수 있다.
표준 C 에서는 strcoll을 위한 비교 문자열도 strcmp와 같다.
효과적으로, 이 함수들을 사용하려면 문자열 안에 있는 문자들을 현재지역의 대조열 안의 문자 위치를 표현하는 byte열로 문자열 안의 문자를 변형시켜 대응되게 사용하면 된다. 간단한 형태의 byte열을 비교함은 지역의 대조열로 문자열을 비교하는 것과 같다. strcoll함수는 비교를 하기 위한 순서의 하나로, 암묵적으로 이 변형을 수행한다. strxfrm와 비교하면 strxfrm은 명시적으로 이 변형을 수행한다. 만일 당신이 동일한 문자열이나 문자열들의 셋을 다중비교하기 원한다면 일단 모든 문자열들은 변형하기 위해 strxfrm을 사용하고 이어서 strcmp로 변형된 문자열들을 비교하는 것이 더 효과적일 것이다.
함수: int strcoll (const char *s1, const char *s2)
strcoll함수는 strcmp와 비슷하지만 어떤 지역의 문자를 대조하는데 쓰인다. (the LC_COLLATE locale) 여기에 strcoll을 사용하여 비교하기 위해 문자열들을 정렬하는 예가 있다. 이 실제 정렬 알고리즘은 여기에 쓰여지지 않았다; qsort이다.
여기에 나타난 프로그램 코드들은 그들을 정렬하는 동안 문자열들을 비교하기 위해 어떻게 하는지를 말해주고 있다. ( 이 절의 끝에 strxfrm을 사용하여 이것을 더 효과적으로 수행하는 방법이 있다. ) /* 이것은 qsort가 사용된 비교함수이다. */ int compare_elements (char **p1, char **p2) { return strcoll (*p1, *p2); } /* 이것은 지역 대조열을 사용하여 문자들을 정렬하기 위한 함수의 진입지점이다. */ void sort_strings (char **array, int nstrings) { /* 문자열을 비교하여 임시문자열을 정렬하다. */ qsort (array, sizeof (char *), nstrings, compare_elements); }
함수: size_t strxfrm (char *to, const char *from, size_t size)
이 함수는 현재의 지역으로 선택된 대조 문자열을 사용하여 문자열을 변형시기는 일을 하고 이 변형된 문자열을 배열 안에 저장한다. character의 크기로 저장된다. ( 종료 널 문자가 포함된) 문자열에서 문자열로 대치되는 행동은 정의되지 않았다. 5.4절을 보라. 반환 값은 변형된 문자열의 전체길이이다. 크기가 값으로서 유용하지는 않지만 만일 그것이 크기보다 크다면 그것은 변형된 문자열이 배열크기에 전체적으로 맞지 않다는 것을 의미한다. 이 경우에 오직 그 문자열의 양만큼 실제적으로 맞추어서 저장된다. 전체적으로 변형된 문자열을 얻으려면 strcfrm 을 좀더 큰 출력 배열을 사용해서 다시 호출하라. 변형된 문자열은 원래의 문자열보다 길 수도 짧을 수도 있다. 만약 크기가 zero라면 어떤 문자들도 안에 저장되어지지 않았다. 이 경우 strxfrm은 변형된 문자열의 길이가 되어질 문자들의 수를 반환한다. 이것은 할당하기 위해 문자열의 크기를 결정하는데 유용하게 쓰인다. 만약 그 크기 가 zero라고 하는 것은 문제가 되지 않는다; 심지어 널 포인터라고 할지라도 여기의 예는 당신이 많은 비교를 수행하려고 계획할 때 strxfrm을 어떻게 사용할 수 있는지를 보여준다. 이것은 전의 예와 같은 것이지만 좀더 빠른데 왜냐하면 다른 문자열과 몇 번의 비교를 수행하던지 상관없이 단 한번의 변형을 위한 작업을 하기 때문이다. 심지어 저장공간을 할당하고 해제하기 위해 필요한 시간이 우리가 많은 문자열들을 저장하기 위한 필요한 시간보다 더 조금 필요하다. struct sorter { char *input; char *transformed; }; /* 이것은 struct sorter의 배열에 정렬하기 위해 qsort를 사용한 비교함수 이다. */ int compare_elements (struct sorter *p1, struct sorter *p2) { return strcmp (p1->transformed, p2->transformed); } /* 이것은 지역 대조열을 사용하여 문자열을 정렬하기 위한 함수의 진입지점이다. */ void sort_strings_fast (char **array, int nstrings) { struct sorter temp_array[nstrings]; int i; /* temp_array를 정한다. 각 요소는 하나의 입력 문자열과 그 변형된 문자열을 포함한다. */ for (i = 0; i < nstrings; I++) { size_t length = strlen (array[i]) * 2; temp_array[i].input = array[i]; /* array[i]를 변형하라. 첫째로 버퍼가 충분히 큰지를 시험해 보라. */ while (1) { char *transformed = (char *) xmalloc (length); if (strxfrm (transformed, array[i], length) < length) { temp_array[i].transformed = transformed; break; } free (transformed); /* 큰 버퍼로 다시 시도하라 */ length *= 2; } } /* for 의 끝 */ /* 비교할 변형된 문자열들에 의해 temp_array를 정렬한다. */ qsort (temp_array, sizeof (struct sorter), nstrings, compare_elements); /* 그들을 정렬된 순서로 영구적인 배열 안에 요소들을 저장하라. */ for (i = 0; i < nstrings; i++) array[i] = temp_array[i].input; /* 우리가 할당했던 문자열을 해제하라 */ for (i = 0; i < nstrings; i++) free (temp_array[i].transformed); }
호환성 참조 : 이 문자열 대조 함수들은 ANSI C에 새로 첨가된 것으로 오래된 C에서는 이와 동등한 작업을 하는 것은 아무 것도 없다.
5.7 탐색 함수들
이 절은 문자열과 배열들에 적용할 다양한 종류의 탐색 오퍼레이션을 수행하는 라이브러리 함수들을 설명하고 있다. 이 함수들을 헤더파일 'string.h'에 정의되어 있다.
함수: void * memchr (const void *block, int c, size_t size)
이 함수는 블록에서 object의 시작부터 지정된 몇 바이트 크기 내에서 byte c( unsigned char로 변화시킨 )가 첫 번째 나타난 곳을 찾는다. 반환 값을 바이트가 위치한 곳의 포이터이거나 만약 발견하지 못하면 널 포인터이다.
함수: char * strchr (const char *string, int c)
이 strchr 함수는 문자c 가 첫 번째 나타난 곳을 스트링의 시작부터 널 종료문자까지, 그 안에서 찾는다. 반환 값은 문자가 위치한 곳을 가리키는 포인터이거나 발견하지 못하면 널 포인터이다. 예를 들어, strchr ("hello, world", 'l') ) "llo, world" strchr ("hello, world", '?') ) NULL 위의 strchr은 'l'이 hello안에 있으므로 그 위치를 포인터로 반환하게 된다. 그래서 그 포인터를 참조하면 "llo, world" 가 나오는 것이다. 밑의 것은 '?'가 없으므로 널 포인터를 반환. 종료 널 문자는 문자열의 일부분으로 간주된다. 그래서 당신은 c 인자 값으로 널 문자를 줄 수 있고, 그러므로 당신은 문자열의 끝을 가리키는 포인터를 이 함수를 사용해서 얻을 수도 있다.
함수: char * index (const char *string, int c)
index는 strchr의 다른 이름이다; 그들은 거의 동일하다.
함수: char *strrchr (const char *string, int c)
이 함수 strrchr은 문자열의 앞에서, 앞으로 진행하며 문자열을 검색하는 대신에 문자열의 뒤에서 반대방향으로 탐색하는 것을 제외하면 strchr과 같다, 예를 들어 strrchr ("hello, world", 'l') ) "ld" 뒤에서부터 찾아서 그 위치를 포인터로 넘기니까..그 포인터를 참조하면 "ld"값이 나오겠죠.
함수: char * rindex (const char *string, int c)
rindex는 strrchr의 다른 이름이다.; 그들은 거의 동일하다.
함수: char * strstr (const char *haystack, const char *needle)
이 함수는 단일 문자보다는 긴 substring을 탐색하는 것을 제외하면 strchr과 같다. 이것은 찾고자 하는 문자열(haystack)에서 찾기를 원하는 문자열이(needle)나타난 첫 번째 문자의 위치를 포인터로 반환하고 그렇지 않으면 널 포인터를 반환한다. 만약 찾고자 하는 문자열이 공백문자열 이라면 haystack을 반환한다. 예를 들어 strstr ("hello, world", "l") ) "llo, world" strstr ("hello, world", "wo") ) "world" 밑에 strstr만 보자면 "wo"의 문자열을 "hello, world"에서 찾으니까 있기 때문에 'w'의 위치를 가리키는 포인터를 반환 합니다. 그래서 그 포인터를 참조하면 "world"가 되죠.
함수: void * memmem (const void *needle, size_t neede_len, const void *haystack, size_t haystack_lne)
이것은 strstr과 같지만 needle과 haystack은 널 종료를 가진 문자열이 아니라 byte array이다. needle_len은 needle의 길이이고 haystack_len은 haystack의 길이이다. 이 함수는 GNU확장이다.
함수: size_t strspn (const char *string, const char *skipset)
이 strspn("string span") 함수는 문자열 sring에서 skipset을 구성하는 문자들로만 이루어진 처음 substring을 찾아서 그 길이를 반환한다. skipset안에서 문자들의 순서는 중요하지 않다. 예를 들어 strspn ("hello, world", "abcdefghijklmnopqrstuvwxyz") ) 5 "hello, world"에서 뒤의 문자들로만 구성된 부문자열을 찾으면 hello까지가 되겠죠.. (,)는 뒤의 문자열에 포함되지 않았으니까... 그래서 길이가 5.
함수: size_t strcspn (const char *string, const char *stopset)
strcspn("string complement span") 함수는 stopset의 문자열을 구성하는 문자들로 구성되지 않은 string의 처음 substring의 길이를 반환한다. ( 달리 말하자면 stopset의 문자 셋의 멤버인, string안의 첫 번째 문자가 있는 offset을 반환한다. ) 예를 들어 strcspn ("hello, world", " \t\n,.;!?") ) 5 hello다음에 뒤의 문자 셋에 포함된 ','가 나왔으니까, 처음부터 뒤의 문자 셋에 포함되지 않는 길이는 즉 hello의 길이가 되는 거죠. 그래서 반환 값은 5.
함수: char * strpbrk (const char *string, const char *stopset)
strpbrk ("string pointer break") 함수는 strcspn이 처음 substring의 길이를 반환 하는 대신에 strpbrk는 stopset의 문자 셋 멤버인 string의 첫 번째 문자의 포인터를 반환 하는 것을 제외하면 strcspn과 연관 되어있다. 만일 문자를 발견하지 못하면 널 포인터를 반환한다. 예를 들어 strpbrk ("hello, world", " \t\n,.;!?") ) ", world" ','가 나타난 곳의 위치를 포인터로 반환 하므로 그 포인터를 참조하면 ", world"가 되는 것이다.
5.8 문자열에서 토큰 찾기
프로그램이 보통의 string을 토큰으로 분리하는 것처럼 어구나 어휘분석과 같은 그런 간단한 종류의 일을 하는 것은 꽤 일반적이다. 당신은 헤더파일 'string.h'에 정의된 strtok함수로 이 일을 할 수가 있다.
함수: char * strtok (char *newstring, const char *delimiters)
string은 호출된 일련의 strtok함수를 통해 토큰들로 분리되어 질 수 있다. 분리시킬 문자열을 오직 첫 번째 호출에서 newstring 인수로 인식된다. strtok함수는 내부적 상황 정보를 맞추기 위해 이것을 사용한다. 같은 문자열에서 부가적으로 토큰을 얻기 위해 다음 호출을 할 때는 newstring인수로 널 포인터를 주어서 지정시킨다. 당신이 strtok를 호출한 뒤에 어떤 라이브러리 함수도 strtok를 호출하지 않았음을 보장한다.delimiters인수는 추출될 토큰을 둘러싸고 있는 구획문자의 셋을 지정한 문자열이다.
이 문자열 셋에서 첫 번째 문자들은 버려진다. 이 구획문자 셋의 멤버가 아닌 첫 번째 문자는 다음 토큰의 시작을 표시한다. 토큰의 끝은 구획문자 셋의 멤버인 다음 문자를 찾음으로서 발견된다. newstring인수로 쓰여진 원래의 문자열 안의 이 문자는 널 문자로 다시 쓰여지고 newstring안의 토큰의 시작점을 가리키는 포인터를 반환한다. ( 제가 이해한 바에 따르면 구획문자를 찾아서 토큰을 얻으면 그 구획문자는 널 문자로 대체시켜 버린다는 말인 것 같은데... 아마 맞을걸요...? )
strtok의 다음 호출에서 다음 문자의 시작점은 전의 토큰의 끝으로 표시된 점을 하나 지난 지점이다. 구획문자의 셋들을 일련의 strtok 호출에서 모든 호출이 모두 같은 것은 아님을 기억하라. (즉, 구획문자열들은 strtok를 호출할 때마다 달라도 된다는 얘기)
만일 newstring 문자열의 끝에 도달되었거나 문자열의 나머지가 오직 구획문자로만 구성되어 있다면 strtok는 널 포인터를 반환한다.
주의: strtok는 파싱하는 문자열을 변화시킨다, 그러므로 당신은 항상 strtok로 파싱하기 전에 임시 버퍼에 문자열을 복사해 놓아야 한다. 만약 당신이 당신 프로그램의 다른 부분에서 온 문자열을 수정하도록 strtok에 허용하면 당신이 스스로 문제를 자처하는 것이다; 그 문자열은 strtok에 의해 변형되어 파싱하는 동안 그 데이터가 다른 목적으로 사용되어 질 수도 있다.
심지어 상수일지도 모르는 문자열에 당신이 명령을 내리면 strtok는 그것을 수정하려 시도할 것이고 당신의 프로그램은 결국 ROM(read only memery)에 쓰기를 시도하므로 심각한 에러신호를 얻을 것이다.
이것은 일반적인 원칙의 특별한 경우이다: 만일 프로그램의 일부분이 어떤 데이터 구조체를 수정하려는 목적을 가지고 있지 않다면 그것을 임시적으로 수정하려 할 때 에러가 발생하는 경향이 있다. strtok함수는 재진입하지 않는다.
재 진입하는 이유나 위치의 중요성에 대한 논의를 위해, 여기에 strtok의 간단한 사용 예가 있다.
#include <string.h> #include <stddef.h> . . . char string[] = "words separated by spaces -- and, punctuation!"; const char delimiters[] = " .,;:!-"; char *token; . . . /* 여기서 구획문자는 공백(" ") 과 콜론, 세미콜론, 느낌표, 대시("-") 이네요... */ token = strtok (string, delimiters); /* token => "words" */ token = strtok (NULL, delimiters); /* token => "separated" */ token = strtok (NULL, delimiters); /* token => "by" */ token = strtok (NULL, delimiters); /* token => "spaces" */ token = strtok (NULL, delimiters); /* token => "and" */ token = strtok (NULL, delimiters); /* token => "punctuation" */ token = strtok (NULL, delimiters); /* token => NULL */ /* strtok는 구획문자가 나오면 그것을 토큰으로 잘라서 반환 시키네요.... */
6 입출력 개요
대부분의 프로그램은 무언가의 유용한 작업을 하기 위하여 입력(데이터 읽기) 또는 출력(데이터 쓰기)을 필요로 하거나, 흔히는 입출력 모두를 필요로 한다. GNU C 라이브러리는 아주 많은 입출력 함수들을 제공하고 있어서 가장 적합한 함수가 엄격하게 적용될 수 있도록 하고 있다. 이 장에서는 입력과 출력에 관련된 개념과 용어를 소개한다.GNU의 입출력 도구에 관련된 다른 장은 다음과 같다.
6.1 입출력의 개념
당신이 어떤 파일의 내용을 읽거나 쓸 수 있으려면 먼저 파일에 연결 채널이나 커뮤니케이션 채널을 설치하여야 한다. 이 과정을 파일열기라고 부른다. 당신은 파일을 읽기모드, 쓰기모드 또는 읽기쓰기 모드로 열 수가 있다. 열려진 파일과의 연결은 스트림으로 나타내거나 파일지시자로 나타낸다. 당신은 이것을 실제의 읽기쓰기 처리를 수행할 함수의 인수에 전달하여, 그 함수가 어떤 파일을 처리할 것인지를 알려준다. 어떤 함수들은 스트림을 받아서 처리하며, 다른 함수들은 파일지시자를 처리하도록 만들어져 있다. 당신이 파일 읽기나 쓰기를 마쳤을 때는, 그 파일을 닫아줌으로써 연결을 끝마칠 수 있다. 당신이 스트림이나 파일지시자를 일단 닫고나면, 더이상 그 파일에 입력이나 출력을 행할 수 없다.
6.1.1 스트림과 파일지시자
당신이 어떤 파일에 입력과 출력을 하고 싶을 때 당신의 프로그램과 파일간의 연결을 나타내기 위한 두 개의 기본적인 메커니즘을 선택해야 한다: 파일지시자와 스트림이 그것이다. 파일지시자는 int 형태의 대상물로 표현되고, 반면에 스트림은 구조체 FILE의 포인터 대상물(FILE * objects)로 표현된다. 파일지시자들은 입출력 처리에 대한 일차적이고 저수준인 인터페이스를 제공한다. 파일지시자나 스트림 모두 어떤 장치(터미널과 같은)로의 연결을 나타낼 수도 있고 보통의 파일뿐만 아니라 다른 프로세스와의 커뮤니케이션을 위한 파이프나 소켓과의 연결을 나타낼 수도 있다. 그러나, 만약 당신이 특정한 종류의 장치에 국한된 통제 처리를 하고 싶다면, 반드시 파일지시자를 사용해야만 한다. 이러한 방식에서 스트림을 사용하는 도구는 없다. 블럭화되지 않은(또는 등록된?) 입력과 같이 특별한 모드로 입출력을 할 필요가 있을 때도, 당신은 반드시 파일지시를 사용하여야 한다.
스트림은 일차적인 파일지시자 도구들 위에 차곡차곡 쌓아 올려진 고수준의 인터페이스를 제공한다. 스트림 인터페이스는 아주 흡사한 모든 종류의 파일을 다루지만 당신이 선택할 수 있는 세 가지 양식의 버퍼에서만 예외가 있다.
스트림 인터페이스를 사용하는 주된 이점은 스트림을 입출력 처리하는(통제처리와 다른 것으로서)함수들의 세트가 그에 상응하는 파일지시자 사용도구들에 비해서 훨씬 풍부하고 강력하다는 점이다. 파일지시자 인터페이스는 단순히 문자 블럭을 옮겨주는 함수들만을 제공하는 데 반해, 스트림 인터페이스는 문자단위 및 행단위 입출력뿐만 아니라 강력한 형식화된 입출력(printf와 scanf) 함수들까지 제공한다.
스트림은 파일지시자에 의해 완성되므로 당신은 스트림에서 파일지시자만을 뽑아내서 직접 파일지시자만을 저수준 처리할 수 있다. 당신은 처음에 파일지시자를 써서 (파일을) 연결하였다가, 나중에 스트림을 만들어서 그 파일지시자와 일치시킬 수도 있다.
일반적으로, 파일지시자로써만 작동시키고 싶은 어떤 특별한 처리가 아니라면, 파일지시자보다는 스트림을 사용하는 습관을 갖는 게 좋다. 만약 당신이 초보 프로그래머이고 사용할 함수들을 잘 모른다면, 당신은 집중적으로 형식화된 입력과 형식화된 출력만을 사용하는 게 좋다.
만약 당신이 GNU 이외의 다른 체제에서 프로그램을 운용하려 한다면, 당신은 파일지시자가 스트림보다 쉽지 않음을 알고 있어야 한다. 당신은 ANSI C를 쓰는 어떤 체제든 스트림을 제공한다고 기대해도 좋지만, GNU가 아닌 다른 체제에서는 파일지시자가 전혀 제공되지 않으며, 파일지시자를 처리하는 GNU 함수의 부수적인 부분을 완성할 뿐이다. 그러나,GNU 라이브러리의 파일지시자 함수의 대부분은 POSIX.1 표준에 포함된다.
6.1.2 파일 위치
열려진 파일의 속성 중의 하나는 파일에서 다음 문자를 읽어오거나 써야할 장소를 추적하고 있는 파일 위치다.GNU 체제에서는 모든 POSIX.1 체제와 마찬가지로 파일 위치는 그 파일의 시작부분으로부터의 바이트 수로 나타내지는 정수일 뿐이다.
파일위치는 보통 파일이 열렸을 때 파일의 시작위치에 놓여지고, 한 문자를 읽거나 쓸 때마다 파일위치가 증가한다. 달리 말하자면, 파일에의 접근은 정상적으로는 연속적이다.
보통의 파일들은 파일내의 어떤 위치에서든 읽기와 쓰기를 허용한다. 어떤 종류의 파일들은 이것을 허용하지 않기도 한다. 이것을 허용하는 파일들은 종종 랜덤 파일이라 불린다. 당신은 스트림에서 fseek 함수를 사용하여 파일지시자에서 lseek 함수를 사용하여 파일위치를 변경할 수 있다. 당신이 랜덤파일이 아닌 파일에서 파일위치를 변경하려 하면 ESPIPE 에러를 만날 것이다.
추가모드로 열린 스트림과 파일지시자는 특별히 출력을 위한 것이다: 그러한 파일에 대한 출력은 항상 파일위치에 상관없이 그 파일의 끝 부분에 연속적으로 추가한다. 그러나, 파일위치는 여전히 파일 내에서 읽기가 수행되는 곳을 통제하고 있다.
이와 같은 점을 생각해보면, 몇 개의 프로그램들이 하나의 파일을 동시에 읽을 수 있음을 알 수 있을 것이다. 각 프로그램이 자신의 페이스대로 파일을 읽기 위해서는 각기의 파일포인터를 가져야만 하고, 그 파일포인터는 다른 프로그램이 수행하는 어떤 일에 의해서도 영향받지 않아야 한다. 사실은, 각각의 파일열기는 다른 파일위치를 만든다. 그러므로, 만약 당신이 같은 프로그램에서 하나의 파일을 두 번 열었다 하더라도, 당신은 독자적인 파일위치를 갖는 두개의 스트림 또는 파일지시자를 갖게된다.
이와는 대조적으로, 당신이 만약 지시자를 열어서 다른 지시자와 중복되게 하였다면, 이 두개의 지시자는 같은 파일위치를 공유하게 된다. 하나의 지시자에서 파일위치를 변경시키면 다른 지시자도 영향을 받는다.
6.2 파일 명칭
어떤 파일을 연결하거나 파일 삭제와 같은 다른 처리를 위해서는 그 파일을 참조할 수 있는 어떤 방법이 필요하게 된다. 거의 모든 파일은 문자열로 된 명칭을 갖는다_테이프 드라이브나 터미널과 같은 사실상의 장치조차도 그와 같다. 이 문자열들을 파일명칭이라 부른다. 당신은 파일명칭을 지정하여 당신이 열거나 처리하고자 하는 파일이 어떤 것인지를 말해줘야 한다. 이 절에서는 파일명칭에 대한 규정들을 기술하고 운영 체제가 그것들을 취급하는 방식을 기술한다.
6.2.1 디렉토리
파일 명칭의 문장을 이해하려면, 파일체제가 디렉토리 계층으로 조직되는 법을 이해할 필요가 있다. 디렉토리는 다른 파일들을 명칭과 결합시켜주는 정보를 담고 있는 파일이다. 이 결합은 링크 또는 디렉토리 엔트리라 불린다. 흔히 사람들은 "디렉토리 내의 파일"이라고 말하지만, 사실은, 디렉토리는 파일에 대한 포인터를 담고 있을 뿐이지 파일 그 자체는 아니다.
디렉토리 엔트리에 담겨진 한 파일의 명칭은 파일명칭 성분으로 불린다. 일반적으로, 한 파일의 명칭은 슬래쉬('/') 문자로 분리된 하나 또는 그 이상의 연속물로 구성된다. 단 하나의 성분으로 이루어진 파일명칭은 그 디렉토리 내의 파일을 지칭한다. 여러개의 성분으로 이루어진 파일명칭은 어떤 디렉토리와 그 디렉토리 내의 파일 등을 지칭한다. POSIX 표준과 같은 어떤 다른 문서들에서는 소위 파일 명칭 대신에 경로명칭이란 용어를 사용하며, 이 안내서에서 파일명칭 성분이라고 부르는 것 대신에 파일명칭 또는 경로명칭 성분이란 용어를 사용한다. 우리는 이 용어를 사용하지 않는다. 왜냐하면,"경로"는 완전히 다른 것(찾고자하는 디렉토리들의 목록)이고, 우리가 생각하는 바로는 다른 것을 지칭하여 "경로명칭"이라고 해버리면 사용자들을 혼동시키기 때문이다.GNU 문서들에서는 항상 "파일명칭"과 "파일명칭 성분"만을 사용한다.
6.2.2 파일 명칭 분석
파일 명칭은 슬래쉬('/') 문자로 분리된 파일 명칭 성분으로 구성된다.GNU C 라이브러리가 지원하는 체제에서는 여러개의 연속적인 '/' 문자들은 단일한 '/' 문자와 동일하다.
파일 명칭이 어느 파일을 참조할 것인가를 결정하는 처리를 파일명칭 분석이라 부른다. 이 처리는 파일 명칭을 구성하고 있는 성분을 왼편에서 오른편으로 조사하여, 각각의 연속적인 성분을 앞의 성분에 의해 지칭된 디렉토리에 배치한다. 물론, 디렉토리로 참조되는 각각의 파일들은 실제로 존재해야 하며, 정규적인 파일 대신에 디렉토리여야 하고, 뿐만 아니라 처리에 의한 접근을 허용하는 적절한 권한이 있어야 한다.
만약 어떤 파일명칭이 '/'로 시작되면 그 파일명칭의 최초의 성분은 그 처리의 루트 디렉토리에 위치한다.(언제나 체제에서의 모든 처리는 같은 루트디렉토리를 갖는다.) 그와 같은 파일명칭은 절대적 파일명칭이라 부른다.
그렇지 않을 경우, 파일명칭에서의 최초 성분이 현재의 작업 디렉토리에 위치한다. 이러한 종류의 파일명칭은 상대적 파일명칭이라 부른다.
파일명칭 성분 '.'("점")과 '..'("점-점")은 특별한 의미를 갖는다. 모든 디렉토리는 이 파일명칭 성분들을 위한 엔트리를 갖는다. 파일명칭 성분 '.'는 디렉토리 그 자체를 말하는 반면에, 파일명칭 성분 '..'은 그것의 부모 디렉토리(해당 디렉토리와의 연결을 갖는 디렉토리)를 말한다. 특별한 경우로서, 루트 디렉토리에 있는 '..'은 루트 디렉토리 그 자체를 말하는데, 왜냐하면 그것은 부모 디렉토리가 없기 때문이다; 이리하여,'/..'는 '/'과 같다. 파일명칭의 예를 보기로 하자.
'/a' '/a/b' 루트 디렉토리 내의 'a'라 불리는 디렉토리에 있는 'b'라는 파일. 'a' 현재의 작업 디렉토리에 있는 'a'라 불리는 파일. `/a/./b' 이것은 '/a/b'와 같다. './a' 현재의 작업 디렉토리에 있는 'a'라는 파일. '../a' 현재 작업 디렉토리의 부모 디렉토리 내의 'a'라는 파일.
디렉토리를 지칭하는 파일명칭이 '/'로 선택되어 끝날 수도 있다. 당신은 루트 디렉토리를 지칭하기 위하여 '/'라는 파일명칭을 규정할 수는 있지만, 공백문자는 의미 있는 파일명칭이 아니다. 만약 당신이 현재의 작업 디렉토리를 지칭하려 한다면,'.'나 '/' 라는 파일명칭을 사용하라.
다른 운영체제들과는 달리,GNU 체제는 파일명칭 문장의 일부로서 파일형태(또는 확장자)나 파일 버전 을 위한 기존의 지원자를 갖지 않는다. 많은 프로그램이나 유틸리티들은 파일명칭에 대한 규정_예를 들면, C 소스 코드를 담고 있는 파일들은 항상 '.c'를 뒤에 붙인 명칭을 가진다_을 사용하지만, 파일체제 그 자체에는 이와 같은 규정을 강제하는 것은 아무 것도 없다.
6.2.3 파일 명칭 에러
파일명칭 인수들을 받는 함수들은 항상 파일명칭 문장과 관련된 errno 에러조건을 검사한다. 이 에러들은 통상적인 파일명칭 에러가 생길 때마다 이 안내서 전체를 통해서 언급된다.
EACCES
처리가 파일명칭의 디렉토리 성분에 대한 허가권을 찾지 못하였다.
ENAMETOOLONG
파일명칭의 전체 길이가 PATH_MAX보다 더 크거나, 개별 파일명칭 성분이 NAME_MAX보다 더 큰 길이를 가질 때 이 에러가 사용된다. GNU 체제에서는, 전체 파일 길이에 대한 주어진 제한은 없다. 그러나 어떤 파일 체제에서는 성분의 길이에 대한 제한들을 두고 있을 것이다.
ENOENT
파일명칭 내에서 디렉토리 성분으로서 지칭된 파일이 존재하지 않거나, 어떤 성분이 그 대상 파일이 존재하지 않는 부호링크일 때, 이 에러가 보고된다.
ENOTDIR
파일명칭 내의 디렉토리 성분으로 지칭된 파일이 존재하지만, 그것이 디렉토리가 아니다.
ELOOP
파일명칭을 찾는 동안 너무 많은 부호링크가 분해되었다. 체제는 루프를 조사할 일차적 방법으로서의 부호링크가 하나의 파일을 찾는데서 분해될 수 있는 개수를 임의로 제한하고 있다.
6.2.4 파일명칭의 운용
6.2 [파일명칭] 에서 언급한 파일명칭 문장에 대한 규칙은 GNU 체제와 다른 POSIX 체제에서 정상적으로 사용되는 규칙들이다. 그러나 다른 운영체제에서는 다른 규정들을 사용할 것이다. 당신이 파일명칭 운용 문제를 아는데서 그것이 중요할 수밖에 없는 두 가지 이유가 있다.
만약 당신의 프로그램이 파일명칭 문장에 가정문을 넣거나 삽입된 리터럴 파일명칭 문자열을 포함한다면 그것을 다른 문장 규정을 갖는 다른 운영 체제에서 실행하려할 때 어려움이 많을 것이다.
설령 당신이 다른 운영체제로 작동하는 장치에서 당신의 프로그램을 실행하려는 의사가 없다하더라도 다른 명칭 규정을 사용하는 파일들에 접근하는 일은 여전히 있음직한 일이다. 예를 들어, 당신은 네트워크 상에서 다른 운영체제를 작동시키는 다른 어떤 컴퓨터의 파일에 접근하거나 다른 운영체제에 의해 포맷된 디스크에서 읽기 쓰기를 할 수 도 있을 것이다.
ANSI C 표준에서는 파일명칭 문장에 관한 언급이 거의 없고 다만 파일명칭이 문자열이란 설명만 있다. 운영체제가 달라짐에 따라 파일명칭의 길이에 대한 제한규정과 파일명칭에 사용될 수 있는 문자의 종류가 변화할 뿐 아니라, 구조화된 디렉토리와 파일 형태, 확장자와 같은 개념에 대한 규정과 문장이 달라진다. 파일 버전과 같은 개념들은 어떤 운영체제에서는 지원되지만 그렇지 않은 경우도 있다.
POISIX.1 표준에서는 파일명칭에서 허용되는 문자와 관련하여 파일명칭 문장에 대한 추가적인 제한을 보충 할 수 있도록 하고 있으며, 또한, 파일명칭과 파일명칭 성분 문자열의 길이에 대한 추가적인 제한도 보충할 수 있도록 하고 있다. 그러나, GNU 체제에서는 당신은 이러한 제한에 대해 염려할 필요가 없다; 널 문자를 제외한 어떠한 문자든지 파일명칭 문자열에 허용되며, 파일명칭 문자열 길이에 대한 제한도 없다.
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 시스템에서 당신이 정한 어떤 파일이나 프로세스도 쉘에서 제공하는 파이프나 리다이렉션을 사용해서 이 스트림들과(표준 입/출력, 에러 스트림) 통할 수 있다. 대부분의 다른 운영체제는 이와 비슷한 메커니즘을 제공하지만 이들을 어떻게 사용하는 지에 대한 세밀한 부분에서는 많은 차이가 있을 수 있다. 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함수가 호출된다면 모든 열려진 스트림 자동적으로 닫혀진다. 그러나 프로그램이 정상적이 아닌 다른 방법으로, 죽 abort함수를 호출하거나 어떤 심각한 신호가 있어서 프로그램이 종료된다면 열려진 스트림들은 완전히 닫혀지지 않을 것이다. 버퍼 안에 있는 내용들도 비워지지 않고, 파일들은 불완전해질 것이다. 좀더 많은 스트림의 버퍼화에 대한 정보를 보려면 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안의 크기가 증가된다. 만일 당신이 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 템플리트 문자열에서 표현될 수 있는 변환 지정의 상세한 문장에 관한 세부사항을 제공한다. 변환자 지정의 일부분이 아닌 템플리트 문자열 내의 문자들은 출력 스트림에 그대로 프린트된다. 수바이트의 문자 연속물들이 템플리트 문자열 내에서 허용된다.
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함수와 '%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내의 에러코드에 해당하는 문자열을 프린트한다. 즉, 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' 변환자의 통제하에서 프린트되는 인수로서도 주어진다면__정의되지 않는다. 경고: 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을 사용한다. ) 여기서 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 함수와 유사하다. 현재 object의 끝에 쓰여있는 문자들, 그들을 얻기 위하여, 당신은 obstack_finish로 object를 끝내야만한다.
7. 9. 9 다양한 인수들을 출력하는 함수들
vprintf 함수와 그 유사한 함수들은 printf 함수를 가지고 형식화된 출력을 할 수 있도록 내부적으로 사용되는 다양한 형식을 당신 자신의 것으로 정의할 수 있도록 해준다.
이런 함수들을 정의하는 가장 좋은 방법은 언어를 사용하여 "printf 함수를 호출하고, 첫 번째 다섯 개 후의 인수모두를 더한 템플릿을 넘겨라" 라고 말하는 것이다. 그러나 C로는 이렇게 할 수 있는 방법이 없다, 그리고 제공되는 방법은 매우 사용하기 어렵다. 그리고 C언어 수준에서는 당신의 함수에 많은 인수들이 어떻게 받아들여진건지 알 수 있는 방법이 없다.
그 방법이 불가능했기 때문에, 우리는 "첫 번째 다섯후의 모든 인수들"도 표현하여 va_list로 넘기도록 한 vprintf 시리즈인 선택함수를 제공한다.
vprintf나 이 절에도 보여주고 있는 다른 함수들은 호출하기 전에 당신은 먼저 va_start를 호출하여 다양한 인수들로 포인터를 초기화해줘야만 한다. 그리고나서 당신은 당신이 다루기 원하는 인수들을 가져오기 위해 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에 프로그램의 이름을 지적하는 접두사와 함께, 에러메시지를 프린트하는 함수이다.
( #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 변수를 갱신하기 위한 자신의 핸들러 함수를 호출하는 함수에 허용된다. )
당신의 핸들러 함수는 단지 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 함수에 의해 정의된 것들. )는 입력 스트림내에서 몇 개의 공백문자가 읽히거나 포기되게 하는 현상을 야기한다. 일치하는 공백문자들이 반드시 템플리트에서 나타나는 공백문자들과 꼭 같을 필요는 없다. 예를 들어, 콤마를 인식시키기 위해서 앞뒤에 선택적인 공백을 추가하여 템플리트 내에 ' , '라고 쓰는 경우. 템플리트 내에서 변환자 지정의 일부분이 아닌 다른 문자들은 입력스트림내의 문자들과 정확하게 일치한다; 만약 이러하지 못하다면, 일치 실패가 일어난다.
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 함수에 대한 문장과 같다. '%i'변환자는 C언어가 정수 상수용으로 정의하는 어떤 형식에서나 선택적 부호 정수를 일치시킨다. 인식되는 문장은 기초인수의 값으로서 0을 갖고있는 strtol 함수에 대한 문장과 같다. (당신은 이 문장상의 정수들을 `%x', `%o'나 `%d' 변환자를 갖는 '#'플래그 문자를 사용하여 printf로 프린트할 수 있다. 7.9.4 [정수 변환자] 참조. ) 예를 들어, '10', '0xa'나 '012'중의 어떤 문자들이든 '%i' 변환자가 관장하는 정수로 읽혀질 수 있다. 이들 각각은 십진 값 10을 갖고서 어떤 수를 지정한다.
'%o', '%u'와 '%x'
변환자는 각기 8진, 10진, 16진무부호 정수를 일치시킨다. 인식되는 문장은 기초인수의 값으로서 적절한 값(8, 10이나16)을 갖고있는 strtol 함수에 대한 문장과 같다.
'%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 함수에 대한 동일한 문장에서 선택적 부호 부동소수점수를 일치시킨다. 부동소수점 입력 변환자에 대해, 내정 인수 형태는 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 ), 그리고 디바이스들의 정해진 부류로 취급되어지는 것과 같은 것이 아닌 파일이나 디바이스들과, 스트림 사이에 문자들이 송신되어질 때 제어하는 것을 취급하고 있다. 당신은 파일기술자에서 동작하는 저수준 입력과 출력 함수들을 사용하면 스트림 버퍼링 도구들은 무시할 수 있다.
7. 17. 1 버퍼링 개념들
버퍼링 방법에 대한 세 가지 종류.
- 비버퍼화된 스트림으로부터 읽거나 쓸 문자들은 가능한 한 빨리 파일로 또는, 파일로부터 개별적으로 전송되어진다.
- 라인 버퍼 스트림으로 부터 읽거나 쓸 문자들은 새줄 문자를 만날 때 블록단위로 파일로, 또는 파일에서 전송되어진다.
- 완전 버퍼 스트림으로부터 읽거나 쓸 문자들은 다양한 크기의 블록 형태로 파일로, 또는 파일로부터 전송되어진다.
새로이 개방된 스트림들은 한가지를 제외하고는, 일반적으로 완전한 버퍼이다; 터미널과 같은 대화식 디바이스와 연결된 스트림은 처음에 라인 버퍼로 되어진다.
대화식(interactive) 디바이스들을 위한 라인 버퍼의 사용에서 새로운 줄로( newline ) 끝나는 출력 메시지는 보통은 즉각적으로 출력될 것이고 이것은 보통 당신이 원하는 것이다. 새로운 줄로 끝나지 않는 출력은 즉시 출력되지 않을지도 모르는데, 그래서 만일 당신이 그들이 즉각적으로 출력되길 원한다면, 당신은 fflush를 사용해서 명시적으로 버퍼된 출력을 나오게 해야한다.
라인 버퍼링은 터미널 상에서 입력을 받기에 좋은데, 그 이유는 대부분의 대화식(interactive) 프로그램들이 보통 단일한 라인으로 된 명령을 읽기 때문이다. 프로그램은 즉시 각 라인을 실행할 수 있을 것이다. 라인 버퍼 스트림은 이것을 허용하지만, 완전한 버퍼 스트림은 그것을 읽도록 프로그램에 허용되기 전에 버퍼를 채워 항상 충분한 텍스트를 읽는다. 라인 버퍼링은 대부분의 운영체제에서 라인 단위의 입력에서 작동하는 보통의 입력-편집기로 알맞다.
어떤 프로그램들은 비버퍼화된 터미널 입력 스트림이 필요하다. 이 프로그램들은 단일-문자 명령들을 읽는 프로그램과 (Emacs와 같은)그들 자신이 입력을 편집하는 프로그램들( readline을 사용하는 것들처럼)이다. 한 번에 한 문자를 읽기 위해서, 입력 스트림의 버퍼링을 멈추게 하는 것만으로 충분하지 않고, 운영체제가 행하는 입력 편집의 작용도 멈추게 해야한다. 이것은 터미널 모드를 바꾸어야 한다. 만일 당신이 터미널 모드를 바꾸기 원한다면, 당신은 개별적으로-단지 모드들을 변화하지 않는 비버퍼화된 스트림을 사용하여 이것을 해야만 한다.
7. 17. 2 버퍼를 쏟아내기
역자주: 이곳에서 쏟아내기란 단어는 버퍼에 있는 내용을 강제적(?)으로 파일로 전송시키고 버퍼를 비우는 그런 의미를 내포하고 있다.
버퍼된 스트림 상에서 출력쏟아내기(Flushing)는 버퍼에 모아진 모든 문자들을 파일로 전송하는 것을 의미한다. 다음은 스트림 상에서 버퍼화된 출력이 자동적으로 쏟아질 때의 많은 상황이다.
- 당신이 출력을 하려 시도하고, 출력 버퍼는 가득 차있을 때.
- 스트림이 닫혀있을 때. 7. 4절 [Closing Streams] 참조.
- 프로그램이 exit를 호출하여 종료될 때.
- 만일 스트림이 라인 버퍼라면, 새줄기호를 만났을 때
파일로부터 데이터를 실제적으로 읽는 어느 스트림 상에서 입력 명령이 있을 때이다. 만일 당신이 다른 때(역자주 : 위의 상황이 아닌경우) 버퍼된 출력을 쏟아내길 원한다면, 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을 사용해야 한다. 자동 배열을 사용하는 것은 배열을 선언한 블록이 존재하기도 전에 그 파일이 닫힐 수도 있으므로 좋은 생각이 아니다. 배열이 스트림 버퍼에 남아있는 동안 스트림 입/출력 함수는 그들의 내부의 목적들로 버퍼를 사용할 것이다. 당신은 스트림이 버퍼링을 위해 사용되고 있는 동안에는 직접적으로 그 배열의 값을 억세스하려 시도할 수 없다. 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 필드에서 발견되어진다. 때때로 사람들은 또한 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을 사용해서). 이것은 버퍼에 어떤 것을 쓰고나서 다시 뒤로 가서 그들을 읽으려할 때 유용한 함수다, 왜냐하면 당신은 버퍼로부터 포인터를 얻을 수 있는 아무런 방법을 가지고 있지 않기 때문이다. (이것을 위해, 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을 사용해서. 필요에 따라 늘린다. 스트림이 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에 데이터를 넣는 출력 스트림을 개방할수 있다.
함수 : FILE * open__obstack__stream (struct obstack *obstack)
이 함수는 obstack에 데이터를 쓰기 위하여 스트림을 개방한다. 이것은 obstack안에서 object를 시작하고, 쓰여진 데이터에 따라 늘려서 만든다. 이 스트림 상에서 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를 호출함으로써 바이트 단위로 그 길이를 얻을 수 있거나 이와 같이 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
주문 스트림을 위한 종료 함수의 데이터 타입
8 저수준 입/출력
이 장은 파일 기술자 상에서 저수준의 입/출력 명령을 행하는 함수들에 대해서 설명하고 있다. 이 함수들은 스트림과는 다른 것들을 위한 저수준의 제어 명령들을 수행하기 위한 함수로써, 7장 [I/O on Streams]안에 설명된 고 수준의 입/출력 함수들에 기본적으로 포함되어 있다.
스트림-수준 입/출력은 더 유연하고, 보통 더 편리하다; 그래서, 프로그래머들은 일반적으로 필요할 때만 기술자-수준 함수들을 사용한다. 이것에는 몇 가지 이유가 있다.
- 큰 덩어리에서 바이너리 파일들을 읽기 위해
- 파싱하기 전에 코어 안에서 전체의 파일을 읽기 위해
- 오직 기술자를 가지고 수행되어지는, 데이터 참조와는 다른 명령을 수행하기 위해 (당신은 스트림과 일치하는 기술자를 얻기 위해 파일번호를 사용할 수 있다. )
- 자식 프로세스에 기술자를 넘겨주기 위해. ( 자식은 상속받은 기술자를 사용하기 위해 자신의 스트림을 만들 수 있다, 하지만 직접적으로 스트림을 상속받을 수는 없다. )
8. 1 파일 열고 닫기
이 절은 파일 기술자를 사용하여 파일들을 열고 닫는 원시적인 것들을 설명하고 있다. open 과 creat 함수들은 헤더파일 'fnctl. h'에 선언되어 있고, close는 'unistd. h'에 선언되어 있다.
함수 : int open (const char *filename, int flags[, mode_t mode])
open 함수는 filename으로 이름지어진 파일을 위한 새로운 파일 기술자를 만들고 반환한다. 처음에, 파일을 위한 파일 위치 지시자는 파일의 시작점이다. mode 인수는 오직 파일을 만들 때 사용되어지지만, 어떤 경우에도 인수로 공급하기 위한 탈이 없다. flags 인수는 파일을 어떤 방식으로 개방할 것인지를 제어한다. 이것은 비트 마스크(bit mask) 이다; 당신은 적당한 인수를 주어 OR연산을 사용해서 값을 만든다. ( C에서는 '|' 연산자를 사용해서 ) 파일 검색 모드를 정하기 위해서 flags인수는 이들 값 중에서 하나를 반드시 포함시켜야만 한다.
- O_RDONLY 읽기 전용으로 파일을 개방
- O_WRONLY 쓰기 전용으로 파일을 개방
- O_RDWR 읽기와 쓰기가 둘다 가능한 개방
flags인수는 아래 값들과 조합을 통해서 포함할 수 있다.
O_APPEND
이것이 설정되면, 모든 쓰기 명령들은 지금 현재의 파일 위치에 개의치 않고, 파일의 끝에 데이터를 쓴다.
O_CREAT
파일이 이미 존재하지 않으면 새롭게 만들어질 것이다.
O_EXCL
만일 O_CREAT 와 O_EXCL 이 설정되면, 파일이 이미 존재할 때 open명령은 실패한다.
O_NOCTTY
만일 filename이 터미널 디바이스 이름이면, 프로세스를 위하여 제어하는 터미널이 만들어지지 않는다.
O_NONBLOCK
이것은 비 블록화 모드로 설정한다. 이 옵션은 보통 FIFO와 같은 특별한 파일들과 터미널과 같은 디바이스에 사용된다. 보통, 이들 파일들을 위하여 파일이 "준비"상태일 동안 블록을 개방한다. 만일 O_NONBLOCK이 설정되면, open은 즉시 반환한다. O_NONBLOCK 비트는 또한 읽기와 쓰기에 영향을 미친다: 만약 그곳에 읽기에 유용한 입력이 없거나, 출력이 쓰여질 수 없다면, 그러한 실패의 상황을 즉각적으로 그들에게 반환하는 것을 허용한다.
O_TRUNC
만일 파일이 존재하고 쓰기 모드로 개방이 되어 있다면, 0의 길이로 그것을 자른다. 이 옵션은 디렉토리나, FIFO들과 같은 특별한 파일들이 아니라 일반적인 파일들에게 유용하다.
이 심볼 상수들에 대한 더 많은 정보는 8.10절 [File Status Flags] 을 참조하라.
open으로 부터 보통 반환되는 값은 음이 아닌 정수 파일 기술자이다. 에러가 발생한 경우에는 대신에 -1의 값을 반환한다.
보통의 파일이름 문법에러에 더하여(6.2.3절 [File Name Errors] 참조), 다음은 이 함수에서 정의하고 있는 errno와 에러상황이다.
EACCES
파일은 존재하지만, flags 인수에 의한 요청으로 읽기/쓰기가 불가능하다.
EEXIST
O_CREAT 와 O_EXCL이 모두 설정되고, 그 파일은 이미 존재한다.
EINTR
open명령은 시그널 (signal) 에 의해 인터럽트 되어졌다.
EISDIR
flags 인수는 쓰기 모드로 지정되어 있고, 파일은 디렉토리이다.
EMFILE
프로세스가 너무 많은 파일을 개방했다.
ENFILE
전제의 시스템이나, 아마도 디렉토리를 포함한 파일시스템이 어떤 순간에 더 이상 파일의 개방을 지원하지 않는다. (이 문제는 GNU시스템에서는 발생하지 않는다. )
ENOENT
같은 이름의 파일이 존재하지 않는데, O_CREAT 플래그도 선언되지 않았다.
ENOSPC
새로운 파일을 넣어야 하는 디렉토리나 파일 시스템이 확장되어질 수 없다. 왜냐하면 그곳에는 남아있는 공간이 없기 때문이다.
ENXIO
O_NONBLOCK 와 O_WRONLY가 둘다 설정되고 filename이름을 가진 파일이 FIFO이고, 어떤 프로세스도 읽기 위해서 파일을 개방하지 않았을 때.
EROFS
파일이 오직 읽기 모드인 파일 시스템 상에 존재하고 flags 인수로 O_WRONLY, O_RDWR, O_CREAT, O_TRUNC 의 어떤 것이라도 설정되면.
이 open 함수는 스트림을 만드는 fopen과 freopen함수들을 위해 원시적인 기초가 되어진다.
시대에 뒤떨어진 함수 : int creat (const char *filename, mode_t mode)
이 함수는 시대에 뒤떨어져 있다. creat(filename, mode) 이렇게 호출하는 것은 open (filename, O_WRONLY | O_CREAT | O_TRUNC, mode) 이렇게 호출하는 것과 동등하다.
함수 : int close (int filedes)
close함수는 파일 기술자 filedes를 폐쇄한다. 파일 닫기는 다음의 결과를 갖는다. 파일 기술자가 해제되어진다. 파일에서 프로세스에 의해 소유된 어느 잠긴(lock) 레코드는 잠금해제 된다. pipe나 FIFO와 연관된 모든 파일 기술자가 닫혀질 때, unread 데이터는 버려진다. close로부터 반환되는 일반적인 값은 0이다. 실패의 경우에 -1이 반환된다. 다음의 errno는 이 함수에서 정의되어진 에러상황이다.
EBADF
filedes 인수가 유용한 파일 기술자가 아니다.
EINTR
close호출이 시그널에 의해 인터럽트 되었다. 이것은 EINTR을 어떻게 취급하는지에대한 적당한 예이다;
TEMP_FAILURE_RETRY (close (desc));
스트림을 닫기 위해서는, close로 파일 기술자를 닫으려 시도하는 대신에 fclose( 7. 4절 [Closing Stream] 참조)를 호출하라. 이것은 버퍼된 출력을 플러쉬하고, 닫혔음을 지적하기 위해 스트림 오브젝트를 갱신한다.
8. 2 기본 입력과 출력
이 절은 파일 기술자상의 기본 입력과 출력 명령을 수행하기 위한 함수들을 설명하고 있다: read, write, 그리고 lseek. 이들 함수들은 헤더파일 'unistd. h'에 선언되어 있다.
데이터 타입 : ssize__t
이 데이터 타입은 단일한 명령으로 읽혀지거나 쓰여질 수 있는 블록의 크기를 나타내기 위해 사용되어진다. 이것은 size_t 와 유사하지만 반드시 부호화된 타입이어야 한다.
함수 : ssize_t read (int filedes, void *buffer, size_t size)
read함수는 기술자 filedes의 파일로부터 size 바이트를 읽고, 그 결과를 버퍼에 저장한다. ( 이것은 문자 스트링이 필요하지 않고 그곳에는 부가된 널 종료문자가 없다. ) 반환 값은 실제로 읽은 바이트의 수이다. 이것은 size보다 적을수도 있다; 예를 들어, 만일 파일에 남겨진 바이트의 수가 적거나 즉시 유용한 바이트의 수가 적은 경우 등이 있다. 정확한 동작은 파일의 종류가 무엇인지에 따라 의존한다. size 바이트보다 덜 읽는 것은 에러가 아님을 기억하라. 0의 값은 파일의 끝을 지적한다. ( 만일 size 인수의 값이 0인 경우를 제외하고. . ) 이것은 에러로 간주하지 않는다. 만일 당신이 파일의 끝인 상태에서 read를 호출하면, 그것은 0을 반환하는 것 외에 아무 일도 하지 않는다. 만일 read가 적어도 한 문자를 반환한다면, 당신이 파일의 끝에 도달했는지를 알 수 있는 아무런 방법이 없다. 그러나 만일 당신이 끝에 도달해 있었다면 다음 read의 호출은 0을 반환해서 파일의 끝임을 지적해줄 것이다. 에러가 발생한 경우에, read는 -1을 반환한다.
다음의 errno는 이 함수에서 정의된 에러의 상황이다.
EAGAIN
일반적으로, 즉시 유용한 입력이 없을 때, read는 입력을 기다린다. 그러나 만일 그 파일에서 O_NONBLOCK가 설정되면( 8.10절 [File Status Flags] 참조), read는 아무런 데이터도 기다리지 않고 즉시 반환하고, 이 에러를 보고한다. 호환성 노트 : BSD Unix의 대부분의 버전은 이것을 위한 다른 에러코드를 사용한다: EWOULDBLOCK. GNU 라이브러리에서는, EWOULDBLOCK은 EAGAIN의 다른 이름이다. 그래서 당신이 어떤 이름을 사용해도 문제가 발생되지 않는다. 어떤 시스템들은, 특별한 문자 파일로부터 데이터의 큰 덩어리를 읽으려 할 때, 만일 커널(kernal)이 당신의 것을 담을 수 있는(to lock down the user's pages), 충분한 물리적 메모리를 얻을 수 없는 경우에 EAGAIN의 에러를 내고 실패했음을 지적한다. 디바이스가 사용자의 메모리 영역을 직접적으로 억세스 하는 것이 제한되어 있는 것은 그들은 커널내부의 분리된 버퍼를 사용하기 때문이다. 그것에 터미널들은 포함되지 않는다,
EBADF
filedes 인수에 주어진 것이 유용한 파일 기술자가 아니다.
EINTR
read가 입력을 기다리고 있는 동안 시그널에 의해 인터럽트 되어졌다.
EIO
많은 디바이스들, 그리고 디스크 파일들을 위하여, 이 에러는 하드웨어 에러를 지적한다. EIO는 또한 제어 중인 터미널로부터 배경 프로세스가 읽기를 시도하고, SIGTTIN의 신호가 아무런 동작도 하지 않고 보내짐에 의해 멈춘 프로세스의 일반적 동작에 대해 발생한다. 이것은 만약 신호가 블록되어지거나 무시되거나, 프로세스 그룹이 부모 프로세스를 잃어버렸다면 발생되어질 것이다. 역자주 : blocked--> 블록된 이라고 이곳에서 해석하였다. 좀더 자세히 설명하자면 블록이란 보통 하나의 입/출력 단위로 표현되는 것이 일반적이지만 이곳에서 쓰인 것의 의미는 아마도(?) 유닉스처럼 다중 프로그래밍 시스템에서 하나의 프로세서가 자원을 획득하지 못하여 아무런 작업도 수행할 수 없는 상태에 처한 것. 이러한 상태를 블록된 상태라고 하는 것 같다. ( 크. . . 자신할 수 없어서 죄송. . 하지만 거의 맞을 듯~)
read 함수는 fgetc처럼 스트림으로부터 읽는 동작을 하는 모든 함수들에 기본적으로 포함되어 있다.
함수 : ssize_t write (int filedes, const void *buffer, size_t size)
- write함수는 기술자 filedes 파일에 버퍼에 있는 size 바이트의 데이터를 쓰는 함수이다. 버퍼에 있는 데이터는 문자 스트링과 널 문자가 필요하지 않다. 반환 값은 실제로 쓰여진 바이트들의 개수이다. 이것은 보통은 size와 같지만, 더 적을수도 있다( 예를 들어, 만일 물리적 매체가 채워져 있는 경우 ). 에러가 발생하면 write는 -1을 반환한다.
다음의 errno는 이 함수에서 정의한 에러상황이다.
EAGAIN
- 일반적으로 write 명령하에서 블록 쓰기 동작은 완벽하다. 그러나 만일 그 파일에서 O_NONBLOCK 플래그가 설정되어 있다면, 그것은 어떤 데이터도 쓰지 않고 곧바로 반환하고, 에러를 발생한다. 그 상황에 대한 하나의 예는 프로세스가 출력하려는 블록을 STOP 문자를 받아들임으로 인해 출력이 일시 중단되고, 흐름제어를 지원하는 터미널 디바이스에 쓰기를 시도할 때 발생한다. 호환성 노트 : BSD Unix의 대부분의 버전은 이것을 위한 다른 에러코드를 사용한다: EWOULDBLOCK. GNU 라이브러리에서는, EWOULDBLOCK은 EAGAIN의 다른 이름이다. 그래서 당신이 어떤 이름을 사용해도 문제가 발생되지 않는다. 어떤 시스템들은, 특별한 문자 파일로부터 데이터의 큰 덩어리를 쓰려 할 때, 만일 커널(kernal)이 당신의 것을 담을 수 있는( to lock down the user's pages ), 충분한 물리적 메모리를 얻을 수 없는 경우에 EAGAIN의 에러를 내고 실패했음을 지적한다. 디바이스가 사용자의 메모리 영역을 직접적으로 억세스 하는 것이 제한되어 있는 것은 그들은 커널내부의 분리된 버퍼를 사용하기 때문이다. 그것에 터미널들은 포함되지 않는다,
EBADF
- filedes 인수는 유용한 파일 기술자가 아니다.
EFBIG
- 파일의 크기가 그 실행에서 지원할 수 있는 것보다 크다.
EINTR
- write 오퍼레이션은 명령이 완전히 수행될 때까지 기다리는 동안 신호에 의해 인터럽트 되어졌다.
EIO
- 많은 디바이스들, 그리고 디스크 파일들을 위하여, 이 에러는 하드웨어 에러를 지적한다. EIO는 또한 제어 중인 터미널로부터 배경 프로세스가 읽기를 시도하고, SIGTTIN의 신호가 아무런 동작도 하지 않고 보내짐에 의해 멈춘 프로세스의 일반적 동작에 대해 발생한다. 이것은 만약 신호가 블록되어지거나 무시되거나, 프로세스 그룹이 부모 프로세스를 잃어버렸다면, 발생되어질 것이다.
ENOSPC
- 디바이스가 차 있다.
EPIPE
- 이 에러는 어느 프로세스에 의해서 읽기 위해 개방되지 않는 파이프나 FIFO에 쓰려 시도할 때 반환된다. 이것이 발생될 때, SIGPIPE 신호를 프로세스에 보낸다;
당신이 EINTR 실패를 방지하기 위해 조정하지 않았다면, 당신은 실패한 write의 호출에 대해서 errno를 체크해야할 것이다. 그리고 만일 errno가 EINTR 이라면, 그냥 간단하게 다시 호출해주면 된다. 이것을 하는 쉬운 방법으로 매크로 TEMP_FAILURE_RETRY 가 있다. 다음처럼:
nbytes = TEMP_FAILURE_RETRY (write (desc, buffer, ount));
write 함수는 fputc처럼 스트림에 쓰는 모든 함수들에 기본적으로 포함되어 있다.
8. 3 기술자의 파일 위치 설정하기
당신이 fseek로 스트림의 파일 위치를 설정할 수 있는 것처럼, 당신은 lseek를 통해서 기술자의 파일 위치를 설정할 수 있다. 이것은 다음에 읽거나 쓸 명령을 위해 파일의 위치를 정한다. 파일의 위치와 그것이 어떤 의미를 갖는지에 대한 정보를 7. 15절 [File Position] 을 참조하라. 기술자로부터 현재 파일 위치의 값을 읽으려면, lseek(desc, 0, SEEK_CUR)을 사용하라.
함수 : off_t lseek (int filedes, off_t offset, int whence)
lseek 함수는 기술자 filedes 파일의 파일 위치를 변경하기 위해 사용된다. whence 인수는 fseek와 같은 방법으로 사용되어 offset을 어떻게 해석되어야 하는지를 정하고, 심볼 상수 SEEK_SET, SEEK_CUR, SEEK_END중에 하나가 될 수 있다.
SEEK_SET 이것은 파일의 시작점을 기준으로 문자들의 수를 셈한다.
SEEK_CUR 현재의 파일 위치를 기준으로 문자들의 수를 셈한다. 여기서 문자들의 수(count)는 양이나 음의 값이 되어진다.
SEEK_END 파일의 끝을 기준으로 문자들의 수를 셈한다. 음의 값은 현재의 파일안의 영역으로 파일의 위치를 정한다; 양의 값은 현재보다 앞의 영역으로 파일 위치를 정한다. 파일 위치를 앞의 영역으로 정하고, 실제로 데이터를 쓰면, 원래의 파일의 끝점에서 현재의 파일위치 사이의 빈 공간은 0으로 채워지고, 결국 파일은 확장되는 것이다.
lseek를 통한 반환 값은 보통 파일의 시작점부터 바이트의 수를 계산한 파일의 위치이다. 당신은 현재의 파일 위치를 읽기 위해 SEEK_CUR와 함께 lseek를 사용할 수 있다. 또한 파일의 현재의 끝점을 넘어선 곳으로 파일의 위치를 설정할 수 있는데, 이것은 스스로 파일의 길이를 길게 만드는 것이 아니다; lseek는 결코 파일을 변화시키지 않는다. 그러나 그 위치에서 그후에 일어나는 출력은 파일의 크기를 확장할 것이다.
역자주 : 즉. . . 파일의 끝을 넘어선 곳으로 파일의 위치를 설정하고 다음에 그 위치에서 그 파일에 무언가를 쓴다면 원래의 파일의 끝점과 금방 파일에 쓴 데이터 사이의 빈 공간은 0으로 채워지고 파일은 확장된 결과에 이른다. . . 그말인 듯.
만일 파일의 위치를 변경할 수 없거나, 그 명령이 유용하지 못한 상황이라면, lseek는 -1의 값을 반환한다.
다음의 errno는 이 함수에서 정의한 에러의 상황이다.
EBADF
- filedes는 유용한 파일 기술자가 아니다.
EINVAL
- whence인수가 유용하지 않거나, 결과로 얻은 파일 offset이 유용하지 않은 것이다.
ESPIPE
- filedes가 위치를 설정할 수 없는 파이프나, FIFO에 해당한다. ( 파일의 위치를 설정할 수 없는 다른 파일의 종류도 있겠지만, 그것들의 경우는 정해져 있지 않다. )
lseek 함수는 파일 기술자 대신에 스트림에서 명령을 수행하는 fseek, ftell과 rewind함수를 위해 기본적으로 포함되어져 있다.
당신이 만일 여러 번 같은 파일을 개방하거나, dup을 통해서 기술자를 중복시킨다면 같은 파일에 다중의 기술자를 가질 수 있다. 분리된 open의 호출을 통해서 얻은 기술자는 독립적인 파일 위치를 가진다; 한 기술자에 lseek를 사용하는 것은 다른 것에 아무런 영향이 없다.
예를 들어,
{ int d1, d2; char buf[4]; d1 = open ("foo", O_RDONLY); d2 = open ("foo", O_RDONLY); lseek (d1, 1024, SEEK_SET); read (d2, buf, 4); }
이것은 'foo'파일의 처음 네 개의 문자들을 읽을 것이다. ( 실제의 프로그램에서는 필요한 에러 검색 코드가 여기서는 간결함을 이유로 생략되어져 있다. )
비교해서, dup로 만들어진 중복된 기술자는 보통의 파일 위치도 중복해서 사용한다. 중복된 파일 기술자중 하나가 읽거나 쓰는 동작이 포함된, 파일 위치를 변경하는 어느 동작을 하면, 그들 모두에게 영향이 미친다.
그래서 예를 들면,
{ int d1, d2, d3; char buf1[4], buf2[4]; d1 = open ("foo", O_RDONLY); d2 = dup (d1); d3 = dup (d2); lseek (d3, 1024, SEEK_SET); read (d1, buf1, 4); read (d2, buf2, 4); }
이것은 파일 'foo'의 1024번째 문자를 시작으로 해서 네 개의 문자를 읽고, 1028번째 문자를 시작으로 다시 네 개의 문자를 읽는다.
데이터 타입 : off__t
이것은 파일 위치를 나타내기 위한 수치적 데이터 타입니다. GNU시스템에서는 이것은 fpos_t나 long int와 동일하다.
다음 'SEEK_. . . '를 위한 세 개는 오래된 버전의 BSD 시스템들과의 호환을 목적으로 존재한다. 그들은 두 개의 다른 헤더파일에 정의되어 있다: 'fnctl. h'와 'sys'file. h'
L_SET : SEEK_SET의 다른 이름.
L_INCR : SEEK_CUR의 다른 이름.
L_XTND : SEEK_END의 다른 이름.
8. 4 기술자와 스트림
open을 통해 주어진 파일 기술자에, 당신은 fdopen함수를 가지고 그 기술자를 위한 스트림을 만들 수 있다. 당신은 fileno함수를 가지고 현존하는 스트림의 파일 기술자를 얻을 수 있다. 이 함수들은 헤더파일 'stdio. h'에 선언되어 있다.
함수 : FILE * fdopen (int filedes, const char *opentype)
fdopen함수는 파일 기술자 filedes를 위한 새로운 스트림을 반환한다. opentype 인수는 'b'옵션이 허용되지 않는 것을 제외하고는, fopen함수 와 같은 방법으로 사용된다( 7. 3절 [Opening Streams] 참조). 'b' 옵션이 허용되지 않는 것은 GNU 시스템이 텍스트와 바이너리 파일의 차이를 구분하지 않기 때문이다. 또한 "w"와 "w+"가 파일을 잘라내는 효과를 내지못한다; 그것은 그 옵션들이 파일을 개방할 때만 오직 영향을 미치는데, 이 경우에는 파일들이 이미 개방되었던 것이기 때문이다. 당신은 opentype인수를 개방한 파일 기술자의 실제 모드와 일치시켜야만 한다. 반환 값은 새로운 스트림이다. 만일 스트림이 만들어지지 못한다면, ( 예를 들어, 파일 기술자에 의해 정해진 파일의 모드가 opentype 인수에서 지정한 억세스를 허용하지 않을 때 ), 널 포인터를 반환한다.
함수 : int fileno (FILE *stream)
이 함수는 스트림 stream과 연관된 파일 기술자를 반환한다. 만일 에러가 검출되거나( 예를 들어, 만일 그 스트림이 유용하지 않다면 ), 만일 스트림이 파일에 입/출력을 할 수 없다면, fileno는 -1을 반환한다. 표준 스트림 stdin, stdout 과 stderr에 속한 파일 기술자를 위한 심볼 상수가 'unistd. h'에 정의되어 있다. ; 7. 2절 [Standare Streams] 참조.
STDIN_FILENO
이 매크로는 표준 입력을 위한 파일 기술자로 0의 값을 가진다.
STDOUT_FILENO
이 매크로는 표준 출력을 위한 파일 기술자로 1의 값을 가진다.
STDERR_FILENO
이 매크로는 표준 에러출력을 위한 파일 기술자로 2의 값을 가진다.
8. 5 스트림과 기술자 혼용의 위험
당신은 동일한 파일과 연결된 다중의 파일 기술자와 스트림을 가질 수 있다( 짧게 스트림과 기술자를 "채널" 이라 부르자), 그러나 당신은 채널들 사이의 혼란을 피하도록 주의해야만 한다. 고려해야 하는 두 가지 경우가 있다: 단일한 파일 위치를 갖고 있는 연결된 채널과, 그들 자신의 파일 위치를 갖고 있는 독립적 채널
모든 억세스가 입력을 위한것임을 제외하고는, 어느 주어진 파일에서 실제의 데이터를 참조하기 위해서는 당신의 프로그램 안에 오직 하나의 채널을 사용하는 것이 좋다. 예를 들어 만일 당신이 파이프를 개방하여, ( 당신이 파일 기술자 수준에서 할 수 있는 어떤 것 ), 기술자를 가지고 입/출력을 하거나, 또는 fdopen으로 기술자로부터 스트림을 구성하고 스트림에 모든 입/출력을 한다면.
8. 5. 1 연결된 채널들
단 한 번 개방된 것으로부터 온 채널들은 동일한 파일 위치를 점유한다; 우리는 그들을 연결된 채널이라 부른다. 연결된 채널은, 당신이 fdopen을 사용하여 기술자로부터 스트림을 만들 때, fileno를 사용해서 스트림으로부터 기술자를 얻을 때, 그리고 dup나 dup2를 사용해서 기술자를 복제할 때 결과로 얻게된다. 터미널과 파이프처럼 랜덤 억세스(임의 접근)가 제공되지 않는 파일들에서 모든 채널들은 실제로 연결되어 있다. 임의 접근이 가능한 파일들에서는 연결된-형태의 출력 스트림들 모두가 서로 연결되어 있다.
만일 당신이 입/출력을 위해 한 스트림을 사용해 오면서, 그것과 연결된 다른 채널(스트림이나 기술자)을 사용해서 입/출력하기 원한다면, 당신은 첫째로 당신이 사용하고 있던 그 스트림을 정리해야만 한다. 8. 5. 3절 [Cleaning Streams] 참조.
프로세스가 종료하거나, 프로세스에 새로운 프로그램을 실행하면 그 프로세스안에 있던 모든 스트림들은 파괴된다. 만일 이 스트림과 연결된 기술자가 다른 프로세스안에 살아남아 있다면, 그들 파일의 위치는 정의되지 않은 상태로 되고 만다. 이것을 방지하기 위해서, 당신은 그들을 파괴하기 전에 그 스트림들을 정리해야만 한다.
8. 5. 2 독립적 채널들
분리되어 탐색 가능한 채널들( 스트림이나 기술자)을 개방할 때, 각 채널들은 자신의 파일 위치를 갖는다. 이들을 독립적 채널이라고 부른다.
시스템은 독립적으로 각 채널을 취급한다. 거의 대부분, 이것은 꽤 예측가능하고, 자연스럽다(특히 입력의 경우): 각 채널은 파일안의 자신의 위치에서 순차적으로 읽거나, 쓸 수 있다. 그렇지만, 만일 어떤 채널들이 스트림이라면, 당신은 이런 것들을 주의해야한다.
당신은 파일의 동일한 부분으로부터 읽거나 쓰는 어떤 것을 하기 전에 사용할 출력 스트림을 깨끗이 해야한다.
독립적인 채널을 사용해서 변경되었을 데이터를 읽기 전에는 입력 스트림을 깨끗이 해야한다. 그렇지 않으면 당신은 스트림의 버퍼에 존재하고 있는 쓸모 없는 데이터(obsolete data)를 읽게 될 것이다.
만일 당신이 파일의 끝에 있는 채널에 출력하려 한다면, 이 채널은 출력을 하기 전에 어느 곳인지에 있을 다른 독립적 채널들을 확실히 무시할 것이다. 만일 당신이 끝에 출력하기 원한다면, 당신은 첫째로, 파일의 끝으로 그들 파일 위치를 설정해야 한다. ( 이것은 연결된-형태의 기술자나 스트림에서는 필요치 않다; 그들은 항상 파일의 끝에서 출력한다. ) 파일의 끝 위치를 정확하게 만들려면, 당신은 만일 당신이 사용하고 있는 것이 스트림이라면 출력 채널을 깨끗이 해야한다. (이것은 심지어 다음에 연결된-형태의 채널을 사용하려 계획할 때도 필요하다. )
파일이 랜덤 억세스를 지원하지 않는다면 파일에서 분리된 파일 위치를 갖고있는 두 개의 채널을 갖는 것은 불가능하다. 그러므로, 파일처럼 읽거나 쓰기 위한 채널들은 항상 연결되어있고, 결코 독립적이지 않다. 연결된-형태의 채널들은 항상 연결되어 있다. 이와 같은 채널들을 위해 연결된 채널들을 위한 규칙을 따라야한다; 8.5.1 [Linked Channels] 참조.
8. 5. 3 채널 깨끗이 하기
GNU 시스템에서, 당신은 fclean을 사용해서 어느 스트림을 정리할 수 있다.
함수 : int fclean (FILE *stream)
버퍼를 비우기 위해 스트림 stream을 깨끗이 한다. 만일 스트림이 출력을 하고 있다면, 강제로 그것을 출력한다. 만일 스트림이 입력을 하고 있다면, 버퍼에 있는 데이터는 그것을 다시 읽도록 조정하는 시스템에 되돌려준다. 다른 시스템들에서는 대부분의 경우 스트림을 깨끗이 하기 위해서 fflush를 사용할 수 있다. 만일 당신이 이미 그 스트림이 깨끗하다란 것을 안다면 fclean이나 fflush를 건너뛸 수 있다. 스트림은 버퍼가 비어있을때는 깨끗하다. 예를 들어 비버퍼화된 스트림은 항상 깨끗하다. 그리고, 파일의 끝에서 입력 스트림은 깨끗하다. 라인 버퍼화된 스트림은 마지막 문자출력이 새줄 문자였을 때 깨끗하다. 스트림을 깨끗하게 하는 것이 대부분의 시스템에서 불가능한 한가지 경우가 있다. 이것은 스트림이 랜덤 억세스가 불가능한 파일로부터 입력을 하고 있을 때이다. 그와 같은 스트림은 이미 읽은 검색 데이터를 되돌려줄 아무런 방법이 없다. 입력 스트림이 랜덤 억세스 파일로부터 입력할 때, fflush로 스트림을 깨끗이 하지 않으면 파일 포인터가 예측 불가능한 영역에 남겨진고 만다. 당신은 입/출력을 하기 전에 먼저 파일 포인터를 설정하라. GNU 시스템에서는 이 문제들의 양쪽을 피하게 한다. 오직 출력만 가능한 스트림을 닫을 때도 fflush를 사용하는데, 이것은 출력 스트림을 깨끗이 하는데 유용한 방법이다. GNU 시스템에서는, 입력 스트림을 닫을 때 fclean을 사용한다. 당신은 터미널 모드를 설정하는 것과 같은 제어 명령을 위해 기술자를 사용하기 전에는 스트림을 깨끗이 할 필요가 없다; 이들 명령들은 파일 위치에 영향을 받지 않고, 그것에 영향을 미치지 않는다. 당신은 이 명령들은 위해서는 어느 기술자라도 사용할 수 있고, 모든 채널들은 동시에 영향을 받는다. 그렇지만, 스트림에 의해 새로운 터미널 모드로 설정되어질 스트림이 여전히 "출력"을 갖고 버퍼화된 상태라면 플러쉬 되어진다. "앞으로"의 출력을 확실히 하기 위해 터미널모드의 설정이 동시에 영향을 받도록 되어졌고, 그 모드를 설정하기 전에 터미널의 모든 출력 스트림들을 플러쉬 한다.
8. 6 입력이나 출력을 위한 기다림
때때로 프로그램들은 입력이 도착할 때마다 다중 입력 채널들로부터 입력을 받아들일 필요가 있다. 예를 들어, 어떤 워크스테이션들은 보통의 비동기적 직렬 인터페이스를 경유하여 연결된, 디지타이징 태블릿 (역자주: 컴퓨터에 좌표 위치를 입력하는 장치), 함수 버튼 박스 (역자주: 버튼들이 모여있는 박스로, 버튼 하나가 한가지 함수를 수행하는 기능을 하는 것. . 일걸요?), 혹은 대화박스(역자주 : 대화상자)와 같은 디바이스들을 갖고 있을 것이다; 좋은 유저 인터페이스의 스타일은 어느 디바이스 상에서 들어온 입력에 대한 즉각적인 대응이 필요하다. 다른 예로써, 파이프나 소켓들을 경유한 여러 가지 다른 프로세스들에게 서버로서 동작하는 프로그램이 있다.
당신은 이러한 목적으로는 read를 사용할 수 없는데, 왜냐하면 프로그램은 어떤 특정한 파일 기술자 상에서 유용한 입력이 있을 때까지 블록 되어지기 때문이다. (역자주 : 여기서 블록의 개념은 입력을 얻지 못해서 아무런 작업도 수행할 수 없는 상태에 처한 것을 말함. ) 다른 채널들에서도 입력을 발생하지 않을 것이다. 당신은 비블럭화 모드로 설정하고 차례로 각 기술자들을 돌아볼 수 있지만, 이것은 매우 비능률적이다.
여기에 대한 훌륭한 해결책으로는 select함수를 사용하는 것이다. 이것은 설정된 기술자들이 입력이나 출력 준비상태가 될 때까지, 또는 설정된 시간이 끝날 때까지 등 이것 중 어떤 것이라도 먼저 될 때까지 프로그램을 블록 시킨다. 이 도구는 헤더파일 'sys/types. h'에 있다.
select 함수를 위해서 파일 기술자를 설정하는 것은 fd_set 오브젝트를 통해서 이루어진다. 다음은 이들 오브젝트들을 다루기 위한 데이터 타입과 매크로 들을 설명한다.
데이터타입 : fd__set
fd_set 데이터 타입은 select함수를 위해서 설정하는 파일 기술자를 나타낸다. 이것은 실제로 비트 배열이다.
매크로 : int FD__SETSIZE
이 매크로의 값은 fd_set 오브젝트가 파일 기술자에 대한 정보를 가질 수 있는 최대의 수이다. 고정된 최대 수를 가진 시스템들에서는, FD_SETSIZE는 적어도 그 개수이다. GNU 시스템을 포함한 다른 시스템에서는 개방하는 기술자의 수에 대한 절대적인 제한이 없지만, 이 매크로는 fd_set의 비트에 개방하는 기술자의 개수를 제어하기 위한 상수 값을 가지고 있다.
매크로 : void FD__ZERO (fd_set *set)
이 매크로는 파일 기술자에 대한 정보를 가지고 있는 set을 빈 공간이 되도록 초기화한다.
매크로 : void FD__SET (int filedes, fd_set *set)
이 매크로는 파일 기술자 정보를 갖고 있는 set에 기술자 filedes를 더한다.
매크로 : void FD__CLR (int filedes, fd_set *set)
이 매크로는 파일 기술자 정보를 갖고 있는 set에서 기술자 filedes를 제거한다.
매크로 : int FD__ISSET (int filedes, fo_set *set)
이 매크로는 만일 filedes가 파일 기술자에 대한 정보를 갖고 있는 set의 멤버라면 0이 아닌 값을 반환하고, 그렇지 않으면 0을 반환한다.
다음은 select 함수에 대한 설명이다.
함수 : int select (int nfds, fd_set *read_fds, fd_set *write_fds, fd_set *except_fds, struct timeval *timeout)
select함수는 정해진 파일 기술자들 중에서 어느 것이 어떤 동작을 하거나, 설정된 시간이 끝날 때까지 호출된 프로세스를 블록 시킨다. read_fds 인수에 의해 정해진 파일 기술자들은 그들이 읽기 위한 준비가 되어있는지 체크되어진다. write_fds 인수로 정해진 파일 기술자들은 그들이 쓰기 위한 준비가 되었는지 체크되어진다. 그리고 except_fds 로 정해진 파일 기술자들은 예외적 상황을 위해 체크되어진다. 만일 당신이 위에 설명된 상황 중에 체크하지 않아도 되는 것에 대한 인수는 널 포인터로 주면 된다. "예외적 상황"은 이미 실행되어지고, 파일 기술자의 상황을 구성하지 않는 잘못된 시스템호출이 있을 때 즉시 보고되어지는 errors_errors를 의미하지 않는다. 오히려, 그들은 소켓에 긴급한 메시지가 나타난 그런 상황을 말한다.
select 함수는 오직 첫 번째 nfds 파일 기술자를 체크하는데, 보통 이 인수의 값으로 FD_SETSIZE가 주어진다. timeout는 기다리기 위한 최대의 시간을 정한다. 만일 당신이 이 인수로 널 포인터를 준다면, 하나의 파일 기술자라도 준비될 때까지 무기한 블록 시킴을 의미한다. 그렇지 않다면, 당신은 struct timeval 형식 안에 시간의 값을 주어야한다. 시간의 값으로 0을 정하면(모두 0의 값인 struct timeval) 이미 준비된 기술자를 찾거나, 만일 준비된 기술자가 없다면 기다리지 않음을 의미한다.
보통 select 함수로부터의 반환 값은 set안에 지정된 기술자 중에서 준비된 기술자의 총수이다. 인수 set들의 각각은 연관된 명령을 위해서 준비된 기술자에 대한 정보로 갱신된다. 만일 입력을 가진 특별한 기술자 desc를 보려면, select 함수가 반환한 후에 FD_ISSET (desc, read`fds)를 사용하라. 만일 시간 설정 값이 끝났다면 select는 0의 값을 반환한다. 어느 신호는 select가 기다리지 않고 즉시 반환해버리는 원인이 된다. 그래서 만일 당신의 프로그램이 신호들을 사용한다면, 당신은 select가 정해진 완전한 시간동안 기다림을 유지한다고 신뢰할 수 없다. 만일 당신이 어느 정해진 시간동안 확실히 기다림을 원한다면, 당신은 반드시 EINTR을 체크하고 현재의 시간에 기초한 새로이 설정된 시간(timeout)으로 select 을 다시 호출하라. 아래에 있는 예를 보라. 만일 에러가 발생하면, select는 -1을 반환하고, 파일 기술자 셋(sets) 인수를 갱신하지 않는다.
다음 errno는 이 함수에서 정의한 에러 상황이다.
EBADF
- 파일 기술자중에 하나가 유용하지 않은 파일 기술자이다.
EINTR
- 명령이 신호에 의해 인터럽트 되어졌다.
EINVAL
- timeout 인수가 유용하지 않다; 구성요소중 하나가 음의 값이거나, 너무 길다.
이식성 노트 : select 함수는 BSD Unix 용이다.
아래의 예는 파일 기술자로부터 읽기 위해 timeout시간을 설정하고 어떻게 select를 사용하는지에 대한 것을 보여주고 있다. input_timeout함수는 파일 기술자 상에 유용한 입력이 있을 때까지, 또는 정해진 timeout시간이 끝날 때까지 호출한 프로세스를 블록 시킨다.
#include <stdio. h> #include <unistd. h> #include <sys/types. h> #include <sys/time. h> int input_timeout (int filedes, unsigned int seconds) { fd_set set; struct timeval timeout; /* 파일 기술자 셋을 초기화 */ <--- 일단 파일 기술자 셋을 공백으로 초기화 한 다음 filedes기술자를 더했네요. */ FD_ZERO (&set); FD_SET (filedes, &set); /* timeout 데이터 구조를 초기화 */ timeout. tv_sec = seconds; timeout. tv_usec = 0; /* select는 정해진 시간이 끝나면 0을 반환하고, 입력이 있으면 1을 반환하고, 에러가 발생하면 -1을 반환한다. */ return TEMP_FAILURE_RETRY (select (FD_SETSIZE, &set, NULL, NULL, &timeout)); --> 세 번째, 네 번째 인수가 널이므로 읽기 위한 기술자에서 정해진 시간동안에 입력이 있는지 체크하겠네요. } int main (void) { fprintf (stderr, "select returned %d. \n", input_timeout (STDIN_FILENO, 5)); return 0; }
8. 7 파일에서의 제어 명령들
이 절은 파일 기술자 상에서, 파일 기술자의 상황을 나타내고 있는 플래그를 세팅하거나, 레코드의 락(locks)을 다루는 것과 같은 다양한 명령들을 어떻게 수행할 수 있는지를 설명하고 있다. 이들 명령들은 모두 fcntl함수를 통해 이루어진다.
fcntl함수의 두 번째 인수는 수행하려는 명령으로 지정된 명령문이다. 그 함수와 매크로와 함께 사용되는 다양한 플래그들은 헤더파일 'fcntl. h' 에 선언되어 있다. ( 이들 플래그들의 대부분은 open함수에서 사용되어 지던 것이다; 8. 1절 [Opening and Closing Files] 참조)
함수 : int fcntl (int filedes, int command, . . . )
fcntl 함수는 파일 기술자 filedes 상에서 command에 의해 정해진 명령을 수행한다. 어떤 command들은 공급해야할 부가적 인수를 필요로 한다. 이곳에 각각의 명령들에 따른 부가적 인수들과 반환 값과 에러 상황 각각에 대한 상세한 설명이 있다.
간단히, 아래는 다양한 명령들에 대한 리스트이다.
F_DUPFD
파일 기술자를 복제하라(동일한 개방된 파일을 가리키는 다른 파일 기술자를 반환). 8. 8절 [Duplicating Descriptors] 참조.
F_GETFD
파일기술자와 연관된 플래그들을 얻어라. 8. 9절 [Descriptor Flags] 참조.
F_SETFD
파일 기술자와 연관된 플래그들을 설정하라. 8. 9절 [Descriptor Flags] 참조.
F_GETFL
개방한 파일과 연관된 플래그들을 얻어라. 8. 10절 [File Status Flags4] 참조.
F_SETFL
개방한 파일과 연관된 플래그들을 설정하라. 8. 10절 [File Status Flags] 참조.
F_GETLK
파일 록을 얻어라. 8.11절 [File Locks] 참조.
F_SETLK
파일 록을 설정하거나, 지워라. 8. 11절 [File Locks] 참조.
F_SETLKW
F_SETLK와 같지만, 완전하기까지 기다린다. 8. 11절 [File Locks] 참조.
F_GETOWN
SIGIO 신호들을 받기 위해서 프로세스나 프로세스 그룹 아이디를 얻어라. 8. 12절 [Interrupt Input] 참조.
F_SETOWN
SIGIO 신호들을 받기 위해서 프로세스나 프로세스 그룹 아이디를 설정하라. 8. 12절 [Interrupt Input] 참조.
8. 8 기술자 복제하기
당신은 파일 기술자를 복제하거나, 동일한 파일 기술자를 참조하기 위한 다른 파일기술자를 할당할 수 있다. 복제한 기술자는 하나의 파일 위치를 점유하고, 하나의 파일 상황 플래그들은 갖는다. ( 8. 10절 [File Status Flags] 참조. ), 그러나, 파일 기술자 플래그는 자신의 것을 소유한다. ( 8. 9절 [Descriptor Flags] 참조)
복제한 파일 기술자의 주된 사용처는 입력이나 출력의 리다이렉션(redirection)을 하기 위한 것이다. 그것은 특정한 파일 기술자에 해당하는 파일이나 파이프를 변화시킨다.
당신은 F_DUPFD 명령으로 fcntl 함수를 사용해서 이 동작을 수행할 수 있지만, 복제 기술자를 만들기 위해서는 dup와 dup2라는 편리한 함수가 있다. fcntl 함수와 플래그들은 'fcntl. h'에 선언되어 있고, dup와 dup2는 헤더파일 'unistd. h'에 있다.
함수 : int dup (int old)
이 함수는 이미 있는 기술자 old를 첫 번째 유용한 파일기술자 번호로 복제한다(첫 번째 번호는 현재 개방되지 않았다. ) 이것은 fcntl(old. F_DUPFD, 0)과 동일하다.
함수 : int dup2 (int old, int new)
이 함수는 기술자 번호 new로 old 기술자를 카피한다. 만일 old기술자가 유용하지 못한 기술자라면, dup2는 아무 것도 하지 않는다; new를 폐쇠하지 않는다. 그렇지 않다면, new가 old로 부터 복제되기 전에 이미 전에 존재하고 있던 기술자라면, new를 일단 먼저 폐쇄시켜야 한다. 만일 old와 new가 서로 다른 번호이고, old가 유용한 기술자라면, dup2는 다음과 동일하다: close (new); fcntl (old, F_DUPFD, new) 그렇지만, dup2는 이것을 자동적으로 한다. new가 폐쇄되어 있고, 아직 old가 복제되지 않았을 때, 호출된 dup2의 도중에는 아무런 걸림돌이 없다.
매크로 : int F__DUPFD
이 매크로는 첫 번째 인수로서 주어진 파일 기술자를 카피하기 위해 , fcntl에게 주는 명령어 인수로서 사용된다. 이 경우 호출 형식은 다음과 같다:
fcntl (old, F_DUPFD, next_filedes)
next_filedes 인수는 int 형이고, 반환될 다음에 유용한 기술자를 정하는데 사용되고, 그 기술자는 이 값보다 하나 크거나, 같은 것이다. 이 명령을 사용한 fcntl로 부터의 반환 값은 보통 new 파일 기술자의 값이다. -1의 반환 값은 에러가 발생했음을 나타낸다. 다음의 errono는 이 명령을 위하여 정의된 에러 상황이다.
EBADF
old 인수가 유용하지 않다.
EINVAL
next_filedes 인수가 유용하지 않다.
EMFILE
당신의 프로그램이 이미 최대한 사용하고 있기 때문에 더 이상 유용한 파일 기술자가 없다.
ENFILE
dup2가 파일의 개방으로 new를 만드는 것이 아니기 때문에 dup2에서는 발생할 수 있는 에러코드가 아니다; 복제한 기술자는 ENFILE이 지적한 제한을 미리 체크하지 않는다. EMFILE는 한 프로세스에서 사용중인 별개의 기술자 번호에 대한 제한을 참조하기 때문에 가능하다.
다음의 예는 리다이렉션을 하기 위해 dup2를 어떻게 사용하는지에 대한 것이다. 특별히, 표준 스트림(stdin 처럼)의 리다이렉션은 자식 프로세스안에서 새로운 프로그램을 수행하기 위해서 수행 함수들의 수행 함수들중 하나를 호출하기 전에 쉘이나 쉘과 같은 프로그램에 의해 행해진다. 새로운 프로그램이 수행되어질 때, 그 프로그램의 메인 함수가 수행되기 전에 연관된 파일 기술자들을 가리키는 표준 스트림을 초기화한다. 그래서 파일로 표준 입력을 리다이렉트 하는 것은 쉘이 다음과 같은 어떤 것을 하는 것이다.
pid = fork (); if (pid == 0) { char *filename; char *program; int file; . . . file = TEMP_FAILURE_RETRY (open (filename, O_RDONLY)); dup2 (file, STDIN_FILENO); TEMP_FAILURE_RETRY (close (file)); execv (program, NULL); }
8. 9 파일 기술자 플래그
파일 기술자 플래그는 파일 기술자의 잡다한 속성들이다. 이 플래그들은 특별한 파일 기술자와 연관되어 있기 때문에, 만일 당신이 한 번 개방한 파일로부터 복제해서 파일 기술자를 만들었다면, 각 기술자는 자신의 플래그 셋(set)을 갖는다.
현재 단지 하나의 파일 기술자 플래그가 있다: FD_CLOEXEC, 이것은 만일 당신이 exec. . . 함수들 중 어느 하나를 사용한다면 기술자가 닫히게 되는 결과를 가져온다.
이 절에서 설명하고 있는 것은 헤더파일 'fcntl. h'에 정의되어 있다.
매크로 : int F__GETFD
이 매크로는 fcntl의 command 인수로 사용되어, filedes 인수와 연관된 파일 기술자 플래그를 반환하도록 한다. 이 명령을 사용한 fcntl의 반환 값은 보통 음이 아닌 수로써, 각각의 플래그( 현재 그곳에서 사용하기 위한 오직 하나의 플래그를 제외하고)들을 비트별 OR 연산을 통해 해석 가능하도록 추출된 값이다. 에러가 발생한 경우에, fcntl은 -1을 반환한다. 다음 errno는 이 명령에서 정의한 에러의 상황이다.
EBADF
filedes인수가 유용하지 않다.
매크로 : int F__SETFD
이 매크로는 filedes 인수와 연관된 파일 기술자 플래그들을 설정함을 지시하기 위해 fcntl함수의 command인수로 사용되어진다. 이것은 새로운 플래그들을 정하기 위해서 세 번째 int 형 인수가 필요하므로, 다음과 같은 형식으로 호출한다: fcntl (filedes, F_SETFD, new_flags) 이 명령과 함께 사용된 fcntl로 부터의 반환 값은 보통 정의되어있지 않지만, 에러가 발생한 경우에는 -1을 반환하여 에러임을 지적한다. 플래그들과 에러발생 상황은 F_GETFD 명령과 같다. 다음 매크로는 fcntl함수와 함께 파일 기술자 플래그로써 사용하기 위해 정의되었다. 이 값은 비트 마스크 값으로써 사용할 수 있는 정수 상수 값이다.
매크로 : int FD__CLOEXEC
이 플래그는 exec 함수가 불리워질 때 닫혀질 파일 기술자를 정한다. 파일 기술자가 할당되었을 때( open이나 dup를 통해서), 이 비트가 새로운 파일 기술자 상에서 클리어 되면 다른 새로운 프로그램에서 그 기술자를 사용할 수 있다.
만일 당신이 파일 기술자 플래그들을 갱신하기 원한다면, 당신은 F_GETFD를 통해서 현재의 플래그들은 얻고, 그 값을 갱신하라. 이곳에서 보여준 플래그들이 각각의 플래그 하나만 사용된다고 생각하지 말라; 당신의 프로그램은 지금으로부터 수년간 실행되어질 것이고 그러면 더 많은 플래그들이 존재하게 될 것이다. 예를 들어, 이곳에 있는 함수는 다른 플래그들을 변경하지 않고 FD_CLOEXEC 플래그만 클리어 하거나 설정하는 함수이다.
/* 만일 value가 0이 아닌 값이면 desc의 FD_CLOEXEC 플래그를 설정하고, value가 0이면 클리어 하라. 성공하면 0을, 실패하면 -1을 반환한다. */
int set_cloexec_flag (int desc, int value) { int oldflags = fcntl (desc, F_GETFD, 0); /* 만일 플래그를 읽는 것이 실패하면, 에러임을 지적하라. */ if (oldflags < 0) return oldflags; /* 우리가 원하는 값으로 플래그를 설정하라. */ if (value != 0) oldflags |= FD_CLOEXEC; else oldflags &= ~FD_CLOEXEC; /* 기술자에 갱신된 플래그 워드를 저장하라 */ return fcntl (desc, F_SETFD, oldflags); }
8. 10 파일 상황 플래그들
파일 상황 플래그들은 개방한 파일의 속성을 정하기 위해 사용되어진다. 8. 9절 [Descriptor Flags] 에서 논의된 파일 기술자 플래그들과 달리, 파일 상황 플래그들은 개방된 파일로부터 복제된 파일 기술자에 의해 공유되어진다.
파일 상황 플래그들은 open 함수의 flags인수를 통해 초기화되어진다. 플래그중 어떤 것은 개방할 때 의미가 있고 계속해서 기억되지 않는다; 나머지 대대 분은 그후에 변화되지 않아서, 당신은 파일 상황 플래그들을 시험함으로 인해서 그들의 값을 읽을 수 있다. 몇몇 파일 상황 플래그들은 fcntl 함수를 사용해서 변화시킬 수 있다. 그들에 O_APPEND 와 O_NONBLOCK 가 포함된다. 이 절에 있는 심볼들은 헤더파일 'fcntl. h'에 있다.
매크로 : int F__GETFL
이 매크로는 기술자 filedes로 개방한 파일의 파일 상황 플래그들은 읽기 위해 fcntl함수의 command 인수로써 사용된다. 이 명령과 함께 사용된 fcntl을 통한 반환 값은 보통 각각의 플래그들을 비트별 OR연산을 통해서 해석 가능한 값으로 추출된 음이 아닌 값이다. 그 플래그들은 open(8. 1절 [Opening and Closing Files] 참조) 함수의 flags 인수처럼 부호화 되었지만, 오직 파일 검색 모드와 O_APPEND 와 O_NONBLOCK 플래그들만 이곳에서 의미가 있다. 파일 억세스 모드들이 단일한-비트 값들이 아니기 때문에, 당신은 그들과 비교하기 위해서 O_ACCMODE을 사용하여 반환된 플래그들에서 다른 비트들을 마스크 시켜서 원하는 비트를 추출할 수 있다. 에러가 발생하면 fcntl은 -1을 반환한다. 다음의 errno는 이 명령에서 정의한 에러상황이다.
매크로 : EBADF
filedes 인수가 부적합하다.
매크로 : int F__SETFL
이 매크로는 filedes 인수와 연관된 개방파일의 파일 상황 플래그를 설정하기 위해서, fcntl 에서 command 인수로써 사용되어진다. 이 명령은 새로운 플래그를 설정하기 위해서 세 번째에 int 형 인수를 필요로 한다. 그래서 이것은 다음처럼 호출한다: fcntl (filedes, F_SETFL, new_flags) 당신은 이 방법으로 파일 엑세스 모드를 변경할 수 있다; 읽거나 쓰기 위하여 개방되어진 파일 기술자든지 간에. 당신은 오직 O_APPEND와 O_NONBLOCK 플래그들을 변경할 수 있다. 이 명령을 사용한 fcntl로부터 반환된 값은 지정된 값이 아니지만, 에러가 발생한 경우에 -1을 반환한다. 에러의 발생 상황은 F_GETFL 명령과 같다.
다음 매크로들은 파일 상황 플래그 값들을 분석하고 구성하기 위해 정의되어졌다.
O_APPEND
파일 덧붙이기가 가능한 모드로 만들기 위한 비트이다. 만일 이 비트가 설정되면, 모든 쓰기(write) 명령들은 파일의 끝에서 쓰기가 이루어지고, 현재 파일의 위치가 무시된다.
O_NONBLOCK
파일을 비블록화 모드로 만들기 위한 비트이다. 만일 이 비트가 설정되면, 파일에서 읽기(read) 요청은 즉시 유용한 입력이 없다면 기다리지 않고 즉시 반환한다. 마찬가지로, 쓰기(write) 요청 또한 즉시 쓰기 가능한 출력이 없다면 즉시 반환하여 쓰기 요청은 실패한다.
O_NDELAY
이것은 O_NONBLOCK의 유사어로써, BSD와의 호환성을 위해 제공된다.
매크로 : int O__ACCMODE
이 매크로는 파일 검색모드를 나타내는 값을 알아내기 위해 파일 상황플래그와 비트별 AND를 시키는 마스크이다. 모드에는 O_RDONLY, O_WRONLY, 또 O_RDWR이 있다.
O_RDONLY
읽기 모드로 파일을 개방.
O_WRONLY
쓰기 모드로 파일을 개방.
O_RDWR
읽기 쓰기 모두 가능하게 파일을 개방.
만일 당신이 파일 상황 플래그를 갱신하기 원한다면, 당신은 F_GETFL을 사용해서 현재의 플래그들은 얻어서 그 값을 갱신하라. 이곳에서 보여준 플래그들이 오직 단독으로 존재한다고 가정하지 말아라; 당신의 프로그램은 지금부터 수년간 실행되어질 것이고 그러면 더 많은 플래그들은 존재할 것이다. 예를 들어, 이곳에 다른 플래그들을 변경함이 없이 오직 O_NONBLOCK 플래그만 설정하고, 클리어 하는 함수가 있다.
/* 만일 value 값이 0이 아닌 값이면 desc의 O_NONBLOCK 플래그를 설정하고, value의 값이 0이면 그 플래그를 클리어 하라. 성공하면 0을 반환하고, 에러가 발생하면 -1을 반환하라 */
int set_nonblock_flag (int desc, int value) { int oldflags = fcntl (desc, F_GETFL, 0); /* 만일 읽기 플래그들이 실패하면, 즉시 에러임을 지적하라. */ if (oldflags < 0) return oldflags; /* 우리가 원하는 플래그를 설정하라 */ if (value != 0) oldflags |= O_NONBLOCK; else oldflags &= ~O_NONBLOCK; /* 기술자에 갱신된 플래그 워드를 저장하라 */ return fcntl (desc, F_SETFL, oldflags);
'공부' 카테고리의 다른 글
가장 쉬운 작곡 프로그램 스튜디오 원 studio one 사용법 (0) | 2021.09.03 |
---|---|
공부 1 (0) | 2021.08.19 |
eMule의 SearchSpam.met 편집 보기 (0) | 2021.04.27 |
버퍼링의 종류 제한하기 (0) | 2021.04.27 |
무제 5 (0) | 2021.04.10 |
무제 4 (0) | 2021.03.13 |
무제 3 (0) | 2021.02.22 |
무제 2 (0) | 2021.02.10 |