1  안내문

	The GNU C Library

1장 안내문

C언어는 입출력,메모리 관리,문자열 처리와 같은 일상적 처리를 수행하는 장치들을 이미 완성된 도구로 제공하지는 않는다.  대신에, 이 도구들은 표준 라이브러리에 정의되어  있기 때문에, (이것들을 사용하여) 당신의 프로그램을 컴파일하고 링크할 수 있는 것이다.

이 문서에 기술되어 있는 GNU C의 라이브러리들은 ANSI C 뿐만아니라 POSIX 또는 다른 유닉스 운영체제 및 GNU 시스템이 지정하는 여타 언어들에서도 적용되는 모든 라이브러리 함수들을 정의한다.

이 안내서의 목적은 GNU 라이브러리의 도구들을 사용하는 방법을 알려주는 데에 있다.  우리는 특히 다른 시스템에서는 아마도 사용할 수 없는 함수들이 가지는 특성들을 언급하였다. 다만, 이 안내서의 규정들이 엄격하게는 적용되지  않을 것이다.

1.1  시작하기

이 안내서는 적어도 당신이 C 포로그래밍 언어나  기본적인 프로그램밍의 개념에 어느 정도 익숙하다는 것을 가정하고 씌어졌다.  특히 "전통적인" ANSI 이전의 C언어 통용어보다는  ANSI 표준 C언어 (1.2.1절,ANSI C,1쪽 참조)에 친숙하다고 생각하였다.

GNU C 라이브러리는 몇몇 헤더파일을 갖고 있는데, 각 헤더파일은 관련함수들을 정의하고 선언한다; 이 정보는 당신의 프로그램이 처리될 때 C 컴파일러가 사용하게 된다. 예컨데, 헤더파일 'stdio.h'는 입출력을 수행하는 함수들을 선언하고, 헤더파일 "string.h'는 문자열 처리 도구들을 선언한다. 이 안내서의 편성은 헤더파일을 분류한 것과  똑같이 나누어져 있다.

이 안내서를 처음으로 읽을 때에는 모든 안내문과 설명들을 몽땅 읽는 게 좋다. GNU C의 라이브러리에는 많은 함수들이 있지만, 당신이 그 모든 것을 모두 정확하게 다 기억할 수는 없을 것이다. 그러므로,라이브러리가 제공하는 각종 도구들에 널리 친숙해지고 난 다음에, 실제로 프로그램을 잘 때에 라이브러리 함수를 쓰는 법과 그 함수들에 대한 안내서의 특정 정보들을 빨리 찾아내는 것이 중요하다.

1.2  표준규정과 호환성

여기서는 GNU C라이브러리가 기초하고 있는 다양한 규정들과 소스에 관해서 논의하자.  이 소스들은 ANSI C와 POSIX 표준규정 및  V 시스템, 그리고 버클리 유닉스 장치들을 포함한다.

이 안내서의 첫번째 주제는 당신이 어떻게  GNU 라이브러리의 도구를 효과적으로 사용할 것인가에 관한 것이다. 그러나 만약 당신이 이미 작성한 프로그램을  이들 표준과 일치시키고 싶어하거나 GNU보다는  운영체제의 활용 문제에 관심을 갖는다면, 이 안내서는 라이브러리 사용법을 아는 데 도움이 될 것이다.

529페이지의 부록 B [Library Summary]를 보면, 라이브러리가 제공하는 함수나 부호들의 알파벳 순서 리스트가 있다. 여기에서 각 함수와 부호들이 어떤 표준도구에서 파생된 것인지를 알 수 있다.

1.2.1  안시 씨

GNU 씨 라이브러리는 미국 국가표준위원회(ANSI)에서 채택된 C언어 표준과 서로 통용된다.  GNU 라이브러리가 작성한 헤더파일들과 라이브러리 도구들은 ANSI 표준 C언어에 의해 규정된 것들을 포함하면서 더 발전시켜 놓은 것이다.

만약 당신이 ANSI C언어만을 엄격하게 고수하겠다면, 프로그램 컴파일할 때에 '-ansi' 옵션을 사용하라.  이렇게 하면, 컴파일러가 알아듣고서 ANSI C 이외의 특성들을 제거해 줄 것이다.  1.3.4절 [Feature Test Macros] 6쪽 참조

ANSI C언어는 라이브러리 도구에 의해서 정의될 수 있는 명칭에 한계를 두고 있지만, GNU는 이를 확장하여 한계를 넘어서고 있으므로, ANSI의 특성만을 살려주는 라이브러리 제한 옵션은 중요한 것이 된다. 1.3.3절 [Reserved Names]의 4쪽을 참조할 것.

이 안내서에서는 GNU와 ANSI C의 차이점에 대한 완벽한 설명은 하지 않겠다. 물론 다른 C언어들과 함께 사용하는 충고들은 하겠지만  모든 것을 완전히 다 설명할 수는 없겠지?

1.2.2  POSIX ( 이식가능한 운영체제와의 호환성 ; The Portable	Operating System Interface )

GNU 라이브러리는 일반적인 컴퓨터 환경을 위한 운영체제 호환성으로 알려져 있는 IEEE POSIX와도  서로 통용된다. POSIX는 대체로 유닉스 운영체제의 수많은 버젼들에서 유래된 말이다. POSIX 표준에 의해 규정된 라이브러리 도구들은 ANSI C에 의해 규정된 것들보다는 더 확장되어 있다; POSIX는 새로운 함수들을 포함할 뿐만 아니라 ANSI C의 함수들에 새로운 특성을 추가해 놓고 있다. 일반적으로, POSIX의 함수들은 다양한 운영체제에서 사용되는 고급 프로그램 언어들을 지원한다기보다는 특정한 운영체제의 환경을 지원하기 위하여 저급언어들을 제공하는데 초점을 둔다. GNU C라이브러리는 흔히 "POSIX.1"이라 불리는 포식스 체제 적용 호환 프로그램,즉, IEEE Std 1003.1-1990에 규정된 모든 함수들을 완성하였다. 여기에서 ANSI C의 도구들을 최초로 확장한 내용은

첫째, 파일 체제 호환(9장 참조, 129쪽)

둘째, 특정 장치 터미널 통제 함수(12장 참조,185쪽)

셋째, 프로세스 통제 함수(23장 참조,325족)등이다.

GNU 라이브러리는 IEEE Std 1003.2-1992의 몇몇 도구들과 포식스 셸,그리고 표준 유틸리티들도 완성해 놓았다. 이들은 정규적인 표현들을 다루는 유틸리티 뿐만아니라 다른 형태로 적용되는도구들까지 포함하고 있다. (16장 참조, 221쪽)

1.2.3  버클리 유닉스

GNU C라이브러리는 몇몇 유닉스 버젼의 도구들을 정의하고 있다.  특히 유닉스 체제의 BSD의 버젼4.2, 4.3, 4.4 (버클리 유닉스라 알려져 있는), 그리고 SunOS (유닉스 체제 V함수모음을 포함하는 4.2 BSD의 파생본)이 그것이다.  이러한 체제들은 ANSI와 POSIX의 대부분의  함수들을 지원하고 있으며,  BSD 버젼 4.4와 새로운 SunOS 배포본은 사실상 이 모든 것을 제공한다고 보아 무방할 것이다.

BSD 도구들이 포함하는 것.

(1) 부호 링크 ( 9.4절 참조 127쪽)

(2) 선별 함수 ( 8.6절 참조 111쪽)

(3) BSD 기호 함수 ( 21.9절 참조 306쪽)

(4) 소켓 ( 11장 참조 149쪽)

1.2.4  SVID ( V체제 호환성 설명 )

SVID는 AT&T 유닉스체제와 V 운영체제를 설명하는  문서다. 그것은 POSIX 표준을 어느 정도 확장한 것이다.(1.2.2 참조 2쪽)

GNU C 라이브러리는 이러한 도구들을 담고 있는 V 유닉스 체제 및 또다른 유닉스 체제들( SunOS와 같은)을 무리없이 사용하기 위해서,  ANSI 또는 POSIX 표준규정들에 의해 요구되는 함수들을 정의하고 있다. 그러나, SVID가 필요로는 하지만 불명확하고 사용빈도가  낮은 함수들은 많이 제외시켰다.  (사실, V 유닉스 체제 그 자체는 그것들을 모두 제공하지는 않는다.)

1.3  라이브러리 사용하기

여기서는 GNU C의 라이브러리를 사용하는 데서 일어나는 실제적인 문제들을 설명하고자 한다.

1.3.1  헤더 파일

C 프로그램에서 사용하는라이브러리는 이 두가지로 구성된다;

첫째, 형태와 매크로를 정의하고 변수와 함수들을 선언해 놓은 헤더파일.

둘째, 변수의 정의나 함수를 담고 있는 실제의 라이브러리나 보관소.

( C언어에서는, 선언은 오로지 함수나 변수가 존재한다는 정보를 제공하거나,그 형태를 규정할 뿐임을 기억하라. 함수를 선언하려면,함수의 형태에 관한 정보도 주어져야 한다. 선언을 하는 목적은 컴파일러가 선언된 변수나 함수를 정확하게 처리하도록 하는 데에 있다.  한편, 정의는 변수에 메모리를 할당하거나 함수가 무슨 일을 해야 하는지를 실제적으로 말해주는 것이다. )

GNU C의 라이브러리를 사용하기 위해서는, 당신은  당신의 프로그램 소스파일이 적합한 헤더파일을 포함시키고 있는지를 분명하게 알아야 한다. 왜냐하면, 컴파일러는 이러한 유용한 도구들의 선언을 갖고 있고 그것들을 정확하게  참조하여 처리를 수행하기 때문이다.  당신의 프로그램이 일단 컴파일되면,링커는 저장된 파일에서  제공되는 실제적인 정의를 참조하면서 일을 수행한다. 헤더파일은 '#include'라는 전처리기 지시어에 의해 프로그램 소스파일로 포함되어진다.  c언어는 이 지시어를 두개의 형태로 제공한다.

#include "header"

는 당신 스스로가 작성한 헤더파일을 포함시킬 때 사용하는  유형이다; 이것은 당신의 프로그램중의 다른 부분에서  불러다 쓸 정의나 선언을 담고 있다.

이와는 달리,  #include <file.h> 는  표준라이브러리를 위한 정의와 선언을  담고 있는 헤더파일 'file.h'를 사용하는 유형이다.  이 파일은 컴퓨터의 시스템 관리자에 의하여 지정된 위치에  정상적으로 인스톨되어 있어야만 한다.  당신이 C 라이브러리의 헤더파일을 사용하려면 반드시 이 두번째의 유형을 사용해야 한다.

형태적으로, '#include'라는 지시어는 C 소스파일 내에서 다른 어떤 코드보다도 맨 앞에 위치해야 한다. 만약에 당신이 이 프로그램은 무엇을 하는 프로그램인지를 설명하는 주석문을 먼저 적고서 (이것은 아주 훌륭한 습관이다) 소스파일의 작성을 시작하려 한다면, 주석문의 바로 다음에 '#include'라는 지시어를 써야 하고 그 뒤에 매크로 정의가 따라 오게 된다.(1.3.4절 6쪽 참조)

헤더파일의 사용과 '#include' 지시어에 관한 더 자세한 내용은 The GNU C Preprocessor Manual(전처리기 사용법)의  "헤더파일"부분을 참조하라.

GNU C는 몇개의 헤더파일을 제공하는데, 각 헤더파일은 일련의 관련기능을 위한 형태와 매크로의 정의,변수및 함수의  선언들을 담고 있다. 이것은 당신이 이 헤더파일들을 당신의 프로그램에 사용할 수 있음을 의미하는 것이며, 그것은 당신이 사용하고자 하는 기능에 맞는 것이어야 한다. 어떤 라이브러리 헤더파일들은 다른 라이브러리 헤더파일들을 자동적으로 담고 있다. 그러나, 프로그래밍의 스타일 문제이긴 하지만, 당신은 이에 의존해서는 안된다; 당신이 사용하고자하는 라이브러리 도구들이 포함된 헤더파일들을 명시적으로 모두 써 주는 편이 훨씬 좋은 것이다. GNU C의 라이브러리 헤더파일들은, 해당 헤더파일이 의도하지 않은 상태로 여러번 중복되어 쓰여도 전혀 문제가 없도록 만들어져 있다;  물론 특정 헤더파일의 두번째 호출은 아무런 효과가 없겠지만.  마찬가지로, 당신의 프로그램이 여러개의 헤더파일을 사용한다하드라도,그것들이 포함되는 순서는 신경쓸 필요가 없다.

호환성 노트 : 표준 헤더파일들을 아무런 순서로 몇번씩 포함시키드라도 어떤 ANSI C의 실행에서도 작동한다. 그러나, 이러한 방법은 이전의 많은 C언어들에서는 전통적으로 허용되지 않는 경우였다. 엄밀히 말하자면 헤더파일이 선언한 함수를 사용하는 경우에도 헤더파일을 포함하지 않을 수도 있다; 이 안내서가 설명하는 데에 따라 당신 스스로가 그 함수를  명시적으로 선언해도 좋다.그렇지만,항상 헤더파일을 사용하는 편이 유리할 것이다. 왜냐하면, 헤더파일은 특정한 방법으로 사용할 수 밖에 없는 형태와 매크로를 정의하고 있으며, 또한 어떤 함수들을 대신할 수 있는  보다 유효한 매크로들을 정의하고 있기 때문이다.

1.3.2  함수의 매크로 정의

이 안내서에서 어떤 것을 함수로 기술하고자 할 때, 그것은  매크로 정의로 될 수가 있다. 이 점은 프로그램의 실행에는 아무런  지장이 없다.__ 매크로 정의는 함수와 똑같은 역할을 한다.  특히 라이브러리 함수에 상응하는 매크로는 함수를  호출할 때와 같은 방식으로 규정들을 꼭 한번씩만 검토한다.  매크로를 정의하는 주된 이유는 종종 매크로가 함수를 호출하는 것보다 훨씬 더 빠른 처리를 하기 때문이다. 라이브러리 함수가 매크로로 정의된다고 하드라도 그 함수의 주소는 구해진다. 이는 구문상으로 볼 때,그 함수의 이름이  매크로를 호출하는 데 필요한 왼쪽 구문보다 뒤에 위치하기 때문이다.

당신은 때로는 --아마도 프로그램의 디버깅을 쉽게하기 위하여--함수의 매크로 정의를 사용하지 않으려 할 것이다. 이렇게 하는데는 두가지의 방법이 있다;

□ 당신은 함수의 이름을 괄호로 묶어서 사용하는 방법으로 매크로 정의를 피할 수 있다. 이렇게 하면,함수의 이름이 매크로를 호출하는 문장상에 나타나지 않을 수 있다.

□ 당신은 '#undef'라는 전처리기 지시어를 사용함으로써 전체 소스상에 있는 어떠한 매크로 정의든지 취소할 수 있다.  만일 부가적으로 그 기능이 명시적으로 서술되지만 않는다면. 예를 들어, 헤더파일 'stdlib.h'가 외부정수 (int)를 갖는  abs라는 명칭의 함수를 선언한다고 가정하자; 또한 abs를 매크로 정의로 한다고 가정하자. 그러면,

#include  <stdlib.h>

int f  (int  *i)  {  return  (abs  (++*i));  }

이 경우에는 매크로로도 함수로도 참조될 수 있게 된다.  다른 한편,다음의 각 경우에는 매크로로는 참조되지 않으며  다만 함수로서 참조될 뿐이다.

#include  <stdlib.h>

int  g  (int  *i)  {  return  ((abs)(++*i));  }

#undef  abs

int  h  (int  *i)  {  return  (abs  (++*i));  }

매크로가 함수와 중복되어 정의되드라도 그것은 실제적인  함수 호출과 똑같은 방식으로 작동하기 때문에,언제나 이러한 방법을 채택할 필요는 조금도 없다.  사실상,매크로 정의를 제거하게되면 프로그램의 실행이  더욱 느려질 것이다.

1.3.3  예약된 명칭들

ANSI C의 표준에서 유래된 모든 라이브러리 형태,매크로, 변수및 함수의 명칭들은 모두 무조건적으로 예약되어 있다; 당신의 프로그램이 이 명칭들을 다시 정의할 필요가 없다. 만약 당신이 그것들을 선언한 헤더파일을 당신의 프로그램에 분명하게 포함시키기만 한다면,다른 모든 라이브러리 명칭들도 예약되는 것이다. 이렇게 하는데는 몇가지 이유가 있다.

□ 예컨데, 당신이 만일 표준규정과는 완전히 다른 이름을 붙여서 멋대로 함수를 사용하게 되면, 다른 사람들이 당신의 소스코드를 읽다가 큰 혼란에 빠질 것이다. 이런 사태를 막기 위해서 당신은 프로그램을 이해하기 쉽도록 짜야되며 모듈화하고 알 수 있게 하여야 한다.

□ 또한 사용자가 우연하게도 다른 라이브러리 함수에서 호출된  함수를 다시 정의하게 될 가능성도 막아야 한다. 만약 중복되는 정의가 허용된다면,다른 함수들은 제대로 작동할 수 없을 것이다.

□ 컴파일러는 사용자가 함수를 다시 정의하지 않드라도 그것들이 호출될 때 어떻게 처리해야 하는지를 알고 있다. 예컨데 variadic arguments나 non-local exits를 다루는 것과 같은 몇몇 라이브러리 도구들은 C 컴파일러의 상당히 많은 부분의 결합을 요구하게 되는데, 이 때 컴파일러는 그 언어에서 이미 만들어져 있는 도구들을 다루는 편이 수월할 것이다.

이 안내서에 적혀있는 명칭들 뿐만아니라,예약된 명칭들은  하나짜리 밑줄('_')로 시작되는 모든 외부 동일규정들(전역 변수와 함수)들을 포함한다.또한 두개짜리 밑줄로 시작되거나 하나짜리 밑줄로 시작하되 대문자가 따라붙는 동일규정들은 전부 예약된 명칭이다. 이렇게 해야만 라이브러리나 헤더파일들이 함수,변수,매크로등을 정의할 수 있게되고,그것들이 사용자 프로그램의 다른 명칭들과  충돌됨이 없이 내부적 목적에 활용될 수 있다.

다른 어떤 종류의 동일규정 명칭들은 미래에서의  C언어의 확장을 기대하며 예약되어 있다.이러한 명칭들은 당장은 당신의 프로그램에서 사용되어도 문제가 없지만, 장래에 C언어의 표준규정들이 버젼업되게 되면 문제를 일으킬 소지가 있으므로 가급적 이 명칭들의 사용을 피하는 게 좋겠다.

□ 영어대문자 'E'로 시작되어서 뒤에 구두점(.)이나 대문자가 따라붙는 명칭들은 후일에 에러코드의 명칭으로 사용될 것이 예상된다.  2장 [에러 보고] 11쪽 참조

□ 'is'나 'to'로 시작해서 소문자가 따라붙는 명칭들은 문자처리나 변환 함수로 사용될 것들이다. 4장 [문자의 처리] 41쪽 참조

□ 'LC_'로 시작해서 뒤에 대문자가 따라붙는 명칭들은 지역적인 범위의 매크로 명칭으로 사용될 것들이다.

□ 'f'나 'l'이 붙어있는 수학함수(13장[수학],203쪽 참조)는  각각 실수나 double형 인수를 작동시키는 함수들의 명칭으로  사용될 것들이다.

□ 'SIG'로 시작해서 뒤에 대문자가 따라붙는 명칭들은  부호 명칭으로 사용될 것들이다. 21.2 [표준 부호],272쪽 참조

□ 'SIG_'로 시작해서 뒤에 대문자가 따라붙는 명칭들은 부호의 작동에 사용될 명칭들이다.  21.3.1 [기본적인 부호 처리],280쪽 참조

□ 'str','mem'이나 'wcs'로 시작해서 소문자가 따라붙는 명칭들은 문자열이나 배열의 함수로 사용될 명칭들이다.  5장 [문자열과 배열의 처리],45쪽 참조

□ '_t'로 끝나는 명칭들은 형태를 규정하는 명칭(? 형명칭)으로 사용될 것이다.

아울러, 어떤 헤더파일들은 실제로 정의되어 있는 명칭들의  범위를 넘어서는 명칭들도 예약하고 있기도 한다. 따라서,당신이 특정한 헤더파일을 프로그램에 포함시킨다면  이러한 제한에 대해 너무 근심하지 않아도 된다.

□ 헤더파일 'dirent.h'는 'd_'로 시작하는 명칭들을 예약하고 있다.

□ 헤더파일 'fcnti.h'는 'l_','F_','O_'및 'S_'로 시작하는 명칭들을 예약하고 있다.

□ 헤더파일 'grp.h'는 'gr_'로 시작하는 명칭들을 예약하고 있다.

□ 헤더파일 'limits.h'는 '_MAX'가 따라붙는 명칭을,

□ 헤더파일 'pwd.h'는 'pw_'로 시작하는 명칭을,

□ 헤더파일 'signal.h'는 'sa_'와 'SA_'로 시작하는 명칭을,

□ 헤더파일 'sys/stat.h'는 'st_'와 'S_'로 시작하는 명칭을,

□ 헤더파일 'sys/times.h'는 'tms_'로 시작하는 명칭을,

□ 헤더파일 'termios.h'는 'c_','V','I','O'및 'TC'로 시작하는 명칭들을 예약하고 있다;또한'B'로 시작하되 구두점(.)이 뒤따르는 명칭들도 예약하고 있다.

1.3.4  Feature Test Macros

당신이 소스파일을 컴파일할때 당신이 선언한 테스트마크로의 특징대로 제어되어져 당신은 정확한 프로그램의 형상을 얻을수 있다.

만약 소스프로그램을 컴파일할때 'gcc -ansi'를 사용하면 오직 ANSI 라이브러리 만으로된 프로그램을 얻을수 있고, 그렇지 않더라고 하나, 혹은 더많은 마크로를 사용해서 부가적인 프로그램의 특징을 명시적으로 요청할수 있다.  이책안에 있는 " GNU CC Command Options "란 부분을 보면 GCC 옵션에 대한 더 많은 정보를 얻을수 있다.

여러분의 소스코드 화일에 '#define'이라는 전처리 지시어를 사용해서 이 마크로를 정의할수 있다.  이 전처리 지시어는 오직 주석문을 제외하고 #include나, 어떤것보다 먼저 와야만 한다.  또한 GCC에 옵션'-D'를 사용할수 있지만 그것은 당신이 독립적인 방법으로( a selfcontained way ) 그들자신의 의미를 지시하는 소스 파일들을 만들고자 할때는 좋다.

__POSIX__SOURCE

만일 당신이 이 마크로를 사용하면 모든 ANSI C는 물론 표준(IEEE Standard 1003.1)POSIX.1의 함수들이 이용되어진다.

__POSIX__C__SOURCE

만일 당신이 이 마크로를 1의 값과 함께 선언하면 표준(IEEE Standaed 003.1)POSIX.1의 함수들을 이용하여 만든다.  마크로를 2의 값과 함게 선언하면 표준 POSIX.1과 표준POSIX.2의 함수들이 이용되어진다. 또,여기에는 ANSI C가 더해져 있다.

__BSD__SOURCE

만일 당신이 이 마크로를 정의하면 ANSI C, POSIX.1, POSIX.2 가 포함된 4.3 BSD UniX에서 함수를 끌어낸다. 4.3 BSD Unix 에서 온 어떤것은 POSIX.1 표준에서 지정한 어떤것 (일치되는 특징을 가진것)과 충돌을 일이키는데 만일 이 마크로가 정의되면 4.3 BSD 정의가 POSIX의 정의에 앞서 인계된다. 4.3 BSD와 POSIX.1사이의 어떤 충돌이 원인이되어 당신이 BSD와 호환되도록 컴파일된 프로그램을 링크할때 특별한 BSD 호환 라이브러리의 사용이 필요하다.  이것은 왜냐하면 어떤 함수들은 두가지 상이한 방법으로 정의되어져 있기 때문이다.  그 두가지중 한가지는 일반적 C 라이브러리이고 또다른 하나는 호환 라이브러리이다. 당신의 프로그램을 _BSD_SOURCE로 정의하면 당신은 컴파일러나 링커에게 -lbsd-compat라고 옵션을 주어야만 하는데, 그것은 함수를 일반 c 라이브러리에서 찾기전에 호환 라이브러리에서 찾도록 컴파일러나 링커에게 알려주기 위함이다.

__SVID__SOURCE

만일 당신이 이 마크로를 정의하면 ANSI C, POSIX.1, POSIX.2의 요소가 포함된 SVID로부터 함수를 끌어온다.

__GNU__SOURCE

이 마크로는 ANSI C, POSIX.1, POSIX.2, BSD, SVID 그리고 GNU 확장부분까지 모든걸 포함한다. 이경우 POSIX1 과 BSD의 충돌때는 POSIX가 우선한다. 그런데 _GNU_SOURCE의 모든 효과는 얻고  싶은데 BSD가 POSIX에 우선하고 싶을때는 아래와 같은 정의 구문을 사용하라.

#define  _GNU_SOURCE

#define  _BSD_SOURCE

#define  _SVID_SOURCE

당신의 프로그램을 BSD 호환 라이브러리와 링크하려면 컴파일러나 링커에게 -lbsd-compat옵션을 주어야한다.  만약 이것을 잊어버리면 실행할때에 매우 이상한 에러를 얻게될것이다. 당신이 새로운 프로그램을 _GNU_SOURCE로 사용하려면 GCC에 -ansi옵션과 어느 특정한 마크로를 명시하지 않으면 _GNU_SOURCE와 같은 효과를 얻게 되는데, 우리는 당신에게 그 방법을 추천한다. 확장된 클래스를 요청하기위해 테스트마크로를 정의할때, 부가적인 테스트마크로를 사용하는것은 무해한데, 그것은 부가적으로 사용된 마크로가 이미 선언된 마크로의 부분집합일때 이다. 예를들어 만약 당신이 _POSIX_C_SOURCE라고 선언하고 다시 _POSIX_SOURCE라고 선언하면 아무런 효과가 발생되지 않는다. 이처럼 _GNU_SOURCE라고 선언하고 더하여서 _POSIX_SOURCE나 _POSIX_C_SOURCE,혹은 _SVID_SOURCE라고 선언하는것도 아무런 효과가 발생하지 않는다. 그렇지만 _BSD_SOURCE는 지원되는 다른 테스트 마크로의 부분집합이 아닌데, 그것은 POSIX에 우선되도록 정의되어져 있기 때문이다. 이때문에 _BSD_SOURCE에 더해서 다른 테스트 마크로를 사용하는것은 효과를 갖게되고 그것은 BSD가 충돌하는 POSIX보다 우선권을 갖는것에 기인한다.

1.4  안내서의 지도

이곳은 이 안내서에 있는 각장의 내용을 개략한다.

제 1장: 안내

제 2장 : [에러 보고] , 11페이지, 라이브러리에 의해 어떻게 에러가 발견되는지에 대한 설명.

제 3장: [메모리 점유], 페이지 21, 동적 메모리 점유를 위해 GNU라이브러리가 갖추고 있는것을	설명. 만일 당신의 프로그램에서 얼마나 많은 메모리가 필요한지 알지못한다면 당신은 그것	을 동적으로 메모리를 점유하도록 만들수 있지만 대신에 포인터로 그것을 다루게 된다.

제 4장: [문자 처리], 페이지 41, 문자분류 함수들( isspace와 같은 )에 대한 정보와 각 변환을 형	성하기 위한 함수들에 대한 정보.

제5장:[문자열과 배열 유틸리티] 45페이지 문자열( 끝에 널문자를 가진  배열 )과 일반적 바이트	배열을 카피하고 비교하는등의 실행을 포함한 여러 함수들에 대한 설명.

제6장:[입출력 개괄] 페이지 57, 입출력의 도구들을 포괄적으로 살펴보고 파일이름처럼 기본적인	개념에 대한 정보 포함.

제7장: [스트림의 입출력] 페이지 63, 스트림( 또는 파일 )의 입출력묘사. 그것은 일반 C 라이브	러리 함수의 `stdio.h'에 있다.

제8장:[저수준 입출력] 페이지 103, 파일기술자의 입출력 오퍼레이션에 대한 정보 함유. 파일 기	술자는 운영체제 유닉스계에 특유한 저수준 메카니즘이다.

제9장:[파일시스템 인터페이스] 페이지 123, 전체적 파일에 대한 명령어, 즉, 지우고 리네임하고	새로운 디렉토리를 만드는것과 같은 명령에 대한 서술. 이 장은 또한 그 자신과 파일 보호모	드와 같은 파일 속성을 어떻게 다루는지에 대한 정보도 포함하고 있다.

제10장:[Pipes and FIFOs] 페이지 145, 간단한 프로세스간 통신 메카니즘에 대한 정보. Pipes는 두	연관된 프로세스( 부모과 자식사이와 같은 ) 사이의 통신을 허용하고 그동안 FIFOs는 프로세	스 사이에 공통된 하나의 파일시스템을 나누는 역할을 한다.

제11장:[Sockets],페이지 149, 네트웍상에서 통신하기위해 다른 시스템 (machines)위에 프로세스들	의 실행을 허용하는 것과 같은 더 복잡한 프로세스간 통신에 대해 설명. 이장은 또한 인터넷	호스트 주소지정과 시스템 네트웍 데이타베이스를 어떻게 사용하는지 설명하고 있다.

제12장:[Low-Level Terminal Interface], 페이지 185, 터미날 디바이스의 속성을 어떻게 변화시킬수	있는지에 대한 설명. 예를들어 만일 당신이, 사용자가 타입하는 문자의 echo를 원하지 않는	다면 이 장을 읽어라.

제13장:[수학] 페이지 203 수학관련 함수들에 대한 정보. 여기에는 난수 발생기와 정수의 나머지 함수, 실수에서 삼각함수와 지수함수와 같은것들을 포함하고 있다. 미정의: 저수준 수학 함수들, 문자열로부터 수를 읽고 실수값을 분석하는, 간단한 함수들을 설명.

제15장:[탐색과 정렬]페이지 217,배열을 탐색하고 정렬하기 위한 정보를 포함. 적당한 비교 함수	를 줌으로써 어떤 종류의 배열에도 이 함수를 사용할수 있다.

제16장:[형식일치] 페이지 221, 일반적 표현과 쉘 파일 이름 형식을 매치 시키기 위한 함수, 그리	고 쉘처럼 단어들을 확장하기위한 함수.

제17장:[날짜와 시간] 페이지 233, 알람과 타이머를 셋팅하는 함수처럼 달력의 시간과 CPU의 시	간을 계산하기 위한 함수 설명.

제18장:[확장된 문자],페이지 249, 일반적 문자 데이타 타입보다 확장된 문자셋을 사용하는 문자	와 문자열을 다루는것에 대한 정보.

제19장:[Locals] 페이지 259, 어떻게 나라를 선택하거나, 라이브러리의 작용에 영향을 미치는 언어	를 어떻게 선택할것인지에 대한 설명. 예를 들면 지역적인건 화폐의 값과 문자열의 대조열에	영향을 미친다.

제20장:[Non-Local Exits]페이지 267 setjump와 longjump함수의 해설. 이 함수들은 다른곳에서 하	나의  함수로 goto 처럼 분기하기 위한 편의를 제공한다.

제21장:[신호처리]페이지 271, 신호에 대한 모든것을 설명한장으로, 인도된 특별한 종류의 신호가	있을때 그것을 부를수 있는 핸들러를 어떻게 만들것인가, 그리고 당신의 프로그램이 중요한	부분에 있을동안 도착되는 신호를 어떻게 방지할것인지에 대한 것들이 있다.

제22장:[Process Startup]페이지 311 당신의 프로그램을 커멘드라인 변수와 환경변수들로 어떻게	실행할것인지 말한다.

제23장:[Child Processes]페이지 325 어떻게 새로운 프로세스들은 시작하고 프로그램을 실행할 것	인지에 대한 정보를 준다.

제24장:[Job Control]페이지 335 프로세스군을 다루기위한 함수들을 설명. 이장은 쉘을 쓰려는	(writing)데 관심이 있는 사람에게 유용하다.

제25.12:[User Database]페이지360, 그리고 25.13[Group Database] 페이지 362, 어떻게 시스템 사용	자와 그룹 데이타베이스를 억세스 할것인지 설명.

제26장:[System Information]페이지 367 당신의 프로그램이 실행되고 있을때 하드웨어와 소프트웨	어에 대한 정보를 얻기위한 함수들을 설명.

제27장:[System Configuration],페이지 369 다양한 운영체제의 제한조건에 대한 정보를 얻을수 있	는 방법에 대한 설명. 이 한계들의 대부분은 POSIX와 호환성을 위해 제공된다.

부록A:[Language Features]페이지 381 C언어의 표준적인 부분을 위해 지원되는 라이브러리에 대	한  정보와, sizeof 명령어와 NULL상수와 같은것을 포함하고, 변수와 변수수들을 어떻게 쓸것인지, 그리고 데이타 타입의 다른 특성과 범위를 설명. 당신이 코딩하는 곳에서 허용되는 간단한 디버깅 메카니즘도 있고,  테스트가 실패하면 나타나는 메세지들에 대한 진단도 있다.

부록B:[Liabrary Summary]페이지 395 라이브러리내에 있는 모든 함수와 변수, 그리고 마크로들에 대한 요약으로 간단한 데이타 타입들과 함수의 특성과 그리고 그것이 표준인지 아니면 어떤 시스템에서 사용되는 것인지를 설명했다.

부록C:[Maintenance]페이지 449 당신의 시스템에 GNU C 라이브러리를 인스톨하는 법, 그리고	어떻게 발견된 버그에 대응하고 새로운 시스템에 라이브러이의 새로운 함수들과 포트를 더할지에 대해 설명.

만일 당신이 이미 당신이 관심있는 곳의 facility(함수, 변수 마크로)들의 이름을 이미 알고 있다면 부록B를 사전처럼 찾아볼수 있다. 그리고 이곳은 당신이 좀더 세밀한 표현을 발견할수 있는곳이 되도록 구문과 포인터에 대한 요약을 했다. 이 부록은 예를들어 당신이 함수들에게 주는 명령과 타입의 검증을 원한다면 특별히 유용한것이 될것이다. 그리고 함수, 변수 마크로들이 표준인지 아니면 각 시스템라이브러리에서 인계되는것인지도 말하고 있다.

2  에러 보고

2장 에러 보고

GNU C의 많은 함수들은 에러 조건들을 조사하여 보고하며, 때로는  당신의 프로그램이 이러한 에러 조건들을 조사할 필요가 있다.  예를 들어,당신이 입력파일을 열 때,당신은 그 파일이 실제로 정확하게  열릴 수 있는지를 확정해야 하며,에러메세지를 기록하고, 라이브러리  함수 호출에 실패하면 다른 적절한 조치를 취해야 한다.

이 장에서는 에러 보고 도구들이 어떻게 작동하는지를 설명하겠다. 이 도구들을 사용하려면 프로그램에 헤더파일 'errno.h'를 포함하라.

2.1  에러 체크

대부분의 라이브러리 함수들은 실패할 때에 지정하는 특정한 값을 반환한다. 그 특정한 값이란 전형적으로 -1,널(null) 포인터, EOF라는 상수등이다. 그러나 이러한 리턴 값은 에러가 발생했다는 사실만을 지적해  준다. 어떤 종류의 에러가 발생했는지를 알려면,변수 errno에 저장된  에러코드를 조사할 필요가 있다.이 변수는 헤더파일 'errno.h'에  선언되어 있다.

변수 volatile int errno

변수 errno는 시스템 에러 숫자를 담는다.당신은 errno의 값을 바꿀 수 있다. errno는 volatile로 선언되어 있으므로 부호취급자에 의해 바꿔질 수도 있다. 21.4 [취급자 정의하기],285쪽 참조. 그러나,합당하게 씌어진 부호취급자는 errno의 값을 저장하기 때문에, 당신이 부호취급자를 적어야 할 대를 제외하고는 바뀔 가능성에 대해서 우려할 필요는 없는 것이다.

프로그램이 시작할 때  errno의 초기값은 제로이다. 많은 라이브러리 함수들은 어떤 에러를 만날 때 그것을 제로가 아닌 값으로 만들도록 되어있다.이러한 에러 조건은 각 함수에 기록되어 있다.  이 함수들이 성공했을 때에는 errno를 바꾸지 않는다;  그러므로,당신은 호출이 실패했는지를 결정하지 않고서는 errno를  사용할 수 없다.그것의 적절한 사용법은 각 함수에 기록되어 있다.  호출이 실패하게 되면,당신은 errno를 검사할 수 있다.

많은 라이브러리 함수들은 다른 라이브러리 함수들을 호출하다가  실패한 결과로서 errno의 값을 제로아닌 값으로 만들 수 있다. 당신은 어떠한 라이브러리 함수라도 그 함수가 에러를 반환 할 때에 errno의 값을 변경할 수 있음을 알아야 한다.

운용 노우트 : ANSI C에서는 errno를 변수로서 다루기보다는 "변경가능한 왼편 값"으로 규정하고,그것이 매크로로 완성되는 것을 허용하고 있다. 예를 들면,그것의 확장은 *_errno()와 같이 함수 호출을 포함할 수 있다. 사실상,그것은 GNU 시스템상에 존재하는 그대로이다. 비 GNU시스템 상에서 GNU 라이브러리는 특정한 시스템이 혀용하는 모든 것을 할 수가 있다.

에러가 발생할 경우에 sqrt와 atan과 같이 완벽하게 적합한 값만을  반환하게 하는 몇몇 라이브러리 함수들도 있다.이와같은 함수들의 경우에는 만약 당신이 에러가 일어났는지를 체크하고자 한다면 함수를 호출하기 전에 지정된 방법으로 errno에 제로를 담아두고, 그런 다음에 그 값을 체크하게 된다. 모든 에러 코드는 부호 명칭을 갖고 있다; 그것들은 'errno.h'에  정의된 매크로이다.그 명칭들은 'E'와 대문자 또는 구두점으로 시작된다;당신은 이러한 명칭들을 예약된 명칭으로 생각하는 게 좋다. 1.3.3.[예약된 명칭],4쪽 참조.

에러코드의 값은 모두 양수의 정수이며 모두 별개로 구분된다. 단 하나의 예외가 있다: EWOULDBLOCK과  EAGAIN은 같은 경우이다. 이 값들은 분명한 것이므로,  당신이 switch 구문에서 라벨로 사용할 수 있다; 물론 EWOULDBLOCK과 EAGAIN을 그대로 사용할 수는 없다.당신은 프로그램에서 이들 부호 정수의 특정한 값에 대해서 다른 가정을 해서는 안된다.

errno의 값이 반드시 이러한 매크로들의 값과 일치할 필요는 없다. 왜냐하면,어떤 라이브러리 함수들은 각각의 상황에 따라 다른 에러 코드를 반환할 수도 있기 때문이다. 특정한 라이브러리 함수에서만 의미를 갖도록 되어있는 값들은 이 안내서가 그 함수의 값으로 보여주는 바로 그 값이다. 비 GNU 체제에서는 대개의 경우 어떤 시스템이 유효하지 않은 포인터가 인수로서 주어질 때에는 EFAULT를 반환할 수 있다.이것은 당신의 프로그램에서 버그의 결과로서 나타나고,GNU 체제에서는 일어나지 않기 때문에 우리는 개개의 함수를 기술할 때 EFAULT를 언급하지 않고 공간을 저장해  두었다.

2.2  에러 코드

에러코드 매크로는 헤더파일 'errno.h'에 정의되어 있다. 모든 에러코드 매크로는 정수의 상수값으로 발전한다. 어떤 에러코드들은 GNU 체제상에서는 일어나지 않는다. 그러나 다른 체제에서 GNU의  라이브러리를 사용할 때에 발생할 수 있다.

매크로  int EPERM

허가되지 않은 작동; 파일의 소유자(또는 다른 자원)나 명령을 수행할수 있는 특별한 권리를	가진 프로세스.

매크로   int ENOENT

그러한 파일이나 디렉토리 없음: 이것은 이미 존재하고 있을 걸로  예상한 파일이 없는 경우	에 일어나는 "파일이 존재하지 않습니다"라는 에러이다.

매크로  int ESRCH

처리가 지정된 처리 ID와 합치되지 않는다.

매크로  int EINTR

가로채기 함수 호출;발생한 비동기 신호와 호출의 방해된 종료.  이럴 경우에 당신은 다시	호출을 시도해보라. 당신은 EINTR로 실패하기 보다는,함수들을 취급된 부호 다음에서 시작	되도록 선택할 수 있다;  21.5 [가로채기 프리미티브],293쪽 참조.

매크로  int EIO

입출력 에러;언제나 물리적인 입출력 에러에 사용됨.

매크로 int ENXIO

그런 장치나 주소가 없음. 시스템이 당신이 파일에서 설정한 장치를  사용하고자 하나 그러	한 장치를 찾을 수 없었다. 이것은 장치파일이  잘못 인스톨되었거나, 물리적인 장치를 빠뜨	렸거나 또는 컴퓨터와 제대로 부합되지 않았음을 의미한다.

매크로 int E2BIG

인수가 너무 길다;실행함수에 의해서 실행되는 새로운 프로그램에 ( 23.5 [파일 실행하기],327쪽 참조) 주어진 인수가 너무 큰 메모리 공간을 사용할 때. 이러한 경우는 GNU체제에서는 생기지 않는다.

매크로   int ENOEXEC

유효하지 않은 실행파일 포맷. 이러한 조건은 실행함수에 의해 조사된다

23.5 [파일 실행하기],327쪽 참조.

매크로  int EBADF

잘못된 파일 기록; 예를 들어,닫혀진 파일을 기록하려고 하든지  쓰기 모드로 열려진 파일을 읽으려고 하는 경우(그 반대의 경우도)

매크로  int ECHILD

자식 프로세스(child process)가 없다.  이 에러는 자식 프로세스를 다루는  오퍼레시션을 사용했는데 다루기위한 어느 프로세스도 존재하재 않을 때 발생한다.

매크로  int EDEADLK

교착상태 회피; 시스템 자원의 점유는 교착상태의 결과를 낳는다.  시스템이 모든 상황을 다 알아챌거라고 보증하지 못한다.이 에러는  당신이 운이 좋아서 시스템이 알아챈 것임을 의미한다; 시스템은  망설이고 있을 뿐이다. 8.11 [파일 교착],118쪽 참조.

매크로  int ENOMEM

이용할 메모리가 없음.메모리 용량을 다 썼으므로 시스템이 더이상 메모리를 할당할 수없다.

매크로 int EACCES

허용되지 않음;파일이 시도하려는 작동을 허용하지 않는다.

매크로 int EFAULT

주소 오류; 유효하지 않은 포인터가 발견됨.

매크로 int ENOTBLK

어떤 상황에서 주어진 파일에 특별한 블록이 없는 경우. 예를 들면, 보통의 파일을 유닉스 파일 시스템에 마운트하려 하면 이 에러가 발생한다.

매크로  int EBUSY

시스템 자원 사용중; 분배될 수 없는 시스템 자원이 이미 사용중일  때, 예를 들어,당신이 현재 마운트된 파일시스템의 루트에서  한개의 파일을 지우려 할 때에, 이 에러를 만난다.

매크로 int EEXIST

파일이 존재: 새로운 파일로 만들겠다고 한 파일이 이미 존재한다.

매크로 int EXDEV

파일 시스템이 인지할수 없는 영역에 부적당한 링크를 만들려고 시도할 때 이 에러메세지가 나온다. 이것은 당신이 링크를 사용할때만 발생하지만 ( 9.3절의 [Hard Links] 127페이지를 보라 )  또, rename으로 파일을 재명명할 때 발생하기도 한다. ( 9.6절의 [Renaming Files] 페이지 129를 보라)

매크로 int ENODEV

디바이스의 특별한 정렬을 하는 함수에 주어진 디바이스가 잘못된 타입이다.

매크로 int ENOTDIR

필요하다고 요청된 디렉토리가 존재하지 않을 때 발생.

매크로 int EISDIR

파일이 하나의 디렉토리 이다; 쓰기(writing)위해 이 디렉토리를   열려고 시도할 때 이 에러는 발생한다.

매크로 int EINVAL

적합하지 않은 인수. 이것은 라이브러리 함수에 잘못된 인수를 주는것과 같은 종류의 다양한 문제를 지적하는데 사용한다.

매크로 int ENFILE

지금 현재 프로세스에 너무 많은 파일이 오픈되어서 더 이상 오픈할수  없다.  복제된 기술자가 이 제한에 대하여 셈한다.

매크로 int ENFILE

전체 시스템에 열려져있는 너무 많은 개별파일들이 있다. 연결되 채널의 어떤 것이라도 파일	하나를 여는것과 같이 센다.; 8.5.1절의 [연결된 체널]을 참조하라. 이 에러는 결코 GNU 시스	템에서는 발생하지 않는다.

매크로 int ENOTTY

하나의 보통 파일에서 터미날 모드를 정하려 시도하는것과 같은 부적합한 입출력 제어 오퍼레이션에 발생.

매크로 int ETXTBSY

현재 사용되고 있는 파일을 다시 읽거나 쓰기위해 오픈하려 시도할 때 발생 ("text fiel busy" 라고 한다.) 이것은 GNU시스템에서는 에러가 아니다; 텍스트는 필요하면 카피된다.

매크로 int EFBIG

파일이 너무 크다; 파일의 크기가 시스템이 허용하는 것 보다 더  크다.

매크로 int ENOSPC

디바이스에 공간이 남겨지지 않았다.; 파일에 쓰기 명령을 줬을 때 디스크가 가득차서 공간이 남아있지 않으면 실패한다.

매크로 int EXPIPE

적합하지 않은 탐색 명령.( pipe에서 처럼 )

매크로 int EROFS

읽기 전용으로 된 어떤 파일시스템에 무언가를 수정하려고 시도할 때

매크로 int ENLINK

너무 많이 연결.; 하나의 단일한 파일의 링크 수가 너무 길다. 리네임은 이미 할수 있는것보다 많은 링크를 가진 한 파일을 리네임하면 이 에러의 원인이 된다. ( 9.6절의 [파일 재명명]페이지 129 참조)

매크로 int EPIPE

부러진 파이프; 다른 파이프의 끝에서 프로세스를 읽는 것은 불가능 하다.  모든 라이브러리 함수는 SIGPIPE로 일반화된 에러코드를 리턴한다.  이 신호는 만약 처리되지 않거나 막아지지 않으면 그  프로그램을 멈추게 한다.  그러므로 당신의 프로그램이 처리되거나  블록된  SIGPIPE를 갖지 않으면  항상 EPIPE가 보일 것이다.

매크로 int EDOM

도메인 에러; 인수의 값이 정의된 함수를 지나 도메인에게 전달되지 않았을 때 수학적 함수에 의해 사용된다.

매크로  int ERANGE

범위 에러; 결과 값이 오버플로우나 언더플로우로 인해 표현되지 않을 때  수학적 함수에 의해 사용된다.

매크로 int EAGAIN

자원을 일시적으로 사용할수 없다. 그 호출은 나중에 당신이 다시 재시도  할수 있도록 한다.  오직 분기점에서 이러한 이유로 EAGAIN에러 코드를 리턴한다.

매크로 int EWOULDBLOCK

비블럭화 모드로 정해진 어떤 대상에 블록을 시도하려 하는 오퍼레이션에 대해 발생.

이식성 : 4.4BSD와 GNU에서 EWOULDBLOCK 와 EAGAIN은 같다.  BSD초기 버전에서는 이것이 두 개의 독립적 코드 로서 EWOULDBLOCK는 비블록화 모드로 정해진 대상에 블록을 시도하는 입출력 오퍼레이션을 지적하는데 사용하고 EAGAIN은 에러의 다른종류에 사용했다

매크로 int EINPROGRESS

비블록화 모드로 선택된 대상에 일으킨 완전하지 않은 오퍼레이션에 대해 발생.  어떤 함수들은 항상, 블록을( connect처럼 ; 11.8.1절의  [연결]을 참조) 결코 리턴하지 않아야한다.EWOULDBLOCK 대신에 그들은 어떤 시간을 취하고( 즉 일정 시간이 지난후) 시작되는  오퍼레이션을 지적하기 위해 EINPROGRESS를 리턴한다. 그 호출에 EALREADY가 리턴되어 완전하기 전에 그 대상을 다루려 시도할 때.

매크로 int EALREADY

한 오퍼레이션이 비블럭화 모드의 선택을 가진 대상에 이미 진행중이다.

매크로 int EALREADY

소켓을 요청했을 때 지정된 소켓이 존재하지 않는다.

매크로 int EDESTADDRREQ

목적 주소가 그것이 필요한 소켓 오퍼레이션에 공급될수 없다.

매크로 int EMSGSIZE

소켓에 보낸 메시지의 크기가 지원되는 최대 크기보다 크다.

매크로 int EPROTOTYPE

그 소켓 타입이 요청된 통신 프로토콜에서 지원하지 않는다.

매크로 int ENOPROTOOPT

당신은 소켓에 의해 사용되어지고 있는 특별한 프로토콜에서 이해할수 없는 소켓옵션을 지정하였다. ( 11.11절의 [Socket Options]를 참조)

매크로 int EPROTONOSUPPORT

그 소켓 도메인은 요청한 통신 프로토콜을 지원하지 않는다. ( 아마도 요청된 프로토콜이 완전히 부적합하다.)   11.7.1절의 [Creating a Socket]를 보라.

매크로 int ESOCKTNOSUPPORT

그 소켓타입을 지원하지 않는다.

매크로 int EOPNOTSUPP

당신이 요청한 그 오퍼레이션을 지원하지 않는다.  어떤 소켓함수는 소켓의 모든 타입들에서 이해할수 없고 다른것들은 모든 통신 프로토콜을 충족시키지 못할 것이다.

매크로 int EPFNOSUPPORT

당신이 요청한 소켓통신 프로토콜 부류들은 지원하지 않는다.

매크로 int EAFNOSUPPORT

소켓을 위하여 지정된 주소의 부류들이 지원되지 않는다;  그 주소가 소켓에서 사용되는 프로토콜과 일치하지 않는 것이다. 11장[Sockets]의 149페이지를 참조하라.

매크로 int EADDRINUSE

요청된 소켓주소가 이미 사용중이다.  11.3절 [Socket Addresses] 150페이지를 보라.

매크로 int EADDRNOTAVAIL

요청된 소켓주소가 유용하지 않다.; 예를 들어 당신이 소켓이름으로 주려고 시도한 것이 로컬 호스트 이름과 맞지 않다. 11.3절[Socket Address]를 보라.

매크로 int ENETDOWN

소켓 오퍼레이션이 네크웍 다운이 이유가 되어 실패했다.

매크로 int ENETUNREACH

소켓 오퍼레이션이 호스트가 포함하고 있는 subnet에 도달할수 없어서 실패했다.

매크로 int ENETRESET

원격 호스트가 파괴되었기 때문에 네트웍 연결을 다시 지정한다.

매크로 int ECONNABORTED

네트웍 연결이 실패하였다. 네트웍 연결이 단절되었는데 그 이유는 원격 시스템이 재부팅하거나 아니면 복구할수 없는 프로토콜 위반인 경우처럼 로컬 호스트의  제어를 벗어난 것이 이유이다.

매크로 int ENOBUFS

입출력 오퍼레이션을 위한 커널의 버퍼들이 모두 사용중이다.

매크로 int EISCONN

당신은 이미 연결된 소켓을 다시 연결하려 시도하고 있다.  11.8.1절[Connectings]를 보라.

매크로 int ENOTCONN

그 소켓은 어느것과도 연결할수 없다.  당신이 첫째로 데이터의   목적지를 정하지 않고 한 소켓을 통해 데이터를 전송하려 할 때 이 에러가 발생한다.

매크로 int ESHUTDOWN

그 소켓은 이미 폐쇠되었다.

매크로 int ETIMEDOUT

정해진 타임아웃을(timeout) 갖은 소켓 오퍼레이션이 정해진 시간 동안 응답을 받지 못했다.

매크로 int ECONNREFUSED

원격 호스트가 네트웍 연결에 대한 허용을 거절하였다. (특별히 요청된 서비스가 실행되지 않기 때문에)

매크로 int ELOOP

파일이름을 탐색하려는데 너무 많은 수준의 기호연결(sysbolic links)이 있다. 이것은 종종 기호연결의 한 주기를 가리킨다.

참고로 sysbolic links는 데이터베이스 프로그램과 스프레드 시트 등과 같은 프로그램들 사이에서 상호 간의 원활한 데이터 교환을 위해 사용되는 데이터 파일의 형식이래요.

매크로 int ENANETOOLONG

파일 이름이 너무 길거나( PATH_MAX보다 더; 27.6절 [Limits for Files] 374 페이지를 참조하라) 호스트 이름이 너무 길다.( gethostname 이나 sethostname에 있는; 26.1절 [Host Identification] 367페이지를 참조)

매크로 int EHOSTDOWN

요청된 네트웍 연결을 위한 원격 호스트가 다운이다.

매크로 int EHOSTUNREACH

요청된 네트웍 연결을 위한 원격 호스트에 도달할수 없다.

매크로 int ENOTEMPTY

빈 디렉토리라고 예상했던 곳이 비어있지 않다. 특별히 이 에러는  당신이 디렉토리를 지우려 시도할 때 발생한다.

매크로 int EUSERS

파일할당 시스템이 너무 많은 유저로 인해 혼란하다.

매크로 int EDQUOT

사용자의 디스크 할당이 초과되었다.

매크로 int ESTALE

맛이간 NFS 파일 핸들.  이것은 파일 시스템이 서버 호스트 상에서 재정열한것에 기인한 것으로 NFS 시스템안의 내부적 혼란을 지적한다.

매크로 int ERENOTE

이미 NFS 마운트 파일로 지정된 한 파일을 한 원격 파일 시스템이 NFS 마운트로 만들려 시도할 때 발생. (이것은 운영체제상의 에러이지만 우리는 이 에러를 불가능한 코드로 만들고 GNU시스템상에서 작업하기를 기대하는 것이다.)

매크로 int ENOLCK

유용한 락이 아니다. 이것은 파일 락킹 함수들에 의해 사용된다.  8.11절 [File Locks] 118페이지를 보라. 이 에러는 GNU 시스템에서는 결코 발생하지 않는다.

매크로 int ENOSYS

함수가 이행되지 않았다.  어떤 함수들은 정의된 옵션이나 명령들이 어떤것에서도 지원되지 않는 것이 있다.  만약 당신이 요청한 함수에서 이런 에러를 얻는다면 그것들은 지원되지 않는 것이다.

매크로 int EBACKGROUND

GNU 시스템에서 어떤 오퍼레이션의 호출자가 터미날의 전면처리 그룹에 없을 때 서버지원 프로토콜에 이 에러가 리턴된다.  사용자들은 보통 이 에러를 보지 못하는데 왜냐하면 함수들은 SIGTTIN이나 SIGTTOU신호로 해석하여 읽고 쓰기 때문이다.  24장[Job Control]  335페이지를 프로세스 그룹과 이들은 신호의 정보를 위해 보라.

매크로 int ED

경험있는 사용자는 무엇이 잘못인지 알 것이다.

매크로 int EGREGIOUS

당신이 무엇을 했지?

매크로 int EIEIO

집에가서 따뜻하고, 신선한 한잔의 우유를 마셔라

매크로 int EGRATUITOUS

이 에러 코드는 목적이 없다.

2.3  에러 메시지

라이브러리에는 당신의 프로그램이 라이브러리를 호출하다가 실패할 때에 흔히 일어나는 에러를 메세지로 보낼 수 있도록하는 함수와 변수들이 있다.  함수 strerror과 perror은 특정한 에러코드로 정형화된 에러메세지를  보낸다; 변수 program_invocation_short_name은 에러가 난 프로그램의  명칭을 쉽게 찾도록 해준다.

함수 char * strerror  (int errnum)

strerror 함수는 인수 errnum을 받아서 에러코드(2.1 [에러 체크] 참조)를 문자열을 기술한 에러메세지로 그려낸다. 리턴값은 이 문자열의 포인터이다. 인수 errnum의 값은 보통 변수 errno에서 전달된다. 당신은 함수 strerror이 반환한 문자열을 변형시켜서는 안된다. 또한,만약 당신이 strerror을 계속 호출하게 되면,그 문자열은 덧씌워질 것이다. (그러나 어떠한 라이브러리 함수도 당신이 알지 못하게strerror함수를 호출하지 않음은 믿어도 좋다.)

함수 strerror은 헤더파일 'string.h'에 선언되어 있다.

함수  void perror  (const char *message)

이 함수는 에러메세지를 문자열 stderr에 기록한다; 7.2 [표준 문자열], 63족 참조. 만약에 널(null) 포인터라든지 공백 문자열인 message를 써서 perror를 호출하게 되면, perror는 errno에 해당하는 에러메세지를  기록하고,뒤에 새로운 행을 추가한다. 그렇지않고 널(null)이 아닌 인수 message를 쓰게 되면,perror는 에러메세지 앞에 해당 문자열을 갖다둔다. 거기에는 errno에 해당하는 에러문자열로부터 메세지를 분리하기 위해서 콜론(:)과 공백문자를 추가한다. 함수 perror는 'stdio.h'에 선언되어 있다.

strerror와 perror는 어떤 에러코드가 주어지든지 간에  같은 메세지를 만들어낸다; 규정된 텍스트는 시스템에 따라 변한다. GNU 체제에서는 메세지가 아주 짧다; 두줄이상의 메세지나 삽입된 줄은아예 없다.각 에러메세지는 대문자로 시작하며 구두점은 쓰지 않는다.

호환성 노트 : 함수 strerror는 ANSI C의 새로운 특성이다. 과거의 많은 C언어 체제에서는 이 함수가 제공되지 않았다.  터미널로부터 입력을 읽어들이지 않는 많은 프로그램들은 어떤 시스템  호출이 실패하게 되면 종료되도록 만들어져 있다. 전통적으로,  그와같은 프로그램으로부터 나오는 에러메세지는 디렉토리는 기술하지 않고 그 프로그램의 명칭만으로 시작한다.  당신은 그 명칭을 변수 program_invocation_short_name에서 찾아볼 수 있다; 완전한 파일 명칭은 변수 program_invocation_name에 저장된다:

변수 char *program_invocation_name;

이 변수의 값은 현재 진행중인 프로그램 실행을 시작하는데 사용되는 명칭이다. 그것은 argv[0]와 동일하다. 이것이 반드시 유효한 파일명칭을 필요로 하는 것이 아님을 주의하라; 흔히 유효한 파일명칭은 디렉토리 명칭을 담고있지 않다.  22.1 [프로그램 인수], 311쪽 참조.

변수  char * program__invocation__short__name

이 변수의 값은 현재 처리중인 프로그램을 부르는 데 사용되는 명칭으로서 디렉토리 명칭이 제거된 것이다.(말하자면,이것은  program_invocation_name에서 마지막 슬래쉬까지를 빼버린 것과 동일하다.)

라이브러리 초기화 코드는 메인을 호출하기 전에 이 두 개의 변수를  구축(構築)한다.

운용 노트 : 이 두개의 변수는 GNU의 확장이다. 만일 당신이 GNU 이외의 라이브러리를 쓰고 싶으면,메인 함수에서 argv[0]의 값을 저장한 다음에 당신이 손수 디렉토리의 명칭을 제거하면 된다. 우리는 메인 함수에서 명시적인 표현을 하지 않고서 그 자체에서 에러 보고용 서브루틴을 담고 있도록 하기 위하여 이와같은 확장을 하게 되었던 것이다.

파일을 제대로 열지 못하고 실패를 하게 되는 예를 들어 보기로 하자. 함수 open_sesame은 읽기모드로 주어진 파일을 열고 성공하면 문자열을 반환한다. 라이브러리함수 fopen은 파일을 열 수 없을 때 널(null) 포인터를 반환한다.이같은 상황에서,open_sesame은 함수 strerror을 사용하는 적절한 에러 메세지를 만들어내고서 프로그램을 종료한다. 만약 우리가 strerror에 에러 코드를 보내기 전에 다른 어떤 라이브러리를 호출하고자 한다면,우리는 그것을 지역 변수에 저장하여야만 한다.왜냐하면, 그 다른 어떤 라이브러리 함수들이 동시에 errno를 덧씌울 것이기 때문이다.

/* 음,..예제를 만났군요..흐흐*/

#include  <errno.h>

#include  <stdio.h>

#include  <stdlib.h>

#include  <string.h>

/*헤더파일을 포함시키는 건 나두 알겠군*/

/*으음..함수의 형태로 FILE을 선택할 수도 있었군*/

FILE  * open_sesame  (char  *name)

/*요건 인수로 문자형 포인터를*/

{

FILE  *stream;

/*우잉? 이건 파일명칭이 포인터로? 흐흐흐..교과서적인 내용*/

errno  =  0;

/*요건 뭔가 이상타? errno를 int라고  선언하지 않아두 되남?*/

stream  =  fopen  (name,  "r");

/*파일을 읽기 모드로 열구*/

if  (stream  ==  NULL)

/*파일이 존재하지 않으면?*/

{

fprintf  (stderr,  "%s:  Couldn't  open  file  %s;

%s\n",program_invocation_short_name,  name,

strerror  (errno));

/*아아! fprintf 함수는 (1) 무엇을 (2)어디에서 읽어서 (3)어딘가에 쓰는 것 3부분으로 구성되네요! 이 몸은 이런 건 외울 수가 엄서여==건망증==프로그램 짜다가 필요하면 그때 보지요*/

exit  (EXIT_FAILURE);

/*난 exit와 return의 차이두 몰러,흑*/

}

else

/*파일이 존재하면?*/

return  stream;

}

3  메모리 할당

3장 메모리 할당

GNU 체제는 프로그램 상에서 분명하게 통제하는 방식으로 메모리를 할당하는 몇가지 방법을 제공한다. 그것들은 일반성과 유효성의 범위 내에서 변화한다.

□ malloc 함수는 일반적인 동적 메모리를 충분히 확보한다.  3.3 [제한없는 할당], 22쪽 참조.

□ 다른 함수로는 obstack이 있는데,이것은 malloc보다는 덜 일반적이지만 스택 할당에서는 훨씬 더 편리하고 유용하다. 3.4 [Obstacks],28쪽 참조.

□ 함수 alloca는 저장소를 동적으로 할당하고 자동적으로 해제해 준다. 3.5 [자동적인 크기 변화],36족 참조.

3.1  동적 메모리 할당이란?

동적 메모리 할당이란 프로그램이 어떤 정보를 어디에 저장해놓고 실행될 것인가를 결정하는 기법이다. 동적메모리는 당신이 작업하는  데이터가 메모리 블럭을 몇 개나 필요로 하는지 또는 그 메모리들을 얼마동안 사용할 것인지에 따라 요구된다.

예를 들면, 당신이 입력파일에서 읽어들인 한 개의 라인을 저장하려면 하나의 블럭을 필요로 한다;그런데,한 개의 라인이 어느 정도의 길이로  되어야 하는지가 제한이 없으므로,당신은 저장소를 동적으로 할당할 수 밖에 없으며,그것을 당신이 더 큰 라인을 읽어들였을 때보다도 더 크게 할당해야만 한다. 또는,당신이 입력 데이터에서 만든 각 레코드나 정의를 위해서 한 블럭을 필요로 하기도 한다;그런데,당신이 사전에 그것이 얼마나 많을지를  모를 수 있으므로,각각의 레코드나 정의가 일혀질 때마다 새로운 블럭을 할당해야만 한다. 당신이 동적 메모리를 할당할 때에는 메모리 한 블럭을 할당할 때마다 프로그램상에서 분명하게 표현하여야 한다. 당신은 공간을 할당하고 싶을 때마다 함수나 매크로를 호출하여야하며, 인수를 가지고 그 크기를 확정해야 한다. 당신이 그 공간을 해제하고 싶을 때에도,다른 함수나 매크로를 호출하여 그렇게 처리하면 된다. 당신은 이러한 작업을 원할 때마다 원하는 만큼 할 수 있는 것이다.

3.2  동적할당과 C

C언어는 C프로그램에 있는 변수들을 통해 메모리 할당을 위한 두가지 종류를 지원한다.

□ 정적할당은 당신이 정적변수를 정의할 때 발생되는 것이다.  각 정적변수는 정해진 크기의, 메모리 한 블록을 정의한다.  그 공간은 당신의 프로그램이 시작되어 일단 할당되면 절대 해제되지   않는다.

□ 자동 할당은 함수의 인자나 지역변수와 같은 자동변수를 정의할 때 발생한다.자동변수를 위한 공간은 그 정의를 포함한 곳(compound statement)에 들어가면 할당되고 나오면 해제된다.	GNU C에서 자동저장의 길이가 다양하게 표현되어질수 있지만 다른 C에서는 상수이어야만 한다.

동적할당은 C변수들에의해 지원되지 않는다; C에는 "다이나믹"이라는 스토리지 클래스가 없고 동적으로 할당된 공간에 저장된 값은 결코 C의 변수를 통해 할수 없다. 오직 포인터를 통해 동적으로 할당된 공간을 참조하는 것이 유일한 방법이다. 그래서 좀 불편한데다, 동적공간을 통해 활동하는 프로세스는 실행시간이 좀 더 필요하기 때문에 프로그래머들은 정적이나 자동변수를 통해 할수 없을 경우에만 이 동적 할당을 사용한다.

예를들어 만약 당신이 구조체 foobar를 위해 어떤 공간을 동적으로 할당하기 원한다면 당신은 단순히 struct foobar라고만 변수를 선언할수 없다. 그러나 struct foobar * 의 형으로 포인터 변수를 선언하면 그 공간의 주소를 지정할수 있다.  그러면 당신은 *와 -> 오퍼레이터를 사용해서 그 공간의 내용을 참조할수 있다.

{

struct  foobar  *ptr

=  (struct  foobar  *)  malloc  (sizeof  (struct   foobar));

ptr->name  =  x;

ptr->next  =  current_foobar;

current_foobar  =  ptr;

}

3.3  자유로운 할당

가장 일반적인 동적할당을 위한 도구는 malloc인데 그것은 어느 크기의 메모리 블록도 언제든지 할당하는걸 허용하고 또한 그들을 언제든지 크게하거나 작게하거나 할수 있고 언제든지 그 불럭들을 개별적으로 메모리 공간을 해제하는 것도 가능하다.

3.3.1  메모리 할당의 기초

메모리 공간을 할당하기 위해 부르는 malloc의 프로토타입은 'stdlib.h' 이다.

void * malloc (size_t size)

이 함수는 새로이 할당된 블록의 포인터를 long의 크기로 리턴하고 아니면, 할당할수 없었을때는 널 포인터를 리턴한다. 할당된 블록은 당신이 스스로 초기화해야한다.( 아니면 calloc을 사용하는데 그것은 3.3.5절의 [Allocating Cleared Space], 25페이지를 보면 있다. ) 보통 당신은 메모리 블록에 저장하기 원하는 대상의 종류대로 포인터에 그 값을 캐스트(cast) 할 것이다. 여기에 그렇게 하는 예를 보여준다. 그리고 memset이라는 라이브러리 함수를 이용하여 블록을 0으로 초기화하는 것도 보여준다.

/*제가 좀 주석을 달아볼까요?*/

struct  foo  *ptr;

. . .

ptr  =  (struct  foo  *)  malloc  (sizeof  (struct  foo));

/* 위 라인은 구조체로 선언된 foo의 크기만큼 malloc을 사용해서  메모리를 할당한다음 구조체 foo의 타입으로 캐스트된 포인터(ptr)에 그 번지값을 넘기네요.. */

if  (ptr  ==  0)  abort  ();

/* 만약 할당이 실패하면.. */

memset  (ptr,  0,  sizeof  (struct  foo));

/* 위의 memset는 인자로 주어진 포인터의 번지를 받아 맨 마직막 인자로 주어진 크기만큼 0으로 메모리 블록을 초기화 해요. */

당신은 캐스트(case)하지 않고 어느 포인터 변수 안에도 malloc의 결과를 저장할수 있는데 그것은 ANSI C는 자동적으로 필요할 때 void형 포인터를 다른 어떤형으로도 변환시키기 때문이다.그러나 캐스트(case)는 지정명령어를 명시하거나 당신이 전통적인 C로 코딩을 원한다면 필요하다.스트링을 위한 메모리를 할당할때 malloc의 인수에 스트링의 길이+1을 하는 것을 잊지마라.이것은 필요한 공간임에도 불구하고 스트링의 길이에 포함되지 않는 널문자로 끝나기 때문이다.

/* 보충하면 스트링의 맨 끝에 있는 널문자를 스트링의 문자열이 끝나는 곳임을 나타내기 위해서 중요한 공간이지만 스트링의 길이를 셀때는 포함되지 않으므로 스트링을 위해 메모리를 할당할때는 그 널문자를  위해 스트링의 길이 +1 만큼의 메모리를 할당받자란 얘기일걸요.*/

예를들면:

char  *ptr;

. . .

ptr  =  (char  *)  malloc  (length  +  1);

5.1절의 [Ropresentation of Strings] 페이지 45를 보면 이것에 대한  더 많은 정보가 있다.

3.3.2  malloc의 예제들

malloc은 원하는 메모리 블록보다 메모리 공간이 적은 경우에 널을 리턴한다. 당신은 모든malloc의 값을 체크해볼수 있기 때문에 malloc을 부르고 그 값이 널 포인터라면 에러를 사용자에게 보고할수 있고 아니면 그값을 오직 리턴하는 서브루틴을 쓰고자 할 때 유용한다. 이 함수는 상투적으로 xmalloc라고 부른다.  여기에 그것이 있다:

void  * xmalloc  (size_t  size)

{

register  void  *value  =  malloc  (size);

/* 위 라인은 size만큼의 메모리를 할당하려고 malloc을 사용하는 데 그 malloc의 리턴값을 *value로 받았네요. */

if  (value  ==  0)

/* value가 0이면, 즉 메모리 할당실패. */

fatal  ("virtual  memory  exhausted");

return  value;

/* 할당에 성공하면 블록의 번지가 저장된 값을 리턴 */

}

여기에 malloc의 실제 사용예가 있다.  savestring이라는 함수는 널문자로 끝나는 새로운 할당 공간에 문자들의 열을 복사하는 함수다.

char  * savestring  (const  char  *ptr,  size_t  len)

{

register  char  *value  =  (char  *)  xmalloc  (len  +  1);

/* 위에서 만든 xmalloc함수를 사용했군요. 그러면 할당에 성공했다면 여기서 value는 len+1의 크기를 가진  블록의 번지를 저장하고 있겠군요.  */

memcpy  (value,  ptr,  len);

/* memcpy함수를 사용해서 savestring함수가 인자로 받아온 ptr을 value로 len길이 만큼 복사했구요... */

value[len]  =  '\0';

/* value의 맨 끝에 널문자를 하나 저장하면 value는 위에서 설명한대로 스트링을 저장한 블록의 주소가 되겠지요? */

return  value;

}

malloc으로 할당된 메모리 블록은 당신에게 어떤 데이터 타입도 저장할 수 있고 그것과 일치시킴을 보증한다. GNU시스템에서 주소는 항상 8의 배수로 증가한다. 그러나 블록이 16개보다 많은 경우에 주소는 항상 16의 배수이다.  오직 드물게 높은 경계가 필요한 경우에는 memalign이나 valloc을 사용한다. ( 3.3.7절의 [Aligned Memory Blocks]를 보라)

무언가를 위해 사용되어진 블록의 끝에 할당된 메모리는 아마도 다른 블록으로 이미 다른 malloc에의해 할당되어졌을 것이다. 만약 이미 당신이 요구한것보다 더 많은 메모리 블록을 취급하길 원한다면 자칫하면 내 자신의 블록의 데이터를 파괴하거나 아니면 다른 블록의 내용을 파괴할지도 모른다.  만약 당신이 이미 한 블록을 할당받았고 거기에 더 큰 공간이 필요하다면 realloc을 사용하라( 3.3.4절의 [Changing Block Size] 24페이지를 보라)

3.3.3  malloc에 의해 할당된 메모리 해제하기

당신이 malloc으로 얻었던 메모리블럭을 더 이상 사용하지 않으면 free함수를 사용하여 어떤 블록이 그 공간을 다시 할당받을수 있도록 메모리를 해제해야 한다.  free함수의 프로도타입은 'stdlib.h'이다.

void free (void *ptr)

이 함수는 ptr에의해 지정된 공간의 블록을 해제하는 역할을 한다.

void cfree (void *ptr)

이 함수는 free와 같은일을 한다. 이것은 SunOS와 호환을 위하여 제공된다.블록의 해제후에 그 블록의 내용은 변한다.  그래서 해제후에 그 블록안에서 어떤 데이터를 발견할지( 마치 연결된 블록에서 다음 블록의 포인터처럼)아무것도 예상할수 없다.  당신은 그 블록을 해제하기 전에 그 블록의 밖으로 당신이 필요한 모든 것을 복사하라.  여기에 연결된 모든 블럭과 포인트된 문자열을 해제하는 적당한 방법의 예가 있다.

struct  chain

{

struct  chain  *next;

char  *name;

}

/* 단순 연결 리스트( Single Linked List) 구조를 가졌군요.  다시 참조해보세요..*./

void free_chain  (struct  chain  *chain)

{

while  (chain  !=  0)

/* chain의 값이 0이 될 때까지 즉 연결된 리스트가 메모리를 다 해제할때까지 루프를 돌겠군요.  */

{

struct  chain  *next  =  chain->next;

free  (chain->name);

free  (chain);

/* 먼저 안에 저장된 name를 먼저 해제하고(그냥 단순변수가 아니라 포인터로 저정됐으므로) 다음에서야 chain블럭을 해제하는군요.  */

chain  =  next;

/* 그리고 다음을 가르키게 해서 위와같은 동작의 반복을 하면 다 해제되겠지요?  */

}

}

가끔, free함수는 운영체제에게 실제로 메모리를 리턴할수 있고 프로세스를 작게 만들지만 보통은 그 공간의 재사용을 위해 malloc에 의해 다시 불리워진 후에 허용된다. 즉, 그 공간(이미 해제된)은 해제된 메모리 리스트를 당신의 프로그램안에 남겨 다른 malloc에의해 내부에서 사용된다.  프로그램의 끝에서 블록을 해제할수 없는데 그것은 프로그램에서 쓰인 모든 공간을 그 프로세스가 끝날 때 시스템에게 되돌려 주어야 하기 때문이다.

3.3.5  깨끗한 공간 할당하기

함수 calloc은 메모리를 할당하고 거기에 0을 채운다. 이 함수는 'stdlib.h'에 선언되어 있다.

함수  void * calloc  (size_t count, size_t eltsize)

이 함수는 각 eltsize 크기마다 count요소의 벡터를 담기에 충분한 길이의 블럭을 할당한다.그 내용은 모두 지워지고 calloc을 반환하기 전에 0으로 채운다.

당신은 calloc을 다음과 같이 정의할 수 있다.

void  * calloc  (size_t  count,  size_t  eltsize)

/*으힉!..size_t라는 데이터 형태도 있었남?..왕초보*/

{

size_t  size  =  count  *  eltsize;

void  *value  =  malloc  (size);

if  (value  !=  0)

memset  (value,  0,  size);

/* value지점을 size만큼의 0으로 채우는 함수이군*/

return  value;

/*이렇게 하면 포인터 주소가 반환되남유? 고수님들?*/

}

요즘은 calloc을 자주 쓰지 않는다,왜냐하면 더 자주 쓰이는 다른 함수들을 간단히 결합해서 써도 되기 때문이다. calloc은 아주 쓸 모 없지는 않지만 구시대의 유물이 되어가고 있다.

3.3.6  malloc을 위한 효율적인 조치

malloc을 가장 잘 사용하려면, malloc의 GNU 버젼은 항상 적은 양의 메모리를 그 크기가 2의 배수인 블럭으로 나누는 것임을 알아야 한다. 그것은 분할된 영역을 2의 배수로 유지한다.이것은 그 크기를 한 페이지의 크기로 만들 수 있게 한다.그러므로,만약 당신이 malloc을 효율적으로 만들기 위해서 작은 블럭의 크기를 선택할 수 있는 경우라면 그것을 2의 배수로 만들어라.

일단 한 페이지가 특정한 블럭의 크기로 나누어지게 되면, 그것은 모든 블럭이 해제되지 않는 이상 다른 크기로 재사용될 수는 없다. 많은 프로그램에서 블럭이 다른 크기로 재사용되는 일은 좀처럼   일어나지 않는다. 따라서,많은 다른 목적으로 사용할 블럭들을 같은 크기로 나누어 놓음으로써 프로그램이 메모리를 효율적으로 사용토록 할 수가 있는 것이다.

한 페이지 또는 그 이상의 메모리 블럭들을 요구할 때, malloc은 다른 방법을 쓴다;그 크기를 한 페이지의 배수로 만들고,그 블럭을 통합하거나 필요한 만큼 나누어서 사용할 수 있다.

두가지의 방법을 쓰는 이유는 작은 블럭들을 가능한한 빨리 할당하고 해제하는 것이 중요하다는 데 있다. 그러나 큰 블럭의 경우에는 프로그램이 그것을 사용하는 데 어느 정도의 시간을 소요하기 때문에 속도가 그리 중요치 않다. 또한 큰 블럭은 당연히 그 숫자에 있어서 적은 것이다. 그러므로, 큰 블럭에 있어서는 낭비된 공간을 최소화하는 데 시간을 좀더 투자하는 방법이 의미가 있는 법이다.

3.3.7  잘 정돈된 메모리 블럭 할당하기

GNU 체제에서 malloc과 realloc에 의해 반환되는 블럭의 주소는 항상 8의 배수이다. 만약 당신이 8보다 더 큰 2의 배수의 배수를 주소로 갖는 블럭을 원한다면, memalign이나 valloc을 사용하라.이 함수들은 'stdio.h'에 선언되어 있다.

GNU 라이브러리를 사용해서 당신은 menalign과 valloc이 반환하는 블럭을 해제하기 위하여 free를 쓸 수 있다. free는 BSD에서는 작동 하지 않으며,BSD는 그러한 블럭들을 해제하기 위한 어떤 수단도 제공 하지 않는다.

함수 void * memalign  (size_t size, size_t boundary )

memalign 함수는 그 주소가 boundary의 배수인 size 바이트의 블록을 할당한다. boundary는 2의 배수여야만 한다! memalign 함수는 malloc  함수를 호출하여 다소간 더 큰 블럭을 할당하고나서,지정된 boundary상에 있는 블럭내의 주소를 반환한다.

함수 void * valloc  (size_t size)

valloc을 사용하는 것은 memalign을 쓰되 두번재 인수의 값인 페이지 크기를 생략하는 것과 같다. 그것은 이렇게 보충된다:

void  * valloc  (size_t  size)

{

return  memalign  (size,  getpagesize  ());

}

3.3.8  메모리 힙의 일관성 체크하기

당신은 mcheck 함수를 사용하여 동적 저장소의 일관성을 체크하는 데  malloc을 요청할 수 있다.이 함수는 GNU 확장 함수이며,'malloc.h'에 선언되어 있다.

함수 int mcheck  (void (*abortfn) (void))

mcheck를 호출하게 되면 malloc에게 가끔씩 일관성 체크를 수행하도록 한다. 일관성 체크는 malloc이 할당해 놓은 블럭의 끝에다 past라고 기록해 놓는 것과 같은 일을 할 것이다.인수 abortfn은 불일치가 발견되었을 때 호출되는 함수이다. 당신이 널(null) 포인터를 주게 되면, abort 함수가 사용된다.malloc을 써서 이미 어떤 것을 할당한 뒤에 할당 체크를 시작하기엔  너무 늦었다.이러한 경우에 mcheck는 아무런 도움이 되지 않는다.  그 함수는 너무 늦게 호출하게 되면 -1을 반환하며,그렇지 않을 경우에(성공하였을 때) 0을 반환한다.일찌감치 mcheck를 호출하여 배열하는 가장 손쉬운 방법은 당신이 프로그램을 링크할 때 '-lmchek' 옵션을 사용하는 것이다;  그러면 당신은 프로그램 소스를 전혀 변경할 필요가 없다.

3.3.9  저장소 할당 hook들

GNU C 라이브러리는 적절한 hook 함수들을 명시함으로써 당신이  malloc,realloc과 free의 행위를 변경할 수 있도록 해준다.이러한  hook들은,예컨데,동적 저장소 할당을 사용한 프로그램들을 디버그하는데 도움을 준다. hook 변수들은 'malloc.h'에 선언되어 있다.

변수 ____malloc__hook

이 변수의 값은 malloc이 호출될 때마다 사용하는 함수에 대한 포인터이다.당신은 이 함수를 malloc과 같은 모양으로 정의할 수 있다.

즉,다음과 같다;

void  *function  (size_t  size)

변수 ____realloc__hook

이 변수의 값은 realloc이 호출될 때마다 사용하는 함수에 대한 포인터이다.당신은 이 함수를 realloc과 같은 모양으로 정의할 수 있다.

즉,다음과 같다;

void  *function  (void  *ptr,  size_t  size)

변수 ____free__hook

이 변수의 값은 free가 호출될 때마다 사용하는 함수에 대한 포인터이다.당신은 이 함수를 free와 같은 모양으로 정의할 수 있다.

즉,다음과 같다;

void  function (void  *ptr)

당신은 이 함수들 중의 하나의 hook로서 당신이 인스톨한 함수는 그 hook의 이전의 값이 먼저 저장되지 않은 상태에서 재차 그 함수를 호출하지는 않는다는 점을 분명하게 알아두어야 한다! 그렇지않으면  당신의 프로그램은 무한반복에 빠져 버릴 것이다.

이제  __malloc_hook를 적절하게 사용하는 법을 예를 들어 보기로 하자. 그것은 malloc이 호출될 때마다 정보를 기록해내는 함수를 인스톨한다.

static  void  *(*old_malloc_hook)  (size_t);

/* my_malloc_hook 함수의 정의가 시작되는 건가?*/

static  void  *my_malloc_hook  (size_t  size)

{

void  *result;

/*포인터 result 선언해 둔 다음*/

__malloc_hook  =  old_malloc_hook;

result  =  malloc  (size);

/*size크기의 메모리 할당하고*/

__malloc_hook  =  my_malloc_hook;

printf  ("malloc  (%u)  returns  %p\n",  (unsigned  int)  size,  result);

/*size와 result의 값을 모니터로 출력한 다음*/

return  result;

/*result를 반환한다???..void함수가???*/

}

main  ()

{

...

old_malloc_hook  =  __malloc_hook;

/*음..A에다 B를 대입하고 B에다 C를 대입?*/

__malloc_hook  =  my_malloc_hook;

/*음,자세히 보면,my_malloc_hook이 작동하면서 새롭게 할당되는 size크기의 메모리를 result에      받아서 __malloc_hook에다 대입시키겠네여..흐흐 ..오리무중!..초보의 설움...우

잉??..__malloc_hook이 인수 size_t  size없이도 작동을 하남??..아..살려줘!*/

...

}

mcheck 함수(3.3.8 [메모리 횝의 일정성 체크하기],26쪽 참조)는  이러한 hook들을 인스톨하	여야 작동한다.

3.3.10  malloc으로 할당한 저장소 통계

당신은 mstats 함수를 호출함으로써 동적 저장소 할당에 관한 정보를 얻을 수 있다.이 함수와 그것의 연관된 데이터 형태는 'malloc.h'에 선언되어 있다.그것들은 GNU의 확장이다.

데이터 형태 struct mstats

이 구조체 형태는 동적 저장소 할당자에 관한 정보를 반환하는데 사용된다.그것은 다음 멤버들을 담고 있다.

size_t bytes_total

이것은 malloc에 의해 관리되는 메모리의 전체 크기의 바이트수이다.

size_t chunks_used

이것은 사용된 메모리 덩어리의 숫자이다. (저장소 할당자는 내부적으로 운영체제로부터 메모리 덩어리를 얻어낸 다음,그것들을 개별적인 malloc의 요구에 만족되도록 나누어준다; 3.3.6 [malloc을 위한 효율적인 조치],25쪽 참조)

size_t bytes_used

이것은 사용된 바이트 수이다.

size_t chunks_free

이것은 사용되지 않은 덩어리의 숫자이다.-- 즉,운영체제에 의해  당신의 프로그램에 할당되었으나,아직 사용되지 않은 메모리이다.

size_t bytes_free

이것은 사용되지 않은 바이트 숫자이다.

함수 struct mstats mstats  (void)

이 함수는 struct mstats 형태의 구조에서 현재의 동적메모리 사용에 관한 정보를 반환한다.

3.3.11  malloc 관련 함수 요약

malloc과 함께 작동하는 함수들을 요약해보자.

void *malloc (size_t size)

size 바이트의 한 블럭을 할당하기. 3.3.1 [기본적인 할당], 22쪽 참조.

void free (void *addr)

malloc에 의해 이전에 할당되었던 블럭을 해제하기. 3.3.3 [malloc이후의 해제],23쪽 참조.

void *realloc (void *addr, size_t size)

malloc에 의해 이전에 할당되었던 블럭을 크게 또는 작게 만들어서, 새로운 지역에 그것을 복사하기. 3.3.4 [블럭 크기 변경],24쪽 참조.

void *calloc (size_t count, size_t eltsize)

malloc을 사용하여 count * eltsize 바이트의 블럭을 할당하여 그 내용을  제로로 채우기. 3.3.5 [깨끗한 공간 할당하기],25쪽 참조.

void *valloc (size_t size)

size 바이트의 블럭을 할당하여,한 페이지 경계지점에서 시작하기. 3.3.7 [잘 정돈된 메모리 블럭],25쪽 참조.

void *memalign (size_t size, size_t boundary )

size 바이트의 블럭을 할당하여 boundary의 배수인 주소로 시작하기.  3.3.7 [잘 정돈된 메모리 블럭],25쪽 참조.

int mcheck (void (*abortfn) (void))

malloc으로 하여금 수시로 동적으로 할당된 메모리의 일관성을 체크하게 하여 불일치가 일어나면 abortfn을 호출한다.  3.3.8 [메모리 횝의 일관성 체크하기],26쪽 참조.

void *(*__malloc_hook) (size_t size)

malloc이 호출될 때마다 사용하는 함수에 대한 포인터.

void *(*__realloc_hook) (void *ptr, size_t size)

realloc이 호출될 때마다 사용하는 함수에 대한 포인터.

void (*__free_hook) (void *ptr)

free가 호출될 때마다 사용하는 함수에 대한 포인터.

struct mstats mstats (void)

현재의 동적 메모리 사용에 관한 정보 반환하기. 3.3.10 [malloc으로 할당한 저장소 통계],27쪽 참조.

3.4  Obstacks

obstack는 object들의 스택들이 있는 메모리의 공동관리소(pool) 이다. 당신은 obstack들을 분리하여 몇 개라도 만들 수 있고 그리고 정해진 obstack에 object를 할당한다. 각 obstack안에 마지막 objedt에서 첫 번째 하나는 반드시 해제된 상태이여야 하지만 개별적 obstack들은 다른것에 독립적이다.

해제에 관한 하나의 제약말고는 obstack들은 전체적으로 일반적이다; obstack는 어느 크기의 object들을 몇 개라도 저장할수 있다. 그들은 마크로로 실행되고 그런 할당은 object가 보통 작아서, 매우 빠르다.  그리고 적당한 경계위에 각 object를 시작하기 위해 필요한 공간이 있는데 그것은 각 대상마다 덧붙여 있다.

3.4.1  Obstacks 만들기

obstacks를 다루기 위한 유틸리티들은 헤더파일 'obstack.h'에 정의되어 있다.

struct obstack Data Type

obstack는 구조체 obstack의 타입으로 데이터 구조를 나타낸다. 이 구조는 작은 정해진 크기를 가진다; 각 레코드들은 obstack의 상황과  할당된 곳에서 공간을 어떻게 발견할것인가를 나타낸다. 그러나 obstack는 object자체를 포함하지는 않는다. 당신은 직접적으로 그 구조체의 내용을 검색하려고 시도할수 없다. 오직 이 장에서 설명하는 함수들을 사용하라.

당신은 struct obstack 타입으로 변수들을 선언할수 있고 obstack들처럼 그들은 사용할수 있다. 또한 다른 종류의 object처럼 동적으로 obstack들을 할당할수 있다.  obstack의 동적 할당은 몇 개의 다른 스택을 가진 한변수를 가질수 있도록 당신의 프로그램에게 허용한다. (당신은 심지어 한 obstack안에 다른 obstack구조를 할당할수 있다. 하지만 이것은 그리 유용하지는 않다.)

당신이 사용하기 위해 obstack를 요청하기 위해서는 obstack로 그와 같은 일을 하는 모든 함수들이 필요하다. 당신은 struct obstack * 타입의 포인터로 이것을 한다.  따라서 우리가 종종 "an obstack"라고 말할 때 엄격히 말하면 그것은 포인터와 같다.

obstack안에 object들은 청크(chunks)라고 불리는 큰 블록들 안에 꾸려져 있다.  구조체 obstack는 현재 사용되고 있는 청크들의 연결을 포인터로 구성한다.

obstack 라이브러리는 당신이 앞의 청크에 넣기 원하지 않는 object를 할당할때마다 새로운 청크를 만든다.  obstack 라이브러리가 청크를 관리 하므로 당신은 청크에게 많은 주의를 쏟을 필요는 없지만 청크를 얻어서 사용하는 함수를 공급해줄 필요는 있다.  보통 당신은 직접적이거나 간접적이게 malloc을 사용하는 함수를 공급한다.  당신은 청크를 해제하기 위해서도 함수를 공급해야만 한다.  이 문제들은 다음절에 설명되어 있다.

3.4.2  Obstack들은 사용하기 위한 준비

당신이 obstack를 사용하려고 계획했다면 각 소스파일안에 'obstack.h'라는 헤더파일을 포함해야만 한다.  이것은 다음과 같다.

#include <obstack.h>

또한 소스파일에 매크로 obstack_init를 사용한다면 obstack 라이브러리에 의해 불리워질 매크로나 두 개의 함수를 선언하거나 정의해야만 한다. 하나는 obstack_chunk_alloc으로 모아져있는( are packed) object들에 메모리의 청크를 할당하기 위해 사용되는 것이다.  다른 하나는obstack_chunk_free로 청크에서 object들을 해제하고 청크를 반환하는데 사용한다.

보통 이들은 xmalloc 이라는 매개자를 거쳐 malloc을 사용하도록 정의되어 있다. ( 3.3장[Unconstrained Allocation] 22페이지를 참조)  이것은 밑에 있는 두 개의 매크로 정의로 한다.

#define  obstack_chunk_alloc  xmalloc

#define  obstack_chunk_free  free

당신이 obstack들을 사용하면서 얻어낸 저장공간은 실제로 malloc을 통해 할당된 것이고,  obstak를 사용하는건 메모리의 큰 블록들은 덜 자주 호출하기 때문에 좀더 빠르다.

실행시 프로그램이 obstack로 struct obstack타입인 object를 사용하기전에 obstack_init를 호출해서 obstack를 초기화 해야만 한다.

함수 void obstack__init (struct obstack *obstack_ptr)

object의 할당을 위해 obstack_ptr을 초기화

여기에 obstack를 위해 어떻게 공간을 할당하고 그것을 초기화 하는 두가지 예가 있다.

첫 번째, 스택변수인 obstack:

static  struct  obstack  myobstack;

. . .

obstack_init  (&myobstack);

둘째로 obstack 그자신이 동적으로 할당되는 것:

struct  obstack  *myobstack_ptr

=  (struct  obstack  *)  xmalloc  (sizeof  (struct  obstack));

obstack_init  (myobstack_ptr);

3.4.3  Obstack에서 할당

obstack에 object를 할당하기 위한 가장 직접적인 방법을 obstack_alloc을 사용하는 건데 이것은 거의 malloc과 같다.

함수 void * obstack__alloc (struct obstack *obstack_ptr, size_t size)

몇바이트의 크기인지 초기화 되지 않은 할당은 그 자신의 주소값을 리턴한다.  여기의 obstack_ptr은 안에 블록을 할당하기 위한 obstack을 지정한다.; 그것은 obstack로 표현된 struct obstack 타입의 object의 주소이다. 각각의 obstack 함수나 매크로는 당신에게 첫 번째 인자로obstack_ptr을 지정할 것을 요청한다.

여기의 예는 정해진 obstack, 즉 변수 string_obstack안에 문자열을 할당하기 위한 함수를

보여준다.

/*또 역시 제가 주석을 좀 붙여 보지요.. 헤헤..*/

struct  obstack  string_obstack;

/* struct obstack 형으로 string_obstack변수를 선언했네요. */

char  * copystring  (char  *string)

{

char  *s  =  (char  *)  obstack_alloc  (&string_obstack, strlen  (string)  +  1);

/*  아까 위에서 obstack_alloc이 거의 malloc 과 같다고 했지요..? 그러니까 문자열을 위해 공간을 할당받기 위해서는 길이 +1 해서 포인터를 리턴하면 s로 받지요...*/

memcpy  (s,  string,  strlen  (string));

/* 위에서 받은 포인터 s에 string 을 길이만큼 복사 */

return  s; /* 그리고 리턴...*/

}

정해진 내용을 갖은 블록을 할당하기 위해서는 obstack_copy 함수를 사용하라.  이것은 다음과 같이 정의한다.

void * obstack__copy  (struct obstack *obstack_ptr, void *address,  size_t size)

이것은 블록을 할당하고 그 주소에 문자열을 정해진 크기대로 복사해서 초기화 하는것이다.

void * obstack__copy0  (struct obstack *obstack_ptr, void *address,  size_t size)

obstack_copy와 같지만 널 문자를 포함하는 하나의 여분의 바이트를 덧붙인다.  이 여분의 바이트는 인수크기에는 들어가지 않는다.  이 obstack_copy() 함수는 널문자로 끝나는 문자열과 같은 문자의 열들을 obstack에 넣기에는 편리한 함수다.

사용예를 보면:

char  * obstack_savestring  (char  *addr,  size_t  size)

{

return  obstack_copy0  (&myobstack,  addr,  size);

}

malloc을 사용한 앞의 stvestring 의 예와 비교해보라 (3.3.1절의 [Basic Allocation]을 보라)

3.4.4  Obstack에서 Objects 해제하기

obstack안에 할당된 object를 해제하기 위해서는 obstack_free함수를 사용한다.  object들의 스택인 obstack는 한 object가 해제되면 그 object이후 최근에 같은 obstack에 할당된 다른 object들도 자동적으로 해재 시킨다.

함수 obstack__free  (struct obstack *obstack_ptr, void *object)

만약 object가 널 포인터 이면 obstack안에 할당된 모든 것이 해제된다. 그렇지 않으면 object는 obstack안에 할당된 object의 주소이여야만 한다. 그러면 object는 해제되고 그 해제된 object이후 obstack안에 할당된 모든 것도 따라서 해제된다.

object에 널 포인터를 주면 그 결과는 obstack의 비초기화라는 것을 알아라.  obstack안에 모든 내용은 해제하지만 다음 할당을 위해 obstack를 유용하게 남기고 싶다면 obstack에 할당된 object의 첫 번째 주소를 주어 obstack_free를 호출하면 된다.

청크안에 모아진 obstack안의 object들은 재 호출하라. 청크안에 object들은 모두 해제할 때 그 obstack 라이브러리는 자동적으로 청크를 해제한다. ( 3.4.2절 [Preparing for Obstacks] 29페이지 참조) 그러면 다른 obstack들이나 비 obstack 할당도 그 청크의 공간을 재사용 할수 있다.

3.4.5  Obstack 함수와 매크로

obstack들의 사용을 위한 인터페이스는 컴파일러에 의존하는 함수나 매크로로 정의되어 진다.  모든 C 컴파일러는 ANSI C와 전통적인 C를 지원하는 obstack 도구를 가지고 있다.  하지만 당신이 GNU C 보다는 다른 컴파일러를 사용하려 한다면 많은 주의를 해야한다.

만약 당신이 오래된 비 ANSI C 컴파일러를 사용하면 모든 obstack 함수들은 매크로로 정위되어 있다.  당신은 이 매크로를 함수들처럼 호출 할 수 있지만 당신은 그것을 다른 방법으로는 사용할수 없다.( 예를 들어, 그들의 주소를 줄수 없다. )

매크로의 호출은 특별한 주의가 요구된다.; 즉, 첫 번째 피연산자 ( obstack 포인터)가 부작용을 포함하고 있다고 할수 있을지도 모르는데, 그것은 그것이 여러번 연산되기 때문이다.  예를 들어 만약 당신이 아래처럼 쓰면;

obstack_alloc  (get_obstack (), 4);

당신은 여러번 get_obstack가 호출되는걸 발견할수 있다. 만약 당신이 obstack 포인터 인자처럼 *obstack_list_prt++을 사용하면 여러번 증가가 발생하여 이상한 결과를 얻을 것이다.

ANSI C는 함수와 매크로 정의 둘을 다 지원한다.  함수 정의는 그것을 호출하지 않고 함수의 주소로 사용되는 것이다.  보통의 호출은 암묵적으로 마크로 정의가 사용되지만 당신은 괄호안에 함수의 이름을 씀으로써 함수정의를 요청할수 있다. 아래처럼;

char  *x;

void  *(*funcp)  ();

/* 마크로 사용 */

x  =  (char  *)  obstack_alloc  (obptr,  size);

/* 함수 호출 */

x  =  (char  *)  (obstack_alloc)  (obptr,  size);

/* 함수의 주소를 취하라 */

funcp  =  obstack_alloc;

이것은 표준 라이브러리 함수를 위한 ANSI C안에 있는 것과 같다. 1.3.2절[Macro Definitions] 4페이지를 보라.

주의 : 심지어 ANSI C에서 조차도 첫번째 피연산자의 부작용을 피하는데 주의를 기울여야 한다.만약 당신이 GNU C컴파일러를 사용하고 있다면 이 주의는 필요하지 않는데,그것은 GNU C안에 다양한 언어의 확장은 오직 한번 각 인수를 실행하도록 그 마크로를 정의하는 것을 허용한다.

3.4.6  성장하는 대상물

obstack 덩어리 내의 저장소는 연속적으로 사용되기 때문에 어떤 대상물을 차례로 쌓아서 대상물의 끝에 한번에 하나 또는 그 이상의 바이트를 추가할 수 있다.이 기법을 사용함에 있어 당신은 그 대상물의 끝에 다다를 때 까지 그 대상물에 얼마만큼의 데이터를 넣을 수 있는지를 알 필요가 없다.이것은 성장하는 대상물 기법이라 불린다.성장하는 대상물에 데이터를 추가하는 특수한 함수들은 이 절에 기술되어 있다.당신은 어떤 대상물을 성장시키기 시작할 때 아무런 특별한  조치도 할 필요가 없다.그 대상물에 데이터를 추가시키는 함수들 중의 하나를 사용하게 되면 그것은 자동적으로 시작된다. 그러나 그 대상물이 끝날 때에는 분명하게 말해 줄 필요가 있다.이것은 함수 obstack_finish에 의해 수행된다. 이렇게 해서 만들어진 대상물의 실제 주소는 그것이 끝나기 전에는 알 수가 없다. 그때까지는 항상 대상물이 새로운 덩어리 속으로 복사되어야 할 만큼의 데이터만이 추가될 수 있을 뿐이다. obstack이 성장하는 대상물을 위해 사용되고 있을 동안에는 당신은 다른 대상물을 할당하기 위해서 그것을 사용할 수는 없다. 만약 그렇게 한다면,이미 성장하는 대상물에 추가된   공간이 다른 대상물의 일부분으로 되어버릴 것이다.

함수 void obstack__blank  (struct obstack *obstack_ptr, size_t size)

성장하는 대상물에 추가하기 위한 가장 기초적인 함수는  obstack_blank이며,이것은 대상물을 초기화하지 않고 공간을 추가한다.

함수 void obstack__grow  (struct obstack *obstack_ptr,  void *data, size_t size)

한 블럭의 초기화된 공간을 사용하려면,obstack_grow를 사용하라.obstack_grow는 성장하는 대상물에 있어서는 obstack_copy와 유사하다.obstack_grow는 성장하는 대상물에 size 바이트의 데이터를 추가하고 데이터로부터 그 내용을 복사한다.

함수 void obstack__grow0 (struct obstack *obstack_ptr, void *data, size_t size)

이것은 성장하는 대상물에 있어서 obstack_copy0와 유사 하다. 그것은 널(null) 문자가 따라오는 데이터에서 복사한 size 바이트를  추가한다.

함수 void obstack__1grow (struct obstack *obstack_ptr,  char c)

한번에 하나의 문자를 추가하려면 함수 obstack__1grow를 사용하라. 그것은 성장하는 대상물에 c를 담아둘 1 바이트를 추가한다.

함수 void * obstack__finish  (struct obstack *obstack_ptr)

당신이 대상물 성장시키기를 끝마쳤을 때 함수 obstack_finish를 사용해서 그것을 닫고 그것의 마지막 주소를 반환하라.당신이 그 대상물을 완료하면,obstack은 또다른 대상물을 할당하거나 성장시키는데 사용될 수 있게 된다. 당신이 하나의 대상믈을 성장시켜서 만들게 되면, 그것이  얼만큼 길어졌는지를 나중에 알 필요가 생길 것이다. 당신은  그 대상물이 성장하는 동안에는 그 과정을 추적할 필요가 없다.왜냐하면 당신은 함수 obstack_object_size를 사용하여 그 대상물의 완료 직전에 obstack으로부터의 길이를 알 수 있기 때문이다.그것은 다음과 같이 선언된다.

함수 size_t obstack__object__size  (struct obstack *obstack_ptr)

이 함수는 성장하는 대상물의 현재 크기를 바이트수로 반환 한다.이 함수는 대상물이 완료되기 전에 호출하여야 함을 기억하라.완료되고 난 다음에는 이 함수는 제로를 반환할 것이다.만약 당신이 대상물 성장시키기를 시작한 다음에 이를 취소하고 싶다면,당신은 이렇게 끝내고 해제하라;

obstack_free (obstack_ptr,obstack_finish (obstack_ptr));

이것은 아무런 대상물도 성장하고 있지 않을 때에는 아무런 효과도 없는 것이다.현재의 대상물을 작아지게 만들고 싶으면 음수의 size 인수를 갖는 obstack_blank를 사용할 수 있다.그러나 그 대상물 자체를 음수 크기로 만들려고 하지는 말아야 한다. 그렇게되면 무슨 일이 일어날른지 알 수가 없다.

3.4.7  특히 급속하게 성장하는 대상물

성장하는 대상물을 만드는 통상적인 함수들은 현재의 메모리 덩어리에 새로운 성장을 위한 공간이 있는지를  체크하기 위하여 부가물을 초래한다.만약 당신이 대상물을 조금씩 단계별로 만들고자 한다면 이 부가물은 중요한 것이 될 수 있다.

당신은 체크하지 않고 대상물을 성장시키는 특수한 "급속한 성장" 함수를 사용함으로써 부가물을 줄일 수 있다.  견실한 프로그램을 만들기 위해서는 당신 자신이 체크를   수행해야만 한다.만약 당신이 대상물에 데이터를 추가하려  할 때마다 가장 간단한 방법으로 이러한 체크를 수행하려 한다면,당신은 아무 것도 저장하지 않아도 통상적인 성장 함수들이 이를 행할 것이다. 그러나 당신이 체크의 횟수를 줄이되 더 효과적으로 체크할 수 있도록 배열한다면, 당신은  프로그램을 빠르게 만드는 것이 된다. 함수 obstack_room은 현재의 메모리 덩어리에서 사용가능한 공간의 양을 반환한다.그것은 다음과 같이 선언된다:

size_t obstack__room  (struct obstack *obstack_ptr)

이것은 가장 빠른 성장 함수를 사용하는 obstack에서 현재의 성장하는 대상물(또는 시작하려하는 대상물)에 안전하게 추가할 수 있는 바이트 수를 반환한다. 공간이 있음을 알 수 있는 한,당신은 어떤 성장하는 대상물에 데이터를 추가하는 급속한 성장 함수들을 사용  할 수 있다.

void obstack__1grow__fast  (struct obstack  *obstack_ptr, char c)

함수 obstack_1grow_fast는 구조체 obstack의 포인터 obstack_ptr에서 성장하는 대상물에 문자 c를 담아둘 1 바이트를 추가한다.

함수 void obstack__blank__fast  (struct obstack *obstack_ptr,  size_t size)

함수 obstack_blank_fast는 구조체 obstack인 obstack_ptr에서 성장하는 대상물에 size 바이트를 초기화하지 않고 추가한다.당신이 obstack_room을 사용하여 공간을 체크해서 당신이 추가하기를 원하는 만큼의 충분한 공간이 없을 때는,급속한  성장함수들은 안전하게 사용할 수가 없다.이러한 경우에는  그저 적합한 통상적인 성장함수만을 사용하라. 이 함수는 곧바로 대상물을 새로운 메모리 덩어리에 복사한다;그리고나면 다시 사용가능한 공간이 많이 있게 될 것이다. 그러고 난 다음에,당신이 통상적인 성장 함수를 사용할 때 마다,함수 사용 후에 obstack_room을 써서 충분한 공간이 남아있는지를 체크하라.일단 대상물이 새로운 덩어리로 복사되고나면 다시 충분한 공간이 생길 것이고,그래서 프로그램은  다시 급속한 성장함수들을 사용하기 시작할 것이다.

예를 들어보자.

/*(1)함수명:add_string(2)인수1:구조체 obstack을 포인터로 선언(3)인수2:문자포인터ptr을 전달*/

void  add_string  (struct  obstack  *obstack,  char  *ptr,  size_t len)

{

while  (len  >  0)

/*함수 전체 내용이 결국은 while루프이군 따라서 인수 len은 양수여야만 의미가 있군*/

{

/*  함수 obstack_room은 그 인수로 받은 obstack 내의 사용가능한 공간을 바이트 수로 반환하므로 그 바이트 수가 len보다 크면..흐흐..공간이 있지여?*/

if  (obstack_room  (obstack)  >  len)

{

/* 공간이 충분하면 급속한 성장함수 사용*/

while  (len--  >  0)

/* 1 만큼씩 헤아리면서 인수로 전달받은 문자 포인터의 내용을 하나씩 obstack에 추가시킨다..그러나 이 루프가 작동을 마칠때면 결국은 한꺼번에 포인터의 모든 내용이 추가되어 버리는 셈이군 */        obstack_1grow_fast  (obstack,  *ptr++);

}

else

{

/* 공간이 불충분하면 서서히 한문자를 추가하다가 그것을 새로운 덩어리로 복사해서 공간을 만듬 */

obstack_1grow  (obstack,  *ptr++);

/* 즉, 일단 인수의 포인터의 1문자를 obstack에 추가하고 */

len--;

/* len을 1만큼 줄여서 obstack_room이 공간을 다시 평가하도록 하는군요.-->while루프--> if문장으로 복귀 */

}

}

/*while루프의 끝지점*/

}

/*함수 전체의 끝지점*/

3.4.8  obstack의 위상

하나의 obstack에서 현재의 할당 위상에 관한 정보를 제공하는 함수들이 있다. 당신은 성장하고 있는 대상물에 대해서 알아보기 위해서 그것들을 사용할 수 있다.

함수 void * obstack__base  (struct obstack *obstack_ptr)

이 함수는 구조체 obstack_ptr에서 현재 성장하는 대상물의 임시 시작주소를 반환한다.만약 당신이 그 대상물을 즉시로 종결한다면 그것은 그 주소를 가질 것이다.반대로 당신이 그것을 더 크게 만든다면 그것은 현재의 덩어리보다 더 성장할 것이다. 그리하여 그 주소가 변할 것이다.아무런 대상물도 성장하지 않고 있다면,이 값은 당신이 할당한 다음번의 대상물이 어디서부터 시작할 것인가를 알려준다.(다시 한번 생각해보면 그것은 곧 현재의 덩어리이다.)

함수 void * obstack__next__free  (struct obstack *obstack_ptr)

이 함수는 구조체 obstack_ptr의 현재 덩어리에서 해제된 첫 바이트의 주소를 반환한다.이것은 현재 성장하고 있는 대상물의 끝부분이다.만약 아무런 대상물도 성장하고 있지 않다면, obstack_next_free는 obstack_base와 같은 값을 반환한다.

함수 size_t obstack__object__size  (struct obstack *obstack_ptr)

이 함수는 현재 성장하고 있는 대상물의 바이트에서 크기를 반환한다. 이것은 obstack_next_free  (obstack_ptr) 에서 obstack_base (obstack_ptr) 를 뺀 것과 같다.

<번역자 주석>

위의 내용들을 종합해 보면, 결국 이런 내용인가?

--> 스택(stack)에 메모리를 확보하고 추가하고 해제한다?

					시작지점 : obstack__next__free
				함수의 반환 주소.
성장방향
					종료지점 : obstack_base함수의 반환 주소.

따라서, 위의 스택에 담긴 대상물의 크기는, "시작점(해제지점) - 종료지점"이 되는건가? 음...,스택과 그 주소를 잘 이해하고 있는 고수들만이  제대로 이해할 수 있겠군...

(어휴!..초보자의 설움!..먼저 스택을 공부할껄껄껄!)

3.4.9  obstack에서의 데이터 할당

각 obstack은 할당 경계지점을 갖는다; obstack에 자동적으로 할당된 각 대상물은 설정된 경계지점의 배수로 된 주소로 시작한다. 디폴트 값으로 이 경계지점은 4 바이트수이다.

obstack의 할당 경계지점에 접근하기 위해서는 매크로 obstack_alignment_mask를 사용하라. 이 함수의 원형은 이렇게 되어있다.

매크로  int obstack__alignment__mask  (struct obstack *obstack_ptr)

그 값은 한 비트의 mask이다; 1로 된 비트는 대상물의 주소에서 대응하는 비트가 0 이어야 함을 나타낸다. mask 값은 2의 배수보다 작은 값이어야 한다; 그 결과 모든 대상물의 주소는 2의 배수의 배수로 된다. mask의 디폴트 값이 3으로 되면 주소들은 4의 배수로 된다. mask의 값이 0으로 되면,대상물이 1의 배수로 시작할 수 있다.(즉,할당이 되지 않는다.)

매크로 obstack_alignment_mask의 확장은 lvalue이므로,당신은 mask를 변경 지정할 수 있다.예를 들면 다음과 같다;

obstack_alignment_mask  (obstack_ptr)  =  0;

위와 같은 문장은 설정된 obstack에서 진행중인 할당을 없애버리는 효과를 갖는다.

할당 mask에서 일어난 변화는 어떤 대상물이 obstack에서  할당되거나 종료되기까지는 아무런 효과도 없다. 만약 당신이 어떤 대상물을 성장시키고 있지 않을 경우라면,당신은 obstack_finish를 호출하여 새로운 할당 mask가 즉각 효과를 갖도록 할 수 있다.이것은 길이가 제로인 대상물을 종료하고다음 대상물을 위한 적절한 할당을 하는 것이다.

3.4.10  obstack 덩어리

obstack은 자체의 기능으로 큰 덩어리에 공간을 할당하고  당신의 요청에 부응하여 덩어리에서 공간을 분배한다.덩어리들은  당신이 별도의 크기를 지정하지 않는 이상 4096 바이트의 크기이다.  덩어리 크기는 대상물들을 저장하는데에 실제로 사용되지 않는  8 바이트의 부가물을 포함한다. 설정된 크기에 관계없이, 큰 대상물을 위해서 필요하다면 더 큰 덩어리들이 할당되어질  것이다.

obstack 라이브러리는 함수 obstack_chunk_alloc을 호출하여  덩어리들을 할당하는데,당신이 그것을 정의하여야 한다.당신이  어떤 덩어리에 있는 대상물을 해제한 결과로서 어떤 덩어리가  더이상 필요치 않게 되면, obstack 라이브러리는 함수 obstack_chunk_free를 호출하여 그 덩어리를 해제한다.

물론 당신은 그것 또한 정의하여야 한다. 이들 둘은  obstack_init(3.4.1 [obstack 만들기],28쪽 참조) 를 사용하는 각 소스파일에서 (매크로로)정의되거나 (함수로)선언되어야만 한다.그것들은 거의 대부분 아래와 같이 매크로로 정의된다:

#define  obstack_chunk_alloc  xmalloc

#define  obstack_chunk_free  free

이들은 (인수가 아니라) 단순히 매크로임을 주의하라. 인수를 갖는 매크로 정의는 유효치 않다! 다만 obstack_chunk_alloc 나 obstack_chunk_free는 그 자체가 함수명칭이 아닐 경우에 함수 명칭으로 확장될 필요가 있다. 실제로 obstack_chunk_alloc을 사용하는 함수는 어떤 형태로든 "실패"를 반환할 수 없다. 왜냐하면 obstack 라이브러리는 실패를 취급할 준비가 되어있지 않기 때문이다.그러므로 malloc 그 자체는 적합하지가 않다.만약 그 함수가 공간을 확보하지 못하게 되면, 그것은 처리를 종료하거나(22.3 [프로그램 종료],320쪽 참조.) longjmp를 사용하는 비지역적 종료를 행하게 된다.(20장 [비지역적 종료],267쪽 참조)

만약 당신이 malloc을 써서 덩어리들을 할당한다면, 덩어리 크기는 2의 배수여야만 한다.덩어리의 디폴트 크기는 4096으로 선택되어 있다.왜냐하면 디폴트 크기만으로도 현재로서는 작은 대상물에서 요구되어질  대부분의 전형적인 요청들을 만족시킬 수가 있으며, 아직은 그 대상물의 크기가 사용되지 않은 가장 최근의 덩어리 부분에 너무 많은 메모리가 낭비되지 않게 해도  될만큼 작기 때문이다.

매크로 size_t obstack__chunk__size  (struct obstack *obstack_ptr)

이것은 주어진 obstack의 덩어리 크기를 반환한다. 이 매크로는 lvalue로 확장되므로, 당신은 그것에 새로운  값을 할당함으로써 새로운 덩어리 크기를 규정할 수 있다.  그렇게 하게 되면 이미 할당되어있는 덩어리에는 영향이 없고, 다만 장래에 특정한 obstack을 위하여 할당될 덩어리들의 크기에만 영향을 주게 된다.그것은 덩어리 크기를 작게 만드는 데에는 소용이 없다. 만약 당신이 그 크기가 덩어리 크기와 비슷한 많은 대상물을 할당하고 싶다면 덩어리 크기를 크게 만드는 것이 매우 효과적일 것이다.

3.4.11  obstack 함수 요약

obstack과 관련된 함수들을 요약해보자. 각 함수들은 첫 인수로서 obstack(구조체 포인터로서)의 주소를 갖는다.

void obstack_init (struct obstack *obstack_ptr)

obstack의 사용을 초기화한다. 3.4.1 [obstack 만들기],28쪽 참조.

void *obstack_alloc (struct obstack *obstack_ptr, size_t size)

초기화되지 않은 바이트 크기의 대상물을 할당한다. 3.4.3 [obstack 할당0,29쪽 참조.

void *obstack_copy (struct obstack *obstack_ptr, void *address, size_t  size)

size 바이트 크기의 대상물을 할당하고 주소에서 복사한 내용을 채운다. 3.4.3 [obstack 할당],29쪽 참조.

void *obstack_copy0 (struct obstack *obstack_ptr, void *address, size_t size)

size+1 바이트 크기의 대상물을 할당하고 주소에서 복사한 그만한 크기의 내용을 채우고 끝에 널(null) 문자를 붙인다.  3.4.3 [obstack에서의 할당],29쪽 참조.

void obstack_free (struct obstack *obstack_ptr, void *object)

대상물 해제(동시에 대상물보다 더 최근에 설정된 obstack에 할당된 모든 것을 해제) 3.4.4 [obstack 대상물 해제하기],30쪽 참조.

void obstack_blank (struct obstack *obstack_ptr, size_t size)

성장하는 대상물에 초기화되지 않은 size 바이트를 추가. 3.4.6 [성장하는 대상물],31쪽 참조.

void obstack_grow (struct obstack *obstack_ptr, void *address, size_t size)

size 바이트를 주소로부터 복사하여 성장하는 대상물에 추가. 3.4.6 [성장하는 대상물],31쪽 참조.

void obstack_grow0 (struct obstack *obstack_ptr, void *address, size_t size)

size 바이트를 주소로부터 복사하여 성장하는 대상물에 추가하고  널(null) 문자를 담고있는 다른 바이트를 덧붙인다. 3.4.6 [성장하는 대상물],31쪽 참조.

void obstack_1grow (struct obstack *obstack_ptr, char data_char)

성장하는 대상물에 data_char를 담고있는 한 바이트를 추가. 3.4.6 [ 성장하는 대상물],31쪽 참조.

void *obstack_finish (struct obstack *obstack_ptr)

성장하고 있는 대상물을 종결하고 그것의 최후주소를 반환 3.4.6[성장하는 대상물],31쪽 참조.

size_t obstack_object_size (struct obstack *obstack_ptr)

현재 성장하고있는 대상물의 현재의 크기를 구한다. 3.4.6 [성장하는 대상물],31쪽 참조.

void obstack_blank_fast (struct obstack *obstack_ptr, size_t size)

충분한 공간이 있는지의 여부를 체크하지 않고서,성장하는 대상물에 초기화되지 않은 size 바이트를 추가.  3.4.7 [급속한 성장함수],32쪽 참조.

void obstack_1grow_fast (struct obstack *obstack_ptr, char data_char)

충분한 공간이 있는지의 여부를 체크하지 않고서,성장하는 대상물에 data_char를 담을 한 바이트를 추가.  3.4.7 [급속한 성장함수],32쪽 참조.

size_t obstack_room (struct obstack *obstack_ptr)

현재의 대상물을 성장시키는 데 사용할 수 있는 공간의 양을 확보. 3.4.7 [[급속한 성장함수],32쪽 참조.

int obstack_alignment_mask (struct obstack *obstack_ptr)

대상물의 시작점을 정돈하기 위해 사용되는 mask. 이것은 lvalue이다. 3.4.9 [obstack에서의 데이터 할당],34쪽 참조.

size_t obstack_chunk_size (struct obstack *obstack_ptr)

덩어리 할당을 위한 크기. 이것은 lvalue이다. 3.4.10 [obstack 덩어리],34쪽 참조.

void *obstack_base (struct obstack *obstack_ptr)

현재 성장하는 대상물의 임시 시작주소. 3.4.8 [obstack의 위상],33쪽 참조.

void *obstack_next_free (struct obstack *obstack_ptr)

현재의 성장하는 대상물이 끝나는 바로 다음의 주소. 3.4.8 [obstack의 위상],33쪽 참조.

3.5  다양한 크기로의 자동저장

불충분한 동적할당을 지원하는 함수들중의 하나인 alloca는 동적으로 할당을 하지만 자동적으로 해제되지는 않는다. alloca를 가지고 블록을 할당하는 것은 명백한 동작이다; 당신은 원하는 만큼의 많은 블록을 할당할수 있고 실행시에 그 크기를 계산할수도 있다. alloca로 할당된 공간의 변수가 단지 그 함수 안에서 자동변수라고만 선언이 되었다면 그렇게 할당된 블록들은 함수를 빠져나갈 때 모두해제된다. 하지만 다른경우는 명백하게 그 공간을 해제하는 방법이 없다. alloca의 프로토타입은 'stdlib.h'이다.  이 함수는 BSD확장이다.

함수 void * alloca (size_t size);

alloca는 불리워진 함수의 스택프레임안에 몇바이트의 크기를 가진 블록을 할당하고 그 블록의 주소값을 리턴한다. 예측할수 없는 결과를 얻을지도 모르는 함수들의 인수내부에 alloca를 사용하지 말라.  왜냐하면 alloca를 위한 스택의 공간은 함수인수들을 위한 그 공간의 중간에 만들어지기 때문이다. 피해야할 예는 foo (x, alloca(4), y)이다.

3.5.1  alloca 예

alloca의 사용예로서 여기에 한 함수가 있다.  이 함수는 인수로 받은 두 개의 문자열을 붙여서 만든 것을 파일이름으로 해서 그 파일을 열고 그것을 파일기술자에 전달하거나 실패한 경우는 -1을 리턴하는 함수이다.

/* 역시 제가 주석을 좀..주석이 틀렸으면 저에게 왕창 욕해주셔요... */

int open2  (char  *str1,  char  *str2,  int  flags,  int  mode)

{

/*  alloca를 가지고 두 개의 문자열 길이에 각각 1을 더한 길이만큼의 공간을 char형으로 할당받고 그 포인터를 name에게 주었어요.*/

char  *name  =  (char  *)  alloca  (strlen  (str1)  +  strlen  (str2)  +  1);

/* strcpy는 문자열을 복사하고 strcat는 문자열을 덧붙이는 함수 */

strcpy  (name,  str1);

strcat  (name,  str2);

/* 복사하고 덧붙여서 만든 name을 파일이름으로 해서 파일하나를 열고 그것을 리턴하네요.. */

return  open  (name,  flags,  mode);

}

이건 malloc을 사용해서 어떻게 같은 결과를 얻고 해제하는지에 대한 예.

int open2  (char  *str1,  char  *str2,  int  flags,  int  mode)

{

/* 말했듯이 malloc을 이용해서 필요한 공간을 할당하네요..*/

char  *name  =  (char  *)  malloc  (strlen  (str1)  +  strlen  (str2)  +  1);

int  desc;

/* 만약 공간이 할당되지 않았으면 에러처리  */

if  (name  ==  0)

fatal  ("virtual  memory  exceeded");

strcpy  (name,  str1);

strcat  (name,  str2);

/* 일시적으로 파일 핸들을 desc에 저장 */

desc  =  open  (name,  flags,  mode);

free  (name);

return  desc;

}

위에서 본것처럼 alloca를 이용하면 더 간단하게 구현할수 있겠지만 alloca를 사용하는 것은 많은 이득이 있는 대신에 몇가지 손해도 얻을수 있다는걸 명심하라.

3.5.2  alloca를 이용해서 얻는 이득

여기에 malloc보다는 alloca를 선택하게 되는 이유가 있다.

alloca를 사용하면 매우 적은 공간을 소비하고 매우 빠르다. ( 그것은 alloca가 GNU C 컴파일러 상에서 open-coded 이어서..)  alloca는 블록을 여러크기로 나누지 않고 이미 있는 다른 크기의 블록을 재 사용할수 있기 때문에 메모리 단편화의 원인이 되지 않는다. 호출된 alloca를 통해 만들어진 할당된 공간은 longjmp( 20장 [Non-Local Exits]를 보라. )로 자동적으로 해제할수 있는 Nonlocal이 있다.

참고로 Nonlocal의 의미를 잘 모르겠어서 책들을 뒤져 보니까 이런게 나와 있네요.

nonlocal: 구조화된 프로그래밍 언어를 사용하여 작성된 프로그램의 어떤 블록에서 자기 블록에 정의되어 있지 않은 변수를 참조하는 것이라구요... 하지만 여기의 Nonlocal을 의미하는지는 잘 모르겠어요.제 생각에는 얼추 비슷할 것 같은데..

이것을 구현하기 위해서,  호출에 성공하면 open함수처럼 한 디스크립터를 리턴하고 실패하면 아무것도 리턴하지 않는 opne_or_report_error 이라는 함수가 있다고 가정하라. 만약 호출에 실패해서 파일이 열려지지 않으면, 에러 메시지를 프린트하고 longjmp를 사용해서 당신의 프로그램의 한 레벨을 빠져나온다.  이것을 사용해서 open2를 바꾸어보자.

subroutine:

int open2  (char  *str1,  char  *str2,  int  flags,  int  mode)

{

char  *name  =  (char  *)  alloca  (strlen  (str1)  +  strlen  (str2)  +  1);

strcpy  (name,  str1);

strcat  (name,  str2);

/*  아까 open2 함수에서 변화된 것은 open함수가 open_or_report_error로 대치된거네요.  */

return  open_or_report_error  (name,  flags,  mode);

}

/* 좀더 자세히 제가 이해한대로 보충설명을 드리자면... 실제로 open_or_report_error 이라는 함수가 존재하지 않는데 그냥 있다고 가정하고 쓰면 프로그램내에서 그것을 호출할수 없으므로 에러가 나겠지요.  그런데 그 alloca란 놈이 일단 에러가 나면 alloca를 통해 할당된 모든 공간을 해제하고 빠져나올수 있기 때문에, 그리고 alloca는 명백하게 공간은 해제해주는 기능이 없으니까.. 이런 편법을 사용하게 되나봐요. 이건 그냥 제가 이해한거구요..  만약 틀리면 틀렸고 그게 아니란걸  게시판에 올려주세요.*/

alloca를 통한 작업에서 이 방법을 사용하는 이유는 alloca를 통해 할당된 공간은 에러가 발생하면 어떤 특별한 노력없이 할당된 공간을 해제할수 있기 때문이다. 앞의 open2와 비교해서( malloc과 free를 사용하는 ) 이 방법을 사용하면 저장공간의 유출을 발견할수 있을 것이다. 그러나 당신이 좀더 많은 변화를 원한다면 그렇게 하는 쉬운 방법은 아무것도 없다.

3.5.3  alloca의 단점

이곳은 malloc과 비교하여 alloca의 단점을 보여준다. 만약 당신이 alloca를 가지고 시스템이 제공할수 있는것보다 더 많은 저장공간을 할당받길 원하면 당신은 명백한 에러 메시지를 얻을수 없다. 대신에 당신은 무한 재귀호출에서 얻을수 있는 것과 같은 심각한 신호를 얻을 것 이다.; 아마도 segmentation 위배 ( 21.2.1절[ 프로그램 에러 신호]페이지 273을 참조) 어떤 비GNU 시스템들은 이 alloca를 지원하지 않아서 이식성의 측면에서는 좀 떨어진다.  그렇지만 alloca는 이런 결점에도 불구하고 유용하게 사용된다.

3.5.4  GNU C 변할수 있는 크기의 배열

GNU C에서 당신은 alloca의 사용을 변화가능한 크기를 갖는 배열을(an array of variable size) 사용해서 대체할수 있다. 여기에 그것을 보여주는 예가 있다.

int  open2  (char  *str1,  char  *str2,  int  flags,  int  mode)

{

/*  위에서 alloca를 사용해서 할당했던 공간을 대신 크기가 'str1의 문자길이 + str2의 문자길이 + 1'인 배열을 만들었네요.*/

char  name[strlen  (str1)  +  strlen  (str2)  +  1];

strcpy  (name,  str1);

strcat  (name,  str2);

return  open  (name,  flags,  mode);

}

그러나 alloca는 여러 가지 이유로 변화가능한 크기를 갖는 배열로 항상 대체할수 있는 것은 아니다. 변화가능한 배열에게 할당된 메모리 공간은 배열이 선언된 영역의 끝에서 해제되지만 alloca로 할당된 공간은 함수가 끝날 때 까지 남아있는다.

반복을 사용했을 때 각 반복마다 더해서 블록을 할당하는 loop에서 alloca를 사용하는 것은 가능하다.  하지만 변화가능한 크기를 갖는 배열로는 불가능하다.

주의 : 만약 당신dl alloca와 변화가능한 크기를 갖는 배열을 한 함수 안에서 혼용하면, 변화가능한 크기를 갖는 배열이 선언된 영역을 벗어날 때 그 영역이 실행되는 동안에 alloca로 할당했던 모든 블록들도 해제된다는 것을 명심하라.

3.6  재조정 할당자

어떠한 체제의 동적 메모리 할당이든간에 부가물을 갖는다: 그것이 사용하는 공간의 양은 프로그램이 요구하는 공간의 양보다 크다.메모리 재조정 할당자는 독자적 판단으로 필요한 만큼의 메모리 블럭을 이동시킴으로써 부가물을 최소화한다.

3.6.1  재조정 할당자의 개념

당신이 malloc을 써서 한 블럭을 할당하고나면,당신이 그 크기를 변경할 목적으로 realloc을 사용하지 않는 이상 그 블럭의 주소는 결코 변하지 않는다.그러므로,당신은 당신이 원하는대로 다양한 공간에서 그 주소를 일시적으로  또는 영구적으로 안전하게 보관할 수가 있다. 그러나 당신이 재조정 할당자를 사용하려 할 때는 안전할 수가 없다.왜냐하면,당신이 어떤 방식으로든 메모리를 할당하려할때마다 모든 재조정가능한 블럭들이 움직일 수 있기 때문이다. malloc이나 realloc을 호출하는 일조차도 재조정가능한 블럭들을 움직일 수 있다. 각각의 재조정가능 블럭들에 대해서 당신은 그 블럭의 주소가 저장될 수 있도록 메모리상에 핸들_포인터 지정을 만들어야 한다. 재조정 할당자는 각 블럭의 핸들의 위치를  알고 있어서 블럭을 옮길 때마다 그곳에 저장된 주소를 갱신하며,그 결과로 핸들은 언제나 그 블럭을 가리키게 된다.당신이 블럭의 내용에 접근하고자 할 때마다,핸들에서 그 블럭의 갱신된 주소를 가져와야만 한다.

부호취급자로부터 재조정 할당자 함수들을 호출하는 것은 거의 분명히 부정확하다.왜냐하면 그 부호는 언제든지 변할 수 있고 모든 블럭을 재조정할 수 있기 때문이다.이것을 안전하게 할 수 있는 유일한 방법은 재조정가능한 어떤 블럭의 내용도 그 부호에 접근하지 못하도록 봉쇄하는 것이다. 21.4.6 [재진입금지],290쪽 참조.

3.6.2  재조정가능한 블럭의 할당과 해제

아래의 서술에서,handleptr이 핸들의 주소를 가리킨다. 모든 함수들은 'malloc.h'에 선언되어 있다.모든 것은 GNU의 확장이다.

함수 void * r__alloc  (void **handleptr, size_t size)

이 함수는 재조정가능한 size 크기의 블럭을 할당한다. 이 함수는 그 블럭의 주소를 *handleptr에 담고 성공했을 때 널(null)이 아닌 문자를 반환한다. 만약 r_alloc이 필요한 공간을 얻지 못하면 *handleptr에 널포인터를 저장하고나서 널포인터를 반환한다.

함수 void r__alloc__free  (void **handleptr)

이 함수는 재조정가능한 블럭을 해제하는 용도로 쓰인다. 이 함수는 *handleptr이 가리키는 블럭을 해제하고 더이상 어떤 할당된 블럭도 가리키고 있지 않음을 표시하기 위하여 *handleptr에 널포인터를 담아둔다.

함수 void * r__re__alloc  (void **handleptr, size_t size)

함수 r_re_alloc은 *handleptr이 가리키는 블럭의 크기를 조정하는데 size바이트만큼 크게 한다.이 함수는 *handleptr에 크기가 변한 블럭의 주소를 저장하고 성공을 하게되면  널이 아닌 포인터를 반환한다. 만약 충분한 메모리를 사용할 수 없게 되면,이 함수는 널 포인터를 반환하고 *handleptr을 변경하지 않는다.

3.7  메모리 사용 경고

당신은 memory_warnings를 호출함으로써 프로그램이 메모리 공간을 다 써버리지 않도록

경고를 요청할 수 있다. 이 함수는 운영체제로부터 더 많은 메모리를 요청할 때마다 메모리

사용을 체크하도록 malloc에게 알려준다. 이것은 'malloc.h'에 선언되어 있는 GNU의 확장이다.

void memory__warnings  (void *start, void (*warn_func) (const char function *))

실제적인 메모리가 고갈되어가고 있음을 경고하려할  때 이 함수를 호출하라.인수 start는 메모리상에서 데이터 공간이 시작되는 곳을 말한다. 할당자는 이것을 사용된 마지막 주소와 대조하고 또 데이터 공간의 한계와 대조하여,사용가능한 메모리의 단편을 사용토록 결정한다.만약 당신이 start에 0을 대입하게 되면 대부분의 환경에서 쓰일 수 있는 내정값이 사용되게 된다. 경고함수를 사용할 때 malloc이 당신에게 경고를 주기  위해 호출할 수 있는 함수를 사용하라.그것은 인수로서  문자열(경고 메세지)을 동반하여 호출된다.보통 그 문자열은 사용자가 읽을 수 있게끔 디스플레이된다. 경고는 메모리의 75%, 85%, 95%를 사용했을 때 주어진다. 95%가 넘었을 때는 메모리가 쓰일 때마다 경고를 얻을 수 있다.

4  문자 다루기

4장 문자 다루기

문자와 문자열들을 가지고 일을하는 프로그램들은 종종 한 문자가 알파벳인지 숫자인지 또는 공백인지, 등 등을 분류할 필요와 각 문자열에 원하는 변환을 시킬필요가 있다.  'ctype.h'안에 있는 함수들이 이와 같은 목적을 위한 함수들을 제공한다. 문자조합과 지역을 선택한 후에 특별한 문자 코드들의 부류로 변화 시킬수 있는데, 이 모든 함수들은 현재의 지역에 의해 영향을 받는다.( 더 정밀히 말하면 LC_CTYPE 분류의 문자분류를 위해 선택되어진 현재의 위치에 의해 영향을 받는다.; 19.3절[Locale Categories] 260페이지를 참조하라.)

4.1  문자들의 분류

이 절은 문자들 분류를 위한 라이브러리 함수들을 설명한다. 예를 들면 isalpha는 한 문자가 알파벳 문자들인지 테스트 하기위한 함수이다. 이 함수는 테스트 하기위한 문자를 하나의 인수로 취하여 만약 그것이 알파벳이면 영이 아닌 정수를 리턴하고 다른 경우는 영을 리턴한다.  당신을 이것을 다음과 같이 사용할수 있다.

if  (isalpha  (c))

printf  ("The  character  `%c'  is  alphabetic.\n",  c);

/* 만약 c가 알파벳이면 "이문자는 알파벳이다" 라는걸 프린트 하겠죠...?   */

이 절안의 각각의 함수들을 문자들이 특별한 부류안에 속하는지를 테스트 하기위한 함수들이다. 각각은 'is'로 함수이름이 시작된다. 그들 함수는 테스트 하기위한 문자를 하나의 인수로 취하고 boolean값으로 취급되는 정수를 리턴한다.  그 문자인수는 int형으로 간주되고 실제 문자 대신에 상수값 EOF로 되어질 것이다. 어느 주어진 문자의 속성은 지역들에 따라서 변화할수 있다. 지역들에 대한 더 다양한 정보는 19장[Locales] 259페이지를 참조하라.

다음은 'ctype.h'의 헤더파일에 정의된 함수들이다.

함수 int islower (int c)

만약 c가 소문자이면 참값을 리턴한다.

함수 int isupper (int c)

만약 c 가 대문자이면 참 값을 리턴한다.

함수 int isalpha (int c)

만약 c 가 영문자이면 참값을 리턴한다. 문자가 대문자이건 소문자이건 isalpha는 모두 참값을 리턴한다.  어떤 지역들에서 isalpha에 의해 그것이 소문자의 경우든 대문자의 경우든지 참값이 리턴되도록 더해진 문자들이 있을수 있다. 하지만 표준C언어에서는 그렇게 더해진 문자들이 없다.

함수 int isdigit (int c)

만약 c 가 십진수('0'에서 '9')이면 참값을 리턴한다.

함수 int isalnum (int c)

만약 c 가 영숫자이면 ( 문자이거나 숫자 ) 참값을 리턴한다.; isalpha나 isdigit 에 의해 참값이 리턴되는 문자는 isalnum에서도 참값을 리턴한다.

함수 int isxdigit (int c)

만약 c 가 16진법의로 사용된 문자 이면 참값을 리턴한다.  16진수는 일반적 10진수인 '0'에서 '9'와 'A'에서 'F" 까지와 'a'에서 'f'까지의 문자를 포함한다.

함수 int ispunct (int c)

만약 c 가 구두문자인 경우 참값을 리턴한다. 이것은 알파벳이나 공백문자는 아니지만 인쇄할수 있는 어느 문자를 의미한다.

함수 int isspace (int c)

만약 c 가 공백문자(white space)이면 참값을 리턴한다.  표준"C"에서는 오직 공백문자(white space)에 참값을 리턴한다.

' '            space   : 공백

'\f'            formfeed  : 종이넘김

'\n'            newline  : 개행

'\r'            carriage return : 복귀문자

'\t'            horizontal tab : 수평탭

'\v'            vertical tab  : 수직탭

함수 int isbalnk (int c)

만약 c 가 blank 문자이면 참값을 리턴한다.; 이것은 space나 tab을 말한다.  이 함수는 GNU 확장이다.

함수 int isgraph (int c)

만약 c 가 그래픽 문자이면 참값을 리턴한다.; 이것은 상형문자와 연관된 문자로 공백문자(white space)는 그래픽으로 간주되지 않는다.

함수 int isprint (int c)

만약 c 가 프린트되는 문자면 참값을 리턴한다.  프린트되는 문자란 모든 그래픽문자에 더하여 space (' ') 문자를 포함한다.

함수 int iscntrl (int c)

만일 c 가 제어문자이면 참값을 리턴한다. ( 그것은 프린트가 가능하지 않은 문자이다. )

함수 int isascii (int c)

만일 c 가 US/UK 문자셋 안에 정의된 7비트 부호화되지 않은 문자이면 참값을 리턴한다.  이 함수는 BSD와 SVID확장이다.

4.2  변환

이절은 문자들을 원하는 목적에 맞도록 변환을 수행하는 라이브러리 함수들에 대해 설명하고 있다. 예를 들어 toupper 변환은 그것이 가능하다면 대문자로 어느 한 문자를 변환한다.  만약 그 문자가 변환할수 없는 것이라면 변환되지않은 문자를 리턴한다.

이 함수들을 변환시키기 원하는 문자를 int 형으로 인자를 받아서 문자를 변환시켜 리턴한다.  만약 주어진 인수에 적용불가능하다면 그 인수는 변환되지않고 리턴된다.

호환성에 대한 참조 : ANSI-C 이전에는 변환되지 않은 문자를 리턴하는 대신에 인수가 변환에 적용불가능 하다면 이 함수들은 그냥 실패만 하고 만다.  그래서 적용성을 위해서 단지 toupper(c) 라고만 하는것보단 islower(c) ? toupper(c) : c 라고 쓰는 것이 필요하다.

이 함수들을 헤더파일 'ctype.h'에 정의되어 있다.

함수 int tolower (int c)

만약 c 가 대문자라면 그와 대응하는 소문자를 리턴한다.  만약 c 가 대문자가 아니라면 c는 변화되지 않고 리턴된다.

함수 int toupper (int c)

만약 c 가 소문자라면 그것에 대응하는 대문자를 리턴한다.  그렇지 않으면 변화되지 않은 c를 리턴한다.

함수 int toascii (int c)

이 함수는 c를, c의 가장 상위 비트를 클리어 함으로써 US/UK ASCII 문자셋 안에 존재하는 7비트 비부호화 문자값으로 변환시킨다. 이 함수는 BSD와 SVID확장이다.

함수  int __tolower (int c)

이 함수는 소문자로 변환시키는 함수로 SVID에 호환성을 위해 제공되고 있다. 1.2.4절 [SVID] 2페이지를 보라.

함수 int __toupper (int c)

이 함수도 위의 __tolower(int c)처럼 SVID의 호환성을 위해 제공되는 함수로 문자를 대문자로 변환시키는 기능을 한다.

웃은 罪

지름길 묻길래 대답했지요.

물 한 모금 달래기에 샘물 떠주고

그리고는 인사하기 웃고 말았지요.

평양성에 해 안뜬대두

난 모르오.

웃은 죄밖에.

5  문자열과 배열 유틸리티

5장 문자열과 배열 유틸리티

문자열(또는 문자의 배열) 처리는 많은 프로그램에 있어 중요한 부분이다.GNU C 라이브러리는 많은 문자열 유틸리티 함수를 제공하는데,여기에는 문자열의 복사, 연결, 비교, 검색을 위한 함수들이 포함되어 있다.많은 문자열 처리함수들은 저장소의 임의의 구역을 처리할 수 있다; 예컨데, memcpy 함수는 어떠한 종류의 배열 내용도 복사해 낼 수 있다.

초보 C 프로그래머들이 자신의 코드로 이 기능을 중복되게 작성하여 "도구를 재발명"하는 일은 너무도 흔한 일이다.그러나 그렇게 함으로써 라이브러리 함수들에 친숙해지고 그것들을 사용할 수 있게되므로,이러한 일은 유지,효율성,운용에 이점을 제공하는 셈이다.

예컨데,당신은 2행의 C 코드에서 하나의 문자열을 다른 문자열과 쉽게 비교할 수 있다.만약 당신이 기존의 strcmp함수를 사용한다면 실수를 줄일 수 있다.또한 이러한 라이브러리 함수들은 전형적으로 매우 최적화되어 있으므로 당신의 프로그램은 빠르게 실행될 수도 있다.

5.1  문자열의 표현

이 절에서는 초보 C 프로그래머들을 위해 문자열의 개념을 간명하게 요약하였다.C에서 문자열이 어떻게 표현되며 흔히  있을 수 있는 함정은 무엇인가를 기술해 놓았다.만약  당신이 이미 이러한 내용들을 잘 알고 있다면,이 절을 건너 뛰어도 좋다.

문자열은 char 대상물의 배열이다.그러나 문자열 값을 가지는 변수들은 항상 문자포인터(char *) 형태로 선언된다.그러한 포인터 변수들은 어떤 문자열의 내용물을 위한 공간을 포함하고 있지 않다;그 내용물은 어떤 다른 곳에 저장된다_즉, 어떤 배열 변수,문자열 상수,또는 동적으로 할당된 메모리 (3장.[메모리 할당],21쪽 참조)에 저장된다. 선택된 메모리 공간의 주소를 포인터 변수에 저장하는 일 당신의 몫이다.선택적으로 당신은 포인터 변수에 널(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(7.9.7 [형식화된 출력 함수], 76쪽 참조.)나 scanf(7.11.8 [형식화된 입력 함수], 90쪽참조.)와 같은 몇몇 함수들이 있다.

함수 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라는 명칭의 스트럭쳐가 2개의 포인터에 할당되고*/

int  arraysize;

. . .

memcpy  (new,  old,  arraysize  *  sizeof(struct foo));

/*  old의 내용을 new에 <스트럭쳐 크기 * 특정숫자>크기 만큼 복사하라는 것*/

함수 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을 써서 할당된다:  3.3 [제한없는 할당],22쪽 참조.

만약 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  ()

{

/*버퍼에 "hello"를 10 바이트만 복사*/

strncpy  (buffer,  "hello",  SIZE);

/*그러면 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  문자열/배열 비교

당신은 문자열과 배열의 내용을 비교하기 위해서 이 절에 있는 함수들을 사용할 수 있다. 비교 뿐만 아니라 정렬을 할 때 순서관계를 결정하는 함수로서도 사용할수 있다. 15장 [Searching and Sorting] 217페이지 참조.

다른 대부분의 비교와 달리 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 분류에 속한 지역을 선택하여 지정할수 있다.; 19장 [Locals] 259페이지 참조.

표준 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이다. ( 15.3절[Array Sort Function] 217페이지 참조 ) 여기에 나타난 프로그램 코드들은 그들을 정렬하는 동안 문자열들을 비교하기 위해 어떻게 하는지를 말해주고 있다. ( 이 절의 끝에 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절[Copying and Concatenation] 46페이지를 보라. 리턴값은 변형된 문자열의 전체길이이다.  크기가 값으로서 유용하지는 않지만 만일 그것이 크기보다 크다면 그것은 변형된 문자열이 배열크기에 전체적으로 맞지 않다는 것을 의미한다.  이 경우에 오직 그 문자열의 양만큼 실제적으로 맞추어서 저장된다.  전체적으로 변형된 문자열을 얻으려면 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;

}

}

/* 비교할 변형된 문자열들에 의해 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)에 쓰기를 시도하므로 심각한 에러신호를 얻을 것이다. 21.2.1절 [Program Error Signals] 273페이지 참조.

이것은 일반적인 원칙의 특별한 경우이다:  만일 프로그램의 일부분이 어떤 데이터 구조체를 수정하려는 목적을 가지고 있지 않다면 그것을 임시적으로 수정하려 할 때 에러가 발생하는 경향이 있다. strtok함수는 재진입하지 않는다.  21.4.6절 [Nonereentrancy] 290페이지, 재진입하는 이유나 위치의 중요성에 대한 논의를 위해, 여기에 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는 구획문자가 나오면 그것을 토큰으로 잘라서 리턴시키네요.... */

****************문제점:process를 "프로세스"라 하느냐 "처리"라 하느냐**********************:link를 "연결"로 할것인가 "링크"라 할것인가.

세모감

사람아 나더러 말썽을 마소.

거슬러 예는 물을 그스른다고

말하는 사람부터 어리석겠소.

가노라 가노라 나는 가노라.

내 성품 끈느 대로 나는 가노라.

열두길 물이라도 나는 가노라.

6  입출력 개요

6장 입출력 개요

대부분의 프로그램은 무언가의 유용한 작업을 하기 위하여 입력(데이터 읽기) 또는 출력(데이터 쓰기)을 필요로 하거나,흔히는 입출력 모두를 필요로 한다. GNU C 라이브러리는 아주 많은 입출력 함수들을 제공 하고 있어서 가장 적합한 함수가 엄격하게 적용될 수 있도록 하고 있다. 이 장에서는 입력과 출력에 관련된 개념과 용어를 소개한다.GNU의 입출력 도구에 관련된 다른 장은 다음과 같다.

□ 7장 [스트림 입출력],63쪽 은 형식화된 입출력을 포함하여 스트림을 처리하는 고수준 함수를 다룬다. <역자주>스트림==소스로 부터 입력되는 일련의 문자들

□ 8장 [저수준 입출력],103쪽은 기본적인 입출력과 파일쓰기를 통제하는 함수를 다룬다.

□ 9장 [파일 시스템 인터페이스],123쪽은 디렉토리를 처리하거나 접근 모드나 소유권과 같은 파일속성에 관한 함수들을 다룬다.

□ 10장 [파이프와 FIFO],145쪽은 기본적인 내부처리 커뮤니케이션 도구들에 관한 정보를 담고 있다.

□ 11장 [소켓],149쪽은 네트워크를 지원하는 좀더 복잡한 내부 처리 커뮤니케이션 도구들을 다룬다.

□ 12장 [저수준 터미널 인터페이스],185쪽은 터미널이나 다른 연결 장치에 대한 입력과 출력을 처리하는 방식을 변경하는 함수들을 다룬다.

6.1  입출력의 개념

당신이 어떤 파일의 내용을 읽거나 쓸 수 있으려면 먼저 파일에 연결 채널이나 커뮤니케이션 채널을 설치하여야 한다.이 과정을 파일열기라고 부른다. 당신은 파일을 읽기모드,쓰기모드 또는 읽기쓰기 모드로 열 수가 있다. 열려진 파일과의 연결은 스트림으로 나타내거나 파일지시자로 나타낸다.당신은 이것을 실제의 읽기쓰기 처리를 수행할 함수의 인수에 전달하여, 그 함수가 어떤 파일을 처리할 것인지를 알려준다.  어떤 함수들은 스트림을 받아서 처리하며, 다른 함수들은 파일지시자를 처리하도록 만들어져 있다. 당신이 파일 읽기나 쓰기를 마쳤을 때는, 그 파일을 닫아줌으로써 연결을 끝마칠 수 있다. 당신이  스트림이나 파일지시자를 일단 닫고나면, 더이상 그 파일에 입력이나 출력을 행할 수 없다.

6.1.1  스트림과 파일지시자

당신이 어떤 파일에 입력과 출력을 하고 싶을 때 당신의 프로그램과 파일간의 연결을 나타내기 위한 두 개의 기본적인 메카니즘을 선택해야 한다: 파일지시자와 스트림이 그것이다.파일지시자는 int 형태의 대상물로 표현되고, 반면에 스트림은 구조체 FILE의 포인터 대상물(FILE * objects)로 표현된다. 파일지시자들은 입출력 처리에 대한 일차적이고 저수준인 인터페이스를 제공한다.파일지시자나 스트림 모두 어떤 장치(터미널과 같은)로의 연결을 나타낼 수도 있고 보통의 파일 뿐만아니라 다른 프로세스와의 커뮤니케이션을 위한 파이프나 소켓과의 연결을 나타낼 수도 있다. 그러나, 만약 당신이 특정한 종류의 장치에 국한된 통제 처리를 하고 싶다면, 반드시 파일지시자를 사용해야만 한다;이러한 방식에서 스트림을 사용하는 도구는 없다. 블럭화되지 않은(또는 등록된?) 입력(8.10[파일 상태 플래그],116쪽 참조)과 같이 특별한 모드로 입출력을 할 필요가 있을 때도,당신은 반드시 파일지시를 사용하여야 한다.

스트림은 일차적인 파일지시자 도구들 위에 차곡차곡 쌓아 올려진 고수준의 인터페이스를 제공한다.스트림 인터페이스는 아주 흡사한 모든 종류의 파일을 다루지만 당신이 선택할 수 있는 세가지 양식의 버퍼(7.17 [스트림 버퍼],95족 참조)에서만 예외가 있다.

스트림 인터페이스를 사용하는 주된 이점은 스트림을 입출력 처리하는(통제처리와 다른 것으로서)함수들의 세트가 그에 상응하는 파일지시자 사용도구들에 비해서 훨씬 풍부하고 강력하다는 점이다.파일지시자 인터페이스는 단순히 문자 블럭을 옮겨주는 함수들만을 제공하는 데 반해, 스트림 인터페이스는 문자단위및 행단위 입출력 뿐만아니라 강력한 형식화된 입출력(printf와 scanf) 함수들까지 제공한다.

스트림은 파일지시자에 의해 완성되므로 당신은 스트림에서 파일지시자만을 뽑아내서 직접 파일지시자만을 저수준 처리할 수 있다. 당신은 처음에 파일지시자를 써서 (파일을) 연결하였다가, 나중에 스트림을 만들어서 그 파일지시자와 일치시킬 수도 있다.

일반적으로,파일지시자로써만 작동시키고 싶은 어떤 특별한 처리가 아니라면,파일지시자보다는  스트림을 사용하는 습관을 갖는게 좋다. 만약 당신이 초보 프로그래머이고 사용할 함수들을 잘 모른다면, 당신은 집중적으로 형식화된 입력(7.11 [형식화된입력],85쪽 참조)과 형식화된 출력(7.9 [형식화된 출력],70쪽 참조)만을 사용하는 게 좋다.

만약 당신이 GNU 이외의 다른 체제에서 프로그램을 운용하려 한다면, 당신은 파일지시자가 스트림보다 쉽지 않음을 알고 있어야 한다. 당신은 ANSI C를 쓰는 어떤 체제든 스트림을 제공한다고  기대해도 좋지만, GNU가 아닌 다른 체제에서는 파일지시자가 전혀 제공되지 않으며, 파일지시자를 처리하는 GNU 함수의 부수적인 부분을 완성할 뿐이다.그러나,GNU 라이브러리의 파일지시자 함수의  대부분은 POSIX.1 표준에 포함된다.

6.1.2  파일 위치

열려진 파일의 속성 중의 하나는 파일에서 다음 문자를 읽어오거나 써야할 장소를 추적하고 있는 파일 위치다.GNU 체제에서는 모든 POSIX.1 체제와 마찬가지로 파일 위치는 그 파일의 시작부분으로부터의 바이트수로 나타내지는 정수일 뿐이다.

파일위치는 보통 파일이 열렸을 때 파일의 시작위치에 놓여지고,한 문자를 읽거나 쓸 때마다 파일위치가 증가한다. 달리 말하자면, 파일에의 접근은 정상적으로는 연속적이다.

보통의 파일들은 파일내의 어떤 위치에서든 읽기와 쓰기를 허용한다.어떤 종류의 파일들은 이것을 허용하지 않기도 한다.이것을 허용하는 파일들은 종종 랜덤 파일이라 불린다.당신은 스트림에서 fseek 함수를 사용하여 (7.15 [파일 위치 결정],93쪽 참조) 파일지시자에서 lseek 함수를 사용하여 파일위치를 변경할 수 있다.당신이 랜덤파일이 아닌 파일에서 파일위치를 변경하려 하면 ESPIPE 에러를  만날 것이다.

추가모드로 열린 스트림과 파일지시자는 특별히 출력을 위한 것이다: 그러한 파일에 대한 출력은  항상 파일위치에 상관없이 그 파일의 끝부분에 연속적으로 추가한다. 그러나, 파일위치는 여전히  파일내에서 읽기가 수행되는 곳을 통제하고 있다.

이와 같은 점을 생각해보면,몇개의 프로그램들이 하나의 파일을 동시에 읽을 수 있음을 알 수 있을것이다.각 프로그램이 자신의 페이스대로 파일을 읽기 위해서는 각기의 파일포인터를 가져야만 하고, 그 파일포인터는 다른 프로그램이 수행하는 어떤 일에 의해서도 영향받지 않아야 한다. 사실은,각각의 파일열기는 다른 파일위치를 만든다. 그러므로, 만약 당신이 같은 프로그램에서 하나의 파일을 두번 열었다 하더라도, 당신은 독자적인 파일위치를 갖는 두개의 스트림 또는 파일지시자를 갖게된다.

이와는 대조적으로,당신이 만약 지시자를 열어서 다른 지시자와 중복되게 하였다면,이 두개의 지시자는 같은 파일위치를 공유하게 된다:하나의 지시자에서 파일위치를 변경시키면 다른 지시자도 영향을 받는다.

6.2  파일 명칭

어떤 파일을 연결하거나 파일 삭제와 같은 다른 처리를 위해서는 그 파일을 참조할 수 있는 어떤 방법이 필요하게 된다.거의 모든 파일은 문자열로 된 명칭을 갖는다_테이프 드라이브나 터미널과 같은 사실상의 장치조차도 그와 같다.이 문자열들을 파일명칭이라 부른다.당신은 파일명칭을 지정하여 당신이 열거나 처리하고자 하는 파일이 어떤 것인지를 말해줘야 한다. 이 절에서는 파일명칭에 대한 규정들을 기술하고 운영 체제가 그것들을 취급하는 방식을 기술한다.

6.2.1  디렉토리

파일 명칭의 문장을 이해하려면, 파일체제가 디렉토리 계층으로 조직되는 법을 이해할 필요가 있다. 디렉토리는 다른 파일들을 명칭과 결합시켜주는 정보를 담고 있는 파일이다;이 결합은 링크 또는 디렉토리 엔트리라 불린다.흔히 사람들은 "디렉토리 내의 파일"이라고 말하지만, 사실은,디렉토리는 파일에 대한 포인터를 담고 있을 뿐이지 파일 그 자체는 아니다.

디렉토리 엔트리에 담겨진 한 파일의 명칭은 파일명칭 성분으로 불린다.일반적으로, 한 파일의 명칭은 슬래쉬('/') 문자로 분리된 하나 또는 그 이상의 연속물로 구성된다.단 하나의 성분으로 이루어진 파일명칭은 그 디렉토리 내의 파일을 지칭한다.여러개의 성분으로 이루어진 파일명칭은 어떤 디렉토리와 그 디렉토리 내의 파일 등을 지칭한다. POSIX 표준과 같은 어떤 다른 문서들에서는 소위 파일 명칭 대신에 경로명칭이란 용어를 사용하며, 이 안내서에서 파일명칭 성분이라고 부르는 것 대신에 파일명칭 또는 경로명칭 성분이란 용어를 사용한다.우리는 이 용어를 사용하지 않는다. 왜냐하면,"경로"는 완전히 다른 것(찾고자하는 디렉토리들의 목록)이고,우리가 생각하는 바로는 다른 것을 지칭하여 "경로명칭"이라고 해버리면 사용자들을 혼동시키기 때문이다.GNU 문서들에서는 항상 "파일명칭"과 "파일명칭 성분"만을 사용한다. 당신은 9장 [파일체제 인터페이스],123쪽에서 디렉토리 처리에 관한 더 상세한 정보를 얻을 수 있다.

6.2.2  파일 명칭 분석

파일 명칭은 슬래쉬('/') 문자로 분리된 파일 명칭 성분으로 구성된다.GNU C 라이브러리가 지원하는 체제에서는 여러개의 연속적인 '/' 문자들은단일한 '/' 문자와 동일하다.

파일 명칭이 어느 파일을 참조할 것인가를 결정하는 처리를 파일명칭 분석이라 부른다.이 처리는 파일 명칭을 구성하고 있는 성분을 왼편에서 오른편으로 조사하여,각각의 연속적인 성분을 앞의 성분에 의해 지칭된 디렉토리에 배치한다. 물론, 디렉토리로 참조되는 각각의 파일들은 실제로 존재해야 하며, 정규적인 파일 대신에 디렉토리여야 하고, 뿐만아니라 처리에 의한 접근을 허용하는 적절한 권한이 있어야 한다.

만약 어떤 파일명칭이 '/'로 시작되면 그 파일명칭의 최초의 성분은 그 처리의 루트 디렉토리에 위치 한다.(언제나 체제에서의 모든 처리는 같은 루트디렉토리를 갖는다.) 그와 같은 파일명칭은 절대적 파일명칭이라 부른다.

그렇지않을 경우,파일명칭에서의 최초 성분이 현재의 작업 디렉토리에 위치한다.(9.1 [작업 디렉토리],123쪽 참조) 이러한 종류의 파일명칭은 상대적 파일명칭이라 부른다.

파일명칭 성분 '.'("점")과 '..'("점-점")은 특별한 의미를 갖는다.모든 디렉토리는 이 파일명칭 성분들을 위한 엔트리를 갖는다.파일명칭 성분 '.'는 디렉토리 그 자체를 말하는 반면에,파일명칭 성분 '..'은 그것의 부모 디렉토리(해당 디렉토리와의 연결을 갖는 디렉토리)를 말한다.특별한 경우로서,루트 디렉토리에 있는 '..'은 루트 디렉토리 그자체를 말하는데,왜냐하면 그것은 부모 디렉토리가 없기 때문이다;이리하여,'/..'은 '/'과 같다. 파일명칭의 예를 보기로 하자.

'/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

파일명칭을 찾는 동안 너무 많은 부호링크가 분해되었다.체제는 루프를 조사할 일차적 방법으로서의 부호링크가 하나의 파일을 찾는데서 분해될 수 있는 갯수를 임의로 제한하고 있다. 9.4 [부호 링크],127쪽 참조.

6.2.4  파일명칭의 운용

6.2 [파일명칭],58쪽에서 언급한 파일명칭 문장에 대한 규칙은 GNU 체제와 다른 POSIX 체제에서정상적으로 사용되는 규칙들이다.그러나 다른 운영체제에서는 다른 규정들을 사용할 것이다. 당신이 파일명칭 운용 문제를 아는데서 그것이 중요할 수 밖에 없는 두 가지 이유가 있다.

□ 만약 당신의 프로그램이 파일명칭 문장에 가정문을 넣거나 삽입된 리터럴 파일명칭 문자열을 포함한다면 그것을 다른 문장 규정을 갖는 다른 운영 체제에서 실행하려할 때 어려움이 많을 것이다.

□ 설령 당신이 다른 운영체제로 작동하는 장치에서 당신의 프로그램을 실행하려는 의사가 없다하더라도 다른 명칭 규정을 사용하는 파일들에 접근하는 일은 여전히 있음직한 일이다. 예를 들어,당신은 네트워크 상에서 다른 운영체제를 작동시키는 다른 어떤 컴퓨터의 파일에 접근하거나 다른 운영체제에 의해 포맷된 디스크에서 읽기 쓰기를 할 수 도 있을 것이다.

ANSI C 표준에서는 파일명칭 문장에 관한 언급이 거의 없고 다만 파일명칭이 문자열이란 설명만 있다. 운영체제가 달라짐에 따라 파일명칭의 길이에 대한 제한규정과 파일명칭에 사용될 수 있는 문자의 종류가 변화할 뿐 아니라,구조화된 디렉토리와 파일 형태, 확장자와 같은 개념에 대한 규정과 문장이 달라진다. 파일 버젼과 같은 개념들은 어떤 운영체제에서는 지원되지만 그렇지 않은 경우도 있다.

POISIX.1 표준에서는 파일명칭에서 허용되는 문자와 관련하여 파일명칭 문장에 대한 추가적인 제한을 보충 할 수 있도록 하고 있으며,또한,파일명칭과 파일명칭 성분 문자열의 길이에 대한 추가적인 제한도 보충할 수 있도록 하고 있다.그러나, GNU 체제에서는 당신은 이러한 제한에 대해 염려할 필요가 없다; 널문자를 제외한 어떠한 문자든지 파일명칭 문자열에 허용되며, 파일명칭 문자열 길이에 대한 제한도 없다.

여기 적힌 먹빛이

희미해 질수록

당신의 사랑하는 마음

희미해 진다면

이 먹빛이 마름하는 날

나는 당신을 잊을 수 있겠습니다.

W□워즈워드의 □초원의 빛□ 에서

7  스트림에서의 입/출력

7장 스트림에서의 입/출력

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

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] 91페이지 참조.

FILE object들은 입/출력 라이브러리 함수들에 의해 내부적으로 할당되고, 관리된다.  당신이 FILE이라는 형으로 object를 창조하려고 시도하지 마라.; 그것을 라이브러리가 하도록 내버려두라. 당신의 프로그램들은 오직 FILE objects를 직접 다루기 보다는 포인터를 이용하여 다루도록 해야한다.( 그것은 FILE * 과 같다. )

7.2  표준 스트림

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

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

변수 : FILE * stdin

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

변수 : FILE * stdout

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

변수 : FILE *stderr

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

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

fclose  (stdout);

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

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

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] 95페이지를 참조.  fseek에 대한 것은 7.15절 [File Positioning] 93페이지를 참조하라. )

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

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

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

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

매크로 : int FOPNE__MAX

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

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

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

7.4  스트림 닫기

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

함수 : int fclose (FILE *stream)

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

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

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

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

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] 91페이지 참조)

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;

/*  질문으로 부터 답을 분리시키시 위해 공백을 출력하라. */

fputc  ('  ',  stdout);

/*  줄의 첫 번째 문자를 읽어라. 이것은 원하는 문자일수도 있고, 아닐수도 있다.     */

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], 91페이지 참조 )

7.7  라인 단위 입력

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

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

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

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

이 함수는 스트림으로 부터 전체의 라인을 읽어서, 버퍼안에 텍스트를 저장하고( 새줄과 널 종료문자가 포함된) *lintptr안에 버퍼의 주소를 저장한다.

getline를 호출하기전에, 당신은 malloc로 할당된 *n 바이트의 길이를 가진 버퍼의 주소를 *lineptr에 주어야한다.  만약 이 버퍼가 라인을 저장하기에 충분히 길면, getline은 이 버퍼에 라인을 저장한다.  그렇지 않으면, getline은 realloc을 사용해서 버퍼를 크게 만든다음, *lineptr에 새로운 버퍼의 주소를 저장하고, *n안의 크기가 증가된다. 3.3[Unconstrained Allocation] 22페이지를 참조.	만일 당신이 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] 93페이지를 참조) 것이 호출되었다면, 아직 쓰여지지 않은 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 기다려주세요.\n", filename,  pct);

위 프로그램의 출력은 이렇다.

'foot.txt'의 처리는 37% 완료됨.

기다려주세요.

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

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

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

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

7.9.2  출력 변환 문장

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

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 [정수 변환자],72쪽 참조,상세설명.'%d'와 '%i'는 출력에서는 동의어이지만, 입력에서 scanf를 사용할 때는 다르다.(7.11.3 [입력 변환자 테이블],86쪽 참조)

'%o'	정수를 부호없는 8진수로 프린트한다.7.9.4 [정수 변환자],72쪽 상세설명 참조.

'%u'	정수를 부호없는 10진수로 프린트한다.7.9.4[정수 변환자],72쪽 상세설명 참조.

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

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

'%f'	부동소수점수를 정상적인(고정소수점) 표기로프린트한다.7.9.5 [부동소수점 변환자],74쪽 상세설명 참조.

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

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

'%c'	단일문자를 프린트한다.7.9.6 [여타의 출력 변환자],75쪽 참조.

'%s'	문자열을 프린트한다.7.9.6 [여타의 출력 변환자],75쪽 참조.

'%p'	포인터의 값을 프린터한다.7.9.6 [여타의 출력 변환자],75쪽 참조.

'%n'	프린트될 문자의 갯수.7.9.6 [여타의 출력 변환자],75쪽 참조.이 변환자 지정은	어떤 출력도 생성할 수 없음에 주의하라.

'%m'	errno 값에 일치하는 문자열을 프린트한다.(이것은 GNU 확장이다.) 7.9.6 [여타의 출력 변환자], 75쪽 참조.

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

7.9.4  정수 변환자

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

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

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

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

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

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

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

'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'변환자에 대한 다른 옵션들을 사용하여 수치를 프린트해보면 다음과 같은

결과를 얻는다:

0 1 -1 100000	0 1 -1 100000	+0 +1 -1 +100000	+0 +1 -1 100000	0 1 -1 100000	00000 00001 -0001 100000	1 -1 100000	00 01 -01 100000	0 1 -1

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

템플리트 문자열 "|%5u|%5o|%5x|%5X|%#5o|%#5x|%#5X|%#10.8x|\n"을 사용하여, 다양한 옵션으로 부호없는 정수를 프린트한 경우를 보여주는 몇개의 예를 보기로 하자.

0 1 100000	0 1 303240	0 1 186a0	0 1 186A0	0 01 0303240	0x0 0x1 0x186a0	0X0 0X1 0X186A0	0x00000000 0x00000001 0x000186a0

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"

출력은 이렇다:

0.0000 1.0000 -1.0000 100.0000 1000.0000 10000.0000 12345.0000 100000.0000 123456.0000	0.0000e+00 1.0000e+00 -1.0000e+00 1.0000e+02 1.0000e+03 1.0000e+04 1.2345e+04 1.0000e+05 1.2346e+05	0 1 -1 100 1000 1e+04 1.234e+04 1e+05 1.234e+05

'%g' 변환자가 뒤따르는 0을 떨어뜨리는 법에 유의하라.

7.9.6  여타의 출력 변환자

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

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

printf  ("%c%c%c%c%c",  'h',  'e',  'l',  'l',  'o');

는 'hello'를 프린트한다.

'%s'	변환자는 문자열을 프린트한다.상응하는 인수는 char * (또는 const char *) 형태여야 한다. 상세지정은 기록할 문자의 최대 개수를 나타내기 위하여 지정될 수 있다;그렇지 않을 경우에는 널문자를 포함하지 않을 동안의 모든 문자가 출력 스트림에 기록된다.'-'플래그는 필드내에서의 왼편 정렬을 지정하기 위해 사용될 수 있지만,다른 플래그나 형태 변경자는 이 변환자에 대해서 주어질 수 없다.예를 들어:

printf  ("%3s%-6s",  "no",  "where");

는 ' nowhere '를 프린트한다.

만약 당신이 우연히 '%s' 변환자에 대한 인수로서 널 포인터를 전달하게 되면,GNU 라이브러리는 그것을'(null)'로 프린트한다.이것은 실패보다는 낫다고 생각할 수 있다.그러나 의도적으로 널 인수를 전달하는 것은 좋은 습관이 아니다.

'%m'	변환자는 errno내의 에러코드에 해당하는 문자열을 프린트한다.2.3 [에러 메세지],17쪽 참조. 즉,

fprintf(stderr, " `%s'을 열 수 없음: %m\n",filename); 은

fprintf(stderr, " `%s'을 열 수 없음: %s\n",filename, strerror (errno)); 과 같다.

'%m' 변환자는 GNU C 라이브러리 확장이다.

'%p'	변환자는 포인터 값을 프린트한다. 상응하는 인수는 void * 형태여야 한다. 사실상,	당신은 어떠한 형태의 포인터도 사용할 수 있다.

GNU C 체제에서는,널이 아닌 포인터들은 마치 '%#x' 변환자가 사용된 것처럼 무부호 정수로 프린트된다.널 포인터들은 '(nil)'로 프린트된다. (포인터들은 체제마다 다르게 프린트될 것이다.) 예를 들어:

printf ( "%p", "연습" );

은 16진수가 뒤따르는 '0x'를 프린트한다_문자열 상수 "연습"의 주소.그것은 '연습'이란 단어를 프린트하는 것이 아니다.

당신은 '%p' 변환자에서 왼편 정렬을 지정하기  위하여 '-' 플래그를 공급할 수는 있지만, 다른 플래그,상세지정,또는 형태 변경자를 정의할 수 없다.

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

int  nchar;

printf  ("%d  %s%n\n",  3,  "bears",  &nchar);

는 3 bears를 프린트하고,'3 bears'는 7문자이기 때문에 nchar에 7을 넣는다.

'%%'	변환자는 리터럴 '%' 문자를 프린트한 이 변환자는 인수를 사용하지 않으며

플래그,필드 폭, 상세지정,또는 형태 변경자가 허용되지 않는다.

7.9.7 형식화된 출력 함수

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

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

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

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

이 함수는 print 함수와 꼭 같으나,출력이 stdout 대신에 스트림 stream에 쓰여진다는 점만 다르다.

<역자주> 스트림 stream이 아니고 파일 stream이 아닌가?

** stream stream ---> FILE stream ????

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

이것은 printf와 같으나,출력이 스트림에 쓰여지는 대신에 문자 배열 s에 저장된다는 점만 다르다.널문자는 문자열의 끝을 표시하기 위해 쓰여 진다.

sprintf 함수는 배열 s에 저장된 문자의 갯수를 반환하지만 종료 널문자는 포함하지 않는다. 이 함수의 행위는 만약에 복사가 중첩되는 대상물사이에서 일어난다면__예를 들어,만약에 s가 '%s' 변환자의 통제하에서 프린트되는 인수로서도 주어진다면__정의되지 않는다.5.4 [복사와 연결],46쪽 참조.

경고: sprintf 함수는 위험할 수가 있다, 왜냐하면그것은 잠재적으로 문자열 s의 할당크기에 적합가능한 것보다 더많은 수의 문자를 출력할 가능성이 있기 때문이다.변환자 지정에서 주어진 필드폭은 최소값일 뿐임을 기억하라.

이 문제를 피하기 위하여,당신은 다음에 기술되는 snprintf나 asprintf를 사용할 수 있다.

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

snprintf 함수는 sprintf 함수와 유사하지만, size 인수가 만들어낼 문자들의 최대갯수를 지정한다는점만 다르다.따라붙는 널문자는 이 한계치에 포함되어 계산되므로,당신은 문자열 s에 대해 최소한 size 문자를 할당하여야만 한다. 반환값은 저장된 문자의 갯수인데,종료 널을 포함하지 않는다. 만약 이 값이 size-1 이라면,s에는 모든 출력을 위한 공간이 불충분한 것이다. 당신은 더 큰 출력 문자열을 갖고서 다시 해야 한다. 이렇게 하는 예를 들어 보자:

/* 명칭이 name이고 값이 value인 변수의 값을 기술하는 메세지 만들기.  */

char  * make_message  (char  *name,  char  *value)

{

/* 정확히 100 char의 공간만 필요히디고 가정.*/

int  size  =  100;

char  *buffer  =  (char  *)  xmalloc  (size);

while  (1)

{

/* 할당된 공간에 프린트 시도.  */

int  nchars  =  snprintf  (buffer,  size, "value  of  %s  is %s", name,  value);

/*  작동하면 문자열 반환함.  */

if  (nchars  <  size)

return  buffer;

/*  그렇지 않을 경우 2배의 공간으로 다시 시도.*/

size  *=  2;

buffer  =  (char  *)  xrealloc  (size,  buffer);

}

}

사실상,다음에 있는 asprintf를 바로 사용하는 편이 더 쉬울 때가 많다.

7.9.8  동적으로 할당하는 형식화된 출력

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

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

이 함수는 sprintf함수가 미리 할당된 버퍼에 output을 저장하는 대신에 동적으로 할당된 곳에 저장한다는 점을 제외하고는 sprintf함수과 유사하다.( malloc을 사용한다; 3.3절 [Unconstrained Allocation] 22페이지 참조) 여기서 ptr 인수는 char * object의 주소가 되어지고, asprintf는 그 위치에 새롭게 할당된 문자열을 가리키는 포인터를 저장한다. 여기에 snprintf와 같은 결과를 얻기위해 asprintf를 사용하는 법을 보여주고 있다.더 쉬운 방법이다.

/* 변수의 값, 즉, 이름은 뭐고, 값은 뭐라는 식으로 알리기 위해 구조화된 출력문이 필요하다.*/

char  *make_message  (char  *name,  char  *value)

{

char  *result;

asprintf  (&result,  "value  of  %s  is  %s",  name,

value);

return  result;

}

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

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

7.9.9  다양한 인수들을 출력하는 함수들

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

(program_invocation_short_name의 기술을 위해서는 2.3절 [Error Messages] 17페이지를 참조.

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

}

}

return  1;

}

7.10  printf 주문하기

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

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

이절에서 설명하고 있는 도구들은 헤더화일 '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] 83페이지 참조하라.  만일 당신이 널 포인터를 지정하면 spec을 위해 존재한 어느 handler function이 제거된다.

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

7.10.2  변환 지시자 옵션들

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

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

타입 : struct printf__info

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

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

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

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

unsigned int is_long_double

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

unsigned int is_double

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

unsigned int is_long

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

unsigned int alt

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

unsigned int space

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

unsigned it left

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

unsigned int showsign

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

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

7.10.3  출력 핸들러 정의하기

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

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

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

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

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

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

데이터 타입 : printf_function

이것은 핸들러 함수가 가질수 있는 데이터 타입이다. 만일 당신이 당신의 응용 프로그램안에 parse_printf_format를 사용하려하면, 당신은 당신이 register_printf_function으로 인스톨한 새로운 각각의 변환을 위해서 arginfo_function을 인수로 넘겨주기 위한 함수를 정의해야한다.

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

int function (const suruct printf_info *info, size_t n, int *argtypes)

그 함수로부터 리턴되는 값은 변환 예상되는 인수들의 수가 되어질 것이다. 그 함수는 또한 이 인수들 각각의 타입에 대한 정보로써 argtypes 배열의 n개의 요소를 채운다.  이 정보는 다양한 'PA_' 마크로를 사용하여 기호화된다. ( 당신은 이것이 parse_printf_format에서 사용되는 동일한 호출 협약임을  기억할 것이다. )

데이터 타입 : printf_arginfo_function

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

7.10.4  printf 확장 예제

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

#include  <stdio.h>

#include  <printf.h>

#include  <stdarg.h>

typedef  struct

{

char  *name;

}  Widget;

int print_widget  (FILE  *stream,  const  struct  printf_info *info,  va_list  *app)

{

Widget  *w;

char  *buffer;

int  len;

/* 문자열안에 출력을 포맷하라   */

w  =  va_arg  (*app,  Widget  *);

len  =  asprintf  (&buffer,  "<Widget  %p:  %s>",  w,  w->name);

if  (len  ==  -1)

return  -1;

/*  최소 필드 너비에 덧붙여서, 스트림을 프린트하라.   */

len  =  fprintf  (stream,  "%*s", (info->left  ?  -  info->width  :  info->width),                          buffer);

/*  지우고 리턴하라     */

free  (buffer);

return  len;

}

int  main  (void)

{

/*  프린트 하기위해 widget를 만들어라 */

Widget  mywidget;

mywidget.name  =  "mywidget";

/* widgets를 print 함수에 등록하라.       */

register_printf_function  ('W',  print_widget,  NULL);

/* arginfo가 아니다. 이제 widget를 프린트 하라. */

printf  ("|%W|\n",  &mywidget);

printf  ("|%35W|\n",  &mywidget);

printf  ("|%-35W|\n",  &mywidget);

return  0;

}

이 프로그램의 출력은 다음과 같다.

|<Widget  0xffeffb7c:  mywidget>        |

|        <Widget  0xffeffb7c:  mywidget>|

|<Widget  0xffeffb7c:  mywidget>        |

7.11  형식화된 입력

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

7.11.1  형식화된 입력의 기초

scanf 함수에 대한 호출은 임의의 인수들이 템플리트 문자열의 통제하에서 읽힌다는 점에서 피상적으로는  printf 함수에 대한 호출과 유사하다. 템플리트에서의 변환자 지정 문장이 printf에서의 그것과 매우 유사한 반면에,그 템플리트의 해석은 고정필드 형식화라기 보다는 자유형식 입력과 단순형식 어울리기라 볼 수 있다. 예를 들어,대부분의 scanf 변환자들은 얼마만큼이든 "공백"(공백,탭,개행을 포함)을 뛰어넘으며, 해당 출력변환자에 대한 상세지정의 개념은 있었던 반면에 수치입력 변환자에 대한 상세지정의 개념이 전혀 없다.보통,템플리트 내에서의 공백이 아닌 문자들은 출력 스트림 내의 문자들과 정확하게 일치 한다.그러나,일치시키기 실패는 스트림 상에서의 입력 에러와는 다르다. scanf가 printf와 다른 점의 또다른 하나는 scanf에 대한 선택적 인수로서는 직접적인 값 대신에 포인터를 공급해야 한다는 점이다; 읽혀지는 값은 포인터가 가리키는 대상물에 저장된다. 노련한  프로그래머들조차도 종종 이 점을 잊어버리는 수가 있다.그러므로 만약에 당신의 프로그램이 scanf와 관련된 것처럼 보이는 에러를 만날 때는 당신은 이것을 다시 체크하라. 일치 실패 현상이 일어나면 scanf는 첫 번째의 불일치 문자를 제쳐두고 스트림에서 읽히는 다음 문자를 반환한다.scanf가 되돌리는 정상적인 반환 값은 할당된 값들의 갯수이다.그러므로 당신은  모든 기대값이 읽혀지기 전에 일치시키기 에러가 발생하였는지를 알아보기 위해 이것을 사용할 수 있다. scanf 함수는 전형적으로 테이블 내용 읽기와 같은 일들을 하기 위해 사용된다.예를 들어,double형의 배열을 초기화하기 위해 scanf를 사용하는 함수가 있다.

void readarray  (double  *array,  int  n)

{

int  i;

for  (i=0;  i<n;  i++)

if  (scanf  ("  %lf",  &(array[i]))  !=  1)

invalid_input_error  ();

/*각 배열요소에 1개씩의 숫자만 들어 있으면, 별일이 없겠으나, 숫자가 안 들어있든지 2개(?)가 들어 있으면 에러 보고??? */

}

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

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

7.11.2  입력 변환자 문장

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

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

%  플래그   폭   형태   변환자

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

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

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

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

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

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

7.11.3  입력 변환자 테이블

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

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

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

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

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

'%x','%X'

16진 정수로 된 무부호 정수를 일치시킨다. 7.11.4 [수치 입력 변환자],87쪽 참조.

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

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

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

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

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

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

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

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

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

7.11.4 수치 입력 변환자

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

'%d'는 10진수에서 선택적인 부호 정수를 일치시킨다. 인식되는 문장은 기초인수의 값으로서 10을 갖고있는 strtol 함수(14.7.[정수 분석],213쪽 참조)에 대한 문장과 같다. '%i'변환자는 C언어가 정수 상수용으로 정의하는 어떤 형식에서나 선택적 부호 정수를 일치시킨다.

인식되는 문장은 기초인수의 값으로서 0을 갖고있는 strtol 함수(14.7.[정수 분석],213쪽 참조)에 대한 문장과 같다.(당신은 이 문장상의 정수들을 `%x', `%o'나 `%d' 변환자를 갖는 '#'플래그 문자를 사용하여 printf로 프린트할 수 있다.7.9.4 [정수 변환자],72쪽 참조.)

예를들어,'10','0xa'나 '012'중의 어떤 문자들이든 '%i' 변환자가 관장하는 정수로 읽혀질 수 있다.이들 각각은 십진 값 10을 갖고서 어떤 수를 지정한다.

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

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

'%X'	변환자는 '%x'변환자와 같다.둘다 대문자나 소문자가 숫자로 사용되는 것을 허용한다.  %d나 %i에 대해 상응하는 인수의 내정 형태는 다른 정수 변환자를 위해 int *와 unsigned int *로 되어있다.당신은 다른 크기의 정수를 지정하려면  다음의 형태 변경자를 사용해야 한다:

'h'	인수가 short int * 또는 unsigned short int * 가 되도록 지정한다.

'l'	인수가 long int * 또는 unsigned long int * 가 되도록 지정한다.

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

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

위 입력 변환자들은 모두 상호교환이 가능하다.이들은 모두 strtod 함수에 대한 동일한 문장에서 선택적 부호 부동소수점수를 일치시킨다.(14.7.2 [부동소수점수 분석],214쪽 참조.) 부동소수점 입력 변환자에 대해,내정 인수 형태는 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] 89페이지 참조.

'%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] 75페이지 참조); 받아들여진 '%x' 전환은 단지 16진수를 위한 입력이다. 대응하는 인수로 void ** 형이 주어져야 하고; 그것은 포인터를 저장하기 위한 장소의 주소이다. 그 결과인 포인터 값은 만약 그 안에 읽는 동일한 프로그램 실행(execution)동안에 원래데로 써지지 않는다면 그것이 유용한 것인지는 보장할 수 없다.

'%n'전환은 이 호출에 의해 읽혀진 문자들의 수를 만든다.  그 대응인수는 int *가 되어진다.  이 전환은 printf에서 사용하는 '%n'전환과 동일한 방법으로 작동한다; 7.9.6[Other Output Conversions] 75페이지에서 예제를 참조하라. '%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] 77페이지를 참조하여 그들은 어떻게 사용하는지에 대한 중요한 정보를 보라.

호환성 노트 : 이 절에 리스트된 함수들은 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] 93페이지 참조) 또한 스트림의 파일끝 지시자를 클리어한다.

함수 : int feof (FILE *stream)

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

함수 : int ferror (FILE *stream)

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

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

==> 지금까지 system을 "체제"로 번역하였으나, operating system은 "운영체제"로 번역하되,다른 system

은 "시스템"으로 번역함. 왜냐하면,이미 업계에서 "시스템"이라고  그냥 부르는 경우가 많기 때문임.

7.14  텍스트와 바이너리 스트림.

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

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

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

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

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

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

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

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

7.15  파일 위치시키기

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

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

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

밑에 설명된 것들은 헤더파일 '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] 92페이지 참조하라.  다음에 설명된 심볼 상수들은 fseek함수의 whence 인수로 사용하기 위해 정의되었다.  그들은 또한 lseek함수에서도 사용되고, ( 8.2절 [I/O Primiteves] 105페이지 참조) 파일 잠금(locks)에서 offset을 정하기 위해( 8.7절 [Control Operations] 113페이지 참조) 사용된다.

매크로 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] 69페이지 참조

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

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

데이터 타입 :  fpos__t

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

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

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

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

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

7.17  스트림 버퍼링

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

7.17.1  버퍼링 개념들

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

□ 비버퍼화된 스트림으로부터 읽거나 쓸 문자들은 가능한 한 빨리  파일로 또는, 파일로부터 개별적으로 전송되어진다.

□ 라인 버퍼 스트림으로 부터 읽거나 쓸 문자들은 새줄 문자를 만날 때  블록단위로 파일로, 또는 파일에서 전송되어진다.

□ 완전 버퍼 스트림으로 부터 읽거나 쓸 문자들은 다양한 크기의 블록 형태로 파일로, 또는 파일로부터 전송되어진다.

새로이 개방된 스트림들은 한가지를 제외하고는, 일반적으로 완전한 버퍼이다; 터미널과 같은 대화식 디바이스와 연결된 스트림은 처음에 라인 버퍼로 되어진다.  다른 종류의 버퍼를 어떻게 선택하는지에 대한 정보는 7.13.3절 [Controlling Buffering] 96페이지를 참조.

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

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

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

7.17.2  버퍼를 쏟아내기

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

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

□ 당신이 출력을 하려 시도하고, 출력 버퍼는 가득 차있을 때.

□ 스트림이 닫혀있을 때. 7.4절 [Closing Streams] 65페이지 참조.

□ 프로그램이 exit를 호출하여 종료될 때. 22.3.1절 [Normal Termination] 320페이지 참조.

□ 만일 스트림이 라인 버퍼라면, 새줄 기호를 만났을 때

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

함수 : int fflush (FILE *stream)

이 함수는 스트림상에서 버퍼화된 어떤 출력을 파일로 배달하는 역할을 한다.  만약 스트림이 널 포인터라면 fflush는 열려진 모든 출력 스트림의 버퍼화된 출력을 강제로 관련 파일에 내보낸다.  이 함수는 만약 쓰기 에러가 발생하면 EOF를 리턴하고, 아니면 0을 리턴한다.

호환성 노트 : 어떤 머리가 모자라는( 음... 이 표현이 가장 적당한 것 같다. 키키키...) 운영체제는 새줄 기호를 만나면 라인 버퍼된 스트림을 쏟아내는 라인-지향 입력과 출력으로 완전히 고정시켜 놓은 것으로 알려져있다!  하지만, 다행스럽게도 이 "특징"은 일반적인 현상은 아닌 것 같다. 당신은 이 GNU 시스템에 대해서는 아무런 걱정할 필요가 없다.

7.17.3  버퍼링의 종류 제어하기

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

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

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

매크로 :  int __IOFBF

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

매크로 :  int __IOLBF

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

매크로 :  int  __IONBF

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

매크로 : int BUFSIZ

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

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

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

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

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

함수 :  void setlinebuf (FILE *stream)

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

7.18  다른 종류의 스트림

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

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

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

7.18.1  문자열 스트림

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

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

이 함수는 buf 인수에 의해 정해진 버퍼에서 읽거나 쓰기 위해, 정해진 opentype 인수로 억세스 하는걸 허용하는 스트림을 열어준다. 이 배열은 적어도 long 바이트의 크기를 가져야만 한다. 만일 당신이 buf 인수로 널 포인터를 정하면, fmemopen은 long 바이트 크기의 배열을 동적으로 할당한다.( malloc을 사용해서; 3.3절 [Unconstrained Allocation] 22페이지 참조).  이것은 버퍼에 어떤 것을 쓰고나서 다시 뒤로가서 그들을 읽으려할 때 유용한 함수다, 왜냐하면 당신은 버퍼로부터 포인터를 얻을 수 있는 아무런 방법을 가지고 있지 않기 때문이다. (이것을 위해, open_memstream, 아래). 이 버퍼는 스트림을 열었을 때 freed된다.  opentype인수는 fopen과 같다( 7.3절 [Opening Streams] 64페이지 찹조) 만일 opentype이 append 모드로 정해진다면, 처음의 파일 위치는 파일에서 첫 번째 널 문자에 위치된다.  그렇지 않으면 파일 처음 위치는 버퍼의 시작점이다.쓰기위해 개방된 스트림이 플러쉬(flushed-위에 이 개념이 있습니다. 히..)되어지거나 닫혀질 때, 널 문자는( 0 바이트 ) 만일 그것이 적당하다면 버퍼의 끝에 쓰여진다.  당신은 이것을 셈하기 위해서 size인수에 여분의 바이트를 더할 수 있다.  버퍼에 size보다 더 많이 쓰기를 시도하면 에러의 결과에 이른다. 읽기 위해 개방된 스트림에서, 버퍼안의 널 문자들은(0 바이트) "파일의 끝"으로 간주되지 않는다.  파일의 위치가 size를 넘겨 진행될 때, 파일의 끝임을 지적한다. 그래서 당신은 만일 당신이 널 종료문자로 끝나는 문자열을 읽기 원한다면, 당신은 size인수로 문자열의 길이를 공급해야한다.

이곳에 문자열을 읽기 위해 fmemopen을 사용해서 스트림을 만드는 예가 있다.

#include  <stdio.h>

static  char  buffer[]  =  "foobar";

int main  (void)

{

int  ch;

FILE  *stream;

stream  =  fmemopen  (buffer,  strlen  (buffer),  "r");

while  ((ch  =  fgetc  (stream))  !=  EOF)

printf  ("Got  %c\n",  ch);

fclose  (stream);

return  0;

}

이 프로그램은 다음과 같은 결과를 낸다.

Got  f

Got  o

Got  o

Got  b

Got  a

Got  r

/* 왜 이런 결과가 나왔는지 아시죠? */

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

이 함수는 버퍼에 쓰기 위해 스트림을 연다.  그 버퍼는 동적으로 할당되고 ( malloc을 사용해서. 3.3절 [Unconstrained Allocation] 22페이지 참조)필요에 따라 늘린다. 스트림이 fclose를 사용하여 닫히거나, fflush를 사용하여 플러쉬될 때 ptr의 위치와 sizeloc는 버퍼의 포인터를 포함하고 있는 곳과, 그 크기로 갱신된다. 그 값은 스트림에 길이만큼 출력으로 저장된다. 만일 당신이 더 출력하기 원한다면, 당신은 그들을 다시 사용하기 전에 새로운 값을 저장하기 위해서 스트림을 플러쉬(flush)해야한다. 널 문자는 버퍼의 끝에 씌여진다.  이 널 문자는 sizeloc에 저장되어진 size값에 포함되지 않는다.  당신은 fseek( 7.15절 [File Positioning] 93 페이지 참조)를 사용하여 스트림의 파일 위치(stream's file position)를 옮길수 있다.  파일 위치를 파일의 끝을 지나서 옮기면 그 공백들은 영으로(zeros) 채워져 씌여진다.

이곳에 open_memstream을 사용한 예가 있다

#include  <stdio.h>

int main  (void)

{

char  *bp;

size_t  size;

FILE  *stream;

stream  =  open_memstream  (&bp,  &size);

fprintf  (stream,  "hello");

fflush  (stream);

printf  ("buf  =  %s,  size  =  %d\n",  bp,  size);

fprintf  (stream,  ",  world");

fclose  (stream);

printf  ("buf  =  %s,  size  =  %d\n",  bp,  size);

return  0;

}

이 프로그램은 다음과 같은 결과를 낸다.

buf  =  `hello',  size  =  5

buf  =  `hello,  world',  size  =  12

7.18.2  Obstack 스트림

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

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

이 함수는 obstack에 데이터를 쓰기위해 스트림을 개방한다.  이것은 obstack안에서 object를 시작하고, 씌여진 데이터에 따라 늘려서 만든다. (3.4.6절 [Growing Objects] 31페이지 참조) 이 스트림상에서 fflush를 호출하면 씌여진 데이터의 양에 맞추어서 object의 현재의 크기를 갱신한다.  fflush를 호출한 후에, 당신은 일시적으로 object를 시험할 수 있다.당신은 fseek(7.15절 [File Positioning] 93페이지 참조)를 사용하여 obstack 스트림의 파일 위치를 옮길 수 있다. 파일의 끝을 지난곳으로 파일 위치를 옮기면 영(0)으로 공백들이 채워 저장된다.

변하지 않는 object를 만들려면, fflush를 사용하여 obstack를 갱신하고, 그다음 object를 결말짓기 위해 obstack_finish를 사용하고, 그 주소를 얻어라. 그 스트림에 쓴 다음에 obstack에 새로운 object를 시작하면 당신이 다른 fflush와 obstack_finish를 사용하여 그 object에 더하여 쓸수 있다. 그러나 당신이 object의 길이가 얼마인지 어떻게 알것인가?  당신은 obstack_object_size( 3.4.8절 [Status of an Obstack] 33페이지 참조)를 호출함으로써 바이트 단위로 그 길이를 얻을 수 있거나 이와같이 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], 64페이지를 참조하라. ( 하지만 "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] 105 페이지를 참조하라.  당신의 함수는 버퍼 안에서 size 바이트를 참조하고,  읽은 바이트의 수를 리턴 하거나, 아니면 end-of-file을 지시하기 위해 0을 리턴 한다.  당신은 에러가 발생한 경우에 -1의 값을 리턴할 수 있다. 당신은 cookie에 데이터를 쓰기 위한 함수를 다음처럼 정의할 수 있다.

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

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

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

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

이 함수에서, position과 whence 인수들은 fgetpos처럼 해석되어진다; 7.16절 [Portable Positiong], 94페이지를 참조하라.  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  저 수준 입/출력

8장 저 수준 입/출력

이 장은 파일 기술자 상에서 저 수준의 입/출력 명령을 행하는 함수들에 대해서 설명하고 있다.  이 함수들은 스트림과는 다른 것들을 위한 저 수준의 제어 명령들을 수행하기 위한 함수로써, 7장 [I/O on Streams], 63페이지 안에 설명된 고 수준의 입/출력 함수들에 기본적으로 포함되어 있다.

스트림-수준 입/출력은 더 유연하고, 보통 더 편리하다; 그래서, 프로그래머들은 일반적으로 필요할 때만 기술자-수준 함수들을 사용한다. 이것에는 몇 가지 이유가 있다.

□ 큰 덩어리에서 바이너리 파일들을 읽기 위해

□ 파싱하기 전에 코어 안에서 전체의 파일을 읽기 위해

□ 오직 기술자를 가지고 수행되어지는, 데이터 참조와는 다른 명령을 수행하기 위해 (당신	은 스트림과 일치하는 기술자를 얻기 위해 파일번호를 사용할 수 있다.)

□ 자식 프로세스에 기술자를 넘겨주기 위해. ( 자식은 상속받은 기술자를 사용하기 위해 자신의 스트림을 만들 수 있다, 하지만 직접적으로 스트림을 상속받을 수는 없다. )

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이 터미날 디바이스 이름이면, 프로세스를 위하여 제어하는 터미날이 만들어지지 않는다.  24장 [Job Control], 335페이지를 참조하여, 제어하는 터미날이 되는 것이 무엇을 의미하는지에 대한 정보를 참조하라.

O_NONBLOCK

이것은 비 블록화 모드로 설정한다.  이 옵션은 보통 FIFO와( 10장 [Pipes and FIFOs} 페이지145 참조) 같은 특별한 파일들과 터미날과 같은 디바이스에 사용된다.  보통, 이들 파일들을 위하여 파일이 "준비"상태일 동안 블록을 개방한다.  만일 O_NONBLOCK이 설정되면, open은 즉시 리턴한다. O_NONBLOCK 비트는 또한 읽기와 쓰기에 영향을 미친다: 만약 그곳에 읽기에 유용한 입력이 없거나, 출력이 쓰여질 수 없다면, 그러한 실패의 상황을 즉각적으로 그들에게 리턴 하는 것을  허용한다.

O_TRUNC    만일 파일이 존재하고 쓰기 모드로 개방이 되어 있다면, 0의 길이로 그것을 자른다.  이 옵션은 디렉토리나, FIFO들과 같은 특별한 파일들이 아니라 일반적인 파일들에게 유용하다.

이 심볼 상수들에 대한 더 많은 정보는 8.10절 [File Status Flags] 페이지 116을 참조하라.

open으로 부터 보통 리턴 되는 값은 음이 아닌 정수 파일 기술자이다. 에러가 발생한 경우에는 대신에 -1의 값을 리턴 한다.  보통의 파일이름 문법에러에 더하여(6.2.3절 [File Name Errors] 60페이지 참조), 다음은 이 함수에서 정의하고 있는 errno와 에러상황이다.

EACCES	파일은 존재하지만, flags 인수에 의한 요청으로 읽기/쓰기가 불가능하다.

EEXIST	O_CREAT 와 O_EXCL이 모두 설정되고, 그 파일은 이미 존재한다.

EINTR	open명령은 시그날 (signal) 에 의해 인터럽트 되어졌다.  21.5절 [Interrupted Primitives], 293 페이지 참조

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호출이 시그날에 의해 인터럽트 되었다. 21.5절 [Interrupted Primitives] 293페이지를 참조.이것은 EINTR을 어떻게 취급하는지에대한 적당한 예이다;

TEMP_FAILURE_RETRY (close  (desc));

스트림을 닫기 위해서는, close로 파일 기술자를 닫으려 시도하는 대신에 fclose( 7.4절 [Closing Stream] 65페이지 참조)를 호출하라.  이것은 버퍼된 출력을 플러쉬하고, 닫혔음을 지적하기 위해 스트림 오브젝트를 갱신한다.

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], 116페이지 참조), read는 아무런 데이터도 기다리지 않고 즉시 리턴하고, 이 에러를 보고한다.

호환성 노트 : BSD Unix의 대부분의 버전은 이것을 위한 다른 에러코드를 사용한다: EWOULDBLOCK. GNU 라이브러리에서는, EWOULDBLOCK은 EAGAIN의 다른이름이다.  그래서 당신이 어떤 이름을 사용해도 문제가 발생되지 않는다. 어떤 시스템들은, 특별한 문자 파일로부터 데이터의 큰 덩어리를 읽으려 할 때, 만일 커널(kernal)이 당신의 것을 담을수 있는(to lock down the user's pages), 충분한 물리적 메모리를 얻을수 없는 경우에 EAGAIN의 에러를 내고 실패했음을 지적한다.  디바이스가 사용자의 메모리 영역을 직접적으로 억세스하는 것이 제한되어 있는 것은그들은 커널내부의 분리된 버퍼를 사용하기 때문이다. 그것에 터미날들은 포함되지 않는다,

EBADF	filedes 인수에 주어진 것이 유용한 파일 기술자가 아니다.

EINTR	read가 입력을 기다리고 있는 동안 시그날에 의해 인터럽트 되어졌다. 21.5절 [Interruped Primitives], 293페이지를 참조.

EIO	많은 디바이스들, 그리고 디스크 파일들을 위하여, 이 에러는 하드웨어 에러를 지적한다.  EIO는 또한 제어 중인 터미날로부터 배경 프로세스가 읽기를 시도하고, SIGTTIN의 신호가 아무런 동작도 하지 않고 보내짐에 의해 멈춘 프로세스의 일반적 동작에 대해 발생한다.  이것은 만약 신호가 블록되어지거나 무시되거나, 프로세스 그룹이 부모 프로세스를 잃어버렸다면 발생되어질 것이다. 24장 [Job Control], 335페이지를 참조해서 job control에 대한 더 많은 정보를 얻고, 21장 [Signal Handling] 271페이지를 참조해서, 신호에 대한 정보를 참조하라.

---- 역자주 : 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 오퍼레이션은 명령이 완전히 수행될때까지 기다리는 동안 신호에 의해 인터럽트 되어졌다. 21.5절 [Interrupted Primitives] 293페이지 참조.

EIO	많은 디바이스들, 그리고 디스크 파일들을 위하여, 이 에러는 하드웨어 에러를 지적한다.  EIO는 또한 제어 중인 터미날로부터 배경 프로세스가 읽기를 시도하고, SIGTTIN의 신호가 아무런 동작도 하지 않고 보내짐에 의해 멈춘 프로세스의 일반적 동작에 대해 발생한다. 이것은 만약 신호가 블록되어지거나 무시되거나,  프로세스 그룹이 부모 프로세스를 잃어버렸다면, 발생되어질 것이다. 24장 [Job Control], 335페이지를 참조해서 job control에 대한 더 많은 정보를 얻고, 21장 [Signal Handling] 271페이지를 참조해서, 신호에 대한 정보를 참조하라.

ENOSPC	디바이스가 차 있다.

EPIPE	이 에러는 어느 프로세스에 의해서 읽기위해 개방되지 않는 파이프나 FIFO에 쓰려 시도할 때 리턴된다. 이것이 발생될 때, SIGPIPE 신호를 프로세스에 보낸다; 21장 [Signal Handling] 271페이지 참조.

당신이 EINTR 실패를 방지하기 위해 조정하지 않았다면, 당신은 실패한 write의 호출에 대해서 errno를 체크해야할 것이다.  그리고 만일 errno가 EINTR 이라면, 그냥 간단하게 다시 호출해주면 된다.  21.5절 [Interrupted  Primitives] 293페이지 참조.  이것을 하는 쉬운 방법으로 마크로 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] 64페이지 참조).'b' 옵션이 허용되지 않는 것은 GNU 시스템이 텍스트와 바이너리 파일의 차이를 구분하지 않기 때문이다.  또한 "w"와 "w+"가 파일을 잘라내는 효과를 내지못한다; 그것은 그 옵션들이 파일을 개방할때만 오직 영향을 미치는데, 이 경우에는 파일들이 이미 개방되었던 것이기 때문이다.  당신은 opentype인수를 개방한 파일 기술자의 실제 모드와 일치시켜야만 한다. 리턴값은 새로운 스트림이다.  만일 스트림이 만들어지지 못한다면, ( 예를 들어, 파일 기술자에 의해 정해진 파일의 모드가 opentype 인수에서 지정한 억세스를 허용하지 않을 때 ), 널 포인터를 리턴한다. fdopen함수를 사용하는 예는 10.1절 [Creating a Pipe] 145 페이지를 참조.

함수 : int fileno (FILE *stream)

이 함수는 스트림 stream과 연관된 파일 기술자를 리턴한다.  만일 에러가 검출되거나( 예를 들어, 만일 그 스트림이 유용하지 않다면 ), 만일 스트림이 파일에 입/출력을 할수 없다면, fileno는 -1을 리턴한다. 표준 스트림 stdin, stdout 과 stderr에 속한 파일 기술자를 위한 심볼 상수가 'unistd.h'에 정의되어 있다.; 7.2절 [Standare Streams] 63페이지 참조.

STDIN_FILENO	이 마크로는 표준 입력을 위한 파일 기술자로 0의 값을 가진다.

STDOUT_FILENO	이 마크로는 표준 출력을 위한 파일 기술자로 1의 값을 가진다.

STDERR_FILENO	이 마크로는 표준 에러출력을 위한 파일 기술자로 2의 값을 가진다.

8.5  스트림과 기술자 혼용의 위험

당신은 동일한 파일과 연결된 다중의 파일 기술자와 스트림을 가질 수 있다( 짧게 스트림과 기술자를 "채널" 이라 부르자), 그러나 당신은 채널들 사이의 혼란을 피하도록 주의해야만 한다.  고려해야 하는 두가지 경우가 있다: 단일한 파일 위치를 갖고 있는 연결된 채널과, 그들 자신의 파일 위치를 갖고 있는 독립적 채널

모든 억세스가 입력을 위한것임을 제외하고는, 어느 주어진 파일에서 실제의 데이터를 참조하기 위해서는 당신의 프로그램안에 오직 하나의 채널을 사용하는 것이 좋다.  예를 들어 만일 당신이 파이프를 개방하여, ( 당신이 파일 기술자 수준에서 할 수 있는 어떤 것 ), 기술자를 가지고 입/출력을 하거나, 또는 fdopen으로 기술자로부터 스트림을 구성하고 스트림에 모든 입/출력을 한다면.

8.5.1  연결된 채널들

단 한 번 개방된 것으로부터 온 채널들은 동일한 파일 위치를 점유한다; 우리는 그들을 연결된 채널이라 부른다.  연결된 채널은, 당신이 fdopen을 사용하여 기술자로부터 스트림을 만들 때, fileno를 사용해서 스트림으로 부터 기술자를 얻을 때, 그리고 dup나 dup2를 사용해서 기술자를 복제할 때 결과로 얻게된다.  터미날과 파이프처럼 랜덤 억세스(임의 접근)가 제공되지 않는 파일들에서 모든 채널들은 실제로 연결되어 있다. 임의 접근이 가능한 파일들에서는 연결된-형태의 출력 스트림들 모두가 서로 연결되어 있다.

만일 당신이 입/출력을 위해 한 스트림을 사용해 오면서, 그것과 연결된 다른 채널(스트림이나 기술자)을 사용해서 입/출력하기 원한다면, 당신은 첫째로 당신이 사용하고 있던 그 스트림을 정리해야만 한다. 8.5.3절 [Cleaning Streams] 110 페이지 참조.

프로세스가 종료하거나, 프로세스에 새로운 프로그램을 실행하면 그 프로세스안에 있던 모든 스트림들은 파괴된다.  만일 이 스트림과 연결된 기술자가 다른 프로세스안에 살아남아 있다면, 그들 파일의 위치는 정의되지 않은 상태로 되고 만다.   이것을 방지하기 위해서, 당신은 그들을 파괴하기 전에 그 스트림들을 정리해야만 한다.

8.5.2  독립적 채널들

분리되어 탐색 가능한 채널들( 스트림이나 기술자)을 개방할 때, 각 채널들은 자신의 파일 위치를 갖는다.  이들을 독립적 채널이라고 부른다.

시스템은 독립적으로 각 채널을 취급한다.  거의 대부분, 이것은 꽤 예측가능하고, 자연스럽다(특히 입력의 경우):  각 채널은 파일 안의 자신의 위치에서 순차적으로 읽거나, 쓸 수 있다.  그렇지만, 만일 어떤 채널들이 스트림이라면, 당신은 이런 것들을 주의해야한다.

□ 당신은 파일의 동일한 부분으로부터 읽거나 쓰는 어떤 것을 하기 전에 사용할 출력 스트림을 깨끗이 해야한다.

□ 독립적인 채널을 사용해서 변경되었을 데이터를 읽기 전에는 입력 스트림을 깨끗이 해야한다.  그렇지 않으면 당신은 스트림의 버퍼에 존재하고 있는 쓸모 없는 데이터(obsolete data)를 읽게 될 것이다.

만일 당신이 파일의 끝에 있는 채널에 출력하려 한다면,  이 채널은 출력을 하기 전에 어느 곳인지에 있을 다른 독립적 채널들을 확실히 무시할 것이다. 만일 당신이 끝에 출력하기 원한다면, 당신은 첫째로, 파일의 끝으로 그들 파일 위치를 설정해야 한다.( 이것은 연결된-형태의 기술자나 스트림에서는 필요치 않다; 그들은 항상 파일의 끝에서 출력한다.)  파일의 끝 위치를 정확하게 만들려면, 당신은 만일 당신이 사용하고 있는 것이 스트림이라면 출력 채널을 깨끗이 해야한다. (이것은 심지어 다음에 연결된-형태의 채널을 사용하려 계획할 때도  필요하다.)

파일이 랜덤 억세스를 지원하지 않는다면 파일에서 분리된 파일 위치를 갖고있는 두 개의 채널을 갖는 것은 불가능하다.  그러므로, 파일처럼 읽거나 쓰기 위한 채널들은 항상 연결되어있고, 결코 독립적이지 않다.  연결된-형태의 채널들은 항상 연결되어 있다.  이와 같은 채널들을 위해 연결된 채널들을 위한 규칙을 따라야한다; 8.5.1 [Linked Channels] 109페이지 참조.

8.5.3  채널 깨끗이 하기

GNU 시스템에서, 당신은 fclean을 사용해서 어느 스트림을 정리할 수 있다.

함수 :  int fclean (FILE *stream)

버퍼를 비우기 위해 스트림 stream을 깨끗이 한다. 만일 스트림이 출력을 하고 있다면, 강제로 그것을 출력한다.  만일 스트림이 입력을 하고 있다면, 버퍼에 있는 데이터는 그것을 다시 읽도록 조정하는 시스템에 되돌려준다.  다른 시스템들에서는 대부분의 경우 스트림을 깨끗이 하기 위해서 fflush를 사용할 수 있다.

만일 당신이 이미 그 스트림이 깨끗하다란 것을 안다면 fclean이나 fflush를 건너뛸 수 있다.  스트림은 버퍼가 비어있을때는 깨끗하다. 예를 들어 비버퍼화된 스트림은 항상 깨끗하다.  그리고, 파일의 끝에서 입력 스트림은 깨끗하다.  라인 버퍼 화된 스트림은 마지막 문자출력이 새줄문자였을 때 깨끗하다.

스트림을 깨끗하게 하는 것이 대부분의 시스템에서 불가능한 한가지 경우가 있다.  이것은 스트림이 랜덤 억세스가 불가능한 파일로부터 입력을 하고 있을 때이다. 그와 같은 스트림은 이미 읽은 검색 데이터를 되돌려줄 아무런 방법이 없다.  입력 스트림이 랜덤 억세스 파일로부터 입력할 때, fflush로 스트림을 깨끗이 하지 않으면 파일 포인터가 예측 불가능한 영역에 남겨진고 만다. 당신은 입/출력을 하기 전에 먼저 파일 포인터를 설정하라.  GNU 시스템에서는 이 문제들의 양쪽을 피하게 한다.

오직 출력만 가능한 스트림을 닫을 때도 fflush를 사용하는데, 이것은 출력 스트림을 깨끗이 하는데 유용한 방법이다.  GNU 시스템에서는, 입력 스트림을 닫을 때 fclean을 사용한다.

당신은 터미날 모드를 설정하는 것과 같은 제어 명령을 위해 기술자를 사용하기 전에는 스트림을 깨끗이 할 필요가 없다; 이들 명령들은 파일 위치에 영향을 받지 않고, 그것에 영향을 미치지 않는다.  당신은 이 명령들은 위해서는 어느 기술자라도 사용할 수 있고, 모든 채널들은 동시에 영향을 받는다. 그렇지만, 스트림에 의해 새로운 터미날 모드로 설정되어질 스트림이 여전히 "출력"을 갖고 버퍼 화된 상태라면 플러쉬되어진다.  "앞으로"의 출력을 확실히 하기 위해 터미날모드의 설정이 동시에 영향을 받도록 되어졌고, 그 모드를 설정하기 전에 터미날의 모든 출력 스트림들을 플러쉬한다.  12.4절[Terminal Modes], 186페이지 참조.

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를 의미하지 않는다.  오히려, 그들은 소켓에 긴급한 메시지가 나타난 그런 상황을 말한다. ( 11장 [Socket] 149 페이지에서 긴급한 메시지들에 대한 정보를 참조 )

select 함수는 오직 첫 번째 nfds 파일 기술자를 체크하는데, 보통 이 인수의 값으로 FD_SETSIZE가 주어진다. timeout는 기다리기 위한 최대의 시간을 정한다.  만일 당신이 이 인수로 널 포인터를 준다면, 하나의 파일 기술자라도 준비될 때까지 무기한 블록 시킴을 의미한다.  그렇지 않다면, 당신은 struct timeval 형식 안에 시간의 값을 주어야한다; 17.2.2.절 [High Resolution Calendar] 235 페이지 참조.  시간의 값으로 0을 정하면(모두 0의 값인 struct timeval) 이미 준비된 기술자를 찾거나, 만일 준비된 기술자가 없다면 기다리지 않음을 의미한다. 보통 select 함수로부터의 리턴 값은 set안에 지정된 기술자 중에서 준비된 기술자의 총수이다.  인수 set들의 각각은 연관된 명령을 위해서 준비된 기술자에 대한 정보로 갱신된다. 만일 입력을 가진 특별한 기술자 desc를 보려면, select 함수가 리턴한후에 FD_ISSET (desc, read`fds)를 사용하라.  만일 시간 설정 값이 끝났다면 select는 0의 값을 리턴 한다. 어느 신호는 select가 기다리지 않고 즉시 리턴해버리는 원인이 된다. 그래서 만일 당신의 프로그램이 신호들을 사용한다면, 당신은 select가 정해진 완전한 시간동안 기다림을 유지한다고 신뢰할 수 없다.  만일 당신이 어느 정해진 시간동안 확실히 기다림을 원한다면, 당신은 반드시 EINTR을 체크하고 현재의 시간에 기초한 새로이 설정된 시간(timeout)으로 select 를 다시 호출하라.  아래에 있는 예를 보라.  21.5절 [Interrupted Primitives]293페이지 참조. 만일 에러가 발생하면, select는 -1을 리턴하고, 파일 기술자 셋(sets) 인수를 갱신하지 않는다.  다음 errno는 이 함수에서 정의한 에러 상황이다.

EBADF	파일 기술자중에 하나가 유용하지 않은 파일 기술자이다.

EINTR	명령이 신호에 의해 인터럽트 되어졌다. 21.5절 [Interrupted Primitives] 293 페이지 참조.

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;

}

다중 소켓으로부터 다중 입력을 위해서는 select를 어떻게 사용하는지에 대한 예제는 11.8.7절 [Server Example] 171 페이지 참조.

8.7  파일에서의 제어 명령들

이 절은 파일 기술자 상에서, 파일 기술자의 상황을 나타내고 있는 플래그를 셋팅하거나, 레코드의 락(locks)을 다루는 것과 같은 다양한 명령들을 어떻게 수행할 수 있는지를 설명하고 있다.  이들 명령들은 모두 fcntl함수를 통해 이루어진다.

fcntl함수의 두 번째 인수는 수행하려는 명령으로 지정된 명령문이다. 그 함수와 마크로와 함께 사용되는 다양한 플래그들은 헤더파일 'fcntl.h' 에 선언되어 있다. ( 이들 플래그들의 대부분은 open함수에서 사용되어 지던 것이다; 8.1절 [Opening and Closing Files] 103페이지 참조)

함수 :   int fcntl (int filedes, int command, . . .)

fcntl 함수는 파일 기술자 filedes 상에서 command에 의해 정해진 명령을 수행한다.  어떤 command들은 공급해야할 부가적 인수를 필요로 한다. 이곳에 각각의 명령들에 따른 부가적 인수들과 리턴 값과 에러 상황 각각에 대한 상세한 설명이 있다.

간단히, 아래는 다양한 명령들에 대한 리스트이다.

F_DUPFD	파일 기술자를 복제하라(동일한 개방된 파일을 가리키는 다른 파일 기술자를 리턴). 8.8절 [Duplicating Descriptors] 114 페이지 참조.

F_GETFD	파일기술자와 연관된 플래그들을 얻어라. 8.9절 [Descriptor Flags] 115페이지 참조.

F_SETFD	파일 기술자와 연관된 플래그들을 설정하라. 8.9절 [Descriptor Flags], 115 페이지 참조.

F_GETFL	개방한 파일과 연관된 플래그들을 얻어라. 8.10절 [File Status Flags] 116페이지 참조.

F_SETFL	개방한 파일과 연관된 플래그들을 설정하라. 8.10절 [File Status Flags] 116 페이지 참조.

F_GETLK	파일 록을 얻어라.  8.11절 [File Locks] 118 페이지 참조.

F_SETLK	파일 록을 설정하거나, 지워라. 8.11절 [File Locks], 118페이지 참조.

F_SETLKW	F_SETLK와 같지만, 완전하기까지 기다린다. 8.11절 [File Locks] 118페이지 참조.

F_GETOWN	SIGIO 신호들을 받기위해서 프로세스나 프로세스 그룹 아이디를 얻어라.  8.12절 [Interrupt Input] 120 페이지 참조.

F_SETOWN	SIGIO 신호들을 받기위해서 프로세스나 프로세스 그룹 아이디를 설정하라.  8.12절  [Interrupt Input] 120 페이지 참조.

8.8  기술자 복제하기

당신은 파일 기술자를 복제하거나, 동일한 파일 기술자를 참조하기 위한 다른 파일기술자를 할당할 수 있다.  복제한 기술자는 하나의 파일 위치를 점유하고, 하나의 파일 상황 플래그들은 갖는다. ( 8.10절 [File Status Flags] 116 페이지 참조.), 그러나, 파일 기술자 플래그는 자신의 것을 소유한다.( 8.9절 [Descriptor Flags] 115 페이지 참조)

복제한 파일 기술자의 주된 사용처는 입력이나 출력의 리다이렉션(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 처럼)의 리다이렉션은 자식 프로세스 안에서 새로운 프로그램을 수행하기 위해서 수행 함수들의 ( 23.5절 [Executing a File] 327 페이지 참조 ) 수행 함수들중 하나를 호출하기 전에 쉘이나 쉘과 같은 프로그램에 의해 행해진다.  새로운 프로그램이 수행되어질 때, 그 프로그램의 메인 함수가 수행되기 전에 연관된 파일 기술자들을 가리키는 표준 스트림을 초기화한다. 그래서 파일로 표준 입력을 리다이렉트 하는 것은 쉘이 다음과 같은 어떤 것을 하는 것이다.

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

}

24.6.3절 [Launching Jobs] 340 페이지에 프로세스의 파이프라인에 어떻게 리다리렉션을 하는지에 대한 상세한 많은 예제가 있다.

8.9  파일 기술자 플래그

파일 기술자 플래그는 파일 기술자의 잡다한 속성들이다.  이 플래그들은 특별한 파일 기술자와 연관되어 있기 때문에, 만일 당신이 한 번 개방한 파일로부터 복제해서 파일 기술자를 만들었다면, 각 기술자는 자신의 플래그 셋(set)을 갖는다.

현재 단지 하나의 파일 기술자 플래그가 있다: FD_CLOEXEC, 이것은 만일 당신이 exec. . . 함수(23.5절 [Executing a File], 327 페이지)들 중 어느 하나를 사용한다면 기술자가 닫히게 되는 결과를 가져온다.

이 절에서 설명하고 있는 것은 헤더파일 '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 함수가 불리워질 때 닫혀질 파일 기술자를 정한다; 23.5절 [Executing a File] 327 페이지를 참조하라.  파일 기술자가 할당 되었을 때( 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] 115 페이지 에서 논의된 파일 기술자 플래그들과 달리, 파일 상황 플래그들은 개방된 파일로부터 복제된 파일 기술자에 의해 공유되어진다.

파일 상황 플래그들은 open 함수의 flags인수를 통해 초기화 되어진다. 플래그중 어떤 것은 개방할때만 의미가 있고 계속해서 기억되지 않는다; 나머지 대대분은 그후에 변화되지 않아서, 당신은 파일 상황 플래그들을 시험함으로 인해서 그들의 값을 읽을 수 있다. 몇몇 파일 상황 플래그들은 fcntl 함수를 사용해서 변화시킬수 있다. 그들에 O_APPEND 와 O_NONBLOCK 가 포함된다. 이 절에 있는 심볼들은 헤더파일 'fcntl.h'에 있다.

매크로 :  int F__GETFL

이 매크로는 기술자 filedes로 개방한 파일의 파일 상황 플래그들은 읽기위해 fcntl함수의 command 인수로써 사용된다.  이 명령과 함께 사용된 fcntl을 통한 리턴값은 보통 각각의 플래그들을 비트별 OR연산을 통해서 해석가능한 값으로 추출된 음이 아닌 값이다. 그 플래그들은 open(8.1절 [Opening and Closing Files], 103 페이지 참조) 함수의 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);

}

8.11  파일 록

위에서 설명한 것 외에 fcntl 에서 쓸 수 있는 명령은 레코드 락킹(locking)을 지원하기 위해 사용되어진다. 레코드 락킹은 다중 사용자 프로그램에서 동시에 파일의 같은 부분을 억세스하는 것을 방지하기 위한 것이다.

배타적이거나 쓰기(write) 락(lock)은 프로세스가 파일의 지정한 부분에서 배타적인 동작으로 쓰기 위해서 주어진다.  쓰기 락이 있는 곳에서는 다른 프로세스들은 파일의 그부분에 락을 걸 수가 없다.

분배하거나 읽기(read) 락(lock)은 파일의 지정된 부분을 읽고 있을 때 다른 프로세스로 부터의 쓰기 락(write lock) 요청을 거부한다.  그렇지만 다른 프로세스가 읽기 락(read lock)을 요청하는 것은 가능하다.

read와 write 함수들은 실제로 그 위치에 락(lock)이 걸린건지를 체크하지 않는다.  만일 당신이 다중 프로세스에 의해 점유된 파일을 위해 락킹(locking) 프로토콜(protocol)을 사용하기 원한다면, 당신의 어플리케이션은 적당한 지점에서 락(lock)을 요청하거나 클리어 하기 위해서 fcntl 함수를 호출해야만 한다.

락들은 프로세스들과 연관되어 있다.  한 프로세스는 주어진 파일의 각 바이트에 한종류만의 락(lock) 설정을 할 수 있다.  파일기술자가 프로세스에 의해 닫혀질 때, 그 파일에서 프로세스가 갖고 있던 모든 락들은 해제된다, 심지어 현재 개방 상태인 다른 기술자를 사용해서 만들어진 락일지라도...  마찬가지로, 프로세스가 존재하고 있을 때 해제된 락들은, fork(23.4절 [Creating a Process], 326페이지 참조)를 사용해서 만들어진 자식 프로세스에게 물려줄 수 없다.

락을 만들 때, 락이 어떤 종류이고 현재 어디에 있는지를 알리기 위해 구조체 struct flock를 사용한다.  이 데이터 타입과 fcntl과 연관된 매크로는 헤더파일 'fcntl.h'에 선언되어 있다.

데이터타입 :  struct flock

이 구조체는 파일락을 설명하기 위해 fcntl함수에서 사용되어진다. 이들 멤버는 다음과 같다.

short int l_type

락의 타입을 정한다. F_RDLCK, FWRLCK, 또는 F_UNLCK 중에 하나.

short int l_whence

이것은 fseek 나 lseek 함수의 인수인 whence 와 일치하고, 그것을 기점으로 offset을 정한다. SEEK_SET, SEEK_CUR, SEEK_END 중 하나.

off_t l_start

이것은 락이 적용되는 지역의 시작 offset을 지정하고, l_whence 멤버에 연관한 지점이 바이트로 주어진다.

off_t l_len

이것은 락이 설정된 지역의 길이를 지정한다.  0의 값은 특별히 취급된다;  그것은 파일의 끝에서 락의 범위를 확장하는걸 의미한다.

pid_t l_pid

이 필드는 락을 갖고 있는 프로세스의 프로세스 ID (23.2절 [Process Creation Concepts], 325 페이지 참조)이다.  F_GETLK 명령을 사용해서 fcntl 함수를 호출하면 이 필드가 채워지지만, 락을 만들때는 무시된다.

매크로 :  int F__GETLK

이 매크로는 락에 대한 정보를 얻고자 함을 알리기 위해, fcntl에게 command 인수로써 사용된다.  이 명령은 fcntl에게 주기위해 struct flock * 타입의 새번째 인수가 필요하다.  그래서 다음과 같은 형식으로 호출한다:

fcntl  (filedes,  F_GETLK,  lockp)

만일 lockp 인수에 의해 묘사된 락이 설정된 블록의 영역에 이미 락이 존재한다면, 그 락에 대한 정보는 *lockp에 다시 쓰여진다. 이미 존재하고 있는 락이 새로 만들고 있는 락과 호환성이 있다면 보고되지 않는다. 그래서, 만일 당신이 읽기와 쓰기 락들에 대해서 찾기를 원한다면 F_WRLK의 타입으로 락을 지정하거나, 만일 당신이 쓰기 락에 대한 것을 찾고 싶다면 F_RDLCK의 타입으로 락을 지정해야 한다.

lockp 인수에 의해 지정된 지역에는 한 개보다 더 많은 락들이 있겠지만, fcntl은 오직 그들중 하나에 대한 정보를 리턴한다.  lockp 구조체중 l_whence 멤버는 SEEK_SET으로 설정되고 l_start 와 l_len 필드들은 락이 걸린 지역과 동일하게 설정된다.  만일 아무것도 락이 걸리지 않았다면, lockp 구조체에 유일하게 가해지는 변화는 F_UNLCK의 값으로 l_type이 갱신되는 것이다. 이 명령을 사용한 fcntl 로부터의 리턴값은 보통 정해지지 않았고, 에러가 발생한 경우에는 -1을 리턴한다.  다음 errno는 이 명령에서 정의한 에러상황이다.

EBADF	filedes 인수가 유용하지 않다.

EINVAL	lockp 인수가 유용한 락 정보를 가지고 있지 않거나, 파일이 락이 지원되지 않는 filedes와 연관이 있다.

매크로 :  int F__SETLK

이 매크로는 락을 설정하거나, 클리어 할것임을 지정하기 위해 fcntl 함수에서 command 인수로 사용되어진다.  이 명령은 fcntl 함수에 주기 위한 세 번째 인수로써 struct flock * 형의 인수를 필요로 한다.  그래서 다음과 같이 호출한다.

fcntl  (filedes,  F_SETLK,  lockp)

만일 프로세스가 락을 설정할 지역에서 이미 어느 한부분이 락을 가지고 있다면, 이미 있던 락은 새로운 락으로 대체된다.  당신은 F_UNLCK의 타입으로 락을 지정하여서 락을 제거할 수도 있다. 만일 락이 설정되지 않았다면, fcntl은 -1의 값을 즉시 리턴한다.  이 함수는 락을 해제하기 위해 다른 프로세스들을 기다리는 블록을 하지 않는다.  만일 fcntl 이 성공하면, -1이 아닌 다른 값을 리턴한다. 다음 errno는 이 함수에서 정의한 에러 상황이다.

EACCES

EAGAIN	파일에 존재하고 있는 락으로 블록되어졌기 때문에  그 락을 설정할 수 없다.  어떤 시스템에서 이 경우에  EAGAIN을 사용하고, 다른 시스템에서는 EACCES를 사용한다;  당신의 프로그램은 F_SETLK 후 처럼 그들을 취급할 것이다.

EBADF	filedes 인수가 유용하지 않거나; 당신이 읽기 락을 요청했지만 filedes는 읽기 모드로 개방되지 않았거나, 또는 쓰기 락을 요청했는데, 그 filedes 는 쓰기모드로 개방되지 않았거나; 이 둘중에 하나이다.

EINVAL	lockp인수가 유용한 락 정보를 지정하지 않았거나, 또는 filedes와 연관된 파일이 락을 지원하지 않거나이다.

ENOLCK	시스템의 파일 락 자원들이 바닥났다;  이미 너무 많은 파일 락들이 그곳에 존재하고 있다.  잘-만들어진 파일시스템들은 결코 이 에러를 발생시키지 않는데, 그이유는 그들이 락의 수에 대한 아무런 제한을 가지고 있지 않기 때문이다.  그렇지만, 다른 컴퓨터(machine)의 파일시스템을 네트웍으로 부터 검색하는 것에서 발생할 수 있는 결과와 같이, 당신은 이처럼 이 에러가 발생할 가능성으로 인해서 여전히 락의 개수를 세어야만 한다.

매크로 :  int F__SETLKW

이 매크로는 락을 설정하거나, 설정된 락을 지울것임을 지정하기 위해서, fcntl 함수에 command 인수로서 사용되어진다.  이것은 F_SETLK 명령과 같지만, 그 요청이 받아들여 질때가지 프로세스를 블록(또는 기다림)하게 한다.  이 명령은 F_SETLK 명령처럼 세번째인수로서 struct flock * 타입의 인수를 필요로 한다.  fcntl의 리턴값과 에러들은 F_SETLK 명령을 위한것과 동일하지만, 이것들에 더해진 에러 상황들이 밑에 설명되어 있다.

EINTR	이 함수는 기다리는 동안 신호에 의해 인터럽트 되어졌다.  21.5절 [Interrrpted Primitives] 293페이지 참조.

EDEADLK	교착상태(역자주: 다중 프로그래밍 시스템에서 발견될 수 있는 상황으로 프로세스들이 서로 작업을 진행하지 못하고 영원히 대기 상태로 빠지게 되는 현상을 교착 상태라 하는데, 보통 프로세스 사이에 할당된 자원의 충돌로 인하여 발생한다.)가 검출되어졌다.  이것은 만일 이미 자신이 제어하고 있는 락된 지역(locked region)을 갖고 있는 두 개의 프로세스가 다른 프로세스에 의해 락된 동일한 지역을 요청했을 때 발생할 수 있다.

다음 매크로는 flock 구조체의 1_type 멤버를 위한 값들로 사용되어지기 위해서 정의되어졌다.  이 값들은 정수 상수이다.

F_RDLCK	이 매크로는 읽기(또는 분배) 락을 지정하기 위해 사용된다.

F_WRLCK	이 매크로는 쓰기(또는 배타적) 락을 지정하기 위해 사용된다.

F_UNLCK	이 매크로는 그 지역에 락이 걸리지 않도록 지정하기 위해 사용되어진다. 파일 락킹의 쓰임세에 대한 하나의 예로써,  만일 동시에 여러 다른 사용자들에 의해 실행되어질 수 프로그램을 생각해보라. 그와 같은 프로그램은 높은 점수를 기록하기 위해 한 파일을 사용하는 게임프로그램이 될 수 있고.  또 다른 예는 계산의 목적을 위한 정보로 사용하는 레코드를 갖는 프로그램이 되어질 것이다.

파일에 쓰기위해 동시에 여러개의 프로그램 복제본을 갖는다면, 그 파일의 내용은 나중에 엉망친장이 되어질 것이다.  그러나 당신은 파일에 쓰기가 실제로 발생하기 전에 그 파일에 쓰기락을 설정해서 그와 같은 문제들의 발생을 예방할 수 있다.

만일 프로그램이 그 파일을 읽을 필요가 있고 그 파일의 내용이 확실하기를 원한다면, 그때 읽기 락을 사용할수 있다.  읽기 락이 설정된 동안 어떤 다른 프로세스도 쓰기 위해서 그 부분에 락을 설정할려면 기다려야 한다.

파일 락은 파일을 억세스 하는 것을 제어하기 위한 자발적 프로토콜임을 기억하라.  그래서 락 프로토콜을 사용하지 않는 프로그램에 의해 그 파일을 억세스할 가능성은 여전이 있다.

8.12  인터럽트로 조종된 입력

만일 당신이 파일 기술자상의 FASYNC 상황 플래그를 설정하면( 8.10절[File Status Flags], 116 페이지참조 ), SIGIO 신호는 파일 기술자 상에서 입력이나 출력이 가능하게 될 때마다 보내어진다.  그 신호를 받을 프로세스나 프로세스 그룹은 fcntl 함수에 F_SETOWN 명령을 사용해서 선택할 수 있다.  만일 그 파일 기술자가 소켓이라면, 이것은 소켓에 도착한 범위 밖의 데이터가 있을 때, 그때 배달된 SIGURG 신호를 받을 수령자를 선택한다; 11.8.8 [Out-of-Band Data], 173페이지 참조.

만일 그 파일 기술자가 터미날 디바이스와 교신한다면, SIGIO 신호는 터미날의 전면(foreground) 프로세스 그룹에 보내어진다. 24장 [Job Control] 335 페이지 참조.

이 절안의 심볼들은 헤더파일 'fcntl.h'에 정의되어 있다.

매크로 :  int F__GETOWN

이 매크로는 SIGIO 신호를 보낸 프로세스나 프로세스 그룹에 대한 정보를 얻음을 정하기 위해 fcntl 함수의 command 인수로서 사용되어진다. (터미날에서, 이것은 실제로는 전면(foreground) 프로세스 그룹 아이디(ID)를 tcgetpgrp을 사용해서 얻을 수 있다;  24.7.3절 [Terminal Access Functions] 350 페이지 참조.)  리턴값은 프로세스 ID로 인터럽트 되어지고 만일 음의 값이면, 그 절대값이 프로세스 그룹 ID 이다.

다음의 errno는 이 명령에서 정의한 에러 상황이다.

EBADF	filedes 인수가 유용하지 않다.

매크로 :  int F__SETOWN

이 매크로는 SIGIO 신호를 보내기 위한 프로세스나 프로세스 그룹 ID를 설정함을 정하기 위해 fcntl 함수의 command 인수로서 사용되어진다. 이 명령은 fcntl 함수의 세 번째 인수로 pid_t 타입의 인수를 필요로 한다, 그래서 다음과 같은 형식으로 호출된다:

fcntl  (filedes,  F_SETOWN,  pid)

pid 인수는 프로세스 그룹 ID이다. 당신이 그 값을 음의 값으로 주더라도 그 절대값이 프로세스 그룹 ID가 되어진다.이 명령을 사용한 fcntl함수로부터의 리턴값은 에러가 발생된 경우에 -1이고, 성공하면 어떤 다른값이 된다. 다음의 errno는 이 명령에서 정의한 에러상황이다.

EBADF	filedes 인수가 유용하지 않다.

ESRCH	pid에 해당하는 아무런 프로세스나 프로세스 그룹이 없다.

연가

그만 묻어두고 싶다.

그 말씀을

사랑을 하면서도

사랑이 무엇인지를 모르듯이

바라는 것이 많아서

바램이 무엇인지도 모르듯이

조용히 일러 주리라

조금만 다가 오라고

김 광림

9  파일 시스템 인터페이스

9장 파일 시스템 인터페이스

이 장은 파일들을 다루기 위한 GNU C 라이브러리 함수들을 설명하고 있다. 7장 [I/O on Streams] 63페이지 에서 설명된 입/출력 함수들과는 달리, 이 함수들은 파일안의 내용보다는 파일 그차제를 다루는것에 대한 함수들이다.

이 절에서 설명된 것들은 디렉토리들을 조사하고, 갱신하기 위한 함수들과 파일들을 삭제하거나 재명명하기 위한 함수들과, 엑세스 허용과, 갱신시간과 같은 파일 속성들을 조사하고, 설정하기 위한 함수들이다.

9.1  작업 디렉토리

각 프로세스는 한 디렉토리와 연관되어 있고, 그것을 현재 작업 디렉토리라 부르거나, 간단하게 작업 디렉토리라 부른다.  디렉토리 이름은 파일이름과 같은 방법으로 사용된다. ( 6.2.2절 [File Name Resolution] 59페이지 참조 )

당신이 로그인하고 새로운 세션(session)을 시작할 때, 당신의 작업 디렉토리는 시스템 유저 데이터베이스안에 당신의 로그인 계정과 연관된 홈 디렉토리(home directory)로 초기설정된다.  당신은 getpwuid 나 getpwnam 을 사용해서 사용자의 홈 디렉토리를 찾을 수 있다; 25.12절[User Database] 360 페이지 참조.

--- 역자주 : 세션(session) : 사용자가 컴퓨터와의 연결이 시작된 순간부터 단절되는 순간까지를 하나의 논리적인 단위로 구분해놓은 것

사용자는 cd와 같은 쉘 명령어를 사용해서 작업 디렉토리를 변경할 수 있다.  이 절에서 설명하고 있는 함수들은 이들 명령에 의해 사용되고, 또한 작업 디렉토리를 조사하고, 변경하려는 다른 프로그램들에 의해 사용된다.  이들 함수들의 프로토타입은 헤더파일 'unistd.h'에 선언되어 있다.

함수 :  char * getcwd (char *buffer, size_t size)

getcwd 함수는 현재의 작업 디렉토리를 나타내는 파일 이름을, 당신이 제공한 문자 배열 버퍼에 그것을 저장하여 리턴한다.  size인수는 버퍼의 할당 크기이다.

이 함수에서 GNU 라이브러리는 당신이 buffer 인수에 널 포인터를 지정하는 것을 허용한다.  그러면 getcwd는 malloc( 3.3절 [Unconstrained Allocation] 22페이지)을 사용해서 자동적으로 버퍼를 할당한다.  만일 그 size가 0보다 크다면 버퍼는 그 크기로 할당되고 그렇지 않다면 버퍼는 결과를 저장하기에 필요한 만큼의 크기로 할당된다.	리턴값은 성공하면 버퍼이고, 실패하면 널 포인터이다.  다음의 errno는 이 함수에서 정의하고 있는 에러 상황이다.

EINVAL	size인수가 0인데 buffer가 널 포인터가 아니다.

ERANGE	size인수가 작업 디렉토리 이름의 길이보다 작다.  당신은 배열을 더 크게 할당하고 다시 시도하라.

EACCES	읽기 요청이나, 파일이름의 구성요소 찾기가 거부되었다.

이곳에 getcwd의 표준동작만을 사용한 GNU의 getcwd (NULL, 0)의 동작을 보여주는 예가있다.

char  *gnu_getcwd  ()

{

int  size  =  100;

char  *buffer  =  (char  *)  xmalloc  (size);

while  (1)

{

char  *value  =  getcwd  (buffer,  size);

if  (value  !=  0)

return  buffer;

size  *=  2;

free  (buffer);

buffer  =  (char  *)  xmalloc  (size);

}

}

/* 다 아시겠지만... 잠시 설명을 합죠~ xmalloc을 이용해서 100바이트 만큼의 버퍼를 할당한 다음...

while 루프를 돌리네요.  while루프의 조건을 1로 해놓고 루프안의 조건에 따라서 루프를 벗어나게 되는데...  getcwd함수를 이용해서 이름을 얻었는데, 그 크기가 0이 아니면 버퍼를 리턴하고 루프를 벗어나고 그렇지 않으면 size에 2를 곱해서 (즉.. 버퍼의 크기를 늘리기 위해 ) 원래의 버퍼는 해제하고 다시 큰 크기의 버퍼를 할당받아서 다시 루프의 처음으로 돌아가고.. 끝..*/

xmalloc에 대한 정보는 3.3.3절 [Malloc Examples] 22 페이지를 참조하라.  xmalloc은 라이브러리 함수가 아니라 대부분의 GNU 소프트웨어에서 사용되는 사용자정의 이름이다.

함수 :  char * getwd (char *buffer)

이것은 getcwd와 유사하다.  GNU 라이브러리는 BSD와의 호환성을 위해 getwd를 제공하고 있다.  버퍼는 적어도 PATH_MAX 바이트 길이의 배열을 가리키는 포인터가 된다.

함수 :  int chdir (const char *filename)

이 함수는 filename으로 프로세스의 작업 디렉토리를 설정하기위해 사용된다.  보통 성공하면 리턴값은 0이다.  만일 -1이 리턴되면 에러가 발생했음을 나타낸다.  이 함수에서 정의하고 있는 에러상황 errno는 대부분 파일 이름의 문법적 에러이고( 6.2.3절 [File Name Errors] 60페이지 참조) ENOTDIR은 파일 이름이 디렉토리가 아님을 나타낸다.

9.2  디렉토리 억세스하기

이절에서 설명하고 있는것들은 한 디렉토리 파일의 내용을 읽는데 쓰여진다.  이것은 당신의 프로그램이 아마도 메뉴의 일부분으로써,디렉토리 안의 모든 파일들의 리스트를 원할 때 유용하다. opendir 함수는 디렉토리 엔트리들(entries)을 그 요소로 갖고 있는 디렉토리 스트림을 개방한다.  readdir은 struct dirent objects로서 표현된 그 엔트리들을 디렉토리 스트림에서 얻기위해 사용한다.  각 엔트리를 위한 파일의 이름은 이 구조체의 d_name 안에 저장되어있다. 7장 [I/O on Streams] 63페이지에 설명된, 원래의 파일들을 위한 스트림 도구들과 이곳에 있는것들과는 유사하다.

9.2.1  디렉토리 엔트리의 형식

이절은 당신이 디렉토리 스트림으로 부터 획득한, 단일 디렉토리 엔트리에서 무엇을 발견할수 있는지를 설명하고 있다.  모든 심볼들은 헤더파일'dirent.h'에 선언되어 있다.

데이터타입 :  struct dirent

이것은 디렉토리 엔트리들에 대한 리턴 정보로서 사용되는 구조체 타입이다. 그것은 다음과 같은 필드들은 포함하고 있다.

char *d_name

이것은 널 문자로 끝나는 파일이름 요소이다. 이것은 모든 POSIX 시스템상에서 당신이 셀 수 있는 유일한 필드이다.

ino_t d_fileno

이것은 파일 시리얼 넘버이다.  BSD와의 호환성을 위해서, 당신은 d-ino로서 이 멤버를 대치할 수 있다.

size_t d_namlen

이것은 널 종료 문자가 포함되지 않은, 파일 이름의 길이이다. 이 구조체는 앞으로 부가적인 멤버들을 포함할 것이다.  파일이 다중의 이름들을 가질 때, 각각의 이름들은 자신의 디렉토리 엔트리를 갖는다. 단일 파일에 속한 디렉토리 엔트리들을 가리키는 유일한 방법은 d_fileno 필드에 동일한 값을 줌으로써 할 수 있다. 크기, 갱신 시간 과 같은 파일 속성과, 파일 그자체의 일부분과 같은 것은 어느 특별한 디렉토리 엔트리가 아니다.  9.8절 [File Attributes] 131페이지 참조.

9.2.2  디렉토리 스트림 개방하기

이절은 디렉토리 스트림을 어떻게 개방하는지 설명하고 있다.  모든 심볼들은 헤더파일 'dirent.h'에 선언되어 있다.

데이터타입 :  DIR

DIR 데이터타입은 디렉토리 스트림을 나타낸다. 당신은 struct dirent 나 DIR 데이터타입들로 objects들을 할당받을 수 없다.  대신에 당신은 다음에 있는 함수들을 사용함으로 인해서 리턴된 포인터를 사용해서 이들 objects를 참조할 수 있다.

함수 :  DIR * opendir (const char *dirname)

opendir 함수는 파일 이름이 dirname인 디렉토리를 읽기 위해서 디렉토리 스트림을 개방하고 리턴한다.  그 스트림은 DIR * 타입을 갖는다. 만일 성공하지 못하면 opendir은 널 포인터를 리턴한다.  보통 쉽게 일어나는 파일 이름 문법 에러들에 더하여( 6.2.3 [File Nmae Errors], 60 페이지 참조), 다음의 errno는 이 함수에서 정의하고 있는 에러 상황이다.

EACCES	dirname의 이름을 가진 디렉토리를 읽기 위한 요청이 거부되었다.

EMFILE	그 프로세스가 너무 많은 파일들을 개방했다.

ENFILE	디렉토리를 포함하고 있는 전체 시스템이나, 아마도 파일 시스템이 그 순간에 더 이상 파일을 개방하는 것은 지원하지 않는다. ( 이 문제는 GNU시스템에서는 발생할 수 없다.

DIR 타입은 파일 기술자와, 그리고 open 함수에서 사용되는 opendir 함수에서 사용되어진다.  8장 [Low-Level I/O} 103페이지 참조. 디렉토리 스트림들과 포함된 파일 기술자들은 exec에서 폐쇠된다. 9 23.5절 [Executing a File] 327 페이지 참조 )

9.2.3  디렉토리 스트림 읽고 닫기

이절은 디렉토리 스트림으로 부터 디렉토리 엔트리들을 어떻게 읽고, 그렇게 한 후 그 스트림을 어떻게 닫을것인지를 설명하고 있다.  모든 심볼들은 헤더파일 'dirent.h'에 선언되어 있다.

함수 :  struct dirent * readdir (DIR *dirstream)

dl 함수는 디렉토리로부터 다음 엔트리를 읽는다.  보통 리턴값은 파일에 대한 정보를 갖고 있는 구조체의 포인터이다.  이 구조체는 정적으로 할당 되고 그후에 일어나는 호출에 의해서 갱신되어질수 있다.

이식성 노트 :  어떤 시스템에서는, readdir은 `.'과 `..'가 어느 디렉토리 에서 유용한 파일이름 일지라도, 그것들을 위한 엔트리들을 리턴하지 않을 것이다.  6.22 [File Name Resolution] 59 페이지 참조

만일 그 디렉토리 안에 더 이상 엔트리가 없거나, 에러가 검출되면, readdir은 널 포인터를 리턴한다.  다음의 errno는 이 함수에서 정의한 에러 상황이다.

EBADF	dirstream인수가 유용하지 않다.

함수 :  int closedir (DIR *dirstream)

이 함수는 디렉토리 스트림 dirstream을 폐쇠한다.  성공하면 0을 리턴하고 실패하면 -1을 리턴한다. 다음의 errno는 이 함수에서 정의하고 있는 에러상황이다.

EBADF	dirstream인수가 유용하지 않다.

9.2.4  디렉토리의 파일을 리스트하는 간단한 프로그램

이곳에 현재 작업 디렉토리의 파일들의 이름을 출력하는 간단한 프로그램이 있다.

#include  <stddef.h>

#include  <stdio.h>

#include  <sys/types.h>

#include  <dirent.h>

int main  (void)

{

/* DIR과 struct dirent는 위에 설명이 있네요... 흐.. */

DIR  *dp;

struct  dirent  *ep;

/* 현재의 디렉토리를 개방해서 dp에 넣어라!!! */

dp  =  opendir  ("./");

/* 만일 개방이 됐다면 dp의 엔트리를 읽어서 ep에 넣고 그 구조체에서 d_name멤버를 출력하라.*/

if  (dp  !=  NULL)

{

while  (ep  =  readdir  (dp))

puts  (ep->d_name);

(void)  closedir  (dp);

}

/* 개방이 안됐다면 에러 메시지 출력... */

else

puts  ("Couldn't  open  the  directory.");

return  0;

}

디렉토리안에 나타나는 파일들의 순서는 불규칙적이다.  이것보다 더 유용한 프로그램은 그들을 출력하기 전에 엔트리들을 정렬(아마도 알파벳 순서로..)하는 것이다; 15.3절 [Array Sort Function] 217 페이지 참조.

9.2.5  디렉토리 스트림의 랜덤 억세스.

이 절은 이미 개방된 디렉토리 스트림으로 부터 읽었던 한 부분을 어떻게 다시 읽을 수 있는지를 설명하고 있다.  모든 심볼들은 헤더파일 'dirent.h'에 선언되어 있다.

함수 :  void rewinddir (DIR *dirstream)

rewinddir함수는 디렉토리 스트림 dirstream을 재설정하는데 사용되어지기 때문에 만일 당신이 readdir을 호출하면 그것은 다시 디렉토리의 첫 번째 엔트리에 대한 정보를 리턴한다.  이 함수는 또한 opendir로 개방된후 디렉토리안의 파일들이 더해지거나, 제거된적이 있는지에 대한것도 지적한다. ( 이들 파일들을 위한 엔트리들은 당신이 마지막으로 호출했던 opendir이나 rewinddir 이후에 더해지거나 제거된것이라면 readdir을 통해 리턴될수도, 리턴되지 않을 수도 있다.

함수 :  off_t telldir (DIR *dirstream)

telldir 함수는 디렉토리 스트림 dirstream의 파일 위치를 리턴한다. 당신은 그 위치에 디렉토리 스트림을 저장하기 위한 함수 seekdir에 이 값을 사용할 수 있다.

함수 :  seekdir (DIR *dirstream, off_t pos)

seekdir함수는 pos로 디렉토리 스트림 dirstream의 파일 위치를 설정한다. 그 값 pos는 이 특별한 스트림상에서 미리 telldir을 호출한것의 결과이어야 한다; 디렉토리를 폐쇠하고, 다시 열기는 telldir에 의해 리턴된 값들을 무효로 만들 수있다.

9.3  하드 링크

POSIX 시스템에서는, 한 개의 파일이 동시에 많은 다른이름을 소유할 수 있다.  그 이름들 모두는 동등하게 실제로 존재하고, 그들중 어느것도 다른것들을 취할 수 있다.

파일에 이름을 더하기 위해서, link 함수를 사용하라. ( 새로운 이름은 파일에 하드링크라고 불리워진다. )  파일에 만들어진 새로운 링크는 파일의 내용들은 복사하지 않는다;  그것은 그냥 간단히 기존에 있는 파일에 새로운 이름을 하나 더할뿐이다.

하나의 파일은 여러개의 디렉토리들에 이름을 가질수 있어서, 파일 시스템의 구조는 정적 계층도나 트리모양이 아니다.

단일한 파일 시스템안에 존재하는 특별한 파일에서, 그 파일 이름들은 모두 단일한 파일 시스템의 디렉토리안에 존재해야만 한다.  link는 다른 파일 시스템으로부터 그 파일에 하드링크를 만들려고 시도하면 에러를 출력한다.

역자주 : 그러니까.. 어떤 파일에서 링크된 것들도 모두 원래의 파일과 같은 파일 시스템안에 존재해야지, 다른곳에 있으면 안된다는 말씀. 즉, 파일 시스템들간에 파일을 링크하는 것은 불가능하다.

link함수의 프로토타입은 헤더파일 'uinstd.h'에 선언되어 있다.

함수 :  int link (const char *oldname, const char *newname)

link함수는 oldname의 이름을 가진 현존하고 있는 파일에 newname이라는 이름을 갖는 새로운 링크를 만든다.  이 함수는 성공하면 0을 리턴하고 실패하면 -1을 리턴한다.  보통의 파일 이름 문법 에러에 더하여서( 6.2.3절[File Name Errors] 60페이지 참조), 다음 errno는 이 함수에서 정의하고 있는 에러상황이다.

EACCES	새로운 링크가 쓰여져야할 디렉토리가 기록 불가능이다.

EEXIST	이미 newname의 이름을 가진 파일이 있다. 만일 새로운 링크로 기존의 링크를 대치하게 원한다면, 당신은 먼저 기존의 링크를 제거해야 한다.

EMLINK	그것엔 이미 oldname에 의해 이름된 너무많은 링크가 있다. ( 링크의 최대갯수는 LINK_MAX이다. 27.6절 [Limits for Files] 374페이지 참조) 잘 만들어진 파일시스템들은 결코 이 에러를 발생시키지 않는다.왜냐하면 그들은 당신의 디스크가 가질수 있는 만큼의 많은 링크를 허용하기 때문이다. 그렇지만 당신은 다른 기계상의 파일 시스템을 네트웍으로 억세스 할수도 있으므로, 당신의 시스템이 이런 에러를 발생시키지 않는다고 하더라도, 당신은 여전히 링크의 개수에 신경을 써야한다.

ENOENT	oldname이란 이름을 가진 파일이 존재하지 않는다.  당신은 그 파일이 없으면, 파일에 링크를 만들 수 없다.

ENOSPC	새로운 링크를 포함해야할 디렉토리나 파일 시스템이 "가득찼고" 더 이상 확장되지 않는다.

EPERM	어떤곳에서는 특권이 부여된 사용자에게만 디렉토리에 링크를 만드는 것을 허용하고,  다른 사용자들에게는 완전히 이 명령을 금지한다. 이 에러는 그 문제를 알리기 위해 사용된다.

EROFS	디렉토리가 읽기 모드 파일 시스템이기 때문에 새로운 링크를 갖고 있는 디렉토리로 갱신되어 질 수 없다.

EXDEV	새로운 이름으로 지정된 디렉토리가 링크하려는 파일과는 다른 파일 시스템상에 존재한다.

9.4  심볼릭 링크

GNU 시스템에서는 소프트 링크 혹은 심볼릭 링크를 지원한다.  이것은 일종의 "파일"로서 본질적으로는 다른 파일이름을 가리키는 포인터이다. 하드링크와는 달리, 심볼릭 링크는 디렉토리나 서로 다른 파일 시스템에 아무런 제한없이 만들 수 있다.  당신은 또한 아직 어느 파일의 이름으로 존재하지 않는 이름에 심볼릭 링크를 만들 수도 있다.( 이런 링크의 개방은 그 이름의 파일이 만들어질때까지 실패할 것이다.).  마찬가지로, 만일 심볼릭 링크가 현존하는 파일에서 만들어지고 나중에 그 파일이 지워졌다면, 심볼릭 링크는 더이상 그파일이 존재하지 않음에도 불구하고 동일한 파일이름을 가리키며 남아있다.

심볼릭 링크라고 불리워지는 이유는 당신이 링크를 개방하려 시도할 때 그들이 행하는 특별한 작업방법 때문이다.  open함수는 당신이 링크의 지정된 이름을 인수로 준다면, 그 링크에 포함된 파일이름을 읽어서, 대신에 그 파일 이름을 개방한다.  stat함수는 마찬가지로, 링크의 그 자체 대신에 심볼릭 링크에 연결된 그 파일상에서 동작한다.  link함수는 하드 링크를 만든다.

하지만, 파일을 지우거나, 재명명하거나 하는 명령들을 링크 그 자신에서 행해진다.  readlink와 lstat 함수는 또한 심볼릭 링크에서 동작하는데, 왜냐하면 그들의 목적이 링크에 대한 정보를 얻는것이기 때문이다. 이들 함수를 위한 프로토타입은 'uinstd.h'이다.

함수 :  int symlink (const char *oldname, const char *newname)

symlink함수는 oldname에 newname으로 이름된 심볼릭 링크를 만든다. symlink로부터 리턴값은 보통 0이다.  만일 에러가 발생하면 -1을 리턴한다.  보통 파일 이름 문법 에러에 더하여서( 6.2.3절 [File Name Errors] 60 페이지 참조), 다음 errno는 이 함수에서 정의된 에러 상황이다.

EEXIST	이미 newname이름을 가진 파일이 존재한다.

EROFS	newname 파일이 읽기-모드 파일 시스템상에 존재할 것이다.

ENOSPC	디렉토리나 파일 시스템이 새로운 링크를 만들도록 확장되어질 수 없다.

EIO	디스크에 데이터를 쓰거나, 읽는 동안 하드웨어 에러가 발생했다.

함수 :  int readlink (const char *filename, char *buffer, size_t size)

readlink함수는 심볼릭 링크 filename의 값을 얻는다. 링크가 가리키는 파일 이름이 버퍼에 카피된다.  이 파일 이름 문자열은 널 종료문자로 끝나지 않는다; readlink는 보통 카피된 문자들의 개수를 리턴한다.  size인수는 카피할 문자들의 최대수를 저정하고, 보통 버퍼의 할당 크기가 된다. 만일 리턴값이 size와 같다면, 당신은 그 버퍼에 완전한 이름이 리턴된 것인지, 아닌지를 알 수가 없다.  그러므로 다시 버퍼를 크게하고, readlink를 다시 호출하라.  이곳에 그 예가 있다.

char  *readlink_malloc  (char  *filename)

{

int  size  =  100;

while  (1)

{

char  *buffer  =  (char  *)  xmalloc  (size);

int  nchars  =  readlink  (filename,  buffer,  size);

if  (nchars  <  size)

return  buffer;

free  (buffer);

size  *=  2;

}

}

-1의 값이 리턴되었다면 에러가 발생한 것이다.  보통 파일이름 문법에러에 대하여( 6.2.3절 [File Name Errors] 60페이지 참조) 다음의 errno는 이 함수에서 정의한 에러상황이다.

EINVAL	주어진 filename이 심볼릭 링크가 아니다.

EIO	디스크상에서 읽거나, 쓰고 있는 동안 하드웨어 에러가 발생했다.

9.5  파일 지우기

당신은 unlink 나 remove 의 함수로 파일을 지울 수 있다. ( 이들 이름들은 동의어이다.)

삭제(Deletion)는 실제로 파일이름을 지운다.  만일 그 이름이 파일의 유일한 이름이라면, 그 파일은 삭제된다.  하지만 만일 그 파일이 다른 이름으로 존재하고 있다면( 9.3절 [Hard Links], 127 페이지 참조), 그것은 다른 이름 아래서 억세스 가능한 상태로 남는다.

함수 :  int unlink (const char *filename)

unlink함수는 파일이름 filename을 지운다.  만일 이 파일이름이 그 파일의 유일한 이름이라면, 그 파일 자체가 삭제된다. (실제로는, 만일 어느 프로세스가 이처럼 unlink될 때 그 파일을 개방된 파일로 갖고 있다면, 모든 프로세스들이 그 파일을 닫을때까지 삭제는 연기된다.)unlink함수는 헤더파일 'unistd.h'에 선언되어 있다. 이 함수는 성공하면 0을 리턴하고 에러이면 -1을 리턴한다.  보통의 파일이름 문법에러에 더하여서( 6.2.3절 [File Name errors] 60 페이지 참조) 다음의 errno는 이 함수를 위하여 정의된 에러 상황이다.

EACCESS	파일을 제거하기 위한 쓰기 요청이 디렉토리 에서 거부되었다.

EBUSY	이 에러는 그 파일이 제거되어질 수 없는, 시스템에서 사용하고 있는 파일임을 나타낸다. 당신이 어디서건 이 에러를 보았다면 그것은 당신이 지정한 파일이름이 루트 디렉토리 이거나 파일 시스템에서 마운트하고 있다는 것이다.

ENOENT	제거하려는 파일이름이 존재하지 않는다.

EPERM	어떤 시스템에서는, unlink는 디렉토리 제거에 사용할 수 없거나, 오직 특권이 부여된 사용자만이 이 방법을 사용할수 있거나 한다.  이와 같은 문제들을 피하기 위해서, 디렉토리를 지우려면 rmdir을 사용하라.

EROFS	지우려는 파일 이름의 디렉토리가 읽기 모드 파일 시스템상에 존재하고 있기 때문에 갱신되어 질 수 없다.

함수 :   int remove (const char *filename)

remove 함수는 unlink의 다른 이름으로 ANSI C 이다.  unlink는 POSIX.1 이름이다.  remove는 'stdio.h'에 선언되어 있다.

함수 :  int rmdir (const char *filename)

rmdir은 디렉토리를 지운다.  디렉토리를 제거하기 전에는 디렉토리는 반드시 비어있어야 한다. 즉, 그 디렉토리는 `.' 와 `..' 의 엔트리만 갖고 있어야 한다. rmdir 은 unlink처럼 동작한다.  다음의 두 개의 부가적 errno는 rmdir을 위해서 정의된 에러상황이다.

EEXIST

ENOTEMPTY	지우려는 디렉토리가 비어있지 않다.

이들 두 개의 에러는 동일하다. 어떤 시스템에서는 어떤 하나를 사용하고, 다른 시스템에서는 다른 하나를 사용한다. 이 함수를 위한 프로토타입은 헤더파일 'uinstd.h'에 선언되어 있다.

9.6  파일 재명명하기

rename함수는 파일의 이름을 변경하는데 사용한다.

함수 :  int rename  (const char *oldname, const char *newname)

rename함수는 파일 이름 oldname을 newname으로 재명명한다.  oldname 이름하에서의 파일 검색 형식은 나중에 newname으로 재명명되더라도, 역시 변함이 없다. (만일 그 파일이 oldname외에 다른 이름들은 갖었다면, 그것들의 이름에도 변함이 없다. ) newname 이름의 파일을 갖고 있는 디렉토리는 그 파일( oldname 이름의 파일 )과 같은 파일 시스템안에 있어야만 한다. 재명명의 특별한 경우는 동일한 파일에 oldname과 newname 두 개의 이름이 존재할 때 이다.  이 경우에 대응하기 위한 일관된 방법은 oldname을 제거하는 것이다.  그렇지만, POSIX는 이 경우에 rename은 아무일도 하지않고, 일치하는 파일이 없었을 때, 성공하게 된다. 우리는 당신의 운영체제가 무엇을 할것인지 알지 못한다.  GNU시스템은 당신이 POSIX 시스템과 호환성을 가지도록, 명백하게 요청하지 않더라도, 옳은일(oldname의 제거)을 할 것이다. 만일 oldname이 디렉토리가 아니라면, 이미 존재하고 있던 newname의 이름을 가진 파일은 재명명의 동작이 수행될 때 지워진다.  그렇지만, 만일 newname이 디렉토리라면, rename은 이 경우 실패로 끝난다. 만일 oldname이 디렉토리일 때, newname의 이름을 가진 어떤것도 존재해서는 안되고, 또는 그것이 비어있는 디렉토리 이름이거나 해야한다. 후자의 경우에, newname의 이름을 가진 디렉토리를 우선 지우고 재명명을 하는 것이다.  newname은 재명명을 시도할 디렉토리로 oldname의 서브디렉토리를 지정하지 않아야만 한다. rename은 기존에 oldname이란 이름을 갖고 있는 파일을 newname이라는 새로운 이름으로 자동적으로 변경할 때 쓰여진다.  old를 new로 변경할 동안 "그 사이에" newname의 이름을 가진 것이 존재하지 않는다면 더 이상 신경쓸것이 아무것도 없다. 만일 rename이 실패하면, -1을 리턴한다.  파일 이름 문법 에러에 ( 6.2.3절 [File Name Errors], 60 페이지 참조) 더하여, 다음 errno는 이 함수에서 정의된 에러 상황이다.

EACCES	newname이나 oldname을 갖고 있는 디렉토리들 중의 하나가 쓰기 요청을 거부했다. 또는 newname과 oldname은 디렉토리인데 쓰기 요청이 그들중에 하나에서 거부되어졌다.

EBUSY	oldname이나 newname이름을 가진 디렉토리가 시스템이 작업하는 동안 재명명을 거부하는 방법으로 현재 그 디렉토리를 시스템에서 사용하고 있다. 디렉토리가 파일 시스템으로 마운트(mount)된 것이고, 디렉토리가 프로세스들의 현재 작업 디렉토리이다.

EEXIST	newname 디렉토리가 비어있지 않다.

ENOTEMPTY

newname 디렉토리가 비어있지 않다.

EINVAL	oldname 디렉토리가 newname을 포함하는 디렉토리이다.

역자주 : newname이 oldname의 서브디렉토리.

EISDIR	newname은 디렉토리 이름인데, oldname은 디렉토리 이름이 아니다.

EMLINK	newname의 부모 디렉토리가 너무 많은 링크들을 가지고 있다.  잘 만들어진 파일 시스템들은 결코 이 에러를 발생시키지 않는다, 왜냐하면 그들은 당신의 디스크가 허용하는 한 많은 링크를 허용하기 때문이다.  하지만 당신은 다른 기계상의 파일 시스템을 네트웍으로 억세스 할수 있으므로, 이 에러에 대한 가능성에 대해서 항상 대비해야만 한다.

ENOENT	oldname 이란 이름을 가진 파일이 존재하지 않는다.

ENOSPC	newname을 포함할 디렉토리가 더 이상의 엔트리를 받아들일 수 없고, 그곳에는 파일 시스템안에 확장할 공감이 남아있지 않다.

EROFS	오직 읽기 모드인 파일 시스템상의 디렉토리에 쓰기를 시도하고 있다.

EXDEV	oldname과 newname이라는 두 개의 파일 이름이 서로 다른 파일 시스템에 존재하고 있다.

9.7  디렉토리 만들기

디렉토리는 mkdir 함수로 만들어진다. ( 같은 일을 하는 mkdir 이라는 쉘 명령어도 있다. )

함수 :  int mkdir (const char *filename, mode_t mode)

mkdir 함수는 filename의 이름을 가진 새로운, 빈 디렉토리를 만든다. mode 인수는 새로운 디렉토리에 대한 접근 권한을 정한다. 9.8.5절 [Permission Bits] 136 페이지를 참조로 해서 접근 권한에 대한 자세한 정보를 얻어라.

수행이 성공적으로 끝나면 0의 값을 리턴하고, 그렇지 않을 경우에는 -1을 리턴한다.  보통의 파일 이름 문법 에러들에 더하여( 6.2.3 [File Name Errors], 60페이지 참조 ), 다음의 errno는 이 함수를 위해 정의된 에러 상황들이다.

EACCES	새로운 디렉토리를 더하게될 부모 디렉토리에서 파일 쓰기 허용이 거부되었다.

EEXIST	filename의 이름을 가진 파일이 이미 존재한다.

EMLINK	부모 디렉토리가 너무 많은 링크들은 가지고 있다.  잘 만들어진 파일 시스템에서는 이러한 에러를 결코 발생시키지 않는다, 왜냐하면, 그들은 당신의 디스크가 저장할 수 있는 만큼의 링크를 허용하기 때문이다.  하지만 당신은 다른 기계상에 존재하는 파일 시스템을 네트웍을 통해서 억세스할 수 있으므로 이러한 에러가 발생될 가능성에 대해서 항상 대비해야 한다.

ENOSPC	파일 시스템이 새로운 디렉토리를 만들만한 충분한 공간을 가지고 있지 않다.

EROFS	디렉토리의 부모 디렉토리가 오직 읽기 모드 파일 시스템상에 존재하고 있고, 그래서 갱신할 수 없다.

이 함수를 사용하려면, 당신의 프로그램에 헤더파일 'sys/stat.h'를 포함 해야 한다.

9.8  파일 속성들

한 파일에 쉘 명령어 'ls -l'을 사용하면, 당신은 파일의 크기, 파일의 소유자, 마지막 갱신된 때, 등 등에 대한 정보를 얻을 수 있다.  이러한 정보들의 종류를 파일 속성이라고 부른다;  이것은 파일 그 자체와 연관되어 있고, 그 이름들의 하나가 특별한 것은 아니다.

이절에서는 당신이 어떻게 파일들의 속성들에 대한 정보를 얻고, 그들을 갱신할 수 있는지를 설명하고 있다.

9.8.1  파일 속성들의 값이 무엇을 의미하는가

당신이 파일 속성을 읽을 때, 그들은 struct stat라고 불리는 구조체로 보내져온다.  이 절은 그 속성들의 이름과, 그들의 데이터 타입들과, 그들이 무엇을 의미하고 있는지에 대해 설명하고 이다.  파일의 속성을 읽기 위한 함수는, 9.8.2절 [Reading Attributes] 133페이지를 참조하라.

이절에서 정의된 모든 심볼들은 헤더파일 'sys/stat.h'에 있다.

데이터타입 :  struct stat

stat 구조체형은 파일의 속성들에 대한 정보를 리턴하기 위해 사용되어진다.  그들은 적어도 다음과 같은 멤버들을 포함하고 있다.

mode_t st_mode

파일의 모드를 정한다.  이것은 파일 타입 정보와( 9.8.3 [Testing File Type], 134 페이지 참조) 파일 접근 권한 비트를(file permission) 포함하고 있다.( 9.8.5절 [Permission Bits] 136 페이지 참조)

ino_t st_ino

파일 일련 번호( file serial number),로서 같은 디바이스 상에서 모든 다른 파일들과 이 파일을 구분한다.

dev_t st_dev

파일을 포함하고 있는 디바이스를 기술한다. st_ino와 st_dev, 이둘을 가지고, 파일을 유일하게 식별한다.

nlink_t st_nlink

파일에 관련된 하드 링크의 개수이다.  이 파일을 엔트리로 가지고 있는 얼마나 많은 디렉토리들이 있는지 추적해서 그 값을 유지한다.  만일 개수가 0 으로 감소하면, 그 파일 자체가 없어진 것이다. 심볼릭 링크들은 총 개수에 포함되지 않는다.

uid_t st_uid

파일 소유자의 유저 ID 이다.  9.8.4 절 [File Owner] 135 페이지 참조.

gid_t st_gid

파일의 그룹 ID이다.  9.8.4절 [File Owner] 135 페이지 참조.

off_t st_size

이것은 바이트의 수로 일반적 파일의 크기를 지정한다.  만일 파일들이 디바이스거나, 그와 같은 파일로 존재한다면, 이 필드는 보통 아무런 의미가 없다.

time_t st_atime

이것은 파일의 최종 억세스 시간이다. 9.8.9절 [File Times] 140 페이지 참조.

unsigned long int st_atime_usec

이것은 파일의 최종 억세스 시간의 부수적인 부분이다. 9.8.9절 [File Times] 140 페이지참조.

time_t st_mtime

이것은 파일의 내용이 마지막으로 갱신된 시간을 말한다. 9.8.9절[File Times]140페이지 참조.

unsigned long int st_mtime_usec

이것은 파일이 마지막으로 갱신된 시간의 부수적인 부분이다.  9.8.9절 [File Times] 140페이지 참조.

time_t st_ctime

이것은 파일의 속성 자체가 마지막으로 갱신된 시간을 말한다.  9.8.9절 [File Times] 140 페이지 참조.

unsigned long int st_ctime_usec

이것은 파일 속성이 마지막으로 갱신된 시간의 부수적인 부분이다.  9.8.9절 [File Times] 140페이지 참조.

unsigned int st_nblocks

이것은 512-바이트 블록의 단위로 계산된, 파일의 점유하고 있는 디스크 공간의 양을 말한다. 디스크 블록들의 개수는 파일이 크기와 완전히 비례하지 않는다.  두가지 이유로...; 파일 시스템이 내부적 레코드를 가지고 있기 위해서 어떤 블록들을 사용할 것이고; 그리고 파일들은 0(zeros)과 같은 "구멍들"을 가지고 있을 것이기 때문이다. 한 파일에서 st_size와 이 값을 비교하면 (대략) 한 파일의 내용이 드문드문한지 아닌지 알 수 있다. 이처럼:

(st.st_blocks  *  512  <  st.st_size)

이 결과는 모든 여기저기 흩어져 있는 것들이 검출이 되지 않을수 도 있기 때문에, 완전한 결과는 아니다.  실용적인 어플리케이션에서, 이것은 문제가 아니다.

unsigned int st_blksize

쓰여진 파일을 읽기 위한 최적의 블록 크기를 말한다.  당신은 쓰여진 파일을 읽기 위한 버퍼 공간을 할당하기 위해서 이 크기를 사용할 것이다.	어떤 파일 속성들은 그들을 위해 특별히 존재하는 특별한 데이터 타입의 이름들을 가지고 있다. ( 그들은 당신이 익히 알고 있는 것들을 정수형으로 이름을 붙여놓은 것이다. )  이들 typedef 시킨  이름들은 'sys/types.h'와 'sys/stat.h'에 정의되어 있다.  여기에 그들에 대한 설명이 있다.

데이터타입 :  mode__t

이것은 현재의 파일 모드들을 나타내기 위해 사용되는 정수형 데이터 타입니다.  GNU 시스템에서, 이것은 unsigned int 와 동일하다.

데이터타입 :  ino__t

이것은 파일의 일련 번호들을 나타내기 위해 사용하는 산술적 데이터 타입이다. (jargon 유닉스에서는, 이들을 때때로 inode 번호라고 불리워 진다. ) GNU 시스템에서는, 이 타입은 unsigned long int와 동일하다.

데이터타입 :  dev__t

이것은 파일 디바이스 번호를 나타내기 위해 사용하는 산술적 데이터 타입니다.  GNU 시스템에서는, 이것은 int 와 동일하다.

데이터타입 :  nlink__t

이것은 파일 링크의 개수를 나타내기 위해 사용되는 산술적 데이터 타입입니다.  GNU 시스템에서, 이것은 unsigned short int 와 동일하다.

9.8.2  파일 속성들 읽기

파일의 속성들을 조사하기 위해서는, stat, fstat 와 lstat 함수를 사용하라. 그들은 struct stat object에 속성정보를 리턴한다.  세 개 함수 모두는 헤더파일 'sys/stat.h'에 선언되어 있다.

함수 :  int stat  (const char *filename, struct stat *buf)

stat 함수는 buf가 가리키는 구조체안에 filename의 이름을 가진 파일의 속성에 대한 정보를 리턴한다. 만일 filename이 심볼릭 링크의 이름이라면, 당신이 얻게될 속성 정보는 링크가 가리키고 있는 파일에 대한 정보이다. 만일 링크가 가리키고 있는 파일이 존재하지 않는 파일이라면, stat는 실패하게 되고, 존재하지 않는 파일이라고 보고한다. 만일 오퍼레이션이 성공하면 0을 리턴하고, 실패하면 -1을 리턴한다. 파일 문법 에러들에 더하여(6.2.3절 [File Name Errors] 60 페이지 참조), 다음의 errno는 이 함수를 위해 정의된 에러 상황이다.

ENOENT	filename의 이름을 가진 파일이 존재하지 않음.

함수 :  int fstat  (int filedes, struct stat *buf )

fstat 함수는 파일 이름을 인수로 받아들이는 것 대신에 개방된 파일 기술자를 인수로 받아들이는 점을 제외하고는 stat와 같다. 8장 [Low_Level I/O], 103 페이지 참조.

stat처럼, fstat도 성공하면 0을 리턴하고, 실패하면 -1을 리턴한다. 다음의 errno는 함수 fstat를 위해 정의된 에러상황이다.

EBADF	filedes 인수가 유용한 파일 기술자가 아니다.

함수 :  int lstat  (const char *filename, struct stat *buf )

lstat 함수는 심볼릭 링크를 따르지 않는다는 점을 제외하고는 stat와 같다.  만일 filename이 심볼릭 링크의 이름이라면, lstat는 링크 그 자체에 대한 정보를 리턴한다; 그렇지 않다면 lstat는 stat처럼 동작한다. 9.4절 [Symbolic Links] 127 페이지 참조.

9.8.3  파일의 형식 알아보기

파일 속성의 st_mode 필드안에 저장된 파일 모드는 두가지 정보를 담고 있다:  하나는 파일 타입 코드이고, 다른 하나는 접근 허가 비트들이다.  이 절은 그 파일이 디렉토리인지, 아니면 소켓인지, 등 등을 알수 있는 파일 타입 코드에 대한 것을 설명할 것이다. 파일 접근 권한에 대한 정보는, 9.8.5절 [Permission Bits] 136 페이지 참조.

파일 모드에서 파일 타입 부분을 검색할 수 있는 미리 정의된 두가지 방법이 있다.  그중 한가지는, 파일 모드 값을 시험하는 매크로를 사용해서 파일이 그 타입인지, 아닌지를 참과 거짓으로 리턴하는 방법이 있다. 둘째로, 파일 모드에서 다른 값들을 제외하고( mask out을 시켜서 ) 오직 파일 타입 코드만을 얻어내는 방법이다.  당신은 이 값을 여러 가지 다양한 상수들과 비교할 수 있다. 이 절에서 설명된 모든 심볼들은 헤더파일 'sys/stat.h'에 정의되어 있다.

다음은 파일 타입을 테스트 하기위한 매크로로써, 주어진 m은 stat 함수에 의해 리턴된 st_mode 필드이다.

int S__ISDIR  (mode_t m)                              Macro

이 매크로는 만일 그 파일이 디렉토리라면 0이 아닌값을 리턴한다.

int S__ISCHR  (mode_t m)                              Macro

이 매크로는 만일 그 파일이 문자 특수화일( 터미널과 같은 디바이스 )이면 0이 아닌값을 리턴한다.

int S__ISBLK  (mode_t m)                               Macro

이 매크로는 만일 그 파일이 블록 특수 파일 ( 디스크와 같은 디바이스 )이면 0이 아닌값을 리턴한다.

int S__ISREG  (mode_t m)                              Macro

이 매크로는 만일 그 파일이 보통의 파일이라면 0이 아닌값을 리턴한다.

int S__ISFIFO  (mode_t m)                              Macro

이 매크로는 만일 그 파일이 FIFO 특수 파일이나, pipe라면 0이 아닌 값을 리턴한다.  10장 [Pipes and FIFOs] , 145 페이지 참조.

int S__ISLNK  (mode_t m)                               Macro

이 매크로는 만일 파일이 심볼릭 링크라면 0이 아닌값을 리턴한다. 9.4절 [Symbolic Links] 127 페이지 참조.

int S__ISSOCK  (mode_t m)                             Macro

이 매크로는 만일 그 파일이 소켓이라면 0이 아닌값을 리턴한다. 11장 [Sockets] 149 페이지 참조. 파일 타입을 테스트하는 비-POSIX의 방법은 BSD와의 호환성을 위해서 지원된다. 그 모드는 파일 타입 코드를 추출하기 위해 S_IFMT와 비트단위 AND연산을 시키고, 적당한 타입 코드 상수와 비교한다. 예를 들어,

S_ISCHR  (mode) 는 다음과 동등하다:

((mode  &  S_IFMT) == S-IFCHR)

int S__IFMT                                            Macro

이것은 mode 값에서 파일 타입 코드의 부분을 추출하기 위해서 사용되는 비트 마스크이다.  이들은 다른 파일 타입 코드들을 위해서 심볼화된 이름을 가지고 있다.

S_IFDIR

이 매크로는 디렉토리 파일을 위한 파일 타입 코드의 값을 나타낸다.

S_IFCHR

이 매크로는 문자-지향(character-oriented) 디바이스 파일을 위한 파일 타입 코드의 값을 나타낸다.

S_IFBLK

이 매크로는 블록-지향(block-oriented) 디바이스 파일을 위한 파일 타입 코드의 값을 나타낸다.

S_IFREG

이 매크로는 보통의 파일을 위한 파일 타입 코드의 값을 나타낸다.

S_IFLNK

이 매크로는 심볼릭 링크를 위한 파일 타입 코드의 값을 나타낸다.

S_IFSOCK

이 매크로는 소켓을 위한 파일 타입 코드의 값을 나타낸다.

S_IFIFO

이 매크로는 FIFO 나 pipe를 위한 파일 타입 코드의 값을 나타낸다.

9.8.4  파일 소유자.

모든 파일은 시스템상에 등록된 사용자 이름들중 하나인 소유자를 갖고 있다.  각 파일은 또한 정의된 그룹들중 하나인 그룹을 갖고 있다. 파일 소유자는 누가 그 파일을 편집했는지( 특별히 당신이 GNU Emacs로 편집을 할 때 ) 알기위해서도 필요하지만, 그것의 주된 목적은 접근 권한을 제어하기 위함이다.

파일 소유자와 그룹은, 그 파일이, 파일 소유자인 사용자를 위한 접근 권한 설정, 그리고 파일의 그룹에 속한 사용자들에 적용시키기 위한 다른 설정, 그리고 모든 다른 사람들에게 적용하기 위한 세 번째 비트 설정을 가지고 있기 때문에, 접근을 결정하는 규칙에 따른다. 9.8.6절 [Access Permission] 137 페이지를 참조하면, 이 데이터를 기초로 해서 어떻게 접근을 결정하는지에 대한 상세한 정보를 얻을 수 있다.

파일이 만들어질 때, 그 파일의 소유자는 그 파일을 만든 프로세스의 사용자 ID를 통해 설정된다( 25.2절 [Process personal], 353 페이지 참조). 파일의 그룹 ID는 그 프로세스의 그룹 ID로 설정되거나, 아니면 그 파일이 포함된 디렉토리의 그룹 ID로 설정되거나 하는데, 그것은 그 파일이 저장된 시스템에 따라 다르다.  당신이 원격 파일 시스템을 억세스할 때, 당신의 프로그램이 구동되고 있는 시스템에 따르는 것이 아니라 그파일 자신이 속한 시스템의 규칙에 따라 행동한다.  그래서 당신의 프로그램은, 당신이 어떤 시스템에서 그 프로그램을 구동시키는지에 상관없이, 당신이 부딪칠지도 모르는 모든 동작에 종류에 대해 미리 대비해야만 한다.

당신은 chown 함수를 사용해서 현존하는 파일의 사용자 와/또는 그룹 사용자를 변경할 수 있다.  이것은 chown과 chgrp 쉘 명령어에 기본적으로 포함되어 있다. 이 함수를 위한 프로토타입은 'unistd.h'에 선언되어 있다.

함수 :  int chown (const char *filename, uid_t owner, gid_t group)

chown함수는 owner로 filename의 이름을 가진 파일의 소유자를 변경하고, group으로 그룹 소유자를 변경한다.  어떤 시스템상에서 파일 소유자의 변경은 파일 접근 권한 비트들에서 set-user-ID 와 set-group-ID 비트를 클리어한다(이것은 이 비트들이 새로운 소유자에게 적당하지 않을지도 모르기 때문이다. ). 다른 파일 접근 권한 비트들은 변경되지 않는다.

성공하면 리턴값은 0이고, 실패하면 -1이다.  보통의 파일 문법 에러에 더하여( 6.2.3절 [File Name Errors] 60 페이지 참조), 다음의 errno는 이 함수를 위해 정의된 에러 상황이다.

EPERM	이 프로세스는 요청된 변경을 수행하기에는 접근권한이 부족하다. 오직 특권이 부여된 사용자나, 파일의 소유자만이 그 파일의 그룹을 변경할 수 있다.  대부분의 파일 시스템들은, 오직 특권이 부여된 사용자만이 그 파일의 소유자를 변경할 수 있다; 어떤 파일 시스템들은 만일 당신이 현재 그파일의 소유자라면, 그 소유자를 변경할수 있도록 허용한다.  당신이 원격 파일 시스템을 억세스 할 때, 당신의 프로그램이 구동되고 있는 시스템에 의해서가 아니라, 그 파일을 실제로 갖고 있는 시스템에 의해서 동작이 결정되어 진다. 27.7절 [Options for Files] 375 페이지를 참조로-POSIX_CHOWN_RESTRICTED 매크로에대한 정보를 얻어라.

EROFS	파일이 오직 읽기 모드 파일 시스템에 존재한다.

함수 :  int fchown (int filedes, int owner, int group)

이것은 개방된 파일 기술자 filedes의 파일 소유자를 변경한다는 점을 제외하고는 chown 함수와 같다.  성공하면 0을 리턴하고 실패하면 -1을 리턴한다.  다음의 errno는 이 함수를 위해 정의된 에러코드들이다.

EBADF	filedes 인수가 유용한 파일 기술자가 아니다.

EINVAL	filedes 인수가 보통의 파일이 아닌, pipe 나 소켓에 해당한다.

EPERM	이 프로세스는 요청된 변경을 수행하기 위한 파일 접근 허가가 부족하다.  좀더 상세한 것은 위의 chmod를 참조하라.

EROFS	파일이 오직 읽기 모드 파일 시스템상에 존재하고 있다.

9.8.5  파일 접근 허가 비트들

파일 속성의 st_mode 필드에 저장되어 있는 파일 모드는 두가지의 정보를 포함하고 있다:  파일 타입 코드와 파일 접근 허가 비트들.  이절에서는 오직, 파일을 누가 읽거나 쓸 수 있는가를 제어하는, 파일 접근 허가 비트에 대해서 다룰 것이다. 이절에서 설명하고 있는 심볼들은 헤더파일 'sys/stat.h'에 정의되어 있다. 이들 심볼들은 파일 접근 하가를 제어하는 파일 모드 비트들을 위해 정의되어졌다.

S_IRUSR

S_IREAD	파일의 소유자를 위한 읽기 허가 비트.  많은 시스템들에서, 이 비트는 0400이다.  S_IREAD는 BSD 와의 호환성을 위해 제공되고 있는 잘 안쓰이는 동의어이다.

S_IWUSR

S_IWRITE	파일의 소유자를 위한 쓰기 허가 비트.  보통 0200.

S_IWRITE는 BSD와의 호환성을 위해 제공되고 있는 잘 안쓰이는 동의어이다.

S_IXUSR

S_IEXEC	파일의 소유자를 위한 실행( 보통의 파일 ) 이나 검색( 디렉토리을 위한 ) 허가 비트이다.  보통 0100.

S_IEXEC는 BSD와의 호환성을 위해 제공되고 있는 잘 안쓰이는 동의어이다.

S_IRWXU	이것은 '(S_IRUSR | S_IWUSR | S_IXUSR)'과 같다.

S_IRG게	파일의 그룹 소유자를 위한 읽기 허가 비트. 보통 040

S_IWG	파일의 그룹 소유자를 위한 쓰기 허가 비트. 보통 020.

S_IXGP	파일의 그룹 소유자를 위한 실행이나 검색 허가 비트. 보통 010.

S_IRWXG	이것은 '(S_IRGRP | S_IWGRP | S_IXGRP)'와 같다.

S_IROTH	다른 사용자를 위한 읽기 허가 비트.  보통 04.

S_IWOTH	다른 사용자를 위한 쓰기 허가 비트. 보통 02.

S_IXOTH	다른 사용자를 위한 실행이나 검색 허가 비트. 보통 01.

S_IRWXO	이것은 '(S_IROTH | S_IWOTH | S_IXOTH)'와 같다.

S_ISUID	이것은 실행 비트에서 set-user-ID로, 보통 04000. 25.4절 [How Change Persona], 354 페이지 참조. 역자주: set-user-ID ; 수행중의 사용자 식별번호를 지정.

S_ISGID	이것은 실행비트에서 set-group-ID로, 보통 02000이다. 25.4절 [How Change Persona], 354 페이지 참조. 역자주: set-user-ID : 수행중의 그룹식별번호를 지정

S_ISVTX	이것은 스티키 비트로 보통 01000이다.

역자주: sticky bit ; 수행중의 텍스트 이미지를 저장.

수행중인 파일에서, 시스템의 스와핑 방법들을 변경한다. 보통은 프로그램이 종료될 때, 기억장치안의 그 페이지들은 즉시 해제되어 재사용가능해진다. 그러나 만일 수행가능한 파일에서 스티키비트가 설정되면, 그 시스템은 마치 그 프로그램이 여전히 실행되고 있는것처럼 기억장소에 페이지들을 계속 유지하고 있다.  이것은 완전한 수행에 많은 시간이 소요될 것 같은 프로그램에 유용하다. 디렉토리에서, 스티키비트는 만일 당신이 어느 파일에 쓰기허가를 가지고 있다면 디렉토리안의 그 파일을 제거 하도록 허용한다. 보통 사용자는 디렉토리안의 모든 파일들을 지울수 있거나, 혹은 그들중 어느것도 지울 수 없거나한다 ( 그 사용자가 디렉토리에 쓰기 허가를 갖고 있는지, 없는지에 기초한다. ).  스티키비트는 개개의 파일들의 제거 권한을 제어하는 것을 가능하게 만든다.

역자주: 여기서 페이지는 기억장치에 저장된 하나의 단위라고 볼 수 있겠네요.

심볼들의 실행 비트 값들은 위의 테이블에 있다.  당신은 당신의 프로그램을 디버깅할 때 파일 모드 값들은 해석할 수 있다.  이들 비트값들은 대부분의 시스템에 정확하게 동작하지만, 그것을 완전히 보장할 수는없다.

주의: 파일 접근 권한을 숫자로 명시하는 것은 좋지않은 습관이다.

이것은 이식성이 없고, 또한 누군가 당신의 프로그램을 읽을 때, 그 비트가 무엇을 의미하는지 알수 없기 때문이다.  프로그램을 기호(symbolic)이름들을 사용하여, 깨끗하게 만들어라.

9.8.6  어떻게 파일에 당신의 접근을 결정하는가

운영체제는 보통 유효 사용자와 프로세스의 그룹 ID들과, 그것을 보충하여 그룹 ID들과 파일의 소유자, 그룹 그리고 허가비트들을 기초로 하여파일에 대한 접근 하가를 결정한다.  이 원칙들은 25.2절 [Process Persona] 353 페이지에 자세히 나와있다.

역자주: 유효사용자ID: 실제 생성된 파일에 대한 소유권을 갖는 사용자 실제사용자ID: 프로세스를 수행시킨 사용자의 식별번호

만일 프로세스의 유효 사용자 ID가 파일의 소유 사용자 ID와 대응됐을 때, 읽기, 쓰기와 실행/검색허가는 해당되는 "사용자" (또는 "소유자") 비트에 의해 제어된다.  그와같이, 만일 유효 그룹 ID 나 프로세스의 그룹 ID가 파일의 그룹 소유자 ID와 대응됐을때, 파일 접근 허가는 "그룹" 비트들에 의해 제어된다.  그렇지 않다면, 파일 접근 허가는 "다른사용자" 비트들에 의해 제어된다. '루트'처럼 특권이 부여된 사용자는 파일 접근 허가에 관계없이 어느 파일이라도 억세스 할 수 있다.  특별한 경우로, 특권이 부여된 사용자를 위한 실행가능 파일을 위해서, 적어도 하나의 실행 비트들을 설정해야만 한다.

9.8.7  파일 접근 허가 정하기

파일을 만드는 원래의 함수들( 예를 들어, open이나 mkdir)은 새로이 만들어질 파일의 접근 허가를 정하기 위해서 mode인수를 취한다. 그러나 정해진 모드는 그것이 사용되기 전에, 프로세스의 파일생성마스크나 umask에 의해 갱신되어진다.

파일 생성 마스크에 설정되어진 비트들은 새로이 개방된 파일에 대한 ,그 설정에 해당하는 권한이 없다는 것을 의미한다.  예를 들어, 만일 당신이 "다른 사용자" 접근 권한 비트들을 모두 마스크에 설정하면, 새로이 생성된 파일에, "다른 사용자" 범주에 들어가는 모든 프로세스들의 접근이 불가된다, 심지어 개방된 파일에 주어진 mode 인수가 접근가능이었다고 하더라도. 즉, 파일 생성 마스크는 당신이 원하는 만큼 파일 접근 허가를 부여하기가 용이하다.

프로그램들은 특별한 파일에 적정한 모든 허가를 포함하고 있는 mode 인수를 지정해서 파일을 개방한다.  보통의 파일은, 모든 부류의 사용자들에게 읽기와 쓰기허가가 있다.  이들 허가들을 개별적으로 사용자 자신의 파일 생성 마스크를 통해서 제한하는 것이다.

이름이 있는 현존하는 파일의 허가를 변경하기 위해서는, chmod를 호출하라.  이 함수는 파일 생성 마스크를 무시한다;  그것은 정확히 정해진 허가 비트들을 사용한다.  보통, 파일 생성 마스크는 사용자의 로그인 쉘에서 초기화되있고 (umask 쉘 명령어를 사용해서 ) , 그리고 모든 부프로세스들이 물려받는다.  응용 프로그램들은 보통 파일 생성 마스크에 대해서 걱정할 필요가 없다.  그것은 미리 가정된 것을 할 것이다.  당신이 파일을 개방하고, 파일 접근 허가를 위해서 umask를 사용할 때, umask를 사용하는 것 보다는, 파일을 개방한 후에 fchmod를 사용하여 그 일을 하는 것이 가장 쉬운 방법이다.

실제로, umask를 통한 변경은 오직 쉘에 의해서 행해진다.  그들은 umask함수를 사용한다.

이절의 함수들은 헤더파일 "sys/stat.h'에 선언되어 있다.

함수 :  mode_t umask  (mode_t mask)

umask 함수는 mask로 현재 프로세스의 파일 생성 마스크를 설정하고, 파일 생성 마스크의 전의 값을 리턴한다.  이곳에서는 그 값들을 변경하지 않고, umask를 사용해서 마스크를 어떻게 읽는지를 보여주고 있다.

mode_t

read_umask  (void)

{

mask  =  umask  (0);

umask  (mask);

}

그렇지만, 만일 당신이 마스크값을 단지 읽기를 원한다면 getumask를 사용하는 편이 낫다, 왜냐하면 재진입 때문이다. (적어도 만일 당신이 GNU 운영체제를 사용하고 있다면 )

함수 :  mode_t getumask  (void)

현재의 프로세스를 위한 파일생성 마스크의 현재의 값을 리턴한다. 이 함수는 GNU확장이다.

함수 :  int chmod  (const char *filename, mode_t mode)

chmod 함수는 mode로 filename의 이름을 가진 파일을 위한 접근 허가비트를 설정한다.  만일 그 filename의 파일이 심볼릭 링크라면, chmod는 링크 그 자체가 아니라, 링크가 가리키고 있는 파일의 접근 허가를 변경한다. 링크의 모드를 설정할 수 있는 방법은 아무것도 없다.  성공하면 0을, 실패하면 -1을 리턴한다.  보통의 파일 이름 문법 에러에 더하여 ( 6.2.3절 [File Name Errors] 60 페이지 참조), 다음의 errno는 이 함수에서 정의된 에러 상황이다.

ENOENT	그 이름을 가진 파일이 존재하지 않는다.

EPERM	이 프로세스가 이 파일의 접근 허가를 변경할 만큼의 권한을 가지고 있지 않다.  오직 파일의 소유자( 프로세스의 유효 사용자 ID로 판단된)나 특권이 부여된 사용자만 그들을 변경할 수 있다.

EROFS	파일이 오직 읽기 모드의 파일 시스템에 존재한다.

함수 :  int fchmod  (int filedes, int mode)

이것은 기술자 filedes를 거쳐서 현재 개방된 파일의 접근 허가를 변경한다는점을 제외하고는 chmod와 같다.  fchmod로부터의 리턴값은 성공하면 0이고, 실패하면 -1이다.  다음의 errno는 이 함수를 위해 정의한상황이다.

EBADF	filedes 인수가 유용한 파일 기술자가 아니다.

EINVAL	filedes 인수가 파이프나 소켓이거나, 아니면 그것이 실제로 접근 허가를 가지지 않는 어떤것에 해당한다.

EPERM	이 프로세스가 파일의 접근 허가를 변경하기위한 허가를 가지고 있지 않다.  오직 파일의 소유자( 프로세스의 유효 사용자 ID로 판단된)나, 특권이 부여된 사용자만이 그들을 변경할 수있다.

EROFS	파일이 오직 읽기 모드 파일 시스템안에 존재한다. 9.8.8  파일을 억세스하기 위하여 파일 접근 허가를 알아보기.  특권이 부여된 사용자로써 프로그램을 실행하고 있을 때, 그 사용자에게 제한없이 파일들을 억세스 하도록 허용한다. 예를 들어, '/etc/passwd/'파일을 수정하기와 같은것도.  유효 사용자 ID로 항상 루트에서 실행되도록 setuid 비트를 사용하는 그런 파일들이 아니라면, 프로그램들은 보통의 사용자에의해 실행되도록 만들어졌다.  정해진 사용자에 의하여 파일을 억세스하는 프로그램에서, 파일들이 명백한 사용자에 의해 억세스되어진다.  루트로 실행되는 프로그램에서는 정해진 사용자가 무슨 파일이든지 억세스가 허가되지만, 보통은 보통의 사용자가 검색할 수 있는 그런 파일들만 허가된다.  프로그램은 그 파일을 읽거나 쓰기전에, 사용자가 파일을 억세스할 필요가 있는지를 명백하게 체크해야만 한다. 이것을 하기 위해서, 유효 사용자 ID 보다는 프로세스의 실 사용자 ID에 의한 접근 허가를 체크하는 access함수를 사용하라. ( setuid는 그 프로그램을 실제로 실행시킨 사용자를 반영한, 실 사용자 ID를 변경하지 않는다.) 설명하기는 쉽지만, 사용하기는 어려운 엑세스를 체크할 수 있는 다른방법이 있다.  이것은 파일 모드 비트들을 시험하고 시스템의 엑세스조작을 흉내내는 것이다.  이 방법은 많은 시스템들이 부가적인 억세스 제어를 가지고 있기 때문에 바람직한 방법은 아니다;  당신의 프로그램이 이식성있게 그들을 흉내낼수도 없고, 다른 시스템들이 가진 다른 종류의엑세스 제어를 알아내려 시도할 수도 없다.  access를 사용하는 것은 간단하고, 당신이 사용하는 시스템이 무엇이든지간에 그 시스템에 적당한 동작을 자동적으로 한다.

이 절에서 설명된 심볼들은 'uinstd.h'에 선언되어 있다.

함수 :  int access  (const char *filename, int how )

access 함수는 how인수로 정해진 방법으로 filename의 이름을 가진 파일을 억세스 할수 있는지를 알아보는 함수이다.  how 인수는 R_OK, W_OK, X_OK,플래그를 비트단위 OR연산된것이거나 존재 테스트 F_OK 플래그이다.  이 함수는 접근 허가를 체크하기 위해서 유효사용자 ID를 사용하기 보다는 호출하는 프로세스의 실 사용자와 그룹 ID들을 사용한다.  그 결과로, 만일 당신이 setuid나 setgid 프로그램( 25.4절 [How Change Persona] 354 페이지 참조)으로부터 함수를 사용한다면, 그것은 그 프로그램을 실제로구동하는 사용자에 대한 정보를 알려준다. 억세스가 허가되면 0을 리턴하고,  그렇지 않으면 -1을 리턴한다. (즉, 하나의 함수로 취급하여, access는 요청된 접근이 거부되면 참을 리턴한다. )  보통의 파일 이름 문법 에러에 더하여 ( 6.2.3절 [File Name Errors] 60 페이지 참조), 다음의 errno는 이 함수를 위해 정의된 에러 상황이다.

EACCES	how인수로 정해진 접근이 거부되었다.

ENOENT	파일이 존재하지 않는다.

EROFS	쓰기 허가가 오직 읽기 모드 파일 시스템상에 존재하는 파일에 요청되었다.

이들 매크로들은 access 함수에 how인수로서 사용하기 위한 것으로 헤더파일 'unistd.h'에 정의되어 있다.

매크로 :  int R__OK

읽기 허가를 위한 테스트를 의미하는 인수.

매크로 : int W__OK

쓰기 허가를 위한 테스트를 의미하는 인수

매크로 : int X__OK

실행/검색 허가를 위한 테스트를 의미하는 인수.

매크로 :  int F__OK

파일의 존재를 위한 테스트를 의미하는 함수.

9.8.9  파일에 관련된 시간들

각 파일은 검색 시간, 갱신 시간, 속성 갱신 시간에 해당하는 세가지 타임 스템프들을 가지고 있다.  이것은 stat의 st_atime, st_mtime, st_ctime 멤버에 해당한다;  9.8절 [File Attributes], 131 페이지 참조.   이 시간들 모두는 time_t objects 안에 표현된다.  이 데이터 타입은 'time.h'에 정의되어 있다.  시간 값들의 표현과 다루는 방벙에 대한 자세한 설명은 17.2절 [Calendar Time] 235 페이지에 있다.  현존하는 파일이 개방되있을 때, 그 속성변경시간과 갱신 시간필드들은 갱신된다.  파일읽기는 검색 시간 속성을 갱신시키고, 파일쓰기도 또한, 갱신시간 속성을 갱신시킨다. 파일이 만들어질 때, 파일을 위한 세가지 타임 스템프는 현재의 시간으로 설정된다.  디렉토리의 속성 변경 시간과 갱신 시간의 필드들은 새로운 엔트리가 들어오면 갱신된다.  link함수로 원래의 파일에 새로운 이름이 더해지면, 링크되어진 파일의 속성 변경 시간(attribute change time)이 갱신되고, link를 통해 만들어진 새로운 이름을 포함하는 디렉토리의 속성 변경 시간과 갱신 시간(modifi-cation) 필드들이 갱신된다.  이와 같은 필드들은 unlink, remove, rmdir로 파일 이름이 제거될 때 영향을 받는다.  rename로 파일을 재명명할 때, 파일이 포함된 부모 디렉토리들의 속성 변경 시간과 갱신시간 필드들 이 영향을 받지만, 파일에 관련된 시간속성들은 변경되지 않는다. 파일의 속성들의 변경은( 예를 들어, chmod를 통해서) 그 속성 변경시간 필드를 갱신한다. 당신은 속성 변경 시간을 제외하고, utime 함수를 사용해서 명시적으로 파일의 타임스템프를 변경할 수 있다.  이것은 헤더파일 'utime.h'에 정의 되어 있다.

데이터 타입 :  struct utimbuf

utimbuf 구조체는 어떤 파일에 새로운 검색 시간과 갱신시간을 정해주기 위해서 utime 함수에서 사용된다.  이것은 다음과 같은 멤버들을 포함한다.

time_t actime  이것은 파일의 검색시간이다.

time_t modtime 이것은 파일의 갱신시간이다.

함수 :  int utime (const char *filename, const struct utimbuf *times)

이 함수는 filename이름을 가진 파일과 연관된 시간을 변경하기 위해 사용된다.  만일 times가 널 포인터라면, 파일의 검색시간과 갱신시간들은 현재의 시간으로 설정된다.  그렇지않다면, 그들은 times가 가리키는 구조체 utimbuf의 actime와 modtime 멤버들로부터 얻은 값으로 시간을 설정한다.

파일의 속성변경시간은 두가지 경우 모두 현재의 시간으로 설정된다. ( 타임 스템프들이 변경은, 파일 속성의 변경을 수반한다.)utime 함수는 성공하면 0을 리턴하고, 실패하면 -1을 리턴한다.  보통의 파일 이름 문법 에러에 더하여( 6.2.3절 [File Name Errors] 60 페이지 ]참조), 다음의 errno는 이 함수를 위해서 정의된 에러 상황들이다.

EACCES	times 인수로 널 포인터를 주었을 때 허가문제가 있다.  파일의 타임 스템프를 갱신하기 위해서는 파일의 소유자이고 파일에 쓰기 허가를 가지고 있거나, 특권이 부여된 사용자이어야 한다.

ENOENT	파일이 존재하지 않는다.

EPERM	만일 times인수가 널 포인터라면, 당신은 파일의 소유자이거나 특권이 부여된 사용자이어야만 한다.  이 에러는 그 문제를 보고하는데 사용된다.

EROFS	파일이 오직 읽기 모드 파일 시스템에 존재한다.

세가지 타임 스템프들은 연관된 마이크로초(microsecond) 부분을 가지고 있다.  이들 필드들은 st_atime_usec, st_mtime_usec, st_ctime_usec라고 불린다;  각각은 마이크로초 단위로 표현된 0에서 999,999사이의 값을 가지고 있다.  그들은 timeval 구조체의 tv_usec에 해당한다; 17.2.2절[High-Resolution Calendar] 235 페이지 참조. utimes 함수는 utime와 같지만, 파일에 관련된 시간들을 아주 세밀하게 정하는 것을 허용한다.  이 함수를 위한 프로토타입은 헤더파일 'sys/time.h이다.

함수 :  int utimes  (const char *filename, struct timeval tvp[2])

이 함수는 filename의 이름을 가진 파일의 검색 시간과 갱신시간을 설정하는 함수이다.  새로운 파일 검색 시간은 tvp[0]에 의해 정해지고, 새로운 갱신시간은 tvp[1]에 의해 설정된다.  이 함수는 BSD에서 왔다.  리턴값과 error 에러 상황은 utime함수와 같다.

9.9  특수 파일 만들기

mknod 함수는 디바이스와 같은, 특수한 파일을 만들기 위한 함수다. GNU 라이브러리는 BSD와의 호환성을 위해서 이 함수를 갖고 있다. mknod의 프로토타입은 'sys/stat.h'에 선언되어 있다.

함수 :  int mknod  (const char *filename, int mode, int dev)

mknod 함수는 filename의 이름을 가진 특수한 파일을 만든다.  mode인수는 S_IFCHR(문자 특수 파일을 위해)나, S_IFBLK(블럭 특수 파일을 위해)와 같은 다양한 특수 파일 비트들을 가지고 파일의 모드를 지정한다.9.8.3절 [Testing Fiel Type] 134 페이지 참조.  dev 인수는 디바이스를 정한다.  그것의 정확한 해석은 만들어진 특수 파일의 종류에 의존한다. 리턴값은 성공하면 0이고, 실패하면 -1이다.  보통의 파일 이름 문법 에러에 더하여 ( 6.2.3절 [File Name Errors] 60 페이지 참조), 다음의 errno는 이 함수를 위해 정의된 에러 상황이다.

EPERM	호출한 프로세스가 특권이 없다.  특수 파일을 만들 수 있는 것은 슈퍼유저뿐이다.

ENOSPC	새로운 파일을 갖고 있는 디렉토리나 파일 시스템이 가득찼고, 더 이상 확장할 수 없다.

EROFS	새로운 파일을 갖고 있는 디렉토리가 오직 읽기 모드 파일 시스템상에 있기 때문에 갱신될 수 없다.

EEXIST	filename의 이름을 가진 파일이 이미 존재한다. 만일 당신이 이 파일을 대체하기 원한다면, 당신은먼저 그 원래의 파일을 제거해야만 한다.

9.10  임시 파일들

만일 당신의 프로그램에서 임시파일의 사용이 필요하다면, 당신은 그것을 개방하기 위해 tmpfile함수를 사용할 수 있다.  또는 임시파일의 이름을 만들기 위해 tmpnam  함수를 사용하고 fopen을 사용해서 보통의 방법으로그 임시파일을 개방할 수 있다.

tempnam함수는 tmpnam함수와 같지만, 당신이 그 임시파일이 들어갈 디렉토리가 무엇인지, 그리고 그들의 파일이름이 무엇인지에 대한 것을 정하도록 허용한다.

함수 :  FILE * tmpfiel  (void)

이 함수는 "wb+" 모드로 fopen을 호출함으로써, 갱신한 모드로 임시 바이너리 파일을 만든다.  그 파일은 그 프로그램이 종료할때나 닫힐 때, 자동적으로 제거된다. ( 어떤 다른 ANSI C 시스템에서 그 프로그램이 비정상적으로 종료되면 파일의 제거도 실패할 것이다.)

함수 :  char * tmpnam  (char *result)

이 함수는 현재 존재하고 있는 파일 이름이 아닌 유용한 파일이름을 만들어서 리턴한다.  만일 result인수가 널 포인터라면, 리턴값은 다음 호출로 갱신된 내부 정적 스트림의 포인터이다.  그렇지않다면, result인수는 적어도 L_tmpnam의 배열을 가리키는 포인터이고 result는 그 배열안에 저장된다.  tmpnam의 호출에 너무 많은 시간이 소요되면 실패의 가능성이 있다. 이것은 임시파일을 정해진 개수의 파일이 있어야 하는곳에서 이미 다른 파일들로 그 개수가 체워진곳에 만들려했기 때문이다.  tmpnam이 실패하면 널 포인터를 리턴한다.

매크로 :  int L__tmpnam

이 매크로 값은 tmpnam함수로 만들어진 파일이름을 저장하기에 충분한 문자열의 최소 할당 크기를 나타내기 위한 정수 상수 표현이다.

매크로 :  int TMP__MAX

매크로 TMP_MAX는 당신이 tmpnam으로 만들 수 있는 임시 파일 이름의 최소 경계이다.  당신이 너무 많은 임시 파일 이름을 만들어 실패를 경험하기 전에, 이 매크로 값에 의존할 수 있다. GNU 라이브러리에서는, 당신은 당신의 디스크의 공간이 허락하는 만큼의 많은 개수의 임시파일을 만들 수 있다.  어떤 다른 시스템에서는 임시파일의 개수를 작게 제한하고 있다.  그 제한은 25보다는 결코 작지않다.

함수 :  char * tempnam  (const char *dir, const char *prefix)

이 함수는 단일한 임시 파일이름을 생성한다.  만일 prefix가 널 포인터가 아니고 이 문자열이 5개 문자 이상이면 그 prefix가 파일이름으로 사용된다. 임시파일 이름을 위한 디렉토리는 밑의것들을 시험함으로 인해 결정한다. 디렉토리는 반드시 존재하고 기록가능해야 한다.

□ 환경 변수 TMPDIR이 정의되었는지...

□ dir 인수가 널 포인터인지...

□ P_tmpdir 매크로의 값.

□ '/tmp' 디렉토리.

이 함수는 SVID와의 호환성을 위해 정의되었다.

SVID 매크로 :  char * P__tmpdir

이 매크로는 임시파일을 위한 디폴트(default) 디렉토리의 이름이다.  오래된 유닉스 시스템들은 설명된 함수들을 가지고 있지 않다.  대신에 그들은 mktemp 와 mkstemp 를 사용한다.  이들 함수들은 당신이 준파일 이름 템플리트 문자열을 갱신하는 작업을 한다.  문자열의 마지막 6섯개 문자들은 만드시 'XXXXXX'이 되어야만한다.  이들 6개 'X' 문자들은 단일한 파일 이름 문자열을 만든 6개의 문자들로 대체된다.  문자열은 보통 '/tmp/prefixXXXXXX'처럼 사용하고 각각의 프로그래은 단일한 prefix를 사용한다.

노트 : mktemp 와 mkstemp가 템플리트 문자열을 갱신하기 때문에, 당신은 그들에게 문자열 상수를 주지않아야 한다.  문자열 상수들은 보통 오직 읽기만 가능한 기억장소에 존재하고, 그래서 당신의 프로그램은 mktemp 나 mkstemp가 문자열을 갱신하려 시도할 때 파괴되고 만다.

함수 :  char * mktemp  (char *template)

mktemp함수는 위에 설명된 것처럼 임시 문자열을 고쳐서 단일한 파일이름을 생성한다.  만일 성공하면, 갱신된 템플리트를 리턴한다.  만일 mktemp가 단일한 파일 이름을 발견할 수 없다면, mktemp는 빈 문자열을 만들고 그것을 리턴한다.  만일 그 템플리트에 'XXXXXX'로 끝나지 않는다면, mktemp는 널 포인터를 리턴한다.

함수 :  int mkstemp  (char *template)

mkstemp함수는 mktemp가 하는것처럼 단일한 파일 이름을 생성하지만, mkstemp는 또한 open의 과정을 통해서 당신을 위해 그 파일을 개방한다 ( 8.1절 [Opening and Closing Files] 103 페이지 참조).  만일 성공하면 적당한 템플리트를 갱신하고 그 파일을 읽기와 쓰기를 위해 개방해서 리턴한다.  만일 mkstemp가 단일한 이름을 가진 파일을 만들 수 없다면 그것은 빈 문자열의 템플리트를 만들고 -1을 리턴한다. 만일 템플리트가 'XXXXXX'로 끝나지 않는다면, mkstemp는 -1을 리턴하고 템플리트를 갱신하지 않는다.

mktemp와 달리, mkstemp는 임시 파일을 만들려 시도하고 있는 다른 프로그램과 충돌의 가능성이 없이 단일한 파일을 만드는 것을 보증한다. 그 이유는 open함수에 O_EXEL 플래그 비트를 사용함으로 인해서, 당신이 항상 새로운 파일을 만들기를 원함을 알리고, 그 파일이 이미 존재한다면 에러를 나타내도록 작업하기 때문이다.

10  파이프 와 FIFO

10장 파이프와 FIFO

파이프는 프로세스사이의 통신을 위한 메카니즘이다; 한 프로세스에 의해 파이프에 쓰여진 것은 다른 프로세스에 의해 읽혀질 수 있다.  데이터는 선입선출(FIFO) 순서로 다뤄진다.  파이프는 이름을 가지고 있지 않다.

그것이 한 번 사용하기 위해 만들어지고 양끝은 그 파이프를 만든 단일 프로세스로부터 상속되어져야만 한다.  특별한 파일인 FIFO는 파이프와 유사하지만, 파이프가 이름이 없고 임시적인 연결인것에 비해, FIFO는 이름을 갖고 있다.  프로세스는 그것을 통해 통신의 목적으로, 이름을 가진 FIFO를 개방한다. 파이프나 FIFO는 동시에 양끝을 개방해야한다.  만일 당신이 파이프나 FIFO에 쓰기위한 프로세스를 가지지않은 파이프나 FIFO로부터 읽을려면, 읽기는 파일의 끝을 리턴한다.  읽기 프로세스를 가지지않은 파이프나 FIFO에 쓰기는 에러가 발생된 것으로 취급된다.  그것은 SIGPIPE 신호로 일반화되고, 만일 그 신호가 블록되어졌다면 에러코드 EPIPE로 에러임을나타낸다.

특별한 파일인 파이프나 FIFO도 파일 위치시키기(positioning)을 허용한다. 읽기와 쓰기 동작은 순차적으로 일어난다; 파일의 앞에서부터 읽어서 그 끝에 출력한다.

10.1  파이프 만들기

파이프를 만드는 함수로는 pipe함수가 있다.  이것은 파이프의 양끝을 읽기와 쓰기로 만든다.  이것은 단일한 프로세스에 사용하기에 유용하지 않고, 특별히 한 프로세스가 하나 혹은 더 많은 자식 프로세스들을( 23.4절 [Creating a Process] 326 페이지 참조) 생성하기 전에 단지 파이프를 만든다.  그리고 나서 그 파이프는 부모 혹은 자식 프로세스들, 또는 두 개의 형제 프로세스 사이의 통신을 위해서 사용되어진다. 파이프 함수는 헤더파일 'unistd.h'에 선언되어 있다.

함수 :  int pipe  (int filedes[2])

pipe 함수는 파이프를 만들고 파이프의 끝에서(각각) 읽기와 쓰기를 위한 파일 기술자인 filedes[0] 과 filedes[1]을 저장한다.  파일기술자 0는 표준 입력이고, 파일 기술자 1은 표준 출력이다.  만일 성공하면, pipe는 0을 리턴하고, 실패하면 -1을 리턴한다.  다음의 errno는 이 함수를 위해 정의된 에러 상황이다.

EMFILE	그 프로세스가 개방된 너무 많은 파일을 갖고 있다.

ENFILE	전제 시스템안에 개방된 파일이 너무 많다. ENFILE에 대한 상세한 것은 2.2절 [Error Codes]12페이지를 참조하라.  이곳의 예는 파이프를 만드는 간단한 예이다.  이 프로그램은 자식 프로세스를 만들기 위해서 fork함수( 23.4절 [Creating a Process], 326페이지 참조)를 사용한다.  부모 프로세스는 파이프에 데이터를 쓰고 자식 프로세스에 의해 그 데이터가 읽혀진다.

#include  <sys/types.h>

#include  <unistd.h>

#include  <stdio.h>

#include  <stdlib.h>

/*  파이프로부터 문자들을 읽고 그들은 표준 출력에 출력하라 */

void

read_from_pipe  (int  file)

{

FILE  *stream;

int  c;

stream  =  fdopen  (file,  "r");

while  ((c  =  fgetc  (stream))  !=  EOF)

putchar  (c);

fclose  (stream);

}

/*  파이프에 어떤 텍스트를 써라   */

void

write_to_pipe  (int  file)

{

FILE  *stream;

stream  =  fdopen  (file,  "w");

fprintf  (stream,  "hello,  world!\n");

fprintf  (stream,  "goodbye,  world!\n");

fclose  (stream);

}

int

main  (void)

{

pid_t  pid;

int  mypipe[2];

/*  파이프를 만들어라 */

if  (pipe  (mypipe))

{

fprintf  (stderr,  "Pipe  failed.\n");

return  EXIT_FAILURE;

}

/* 자식 프로세스를 만들어라  */

pid  =  fork  ();

if  (pid  ==  (pid_t)  0)

{

/* 이것은 자식 프로세스이다.  */

read_from_pipe  (mypipe[0]);

return  EXIT_SUCCESS;

}

else  if  (pid  <  (pid_t)  0)

{

/*  fork가 실패했다.  */

fprintf  (stderr,  "Fork  failed.\n");

return  EXIT_FAILURE;

}

else

{

/*  이것은 부모 프로세스이다.  */

write_to_pipe  (mypipe[1]);

return  EXIT_SUCCESS;

}

}

10.2  부프로세스를 위한 파이프

파이프는 부프로세스로 실행되어지고 있는 한 프로그렘으로부터 데이터를 받거나 혹은 데이터를 보내는데 일반적으로 사용된다.  이것들은 pipe( 파이프를 만드는 ), fork(부프로세스를 만들기 위한 ), dup2( 표준 입력 또는 출력 채널로 파이프를 사용하기 위한 부프로세스를 위해 ), 그리고 exec( 새로운 프로그램은 실행하기 위한)을 복합적으로 사용한다. 또는 popen과 pclose를 사용할 수 있다.

popen과 pclose를 사용함으로 인해서 얻는 이득은 사용하기 쉽고 좀더 간단한 인터페이스를 갖는다는 것이다.  그러나 직접적으로 저수준 함수를 사용함으로 인해서 프로그램의 유연성은 얻을수가 없다.

함수 :  FILE * popen  (const char *command, const char *mode)

popen 함수는 system함수와 매우 가까운 연관을 갖고 있다;  23.1절 [Running a Command], 325 페이지 참조.  그것은 부프로세스로써 command 쉘 명령을 실행한다.  그렇지만 그 명령이 수행되기를 기다리는 대신에, 그것은 부프로세스에 파이프를 만들고 그 파이프에 해당하는 스트림을 리턴한다.

만일 mode 인수가 "r"로 정해진다면, 당신은 부프로세스의 표준 출력 채널로부터 데이터를 받아들인 스트림으로부터 데이터를 읽을 수 있다. 부프로세스는 부모 프로세스로부터 표준 입력 채널을 상속받는다. 유사하게, 만일 당신이 mode 인수를 "w"로 정한다면, 당신은 부프로세스의 표준 입력 채널로 데이터를 보내기위한 스트림에 데이터를 쓸수 있다. 부프로세스는 부모 프로세스로부터 표준 출력 채널을 상속받는다. 에러가 발생하면, popen는 널 포인터를 리턴한다.  이것은 파이프나 스트림이 만들어지지 않았거나, 부프로세스가 생성되지 않았거나, 혹은 프로그램이 실행되지 않았거나 할 때 발생할 것이다.

함수 :  int pclose  (FILE *stream)

pclose 함수는 popen으로 만들어진 스트림을 닫을 때 사용한다.  그것은 시스템 함수로써, 자식 프로세스가 종료되고 그 상황값을 리턴하기를 기다린다.

여기에 다른 프로그램을 통한 출력 필터로 popen과 pclose를 어떻게 사용하는지 보여주는 예가 있다.

#include  <stdio.h>

#include  <stdlib.h>

void

write_data  (FILE  *  stream)

{

int  i;

for  (i  =  0;  i  <  100;  i++)

fprintf  (stream,  "%d\n",  i);

if  (ferror  (stream))

{

fprintf  (stderr,  "Output  to  stream  failed.\n");

exit  (EXIT_FAILURE);

}

}

int

main  (void)

{

FILE  *output;

output  =  popen  ("more",  "w");

if  (!output)

{

fprintf  (stderr,  "Could  not  run  more.\n");

return  EXIT_FAILURE;

}

write_data  (output);

pclose  (output);

return  EXIT_SUCCESS;

}

10.3  FIFO 특별한 파일들

특별한 파일인 FIFO는 다른 방법으로 만들어진다는 점을 제외하고는 파이프와 유사하다.  파이프가 작자 불명의 통신 채널인점에 비해 FIFO 특별화일은 mkfifo를 호출함으로써 파일 시스템에 삽입된다.  일단 당신이 이 방법으로 FIFO 특별화일을 만들면, 어느 프로세스도 일반적 파일을 다루는것과 같은 방법으로 읽기나 쓰기를 위해 그것을 개방 할 수 있다.  그렇지만, 그것에 입력이나 출력 명령을 행하기 전에 양끝이 동시에 개방되도록 해야한다.  보통의 블록을 읽기 위해서 FIFO를 개방한 동안 어떤 다른 프로세스는 출력을 위해서 같은 FIFO를 개방한다. mkfifo 함수는 헤더파일 'sys/stat.h'에 선언되어 있다.

함수 :  int mkfifo  (const char *filename, mode_t mode)

mkfifo 함수는 filename의 이름을 가진 FIFO특별화일을 만든다.  mode 인수는 파일의 허가를 설정하기 위해 사용되어진다;  9.8.7절 [Setting Permissions], 137 페이지 참조.  일반적으로, 성공하면 mkfifo는 0을 리턴한다.  에러가 발생한 경우에는 -1을 리턴한다.   보통의 파일 이름 문법에러에 더하여 ( 6.2.3절 [File Name Errors] , 60 페이지 참조), 다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EEXIST	그 이름을 가진 파일이 이미 존재한다.

ENOSPC	디렉토리나 파일 시스템이 확장되어진 수 없다.

EROFS	그 파일이 들어갈 디렉토리가 오직 읽기 모드 파일 시스템에 존재한다.

10.4  파이프 입/출력의 원소수

읽거나 쓰여진 파이프의 데이터에서 만일 그 데이터의 크기가 PIPE_BUF 보다 작다면 원소이다. 이것은 시스템안의 어떤것도 부분적으로 수행된 상황을 말해줄 수 없는, 즉각적으로 만들어진 단위의 데이터를 참조함을 의미한다. 원소단위 입/출력은 바르게 시작되지 않았을 것이다. (그것은 데이터나 버퍼 공간을 확보하기 위해서 기다릴 필요가 있었지만, 일단 시작되었고, 그것은 즉시 끝난다.) 데이터의 많은 양을 읽거나 쓰는 것은 원소단위가 되어지지 않을 것이다. 예를 들어, 다른 프로세스가 점유하고 있는 기술자로부터의 출력 데이터는 흩어지게 될 것이다. 27.6절 [Limits for Files], 374 페이지를 참조해서 PIPE_BUF 파라미터에 대한 자세한 정보를 얻어라.

11  소켓

11장 소켓

이 장에서는 소켓을  사용하여 프로세스간의 통신에  관한 GNU장치에 대해 서술한다.

소켓은 일반적인 프로세스간의 통신 채널이다. 파이프처럼 소켓은 파일 서술자처럼 표현된다. 그러나 파이프와는 달리 소켓은 관련 없는 프로세스 간의 통신,네트워크를 통하여 서로 다른 컴퓨터에서 수행되고 있는 프로세스간의 통신을 제공한다. 소켓은 주로 다른 컴퓨터간의 통신의 수단이다. telnet, rlogin, ftp, talk, 그리고 네트워크 프로그램들은 소켓을 사용한다.  모든 운영체제가  소켓을 지원하는  것은 아니다.  GNU라이브러리에서 'sys/socket.h' 은 운영체제와  상관없이 존재한다.  그러나 만약 시스템이 소켓을 지원하지 않는다면, 소켓함수는 항상 실패한다.

미완성:우리는 현재 인터넷 인터페이스 설정이나 브로드캐스트 메시지기능에 관하여는 문서화하지 못했다.

11.1  소켓 개념

당신이 소켓을 만들때, 반드시 당신이 어떤 통신 스타일을 쓸 것인지, 어떤 프로토콜을 사용할 것인지 명시해야 한다.  소켓의 통신 스타일은 소켓으로 데이터를 보내고 받는 유저레벨 구문을  정의한다. 통신 스타일을 선택하는 것은 다음과 같은 질문의 응답이 된다.

□  데이터 전송의 단위는 무엇인가?

어떤 통신 스타일은 큰 구조체를 가지지 않는, 바이트의 열로 된다. 다른 것들은 (패킷같은) 레코드  안에 바이트를 묶어 놓는다.

□  보통 작업을 하는 동안에 데이터를 잃어 버릴수 있나?

어떤 통신 스타 일에서 보내진 데이터가 (시스템이나 네트워크  충돌을 제외하고) 보내진 순서대로 모두 도달해야 한다. ; 다른 스타일은 때로는 보통  동작시에도 데이터를 잃어 버리거나, 하나 이상의 같은 패킷이 전달될수도 있고, 순서가 뒤바뀔수도 있다.

신뢰있는 통신스타일을 사용하는 프로그램을 설계하는 것은 보통  데이터 손실이나, 순서가 뒤바뀌는 것을 검출하고  필요에 따라 재전송하는 대책을 포함한다.

□  상대측과 완전한 통신을 하느냐?

어떤 통신스타일은 전화 거는 것와 같이 상대측과 원격 소켓 연결을  만들고나서, 자유롭게 데이터를 교환한다.; 다른 스타일은 편지 쓰는 것처럼 당신이 보내고자하는  메시지의 주소를 써서 보낸다.

당신은 또한 소켓의 이름공간에서 이름을 선택해야 한다. 소켓이름("주소")는 특정한 이름공간의 의미심장한 내용이 있다. 이름공간은 또한 "도메인"이라고 불린다. 그러나, 우리는 같은 용어의 다른 사용과  혼돈할 수 있어 그 말을 피하겠다. 각 이름공간은  'PF_'로  시작하는 상징적인 이름을 가진다. 'AF_'로 시작하는 유사한  상직적 이름은 그 이름공간에 주소  포맷을 가리킨다.

마지막으로, 당신은 통신을 제공하는 프로토콜을 선택해야 한다.   프로토콜은 데이터를 주고 받는데 사용되는 저수준 메카니즘을 말한다.  각 프로토콜은 특정한 이름공간과  통신스타일에 대하여 유효하다.;  이름공간은 종종 프로토콜 패밀리라고 불리운다. 왜냐하면 이름  공간이 'PF_'로 시작하기 때문이다.

프로토콜의 규칙은 비록 다른 컴퓨터에서 일지라도 두 프로그램간의 데이터 전송을 제공한다. 이러한 규칙의 대부분은 운영체제에 의하여 다루어지고, 당신이 그것에 대해 알 필요는 없다. 당신이 프로토콜에 대하여 알아야 할 것은 이것이다.:

□ 두 소켓간의 통신을 위하여, 그것들은  반드시 같은 프로토콜을 나타내야 한다.

□ 각 프로토콜은 특정한 스타일과 이름공간 결합에 의미를 가지며, 적당하지 않은 결합의 사용은 할 수 없다. 예를 들어 TCP 프로토콜은 바이트 스트림 스타일의 통신과 인터넷 이름공간에 맞추어져 있다.

□ 스타일과 이름공간의 각 조합에서 프로토콜 번호를 0으로 나타내어 요구하면 그것은 기본프로토콜(디폴트 프로토콜)로  된다. 그리고, 당신은 보통 하는 것(디폴트를 사용하는 것)이 이런 것이다.

11.2  통신스타일

GNU라이브러리는 각기 다른 특성을 지닌 몇  종류의 소켓을 지원하고 있다. 이 절에서는 지원되는 소켓 형태를 서술한다. 여기 나타난 심볼릭 상수는 'sys/socket.h'에 정의되어 있다.

int SOCK_STREAM                          Macro

SOCK_STREM스타일은 파이프와 같다. (10장[파이프와  FIFO]을 보라, 145페이지); 그것은 특정한 원격 소켓과의 연결후에 동작한다. 그리고 바이트의 스트림처럼 신뢰성 있는 데이터 전송을 한다. 이 스타일의 사용은 11.8절[연결 166페이지]에 자세히 나와 있다.

int SOCK__DGRAM                          Macro

SOCK_DGRAM 스타일은 개별적으로 주소가 쓰여진  패킷을 전달하는데 사용된다. 그것은 SOCK_STREAM과 정반대이다. 당신이 이 종류의 소켓에 데이터를 써넣는다. 그러면 그 데이터는 하나의 패킷이 된다. SOCK_DGRAM 소켓은 연결을 가지지 않는다. 당신은 각 패킷에 반드시 반복적으로 주소를 써넣어야 한다. 데이터를 전송하고자 하는  당신의 요구에 관하여  시스템의 책임은, 각 패킷이 가장 잘 배달될 수 있도록 시도하는 것이다. 그것은 네 번째와 다섯 번째 실패 후에 여섯  번째에 성공할 수 있다.; 일곱  번째 패킷은 여섯 번째 전에 도착될 수도 있다. 그리고 여섯 번째 후에 두 번 도착할 수도 있다. SOCK_DGRAM은 일반적으로 응답해야할  시간내에 응답이  없을때  간단히 패킷을 재전송하는데 적합하다. 데이터그램 소켓을 사용하는 방법에 대한 자세한 정보는 11.9절[데이터그램],176쪽을 보라

int SOCK__RAW                             Macro

이 스타일은 저수준 네트워크 프로토콜과 인터페이스를 억세스하도록 해 준다. 보통 사용자  프로그램은 이 스타일을  자주 사용할 필요가 없다.

11.3  소켓 주소

소켓의 이름은  보통 "주소"라고   불리운다. 소켓주소와 관계된   기호(symbol)와 함수들은, 때때로 "이름"이라는 말이나 "주소"를 사용되어,  어떤 규칙 없이 이름  지어진다. 당신은 소켓이 고려되는  곳에 이러한 말을 동일한 것처럼 생각할 수 있다.  socket함수로 새롭게 만든 소켓은 주소를 가지지 않는다.  당신이 그것에게 주소를 주어야만, 다른 프로세스가 통신을  위한 주소를 찾을수 있게 된다. 이것을 주소를 소켓에 묶는다고 하고, bind함수를 이용한다.  만약 다른 프로세스들이 주소을 찾고,  주소를 이용하여 통신을 시작하는 것이라면, 당신은 소켓의 주소를  생각해 볼 필요가 한다. 당신은  다른 소켓의 주소를 규명할 수 있지만, 그것은 보통 무의미하다; 처음, 소켓으로부터 데이터를 보내거나 연결을 초기화하기 위해  그것을 사용할 때, 만약 구체적으로 나타내진 것이 없다면, 시스템은 주소를 자동으로 설정한다.

서버는 주소에 기초하여  판변되기 때문에, 보통  클라이언트는 주소를 명시해야 한다.; 예를 들어, rsh 와 rlogin 프로토콜은 클라이언트의 소켓주소를 살피고, 만약 그것이 IPPORT_RESER

VED보다 작지  않다면, 암호검사를 거쳐야 한다. (11.5.3절의 [포트],159쪽을 보라)

소켓주소의 자세한 것은 당신이 사용하는 어떤 이름공간에 의존하여 변한다. 자세한 정보를  위해서는 11.4절 [파일  이름공간] (153쪽)을 보거나, 11.5절 [인터넷 이름공간] (155쪽)을 보라. 이름공간에 상관없이 , 소켓의 주소를  세트하고 사용하기 위해 당신은 같은 bind와 getsockname함수를 사용한다. 이러한 함수들은 주소를 받아들이기 위해 struct sockaddr *라는 위조 자료형(phony data type)을 사용한다. 실제적으로, 주소는 당신이 사용하는 주소포맷과는 다른 자료형의 구조로 되어 있다. 그러나, 당신은 그것을 bind하기 위해 넘겨줄 때, 그것의 주소를 struct sockaddr *로 자료형변환(cast)을 해주어야 한다.

11.3.1  주소 포맷

bind와 getsockname함수는  소켓의 주소의  포인터를 의미하는  struct sockaddr *이라는 일반적인 자료형을 사용한다. 당신은 주소를 번역하거나 만들기 위해 이 자료형을 효율적으로 사용할 수 없다; 대신, 소켓의 이름공간을 위한 자료형을 사용해야만 한다. 그러므로, 보통 실제적인 사용은 적합한  이름공간의 형태의 주소를 만드는데 사용하고나서, bind나 getsockname을 호출할 때 struct sockaddr *로 포인터형태를 변환한다. 당신이 struct sockaddr 자료형으로부터 얻을 수 있는  정보 중 하나는, 주소를 완전히 이해하기  위해 사용되는 자료형이  어떤 것인지  알려주는 "주소 포맷" 지정자이다. 이 절에서의 기호는 'sys/socket.h'라는 헤더파일안에 정의되어 있다.

struct sockaddr                               자료형

struct sockaddr형 자체는 다음 멤버를 가지고 있다:

short int sa_family

이것은 이 주소의 주소 포맷용 코드이다. 그것은 다음에 나오는 데이터의 포맷을 지시한다.

char sa_data[14]

이것은 실제적인 소켓주소 데이터이다. 이것은 포맷에 의존한다. 그것의 길이는 또한 포맷에 의존하고  아마 14보다 클 것이다. sa_data의 14라는 길이는 원래 변할 수 있다. 각 주소포맷은 AF_로 시작하는 기호이름으로 시작된다. 그것들은 각각 이름공간에 대응되는 'PF_' 기호에 맞춰져 있다.

AF_FILE

이것은  파일 이름공간에  맞는 주소포맷이다.  (PF_FILE은 이 이름공간의 이름이다.) 이 주소 포맷의 정보를 보려면 11.4.2절 [파일 이름공간 구체적 내용], 153쪽을 보라.

AF_UNIX

이것은 호환성을 위한 AF_FILE과 같은말이다. (PF_UNIX는 PF_FILE과 역시 동의어이다.)

AF_INET

이것은 인터넷 이름공간에 맞는 주소포맷이다. (PF_INET은 이 이름공간의 이름이다.) 11.5.1절 [인터넷 주소 포맷], 155쪽을 보아라.

AF_UNSPEC

이것은 특정한 주소포맷을 지정하지 않는다.  그것은 "연결된" 데이터그램 소켓의 기본(default)목적지 주소를 지우는 것 같은 드문 경우에만 사용된다. 11.9.1절[데이터그램 보내기],176쪽을 보아라. 여기에 맞는 이름 공간지정자 기호도 완전성을 위해 존재한다.  그러나, 그것을 프로그램에서 사용할 필요는 없다.'sys/socket.h' 에서 대부분 실제로 구현되지 않은  많은 다른 종류의 네트워크에 'AF_'로 시작하는 기호를 정의하고 있다. 어떻게  그것을 사용하는 것에 관한 정보를 우리가 받으면, 우리는 그것을 정말로 문서화할 것이다.

11.3.2  소켓의 주소를 세팅하기

소켓에 주소를 할당하기 위하여 bind함수를 사용하라. bind의 프로토타입은 'sys/socket.h'헤더파일에 있다. 사용예는  11.4절[파일 이름공간], 153쪽, 또는 11.5.7절[Inet 예제], 163쪽을 보라.

int bind  (int socket, struct sockaddr *addr, size_t length)     Function

bind함수는 "socket"소켓에 주소를 지정한다. addr과 length인수는 주소를 구체화한다. 자세한 주소포맷은 이름공간에 의존한다. 주소의 첫 번째 부분은 항상 포맷지정자이다.  포맷지정자는 이름공간을 명시하고, 그 주소가 그 이름공간의 포맷의 주소임을 말한다. 성공하면 0을 리턴하고, 실패하면 -1을 리턴한다. 다음은 이 함수에 정의되어 있는 errno 에러조건이다.

EBADF	소켓 인수가 유효하지 않은 파일 지정자이다.

ENOTSOCK	서술자 socket이 소켓이 아니다.

EADDRNOTAVAIL   지시된 주소가 이 장치에서는 유효하지 않다.

EADDRINUSE	또 다른 소켓이 이 주소로 이미 사용되고 있다.

EINVAL	socket소켓이 이미 주소를 가지고 있다.

EACCESS	당신은 요구된 주소를 제어할 허가권을 가지고 있지 않습니다. (인터넷 도메인에서, 단지 슈퍼유저만이 0에서 IPPORT_RESERVED-1까지 범위의 포트넘버를 사용할 수 있다. 11.5.3절[ports] (159쪽)을 보라. )

추가적인 조건은 소켓의 각각 이름공간에 의존한다.

11.3.3  소켓의 주소 읽기

getsockname함수를 사용하여, 인터넷  소켓의 주소를 확인할  수 있다. 이 함수의 프로토타입은 'sys/socket.h'라는 헤더파일에 있다.

int getsockname  (int socket, struct sockaddr *addr, size_t *length_ptr)  함수

getsockname함수는 addr과 length_ptr 인수로 명시되는  곳의 socket이라는 소켓의 주소에 관한 정보를 리턴한다.  length_ptr은 포인터임을 주목하라; 당신이 addr의 크기 할당을 초기화할 것이다.  당신은 addr의 할당크기를 초기화 할 것이고, 주소데이터의 실제크기를  포함한 것이 리턴될 것이다. 주소데이터의 포맷은 소켓 이름공간에 의존한다. 정보의 길이는 주어진 이름공간에 보통 맞추어진다. 그래서,  보통 당신은 필요한 공간이  정확히 얼마인지 알 수 있고, 그것만큼  제공할 수 있다. 실제적인 사용은 소켓의 이름공간에 적합한 자료형을 사용하는 값으로 공간을 할당하고 나서, 그것을 getsockname으로 넘기기 위해 그  주소를 struct sockaddr*로 형변환(cast)한다. 리턴값 0은 성공이고, -1은 에러이다. 다음 에러조건은 이 함수에서  정의된 것이다.

EBADF	소켓인수가 유효한 파일 서술자가 아니다.

ENOTSOCK	서술자 socket이 소켓이 아니다.

ENOBUFS	동작을 수행할만한 충분한 버퍼가 없다. 당신은 이름공간의 소켓의 주소를 읽을 수 없다. 이것은 시스템의 나머지 사항에 일치한다. 일반적으로,  그 파일의 서술자로부터 파일의  이름을 얻어낼 방법은 없다.

11.4  The File Namespace

이 부분은 상징적인 이름(소켓을 만들 때 요구하는) 은 PF_FILE이라는 파일 이름공간에 대해 상세히 설명한다.

11.4.1  파일 이름공간 개념들

파일이름공간에서, 소켓 주소는 파일 이름들이다. 당신은 소켓의 주소처럼 당신이 원하는 어떤 파일 이름을 명시할  수 있다. 그러나 당신은 반드시 그것을 포함하는 디렉토리에 쓰기 허가를  가지고 있어야 한다. 소켓에 연결하기 위해, 당신은 그것에 대한 읽기 허가도 가지고 있어야 한다. 보통 /tmp디렉토리 안에 그러한 파일들을 넣는다.

파일이름공간의 특별한 점 하나는 연결을 할 때 그 이름이 한  번만 사용된다는 것이다. 즉, 그것을 한 번 사용하고 나면, 그 주소는 의미가 없고, 존재하지 않을지도 모른다.

또 다른 특징은 만약 소켓의 이름을 포함하는 파일시스템을  다른 기기가 공유하지 않는다면 다른 기기의 어떤 소켓으로 연결할 수  없다는 것이다. 당신은 디렉토리 리스트 안에서 소켓을 볼 수 있으나, 그것에 연결하는 것은 불가능 하다. 어떤  프로그램들은 클라이언트가 자기  프로세스 ID를 보내도록 요구하는 것과 프로세스ID를 사용하여 클라이언트 간을 식별하는 것을 이용하는 것 등을 이용한다. 그러나, 우리는 당신이  설계하는 프로토콜들 안에서 이 방법을 사용하지 말기를  원한다. 우리는 같은 파일시스템을 사용하는 다른 기기로 부터의 연결들을 허락해야 할지도 모르기 때문이다. 대신에, 만약 그것을 가지는 것을 원한다면, 각 클라이언트에게 식별번호를 보낸다.

파일 이름공간안의 소켓을 닫은 후에, 당신은 파일시스템으로부터 파일이름을 지워야 한다. unlink나 remove를 사용하여  이것을 하라. 9.5절 [파일 삭제하기], 129쪽을 보라.

파일 이름공간은 단지 어떤 통신 스타일에 대해 하나의  프로토콜만 지원한다. 그것은 프로토콜 번호 0이다.

11.4.2  파일이름 공간의 자세한 것

파일 이름 공간에 소켓을 만들려면,  socket또는 sockerpair의 이름공간 인수에 상수 PF_FILE을  사용하라. 이 상수는  'sys/socket.h'에 정의되어 있다.

int PF__FILE                                        Macro

이것은 소켓주소가 파일이름인 파일이름 공간과 그것이 연합된 프로토콜 패밀리를 가리킨다.

int PF__UNIX                                        Macro

이것은 호환성을 위한 PF_FILE의 동의어이다.

파일이름공간의 소켓이름을 명시하는  구조체는 'sys/un.h'라는 헤더파일에 정의되어 있다.

struct sockaddr__un                                 Data Type

이 구조체는 파일이름공간을 소켓 주소로 명시하는데 사용된다.

short int sun_family

이것은 소켓주소를 위한 형태나 주소패밀리를 명시한다. 당신은 파일이름공간을 지정하기 위하여  AF_FILE값을 저장해야 한다. 11.3절 [소켓주소], 150쪽을 보라.

char sun_path[108]

이것은 사용되는 파일이름이다.

미완성: 왜 108이 매직넘버인가?  RMS는 이것을 길이가 0인 배열을 만드는 것과 파일이름의 길이에 기초하여 적합한 저장공간을 할당하기 위해 allocate를  사용하는 것과 같은  예를 꼬집어 제안한다.

당신은 sun_family요소의 크기와 파일이름문자열의 (할당 크기가 아닌)문자열 길이의 합인, 파일이름공간에 소켓주소를 위한 길이 매개변수를 계산해야 한다.

11.4.3  파일-이름공간 소켓의 예

여기있는 것은 파일이름공간의 소켓을 만들고 이름지어주는 방법을  보여주는 예이다.

#include  <stddef.h>

#include  <stdio.h>

#include  <errno.h>

#include  <stdlib.h>

#include  <sys/socket.h>

#include  <sys/un.h>

int

make_named_socket  (const  char  *filename)

{

struct  sockaddr_un  name;

int  sock;

size_t  size;

/*  소켓만들기  */

sock  =  socket  (PF_UNIX,  SOCK_DGRAM,  0);

if  (sock  <  0)

{

perror  ("socket");

exit  (EXIT_FAILURE);

}

/*  Bind a name to the socket.  */

name.sun_family  =  AF_FILE;

strcpy  (name.sun_path,  filename);

/*  The size of the address is the offset of the start of the filename, plus its length, plus one for the terminating null byte.  */

size  =  (offsetof  (struct  sockaddr_un,  sun_path) + strlen (name.sun_path) + 1);

if ( bind (sock, (struct  sockaddr  *)  &name,  size)  <0)

{

perror  ("bind");

exit  (EXIT_FAILURE);

}

return  sock;

}

11.5  인터넷 이름공간

이 장에서는 프로토콜과 소켓이 인터넷 이름공간에서 사용되는 이름짓는 방법에 대해 서술한다. 인터넷 이름공간에서 소켓을 만들려면, socket이나 socketpair의 이름 공간인수로 이 이름공간의   PF_INET기호를 사용한다. 이 매크로는 'sys/socket.h'에 정의되어 있다.

int PF__INET                                          Macro

이것은 인터넷 이름공간을 지정하고 프로토콜의 연합된 패밀리를  지정한다.

인터넷 이름공간의 소켓 주소는 다음과 같은 성분을 포함한다:

□ 당신이 연결하기 원하는 장치의 주소. 인터넷 주소는 몇가지 방법으로 표현될 수 있다. ;  이러한 방법들은 11.5.1절  [호스트 주소] 155페이지 와 11.5.2.4절 [호스트 이름] 158페이지에서 논의한다.

□ 그 장치의 포트 번호. 11.5.3절 [포트] 159쪽을 보라

당신은 주소와 포트번호가 네트워크 바이트 순서라고 불리우는  정규화 된 포맷으로 표현된다는 것을 알아야 한다. 이에 관한 정보는 11.5.5절 [바이트 순서] 161쪽을 보라

11.5.1  인터넷 소켓 주소 포맷

인터넷 이름공간에서, 소켓주소는 호스트의 주소와 그 호스트의 포트를 포함한다. 추가적으로 당신이 선택한 프로토콜은  주소의 일부분처럼 효과적으로 제공된다. 왜냐하면  지역적인 포트번호는  특정한 프로토콜안에서 의미를 가지기 때문이다. 인터넷 이름공간안의 소켓 주소의 표현에 관한  자료형은 'netinet/in.h' 헤더파일에 정의되어 있다.

struct sockaddr__in                                Data Type

이것은 인터넷 이름공간안의 소켓주소를 표현하는데 사용되는 자료형이다. 그것은 다음 멤버를 가진다.

short int sin_family

이것은 소켓주소의 포맷이나 주소 패밀리를 지시한다. 당신은 이 멤버내에 AF_INET값을 저장해야 한다. 11.3절 [소켓주소] 150쪽을 보라

struct in_addr sin_addr

이것은 호스트 장치의 인터넷 주소이다.  어떻게 여기에 넣을 값을 얻는지에   관하여는 11.5.2절   [호스트 주소]   155쪽과 11.5.2.4 [호스트 이름] 158쪽을 보라.

unsigned short int sin_port

이것은 포트번호이다. 11.5.3절 [포트] 159쪽을 보라.

당신이 bind나 setsockname을 호출할 때, 만약 당신이 인터넷 이름공간 소켓 주소를 사용한다면, '길이' 매개변수로  sizeof (struct sockaddr_in)을 명시해야 한다.

11.5.2  호스트 주소

인터넷의 각 컴퓨터는 하나 또는 그  이상의 인터넷 주소를 가진다. 주소는 인터넷의 모든 것들 사이에서 그  컴퓨터를 지시하는 숫자이다. 사용자는 통상 숫자로된 호스트 주소를 사용한다. 그것은  '128.52.46.32'처럼 네 숫자로 되어 있고 점으로 나뉘어져 있다.  각 컴퓨터는 또한 하나 또는 그  이상의 호스트 이름을 가진다. 그것은 'churchy.gnu.ai.mit.edu'처럼 점으로 나뉘어진 단어의 문자열이다.

사용자가 호스트를 명시하게하는 프로그램은 전형적으로 숫자주소와 호스트 이름 모두를 수용한다. 그러나 프로그램은  연결을 위해 숫자로된 주소를 필요로 한다. 즉 호스트 주소를 사용하기 위해, 당신은 그것이 상징하고 있는 숫자 주소로 변환해야 한다.

11.5.2.1  인터넷 호스트 주소

인터넷 호스트 주소는 데이터의 4바이트를  포함하는 숫자이다. 그것들은 두부분으로 나뉘는데, 네트워크 번호와 그 네크워크내의 지역 네트워크 주소 번호가 그것이다. 네트워크 숫자는 처음 하나,둘또는 세 개의 바이트를 포함한다. 나머지 바이트는 지역 주소이다.

네트워크 번호들은  네트워크 정보  센터 (NIC,  Network Information Center)에 등록되어 있다. 그것들은 세 개의 클래스 A,B,C로  분할된다. 각각 컴퓨터마다의 지역네트워크 주소는 각 네트워크의 관리자에  의해 등록되어 있다.

클래스 A 네트워크는 0에서 127범위의 한 바이트 숫자를 가진다.  클래스 A 네트워크의 수는 매우 작지만, 각각은 굉장히 많은 수의 호스트를  지원한다. 중간크기의 클래스 B 네트워크는 두바이트의 네트워크  번호를 가진다. 첫 번째 바이트의 범위는 128에서 191까지이다. 클래스 C 네트워크는 가장 작다. 그것들은 세 바이트의 네트워크 번호를 가지며, 첫 번째 바이트의 범위는 192에서 255까지이다. 그러므로, 인터넷 주소의  처음 1, 2, 3바이트는 네트워크를 명시하는 것이다. 인터넷 주소의 나머지 바이트는 네느퉈크 안에서의 주소를 명시한다. 클래스 A네트워크 0은 모든  네트워크에 브로드캐스트용으로 예약되어 있다. 추가적으로 각 네트워크 내에서의 호스트  번호가 0인 것은 그 네트워크 내의 모든 호스트들에게 방송(broadcast)하기 위해 예약되어 있다. 클래스 A 네트워크 127은 루프백(loopback)용으로 예약되어 있다. 당신은 그러므로 인터넷 주소 '127.0.0.1'을 사용하면 자신의 호스트장치를 참조 할 수 있다.

단일 장치는 여러개의 네트워크의 멤버가 될 수 있으므로, 그것은 여러개의 호스트 주소를 가질수 있다. 그러나, 같은 호스트 주소를 가진 장치가 하나 이상 있어서는 안 된다.

인터넷 주소를 위한 숫자와 이름 표현은 네가지 형태가 있다.

a.b.c.d	이것은 각개별적으로 주소의 모든 네 개의 바이트를 나타낸다.

a.b.c	주소의 마지막 부분인  c는 2바이트로  취급된다. 이것은 네트워크 번호 a.b를 가진 클래스  B의 호스트 주소를 나타내는데  유용하다.

a.b	주소의 마지막  부분인 b는  3바이트로 취급된다. 이것은 네트워크 주소 a를 가진 클래스A의 호스트 주소를 표시하는데 유용하다.

a	만약 하나의 부분만 주어진다면, 이것은 호스트 주소에 직접 일치하게 된다.

주소의 각부분에서, 기수를 표시하기 위해  보통 C관례를 사용한다. 다른말로 하면, '0x' 또는 '0X'로 시작하는 것은 16진수 기수를 내포한다. '0'으로 시작하는 것은 8진수를 내포하고, 다른 것은 10진수 기수로 가정한다.

11.5.2.2  호스트 주소 자료형

인터넷 호스트 주소는 정수형들(unsigned long int형)같은  형식으로 표현된다. 다른 말로하면, 정수들은 struct in_addr의 형태의 구조체안에 들어가게 된다. 만약 용법이 일치되면 좋을 것이나, 그렇지 못하다면, 구조체로 부터 정수를 뽑아내거나, 정수를 구조체에 넣는 것이 쉽지 않을 것이다. 다음 인터넷 주소에 관한 기본적인 정의는 'netinet/in.h'헤더 파일 안에서 볼수 있다:

struct in_addr                                  자료형

이 자료형은 인터넷 주소를 포함하는  정확한 문맥에 사용된다. 그것은 단지 s_addr이라는 하나의 필드를  가진다. 그것은 unsigned long int 처럼 호스트 주소번호를 저장한다.

unsigned long int INADDR__LOOPBACK                Macro

당신은 이 장치의 실제주소를 찾는 대신에,  이 상수를 사용할 수 있다. 그것은   보통 'localhost(로컬호스트)'라  불리우는 인터넷   주소 '127.0.0.1'이다. 이 특별한 상수는 당신의 장치의 주소를  살피는데 생기는 문제를 해결해 준다. 또한, 시스템은 한  장치에서 자기자신으로 전송하는   경우 어떤 네트워크전송을 피하기 위해, 주로 INADDR_LOOPBACK을 특별히 구현하고 있다.

unsigned long int INADDR__ANY                       Macro

주소를 바인딩할 때,  "어떠한 오는  주소(any incoming address)"에 대한 것을 이 상수를 사용할 수  있다. 11.3.2절 [주소 설정], 152페이지를 보라.   이것은 당신이   인터넷 연결을  받아들이길  원할 때, sockaddr_in구조체의 sin_addr멤버에 주로 들어가는  주소이다.

unsigned long int INADDR__BROADCAST              Macro

이 상수는 방송메시지를 보내는데 사용되는 주소이다.

unsigned long int INADDR__NONE                     Macro

이 상수는 어떤 함수들이 에러를 지시하기 위해 반환된다.

11.5.2.3  호스트 주소 함수들

인터넷 주소를 조작하는 추가적인 함수들은 'arpa/inet.h'안에 선언되어 있다. 그것들은 네트워크 바이트 순서로 인터넷 주소를 표현한다.  즉 그것들은 네트워크 번호와 네트워크 안에서의 지역 주소를 호스트 바이트 순서로 표현한다. 네트쿼으와 호스트 바이트  순서에 관한 설명은 11.5.3절 [바이트 순서] 161쪽을 보라.

int inet__aton  (const char *name, struct in_addr *addr) 함수

이 함수는 인터넷 호스트 주소 이름을 표준 숫자와  점 표시형식에서 이진데이터 형으로 변환한다. 그리고,  그것을 addr이 지시하고  있는 struct in_addr안에 저장한다.

unsigned long int inet__addr  (const char *name)        함수

이 함수는 인터넷주소 이름을 숫자와 점으로 된 것에서 이진 데이터로 변환한다. 만약 입력이 유효하지않으면, inet_addr은 INADDR_NONE를 반환한다. 이것은 위에서 서술한 inet_aton의  구식의 인터페이스이다. INADDR_NONE은 유효한 주소 ( 255.255.255.255 ) 이기때문에 그것은 쓸모 없이 되었다. 그리고 inet_aton 은 에러 반환을 지시하는 더 깔끔한 방법을 제공한다.

unsigned long int inet__network  (const char *name)     함수

이 함수는 숫자와 점으로 표현되는 주소이름으로 부터 네트워크 번호를 뽑아낸다. 만약 입력을 제대로 하지 않으면,  inet_network는 -1을 반환한다.

char * inet__ntoa  (struct in_addr addr)                 함수

이 함수는 인터넷 호스트 주소 addr을 숫자와 점으로  표현되는 문자열로 변환한다. 반환되는 값은  정적으로 할당된 버퍼의  포인터이다. 계속 호출하면 같은 버퍼에 덮어 쓰기를 하므로, 만약 그것을 저장하기 위해서 그 문자열을 복사해두어야 한다.

struct in_addr inet__makeaddr  (int net, int local)        함수

이 함수는 지역 네트워크 내의 지역 주소 local과 네트워크  번호 net를 묶어 인터넷 호스트 주소를 만든다.

int inet__lnaof  (struct in_addr addr)                    함수

이 함수는 인터넷 호스트 주소 addr의 일부인 네트워크  내에서의 지역 주소를 반환한다.

int inet__netof  (struct in_addr addr)                    Function

이 함수는 인터넷 호스트 주소의 일부인 네트워크 번호를 반환한다.

11.5.2.4  호스트 이름들

인터넷 주소를 위한 숫자와 점으로 표현하는 것 외에, 당신은 의미있는 이름을 사용하여 호스트를 참조할 수 있다.  의미있는 이름을 사용하는 것의  장점은 역시 외우기 쉽다는  것이다. 예를 들어 인터넷 주소 '128.52.46.32'를 가진 장치는 'churchy.gnu.ai.mit.edu'로도 표현된다. 또한 'gnu.ai.mit.edu' 도메인내의 다른 컴퓨터들은  간단하게 'churchy'로 이 장치를 참조할수 있다.

내부적으로, 시스템은 호스트 이름과 호스트  번호 사이의 매핑의 정보를 유지하는 데이터베이스를  사용한다. 이 데이터베이스는 보통 '/etc/hosts'이거나 네임서버에 의해 제공된다. 이 데이터 베이스에  접근하기 위한 함수와기호들은 'netdb.h'에  선언되어 있다. 만약 당신이 'netdb.h'를 포함한다면, 그것들은  무조건적으로 정의되어 있는  BSD들의 특징이다.

struct hostent                                자료형

이 자료형은 호스트 데이터베이스안의 내용을 표현하는 데 사용된다. 그것은 다음 멤버를 가진다.

char *h_name

이것은 호스트의 "사무적인" 이름이다.

char **h_aliases

스트링의 null로 끝나는 벡터로 표현된 호스트의 또다른 이름이다.

int h_addrtype

이것은 호스트 주소의 형태이다. 실제적으로, 이것의  값은 항상 AF_INET이다. 원래 다른 종료의 주소도 인터넷 주소처럼 데이터베이스 안에 표현될 수 있다. 만약 그렇게 한다면, 당신은 이 필드에 AF_INET이 아닌 어떤 값을 찾아야  한다. 11.3[소켓 주소들] 150쪽을 보라.

int h_length

이것은 각 주소의 바이트 길이다.

char **h_addr_list

이것은 호스트의 주소들의 벡터이다.  (호스트들은 다중 네트워크에 연결되고 각각 여러개의  다른 주소를 가진다는 것을 회상하라) 벡터는 널포인터로 종료된다.

char *h_addr

이것은 h_addr_list[0]과 동의어이다. 다른 말로, 이것은 첫 번째 호스트 주소이다.

호스트 데이터베이스가 고려되는 것과는 달리,  각 주소는 h_length 바이트 길이의 메모리 블록이다.  그러나 다른 문맥에서,  그것들은,  당신이 이것을 in_addr 또는 unsigned long int로 변환할 수 있는 암시적인 가정이다. struct hostent 구조체의 호스트 주소들은  항상 네트워크 바이트 순서로 주어진다. 11.5.5절 [바이트 순서] 161쪽을 보라.	당신은 특정한 호스트에 관한 정보를 찾기 위해 호스트 데이터베이스를 검색하려면 gethostbyname 또는 gethostbyaddr를 사용할  수 있다. 그 정보는 정적으로 할당된 구조체에 반환된다. 만약  당신이 그것을 호출로 부터 저장하기 위해서는 정보를 복사해야 한다.

struct hostent * gethostbyname  (const char *name)     함수

gethostbyname함수는 name으로  이름지어진 호스트에  관한 정보를 반환한다. 만약 검색에 실패하였으면, 널포인터를 반환한다.

struct hostent * gethostbyaddr  (const char *addr, int length, int format) 함수

gethostbyaddr함수는 인터넷 주소 addr의 호스트에 관한 정보를 반환한다. 인수의 length는 addr 주소의 (바이트 단위의)크기이다.  format는 주소형식을 명시한다. 즉 인터넷  주소라면 AF_INET값으로 명시하면 된다. 만약 검색에 실패를 하면, gethostbyaddr는 널포인터를 반환한다. 만약 gethostbyname 또는 gethostbyaddr을 이용한  이름검색이 실패하면, 당신은 변수h_errno의 값을 살펴보고 그  원인을 찾을수 있다. (그것은 그러한 함수가 errno를  세팅하도록 더  깔끔하게 설계되어  있다. 그러나 h_errno의 사용은 다른 시스템에 대해 호환성 있게 되어 있다.) h_erro사용하기 전에, 당신은 다음과 같이 그것을 선언해 주어야 한다.

extern  int  h_errno;	아래는 h_errno안에서 찾을수 있는 error코드들이다:

HOST_NOT_FOUND

어떤 호스트가 data base안에 없다.

TRY_AGAIN

이 상태는 네임서버에 연결되지 않을 때 발생한다. 만약 당신이 나중에 다시 시도한다면, 성공할 수도 있다.

NO_RECOVERY

복구할수 없는 에러 발생.

NO_ADDRESS

호스트 데이터베이스는 이름에 대한 항목을 포함하나, 그것은 적합한 인터넷 주소를 가지지 않았다.

당신은 또한 sethostent,gethostent,endhostnet를  사용하여 한  번에 한 항목씩 전체 호스트 데이터베이스를 주욱 살펴본다. 이러한 함수들을 사용할 때는 주의해야 한다. 왜냐하면 그것들은  재진입성을 가지지 않기 때문이다.(즉, 스케쥴을 점유한다)

void sethostent  (int stayopen)                      함수

이 함수는 데이터베이스를 살펴보는 것을 시작하기 위해서 호스트 데이터베이스를  연다.  당신은   항목들을 읽기 위해 그러고 나서 gethostent를 호출해야 한다. 만약 stayopen인수가 0이 아니면,  다음으로  gethostbyname 또는 gethostbyaddr를 호출하는 것이 데이터베이스를 닫지 않도록 (그것들이 그래야 하는 것처럼) 이것은 플래그를 세트한다.

struct hostent * gethostent  ()                          함수

이 함수는 호스트 데이터베이스 안의 다음 항목을 반환한다. 만약 더 이상 항목이 없으면, 널 포인터를 리턴한다.

void endhostent  ()                                     함수

함수는 호스트 데이터 베이스를 닫는다.

11.5.3  인터넷 포트

인터넷 이름공간에서의 소켓주소는, 장치의  인터넷 주소에 추가적으로 주어진 장치의 소켓을 구별하는 포트번호로 구성된다.  포트번호는 0에서 65535번까지의 범위이다. IPPORT_RESERVED보다  작은  포트번호는   표준서버에서 finger나 telnet과 같은 것을 위해 예약되어 있다. 시스템  내부에 이러한 것들을 유지하고 있는 데이터베이스가 있고, 당신은 getservbyname함수를 사용하여 서비스 이름과 포트번호의 맵을 만들 수 있다.;  11.5.4절 [서비스 데이터베이스], 160쪽을 보라.

만약 데이터베이스안에 정의된 표준 중의 하나가 아닌 서버를 작성하려면, 그것을 위한 포트번호를 선택해야  한다.  IPPORT_USERRESERVED 보다 더 큰 숫자를 사용하라. 어떤 번호들은 서버들을 위해 예약되어 있고 시스템에 의해 자동으로 생성되지 않을 것이다.  다른 사용자들에 의한 서버사용과의 충돌을 피하는 것은 당신에게 달렸다.

당신이 그것의 주소를 명시하지 않고 소켓을 사용할 때, 시스템은 포트번호를  하나  생성한다.  이  번호는  IPPORT_RESERVED와  IPPORT_ USERRESERVED사이의 값이다.

인터넷에서, 같은 소켓주소(호스트 주소 + 포트주소)로 통신을 시도하지 않는다면, 같은 포트번호로 두 개의 다른 소켓을 가지는 것은 실제로 적당한 방법이다. 상위프로토콜이 그것을 요구할때, 특별한 상황을 기대하고 같은 포트번호를 복사하지 말아라. 보통, 시스템은 그러한 일을  수행하지 않을 것이다. bind는 보통 다른 포트번호를 요구한다.  포트번호를 다시 사용하기 위해서는, 소켓옵션 SO_REUSEADDR를  세트하라. 11.11.2절 [소켓-수준 옵션] 181쪽을 보라.

다음 매크로들은 'netinet/in.h'에 정의되어 있다.

int IPPORT__RESERVED                          매크로

IPPORT_RESERVED 보다 작은 값의 포트는 수퍼유저용으로 예약되어 있다.

int IPPORT__USERRESERVED                     매크로

명백히 사용하기 위해 예약된, IPPORT_USERRESERVED보다  크거나 같은 포트이다. 그것들은 보통 자동으로 할당된다.

11.5.4  서비스 데이터베이스

유명한 서비스의 경로를 유지하는 데이터베이스는 보통 '/etc/services'파일이거나 네임서버의 동등한 파일이다. 이 서비스 데이터베이스를 억세스하기 위해 'netdb.h'에 선언되어 있는 다음 유틸리티들을 사용할 수 있다.

struct servent                                          자료형

이 자료형은 서비스 데이터베이스로부터의 항목에 관한 정보를  나타낸다.

char *s_name

이것은 서비스의 "공식적"인 이름이다.

char **s_aliases

문자열의 배열로 표현된 서비스의 또다른 이름들이다. 이 배열은 마지막에 널 포인터가 들어간다.

int s_port

이것은 서비스의 포트 번호이다. 포트번호는 네트워크 바이트 순서로 되어 있다. ; 11.5.5절 [바이트 순서] 161페이지를 보라.

char *s_proto

이것은 이 서비스와 사용되는  프로토콜의 이름이다. 11.5.6절 [프로토콜 데이터베이스] 162쪽을 보라.

특정한 서비스에 관한 정보를 얻으려면, getservbyname이나 getservby port 함수를 사용하라. 이 정보는 정적으로 할당된 구조체안으로 반환된다. 즉 그것을 여러번 호출하는 것으로 부터 보호하려면 그 정보를 복사해 놓아야만 한다.

struct servent   * getservbyname   (const  char  *name, const  char  *proto) 함수

getservbyname함수는 name이라고  proto라는  프로토콜을 사용하고 이름지어진 서비스에 관한  정보를 반환한다. 만약  그러한 서비스를 찾지 못하면, 널포인터를 반환한다.

이 함수는 클라이언트들뿐만 아니라  서버들에서도 유용하다. 서버에서는 듣고자 할 때 어떤 포트를 사용할 것인가를 결정할 때, 이 함수를 사용한다. (듣는 것에 관하여서는 11.8.2절[듣기], 167쪽을 보라)

struct servent * getservbyport  (int port, const char *proto)    함수

getservbyport함수는 proto프로토콜을 사용하고 port라는 포트를 사용하는 서비스에 관한 정보를 반환한다. 당신은 또한 setservent, getservent,endservent를 사용하여 서비스 데이터베이스를 조사할 수  있다. 이러한 함수들은  재진입성을 가지지 않기 때문에,  사용할 때 주의해야 한다.

void setservent  (int stayopen)                          함수

이 함수는 당신이 조사하려는 서비스 데이터베이스를 연다.

만약 stayopen 인수가 0이 아니면, getservbyname또는 getservbyport를 다음에 호출을 하였을때 데이터베이스를 닫지 않도록(보통은 닫는다), 플래그를 세트한다. 여러번의 호출이 데이터베이스를 여러번 열지 않도록 할 때, 이것은 더욱 효율적이다.

struct servent * getservent  (void)                      함수

이 함수는 서비스 데이터베이스안의 다음  항목을 반환한다. 만약 더 이상의 항목이 없으면, 널포인터를 반환한다.

void endservent  (void)                                 함수

이 함수는 서비스 데이터베이스를 닫는다.

11.5.5  바이트순서 변환

컴퓨터의 종류가 틀리면, 한 워드 내에서 바이트의 순서가 틀리게 사용 될 수 있다. 어떤 컴퓨터들은 워드안에 상위 바이트가 먼저 오기도 하고 (이를 "큰 종결자(big-endian)"라 부른다), 다른 것들은 상위 바이트가 나중에 오기도 한다.(이를 "작은 종결자(little-endian)"라 한다)

다른 바이트순서를 사용하는 장치들이 통신하기 위해, 인터넷 프로토콜들은 네트워크 상에서 전송되는 데이터를 위해 규정된 바이트순서를 명시하고 있다.

인터넷 소켓 연결을 설정할 때, 당신은 sockaddr_in구조체의 sin_port와 sin_addr멤버를 네트워크 바이트순서로 표현해야만 한다. 소켓을 통해 전송되는 정수 데이터를 코딩할 때 역시 그것을 네트워크 바이트 순서로 변환해야 한다. 만약 그렇게 하지 않는다면, 프로그램은 다른 종류의 장치와 통신을 하는데 실패할 것이다.

포트번호나 호스트 주소를 얻기 위해 getservbyname, gethostbyname, inet_addr를 사용한다면, 사용되는 값들은 이미 네트워크 바이트 순서로 되어 있는 것이다. 당신은 그것들을 sockaddr_in에 직접 복사할 수 있다.

그렇지 않은 경우, 값들을 정확히 변환하지 않으면 안된다.  sin_port멤버로 사용하기 위해 htons와 ntohs를 사용하여 값들을 변환하라. sin_addr멤버로 사용하기 위해 htonl과 ntohl를 사용하여 값들을 변환하라. (struct in_addr은 unsigned long int과 같은 것임을 기억하라) 이러한 함수들은 'netinet/in.h'안에 선언되어 있다.

unsigned short inthtons  (unsigned short int hostshort) 함수

이 함수는 호스트 바이트순서로 된 short형 정수 hostshort를  네트워크 바이트 순서로 변환한다.

unsigned short int ntohs  (unsigned short int netshort)   함수

이 함수는 네트워크 바이트순서로 된 short형 정수 netshort를 호스트바이트순서로 변환한다.

unsigned long int htonl  (unsigned long int hostlong )   함수

이 함수는 호스트 바이트순서로된 long형 정수 hostlong을 네트워크 바이트순서로 변환한다.

unsigned long int ntohl  (unsigned long int netlong )    함수

이 함수는 네트워크 바이트순서로 된 long형 정수 netlong을 호스트 바이트순서로 변환한다.

11.5.6  프로토콜 데이터베이스

소켓에서 사용되는 통신 프로토콜은 어떻게 데이터를 교환할 것인가에 대한 저수준 세부사항을 제어한다. 예를 들면, 프로토콜은, 전송상의 에러검출을 위한 검사합계(checksum)같은 것이나, 메시지의 경로설정 명령 같은 것들을 구현한다. 일반 사용자 프로그램들은 이러한 세부사항들에 대해서  직접적인 영향을 받지는 않는다.

인터넷 이름공간의 기본 통신 프로토콜은 통신스타일에 의존한다. 스트림 통신의 기본 프로토콜은 TCP("전송 제어 프로토콜")이다. 데이터그램 통신의 기본 프로토콜은 UDP("유저 데이터그램 프로토콜")이다. 신뢰성 있는 데이터그램 통신의 기본 프로토콜은 RDP("신뢰성 있는 데이터그램 프로토콜")이다. 당신은 아마 거의 대부분 기본 프로토콜을 사용할 것이다.

인터넷 프로토콜은 일반적으로 숫자 대신 이름으로 명시된다. 호스트가 알고 있는 네트워크 프로토콜은 데이터베이스 안에 저장되어 있다. 이것은 보통 '/etc/protocols'파일이에서 얻어지거나 , 네임서버에 의해 동등하게 제공될 수 있다. 당신은 getprotobyname함수를 사용하여 데이터베이스내에서  프로토콜 이름에 연계된 프로토콜 번호를 찾아 낼 수 있다.

여기에 프로토콜 데이터베이스를 억세스하기 위한 유틸리티들의 자세한 정의가 있다. 이것들은 'netdb.h'에 선언되어 있다.

struct protoent                                         자료형

이 자료형은 네트워크 프로토콜 데이터베이스의 항목들를 표현하는데 사용된다. 그것은 다음 멤버를 가진다.

char *p_name

이것은 프로토콜의 공식적인 이름이다.

char **p_aliases

이것들은 프로토콜의 또다른 이름들이다. 문자열의 배열로 되어 있으며, 배열의 마지막 요소는 널포인터이다.

int p_proto

이것은 (호스트 바이트순서로 된) 프로토콜 번호이다.  소켓의 protocol인수로 이 멤버를 사용하라

구체적인 프로토콜에 관하여 프로토콜 데이터베이스를 검색하기 위해 getprotobyname과 getprotobynumber를 사용할 수 있다. 정적으로 할당된 구조체에 정보가 반환된다. 여러번 호출되는 것으로부터 정보를 보호하기 위해 그것을 복사해야 놓아야 한다.

struct protoent * getprotobyname  (const char *name)   함수

getprotobyname함수는 name이라는 이름의 네트워크프로토콜에 관한 정보를 반환한다. 만약 같은  이름의 프로토콜이 없으면,  널포인터를 반환한다.

struct protoent * getprotobynumber  (int protocol)       함수

getprotobynumber함수는 protocol이라는 숫자를 가진  네트워크 프로토콜에 관한 정보를 반환한다. 만약  이러한 프로토콜이 없다면 널 포인터를 반환한다. setprotoent, getprotoent, endprotoent를 사용하여 전체 프로토콜 데이터베이스를 한 번에 한 프로토콜씩 조사할 수 있다. 이들 함수는 재진입성을 가지지 않기 때문에 주의해서 사용해야 한다.

void setprotoent  (int stayopen)                         함수

이 함수는 검색을 위해 프로토콜 데이터베이스를 연다. 만약 stayopen인수가  0이 아니면,  getprotobyname 또는  getproto-bynumber의 호출이 데이터베이스를 (보통 닫는데) 닫지 않도록 플래그를 세트한다. 이것은 여러번의 호출로 데이터베이스가 여러번 열리지 않도록 하는데 더욱 유용하다

struct protoent * getprotoent  (void)                     함수

이 함수는 프로토콜 데이터베이스의 다음  정보를 반환한다. 만약 더 이상의 항목이 없으면 널 포인터를 반환한다.

void endprotoent  (void)                                Function

이 함수는 프로토콜 데이터베이스를 닫는다.

11.5.7  인터넷 소켓 예제

여기에 인터넷 이름공간 안에 소켓을 만들고 이름짓는 방법을 보여주는 예제가 있다. 프로그램이 수행되는 장치에 새로 소켓이 만들어 진다.

장치의 인터넷 주소를 찾고 사용하는 것 대신, 호스트 주소에는 INADDR_ANY를 명시하였다. 시스템은 장치의 실제주소로 그것을 대치한다.

#include  <stdio.h>

#include  <stdlib.h>

#include  <sys/socket.h>

#include  <netinet/in.h>

int

make_socket  (unsigned  short  int  port)

{

int  sock;

struct  sockaddr_in  name;

/*  소켓을 만든다 */

sock  =  socket  (PF_INET,  SOCK_STREAM,  0);

if  (sock  <  0)

{

perror  ("socket");

exit  (EXIT_FAILURE);

}

/*  소켓에 이름을 준다 */

name.sin_family=  AF_INET;

name.sin_port  =  htons  (port);

name.sin_addr.s_addr  =  htonl  (INADDR_ANY);

if  (bind   (sock,  (struct   sockaddr  *)   &name,  sizeof    (name))  <  0)

{

perror  ("bind");

exit  (EXIT_FAILURE);

}

return  sock;

}

여기에 또다른 예제가 있다. 이것은 주어진 호스트 이름문자열과 포트번호를 sockaddr_in구조체안에 어떻게 넣는 지를 보여준다:

#include  <stdio.h>

#include  <stdlib.h>

#include  <sys/socket.h>

#include  <netinet/in.h>

#include  <netdb.h>

void

init_sockaddr  (struct  sockaddr_in  *name, const  char  *hostname,

unsigned  short  int  port)

{

struct  hostent  *hostinfo;

name->sin_family  =  AF_INET;

name->sin_port  =  htons  (port);

hostinfo  =  gethostbyname  (hostname);

if  (hostinfo  ==  NULL)

{

fprintf  (stderr,  "Unknown  host  %s.\n",  hostname);

exit  (EXIT_FAILURE);

}

name->sin_addr  =  *(struct  in_addr  *)  hostinfo->h_addr;

}

11.6  다름 이름공간들

다른 이름공간과 프로토콜패밀리들도 지원되지만 별로 사용되지 않으므로 아직 문서화되지 않았다.  PF_NS는 Xerox 네트워크 소프트웨어 프로콜에 관련된 것이다. PF_ISO 는 개방시스템상호연결(OSI)에 관련된 것이다. PF_CCITT는 CCITT의 프로토콜들에 관련된 것이다. 'socket.h'에 실제로 구현되지 않은 기호와 다른 이름의 프로토콜이 정의되어 있다.

PF_IMPLINK는 호스트와 인터넷 메시지 처리기와의 통신에 사용된다. 이에 관한 정보와 가끔 사용되는 지역 경로설정 프로토콜인 PF_ROUTE에 관하여는 (미래에 나올) GNU Hurd Manual을 보라.

11.7  소켓 열고 닫기

이 절에서는 소켓을 열고 다는 실제적인 라이브러리 함수에 관하여 서술한다. 모든 이름공간과 연결스타일에 대해 같은 함수가 사용된다.

11.7.1  소켓 만들기

소켓을 만들기 위해서는 'sys/socket.h'에 선언되어 있는 소켓함수를 사용한다.

int socket  (int namespace, int style, int protocol)        함수

이 함수는 소켓을 만들고 통신스타일 style을 명시한다. 통신스타일은 11.2절[통신스타일],150쪽에 열거된 소켓 스타일중 하나를 사용하여야 한다. namespace인수는 이름공간을 명시하고, PF_FILE이거나 PF_INET이어야 한다( 11.4절[파일 이름공간],153쪽, 11.5절[인터넷 이름공간],155쪽을 보라). protocol은 프로토콜을 정한다 (11.1절 [소켓개념],149쪽을 보라); protocol에는 보통 0이 사용된다. socket으로부터의 반환값은  새로운 소켓의  파일서술자이다. 에러가 발생하면 -1이 반환된다. 다음은  이 함수가 정의해 놓은  에러 상황 errno이다:

EPROTONOSUPPORT

The protocol or style  is not supported  by the namespace specified.

EMFILE	프로세스가 이미 너무 많은 파일서술자들을 열어 놓았다.

ENFILE	시스템이 이미 너무 많은 파일서술자들을 열어 놓았다.

EACCESS 프로세스가 명시된 style이나 protocol에 맞는 소켓을 만들 권한을 갖고 있지 않다.

ENOBUFS 시스템 내부 버퍼 공간이 부족하다.

socket 함수에 의해 반환되는 파일서술자는 읽기와 쓰기 기능 모두를 지원한다. 그러나 파이프 같은 소켓들은 파일 위치에 관한 동작은 하지 못한다. socket함수를 어떻게 호출하는 지에 대한 예는 11.4절 [파일 이름공간],153쪽, 또는 11.5.7절 [Inet 예제],163쪽을 보라.

11.7.2  소켓 닫기

소켓의 사용이 끝나면, 간단히 그 파일서술자를 close함수를 이용해 닫으면 된다. 8.1절 [파일 열고 닫기], 103쪽을 보라. 만약 연결을 통하여 전송되기를 기다리는 데이터가 여전히 있으면, 보통 close는 이 전송이 완료되도록 시도한다. SO_LINGER소켓 옵션을 사용하여 시간제한 주기를 명시해 주면, 이 과정을 제어할 수 있다. 11.11절 [소켓 옵션],180쪽을 보라.

당신은 'sys/socket.h'에 서술되어 있는 shutdown을 호출하므로써 연결하의 송신만 또는 수신만을 종료시킬 수 있다.

int shutdown  (int socket, int how )                    함수

shutdown함수는 socket이라는 소켓의 연결을 종료시킨다. how인수는 어떤 기능을 수행할 것인가를 명시한다.

0. 이 소켓으로부터 데이터 수신을 중단한다. 만약 더 이상의 데이터가 도착하면,  그것을

무시한다.

1. 이 소켓으로의 전송을 중단한다. 보내기 위해 대기중인 데이터는 취소된다. 이미 보낸 데이터의 응답을 기다리는 것도 중단한다. 만약 그것을 잃어버릴지라도 재전송하지 않는다.

2. 수신과 송신 모두를 중단한다.

성공하면 반환값이 0이고, 실패면 -1이다. 다음은 이 함수에 정의 되어 있는 errno 에러 상	황이다.

EBADF         socket이 유효한 파일 서술자가 아니다.

ENOTSOCK    socket이 소켓이 아니다.

ENOTCONN    socket이 연결되어 있지 않다

11.7.3  소켓 쌍

소켓쌍은 연결된 (그러나 이름없는) 소켓의 쌍으로 구성된다. 그것은 pipe와 유사하고 무척 같은 방법으로 사용된다. 소켓쌍은 'sys/socket.h'에 선언된 socketpair함수로 만들어진다. 소켓쌍은 파이프와 대단히 유사하다:

주된 차이점은 소켓쌍은 양방향이라는 것이다. 파이프는 하나의 입력만 되는 쪽과 출력만 되는 한 쪽만을 가지고 있다. (10장[Pipe와 FIFO]를 보라 145쪽)

int socketpair  (int namespace, int style, int protocol, int filedes[2]) 함수

이 함수는 소켓쌍을 만들며, filedes[0]과  filedes[1]안에 파일서술자를 반환한다. 소켓쌍은 전이중(full-duplex)성 통신 채널이기 때문에 한쪽에서 읽거나 쓰기가 가능하다. namespace,style,protocol인자는 socket함수에서 처럼 해석된다.  style은 11.2절[통신스타일]에 나열된  것이다. namespace인자는 이름공간을 명시하며, 반드시 AF_FILE이어야 한다. (11.4절[파일이름공간],153쪽을 참조하라); protocol은 통신 프로토콜을  서술하고 0도 의미있는 값이다. 만약 style 이 비연결통신스타일를 명시하면, 당신이 얻게 되는 두 소켓은연결되지 않는다. 엄밀히 말해, 그들 각각은 서로의 목적지 주소를 알고 있어, 서로에게 패킷을 보낼수 있게 된다. socketpair함수는 성공시에 0을, 실패시에  -1을 반환한다. 다음은 이 함수에 정의되어 있는 errno에러 상황이다:

EMFILE	프로세스가 너무 많은 파일 서술자를 열어 놓고 있다.

EAFNOSUPPORT	명시된 이름공간이 지원되지 않는 것이다.

EPROTONOSUPPORT	명시된 프로토콜이 지원되지 않는 것이다.

EOPNOTSUPP	명시된 프로토콜이 소켓쌍을 만드는 것을 지원하지  않는다.

11.8  연결하는데 소켓사용

대부분의 통신 스타일은 특정한 다른 소켓과 연결을 만들고 소켓과 소켓을 통하여 데이터를 교환한다. 연결을 만드는 것은 비대칭이다.; 한쪽(클라이언트)는 다른 쪽(서버)이 소켓을 만들고 연결요구를 기다릴때까지 연결을 요구하도록 되어 있다.

□ 11.8.1절 [연결하기], 166쪽은 클라이언트 프로그램이 서버와 연결을 초기화하기 위해 무엇을 해야 하는지를 서술한다.

□ 11.8.2절 [듣기],167쪽과 11.8.3[연결을 수락하기], 167쪽에서는 서버 프로그램이 클라이언트로부터 연결요구를 기다리고 받아들이는 것에 대한 서술을 한다.

□ 11.8.5[데이터전송],169쪽은 어떻게 연결된 소켓을 통하여 데이터를 전송하는 방법을서술한다.

11.8.1  연결 만들기

연결을 만들 때, 클라이언트는 서버가 연결을 기다리고 수락하는 동안 연결을 만들어야 한다. 여기에 클라이언트가 'sys/socket.h'에 선언된 connect함수를 이용하여 무엇을 해야 할 것인지를 논의하고 있다.

int connect  (int socket, struct sockaddr *addr, size_t length)  함수

connect함수는 addr와 length의 길이로 명시된 주소를 가지는 소켓의 socket이라는 파일서술자를 이용하여 소켓으로부터 연결을 초기화 한다. (이 소켓은 전형적으로 다른 장치의 것이다.  그것은 서버처럼 이미 설정되어야 한다.)  이 인수들이  어떻게 해석되는지에 관한 정보는 11.3절[소켓주소],150쪽을 보라

보통, connect는 서버가 요구에  응답할때까지 기다린후 반환하게 된다. 응답을 기다리지 않고 즉시  반환하도록 소켓 socket을 블록킹되지 않도록(non-blocking) 설정할 수 있다. 블록킹되지 않는 모드에 관한 정보는 8.10절 [파일 상태 플랙], 116쪽을 보라.

connect로부터의 반환치는 보통 0이다. 만약 에러가 발생시에는 -1을 반환한다. 이 함수에 선언되어 있는 errno에러 상황은 다음과 같다.

EBADF	소켓socket이 유효한 파일 서술자가 아니다.

ENOTSOCK	소켓socket이 소켓이 아니다.

EADDRNOTAVAIL	명시된 주소가 원격장치(상대방기기)에 사용할수 없는 것이다.

EAFNOSUPPORT	addr의 이름공간이 이 소켓에서 지원하지 않는 것이다.

EISCONN	소켓socket이 이미 연결되어 있다.

ETIMEDOUT	연결설정을 시도하다가 시간을 초과하였다.

ECONNREFUSED	서버가 연결설정을 직접 거절하였다.

ENETUNREACH	주어진 addr의 네트워크는 이 호스트로부터 도달할수 없다.

EADDRINUSE	주어진 addr의 소켓주소가 이미 사용 중이다.

EINPROGRESS	소켓socket이 블록킹되지 않는 것이고,연결을 즉시 설정할수 없었다.

EALREADY	소켓socket이 블록킹되지 않는 것이고, 이미 진행중인 미결정의 연결이있다.

11.8.2  연결 듣기

이제 서버프로세스가 소켓에서 연결을 수락하기 위해 무엇을 해야 하는지 고려해보자. 이것은 listen함수를 사용하여 소켓에서 연결요구를 가능하게 하고 나서, accept함수 (11.8.3절 [연결수락], 167쪽을 보라)을 사용하여 요구를 받아들이는 것을 포함한다. listen함수는 비연결 통신 스타일을 지 원하지 않는다. 연결 요구가 있을 때까지 수행을 시작하지 않도록 네트워크 서버를 작성할 수 있다. 11.10.1절[Inetd 서버들], 179쪽을 보라.

인터넷 이름공간에서, 한 포트로의 연결에 접근을 제어하는 보호 메카니즘이 없다. 어떤 장치에서 어떤 프로세스가 당신의 서버에 연결을 만들 수 있다. 만약 당신이 당신의 서버에 접근을 제어하기를 원한다면, 연결요구에 연계된 주소를 검사하거나, 다른 협상과정이나 식별기능이 있는 프로토콜을 구현하면 된다.

파일 이름공간에서, 보통 파일 보호 비트는 소켓 연결에 접근하는 사람을 제어할 수 있다.

int listen  (int socket, unsigned int n)                   함수

listen함수는 소켓socket을 연결을 수락이 가능하도록 하여, 그것을 서버소켓으로 만든다.

인수 n은 미결된 연결의 큐의 길이를 명시한다. listen함수는 성공시에 0을 실패시 -1을  반환하며, 이 함수에 정의된 errno에러 상황은 다음과 같다.

EBADF	인수socket는 유효한 파일서술자가 아니다.

ENOTSOCK	인수socket이 소켓이 아니다.

EOPNOTSUPP	소켓socket는 이 기능을 지원하지 않는다.

11.8.3  연결 수락

서버가 연결요구를 수신하였을 때, 연결요구를 수락함으로써 연결이 끝날 수 있다. accept함수를 사용하여 그것을 수행한다. 서버로 설정된 소켓은 여러 클라이언트로부터의 연결요구를 수락할 수 있다. 서버의 원 소켓(original socket)은 연결의 부분이 되지 않는다. 대신에, accept는 연결에 특정한 새로운 소켓을 만든다. accpect는 이 소켓에 대한 서술자를 반환한다. 서버의 원 소켓은 다음 연결요구를 듣기 위하여 계속 남아있다.

서버소켓의 비결된 연결요구의 수는 제한되어 있다. 만약 클라이언트들로 부터의 연결 요구가 서버가 그것을 처리할 수 있는 거 보다 더 빠르다면, 큐가 가득채워 질 것이고, 추가적인 요구는 ECONNREFUSED에러로 거절 된다. 비록 시스템 내부의 큐가 제한되어 있을 지라도 listen함수의 인수로 이 큐의 최대 길이를 명시할 수 있다.

int accept  (int socket, struct sockaddr *addr, size_t *length_ptr) 함수

이 함수는 서버소켓 socket의 연결요구를 수락하는데 사용된다. 만약 미결된 연결이 없고 소켓socket이 블록킹되도록 세트되어있으면 accept함수는 계속 기다린다. (블록킹 안되는 소켓으로  기다리게하도록 선택할 수도 있다.) 블록킹 안되는 모드에 관한 정보는 8.10절 [파일 상태 플랙], 116쪽을 보라. addr과 length_ptr인수는 초기화된 연결의 클라이언트 소켓의 이름에 관한 정보를 반환하는데 사용된다. 11.3절 [소켓주소], 150쪽에 정보포맷에 관한 정보가 있다. 연결을 수락하는 것은  socket부분을 연결의  일부로 만들지 않는다. 대신에, 연결될 새로운 소켓을 생성한다. accept의 정상적인 반환치는 새로운 소켓의 파일서술자이다. accept후에, 원래의 소켓인 socket은 열려  있고, 연결되지 않은 상태로 남아있게 된다. 그리고 그것이 닫힐 때까지 듣기를 계속할 것이다. accept를 호출하여 이 소켓을 통하여 추가적인 연결을  수락할 수 있다. 만약 에러가 발생하면 accept는 -1을 반환한다. 다음은 이 함수에 정의되어 있는 errno에러조건이다:

EBADF	socket인수가 유효한 파일서술자가 아니다.

ENOTSOCK	서술자 socket인수가 소켓이 아니다.

EOPNOTSUPP	서술자 socket이 이 기능을 지원하지 않는다.

EWOULDBLOCK	socket이 블록킹안되는 모드로 세트되어 있고, 즉시 연결될 수 있는 미결의 연결이 없다.

accept함수는 비연결성 통신 스타일의 소켓에는 사용될 수 없다.

11.8.4  누가 나에게 연결되어 있나

int getpeername  (int socket, struct sockaddr *addr, size_t *length_ptr) 함수

getpeername함수는 socket이 연결되어 있는 소켓의 주소를 반환한다; addr과 lengh_ptr에 의해 명시된 메모리 공간에 그  주소가 저장된다. *length_ptr안에 주소의 길이를 저장한다.

주소의 포맷에 관하여는 11.3절 [소켓 주소], 150쪽을 보라. 어떤 운영체제에서, getpeername은 단지  인터넷 도메인의 소켓에서만  동작한다.

성공시에 0을  실패시에 -1을  반환한다. 다음은 이 함수에 정의된 errno에러조건이다:

EBADF            socket인수가 유효한 파일 서술자가 아니다.

ENOTSOCK       socket서술자가 소켓이 아니다.

ENOTCONN       socket소켓이 연결되어 있지 않다.

ENOBUFS         충분한 내부 버퍼가 없다.

11.8.5  데이터 전송

일단 소켓이 상대측과 연결이 되면, 데이터를 전송하는 일반적인 write와 read 동작을 수행할 수 있다. (8.2절 [I/O 추상], 105쪽을 보라)  소켓은 양방향 채널이므로 한쪽에서 읽고 쓰기가 가능하다. 소켓의 동작을 명시하는 몇가지 I/O모드가 있다. 그러한 모드를 명시하려면, 더 일반적인 read와 write대신에 recv와 send 함수를 사용해야 한다. recv와 send함수는 특별한 I/O모드를 제어하기 위한 여러가지 플랙을 명시하는데 사용되는 추가적인 인수를 필요로 한다. 예를들어 out-of-band 데이터를 읽거나 쓰기 위하여 MSG_OOB플랙을 명시할 수 있고, 입력을 엿보기 위하여 MSG_PEEK를 세트하거나, 출력시 라우팅정보의 포함을 제어하기 위하여 MSG_DONTROUTE플랙을 명시할 수 있다.

11.8.5.1  데이터 보내기

send함수는 'sys/socket.h'에 선언되어 있다. 만약 flag인수를 0으로 하는 경우면, send대신에 write를 쓸 수 있다. 8.2절 [I/O 추상], 105쪽을 보라. 만약 소켓이 연결되어 있는 상태라면, send나 write를 사용할 때 SIGPIPE라는 시그널을 얻을 것이다. (21.2.6절 [여러가지 시그널], 278쪽을 보라)

int send  (int socket, void *buffer, size_t size, int flags)      함수

send함수는 write와 비슷하지만,  추가적인 flags라는  플랙을 가지고 있다. flags에 가능한  값들은 11.8.5.3절[소켓  데이터 옵션], 170쪽에 서술되어 있다. 이 함수는 전송된 바이트의 수를 반환하며, 실패시 -1을 반환한다. 만약 소켓이 블록킹되지 않는 모드라면,  send가 (write처럼)  데이터의 일부만 전송한 뒤 반환될 것이다.  블록킹되지 않는 모드에 관하여는 8.10절 [파일 상태 플랙] 116쪽을 보라. 어째거나, 성공시의 반환치는 에러없이  전송된 메시지를 가리킬  뿐,  에러없이 수신되는 것처럼 필수적인 것이 아님에 주의하라.

다음은 이 함수에 정의된 errno에러 조건이다.

EBADF	socket인수가 유효한 파일 서술자가 아니다.

EINTR	데이터를 보내기 전에, 시그널에 의해 인터럽트가 걸렸다. 21.5절 [인터럽트 추상] 293쪽을 보라.

ENOTSOCK	socket서술자가 소켓이 아니다.

EMSGSIZE	소켓종류가 메시지를  작게 보낼  것을 요구하는데, 이 메시지가 너무 크다.

EWOULDBLOCK

블록킹되지 않는 모드가  소켓에 세트되어  있으나, 쓰기 동작이 블록킹될 것이다. (보통 send는 동작이 끝날 수 있을 때까지 블록킹된다.)

ENOBUFS	충분한 내부버퍼공간이 없다.

ENOTCONN	당신은 이 소켓에 연결되어 있지 않다.

EPIPE	이 소켓은 연결되어 있지만, 연결이 깨져있다. 이 경우 send는 SIGPIPE시그널을 먼저 생성한다. 만약 시그널이 무시 되거나 블록킹되거나, 핸들러가 반환된다면, send는 EPIPE로 실패를 알리게 된다.

11.8.5.2  데이터 수신하기

recv함수는 'sys/socket.h' 헤더 파일에 선언되어 있다. 만약 flags인수가 0이면 recv대신에 read를 써도 된다. 8.2절 [I/O추상], 105쪽을 참조하라.

int recv  (int socket, void *buffer, size_t size, int flags)       함수

recv함수는 read와 유사하지만, 추가적으로 flags라는  플랙을 가지고 있다. flags에 가능한 값은 11.8.5.3절 [소켓 데이터 옵션], 170쪽에 서술되어 있다.

만약 블록킹되지 않는 모드로 소켓에  세트되어 있고, 읽을 데이터가 없으면, recv는 기다리지 않고 즉시 실패로 끝난다.  블록킹되지 않는 모드에 관한 정보는 8.10절 [파일 상태 플랙], 116쪽을 보라. 이 함수는 수신된 바이트수를 반환하거나,실패시 -1을 반환한다.  다음은 이 함수에 선언된 errno에러 조건이다.

EBADF	socket인수가 유효한 파일서술자가 아니다.

ENOTSOCK	socket서술자가 소켓이 아니다.

EWOULDBLOCK

소켓에 블록킹되지 않는  모드가 세트되어 있고, 읽기 동작이 블록될 것이다. (보통  recv는 읽기 가능한 입력이 있을 때까지 블럭킹된다.)

EINTR	데이터를 읽기도 전에 시스널에 의해 인터럽트가 걸렸다. 21.5절[인터럽트 추상], 293쪽을 보라.

ENOTCONN	당신은 이 소켓에 연결되어 있지 않다.

11.8.5.3  소켓 데이터 옵션

send와 recv에 사용되는 flags인수는 비트 마스크이다. 당신은 이 인수에 값을 얻기 위해 아래 매크로로 비트별-OR연산을 수행할 수 있다. 'sys/socket.h'헤더파일에 모두 정의되어 있다.

int MSG__OOB                                      매크로

out-of-band데이터를 송신하거나 수신한다. 11.8.8절 [Out-of-band 데이터], 173쪽을 보라.

int MSG__PEEK                                       매크로

수신된 큐로부터 데이터를 보기만 하고  그것을 지우지는 말아라. 이것은 send가 아닌 recv같은 입력함수에서만 의미가 있다. send.

int MSG__DONTROUTE                              매크로

이 메시지에 라우팅정보를  포함하지 마라. 이것은  출력 함수에서만 의미를 갖는다. 그리고 분석이나 라우팅 프로그램에서만 흥미롭게 사용된다. 그것에 대하여는 여기서 더 이상 설명하지 않는다.

11.8.6  Byte Stream 소켓 예제

이것은 인터넷 이름공간의 byte stream을 위한 연결을 만드는 프로그램의 예제이다. 한 번 서버에 연결하고 특별한 일을 하지 않는다. 그것은 단지 텍스트 문자열을 서버에게 보내고 종료한다.

#include  <stdio.h>

#include  <errno.h>

#include  <stdlib.h>

#include  <unistd.h>

#include  <sys/types.h>

#include  <sys/socket.h>

#include  <netinet/in.h>

#include  <netdb.h>

#define  PORT                5555

#define  MESSAGE            "Yow!!!  Are  we  having  fun  yet?!?"

#define  SERVERHOST        "churchy.gnu.ai.mit.edu"

void

write_to_server  (int  filedes)

{

int  nbytes;

nbytes  =write  (filedes,  MESSAGE,  strlen  (MESSAGE)  +   1);

if  (nbytes  <  0)

{

perror  ("write");

exit  (EXIT_FAILURE);

}

}

int

main  (void)

{

extern  void  init_sockaddr  (struct  sockaddr_in  *name,

const  char  *hostname,

unsigned  short  int   port);

int  sock;

struct  sockaddr_in  servername;

/*  소켓만들기  */

sock  =  socket  (PF_INET,  SOCK_STREAM,  0);

if  (sock  <  0)

{

perror  ("socket  (client)");

exit  (EXIT_FAILURE);

}

/*  서버에 연결  */

init_sockaddr  (&servername,  SERVERHOST,  PORT);

if  (0  >  connect  (sock,

(struct  sockaddr  *)  &servername,

sizeof  (servername)))

{

perror  ("connect  (client)");

exit  (EXIT_FAILURE);

}

/*  서버에 데이터 보내기  */

write_to_server  (sock);

close  (sock);

exit  (EXIT_SUCCESS);

}

11.8.7  Byte Stream Connection 서버 예제

서버쪽은 좀더 복잡하다. 여러 클라이언트가 동시에 서버에 연결할 수 있도록 하기를 원하므로, 간단히 하나의 read나 recv를 호출하여 하나의 클라이언트로부터 입력을 기다려서는 안 된다. 대신에, 모든 열린 소켓으로부터 입력을 기다리도록(8.6절 [I/O 기다리기], 111쪽), select를 사용하는 것이 올바른 것이다. 이것은 또한 추가적인 연결 요구를 다루도록 허락한다.

이 특별한 서버는 클라이언트로부터 메시지를 한 번 받는 다는 것 외에 특별히 흥미로운 일을 하지 않는다. 그것은 (클라이언트가 연결을 종료하는 결과로써) 파일의 끝(end-of-file)조건을 검출하였을 때,그 클라이언트에 대한 소켓을 닫는다.

이 프로글램은 소켓주소를 세트하기 위해 make_socket과 init_sockaddr를 사용하였다. ; 11.5.7 [Inet 예제],163쪽을 보라

#include  <stdio.h>

#include  <errno.h>

#include  <stdlib.h>

#include  <unistd.h>

#include  <sys/types.h>

#include  <sys/socket.h>

#include  <netinet/in.h>

#include  <netdb.h>

#define  PORT     5555

#define  MAXMSG   512

int

read_from_client  (int  filedes)

{

char  buffer[MAXMSG];

int  nbytes;

nbytes  =  read  (filedes,  buffer,  MAXMSG);

if  (nbytes  <  0)

{

/*  읽기 에러  */

perror  ("read");

exit  (EXIT_FAILURE);

}

else  if  (nbytes  ==  0)

/*  파일의 끝  */

return  -1;

else

{

/*  데이터 읽기  */

fprintf  (stderr,  "Server:  got  message:  `%s'\n",  buffer);

return  0;

}

}

int

main  (void)

{

extern  int  make_socket  (unsigned  short  int  port);

int  sock;

int  status;

fd_set  active_fd_set,  read_fd_set;

int  i;

struct  sockaddr_in  clientname;

size_t  size;

/*  Create the socket and set it up to accept connections.   */

sock  =  make_socket  (PORT);

if  (listen  (sock,  1)  <  0)

{

perror  ("listen");

exit  (EXIT_FAILURE);

}

/*  Initialize the set of active sockets.  */

FD_ZERO  (&active_fd_set);

FD_SET  (sock,  &active_fd_set);

while  (1)

{

/*  Block until input arrives on one or more active sockets.  */

read_fd_set  =  active_fd_set;

if  (select  (FD_SETSIZE,  &read_fd_set,  NULL,  NULL,  NULL)  <  0)

{

perror  ("select");

exit  (EXIT_FAILURE);

}

/*  Service all the sockets with input pending.  */

for  (i  =  0;  i  <  FD_SETSIZE;  ++i)

if  (FD_ISSET  (i,  &read_fd_set))

{

if  (i  ==  sock)

{

/*  Connection request on original socket.  */

size  =  sizeof  (clientname);

if  (accept  (sock,

(struct  sockaddr  *)  &clientname,  &size)  <  0)

{

perror  ("accept");

exit  (EXIT_FAILURE);

}

fprintf  (stderr,

"Server:  connect  from  host   %s,  port  %hd.\n",

inet_ntoa   (clientname.sin_addr),

ntohs  (clientname.sin_port));

FD_SET  (status,  &active_fd_set);

}

else

{

/*  Data arriving on an already-connected  socket.  */

if  (read_from_client  (i)  <  0)

{

close  (i);

FD_CLR  (i,  &active_fd_set);

}

}

}

}

}

11.8.8  Out-of-Band 데이타

연결을 가진 스트림은 일반 데이터보다 높은 우선순위로 배달되는 데이터인 out-of-Band데이터를 가능하게 한다. 전형적으로 out-of-band데이터를 전송하는 이유는 예외적인 상황의 통보 때문이다. out-of-band데이터를 보내는 방법은 MSG_OOB플랙을 세트한 (11.8.5.1절[데이터 전송],169쪽 참고) send를 사용하는 것이다.

수신 프로세스가 순서대로 그것을 받지 않으므로, out-of-band 데이터는 더 높은 우선 순위로 받아진다. 즉, 다음 가능한 out-of-band 데이터를 읽으려면, MSG_OOB플랙을 세트하여 recv를 사용한다. (11.8.5.2절 [데이터 수신], 169쪽을 보라).  보통 읽기명령은 out-of-band데이터를 읽지 않는다. 그것들은 단지 일반 데이터만 읽는다. 소켓이 out-of-band데이터가 있음을 알게 되면, 소유프로세스나 소켓의 프로세스 그룹에 SIGURG시그널을 보낸다. F_SETOWN명령으로 fcntl함수를 사용하여 소유자를 명시할 수 있다.; 8.12절 [인터럽트 입력], 120쪽을 보라. 또한 이 시그널의 핸들러를 설정할 수 있는데, out-of-band데이터를 읽는 등의 적절한 동작을 취하기 위해, 21장[시그널 핸들링], 271쪽을 보라.

다른 것으로, select함수를 사용하여, 미결의 out-of-data를 검사하거나 out-of-band데이터가 있을 때까지 기다릴수 있다.; 그것은 소켓 예외 조건을 기다릴 수 있다. select에 관한 많은 정보는 8.6절 [I/O를 기다리기], 111쪽을 보라.

(SIGURG를 사용하거나 select를 사용할 것이던지) out-of-band 데이터의 통고는 out-of-band데이터가 진행중에 있음을 가리킨다.; 즉 데이터는 아직 실제로 도착하지 않았다. 만약 그것이 완전히 도착하기 전에 out-of-data를 읽으려고 하면, recv 는 EWOULDBLOCK에러로 실패하게 된다.

out-of-band데이터 전송은 자동으로 일반 데이터의 흐름 안에 "마크"를 둔다. 그것은 어디에 out-of-band데이터가 어디에 있을지를 보여준다. out-of-band 데이터가 먼저 보내진 모든 것들을 취소시킨다는 의미를 가질 때 유용하다. 여기에 수신 프로세스에서 마크전에 보내진 보통 데이터인지 아닌지 어떻게 테스트하는지가 나와있다.

success  =  ioctl  (socket,  SIOCATMARK,  &result);

이 함수는 out-of-band 마크 앞의 일반 데이터를 무시하는 것이다.

int

discard_until_mark  (int  socket)

{

while  (1)

{

/*  This is not an arbitrary limit; any size will do.  */

char  buffer[1024];

int  result,  success;

/*  If we have reached the mark, return.   */

success  =  ioctl  (socket,  SIOCATMARK,  &result);

if  (success  <  0)

perror  ("ioctl");

if  (result)

return;

/*  Otherwise, read a bunch of ordinary data and discard it This is guaranteed not to read past the mark if it starts before the mark.   */

success  =  read  (socket,  buffer,  sizeof  buffer);

if  (success  <  0)

perror  ("read");

}

}

만약 마크앞의 일반 데이터를 무시하지 않으려면, 그것을 어떻게든 읽기 위해, out-of-band데이터를 위한 내부시스템버퍼 공간을 만들어야 한다. 만약 out-of-band데이터를 읽으려 하는데 EWOULDBLOCK에러를 받는다면, 약간의 일반데이터를 (그것을 얻었을 때 사용할 수 있도록 저장을 하며) 읽기를 시도하라. 그리고 그것이 공간을 만드는지를 보아라. 여기 예제가 있다.

struct  buffer

{

char  *buffer;

int  size;

struct  buffer  *next;

};

/*  Read the out-of-band data from SOCKET and return it as a `struct buffer', which records the address of the data and its size.

It may be necessary to read some ordinary data in order to make room for the out-of-band data. If so, the ordinary data is saved as a chain of buffers found in the `next' field of the value.   */

struct  buffer  *

read_oob  (int  socket)

{

struct  buffer  *tail  =  0;

struct  buffer  *list  =  0;

while  (1)

{

/*  This is an arbitrary limit. Does anyone know how to do this without a limit? */

char  *buffer  =  (char  *)  xmalloc  (1024);

struct  buffer  *link;

intsuccess;

int  result;

/*  Try again to read the out-of-band data.   */

success = recv (socket,buffer,sizeof  buffer,MSG_OOB);

if  (success  >=  0)

{

/*  We got it, so return it.   */

struct  buffer  *link

=(struct buffer*) xmalloc(sizeof(struct buffer));

link->buffer  =  buffer;

link->size  =  success;

link->next  =  list;

return  link;

}

/*  If we fail, see if we are at the mark.   */

success  =  ioctl  (socket,  SIOCATMARK,  &result);

if  (success  <  0)

perror  ("ioctl");

if  (result)

{

/*  At the mark; skipping past more ordinary data cannot help. So just wait a while.   */

sleep  (1);

continue;

}

/*  Otherwise, read a bunch of ordinary data and save it. This is guaranteed not to read past the mark if it starts before the mark.   */

success  =  read  (socket,  buffer,  sizeof  buffer);

if  (success  <  0)

perror  ("read");

/*  Save this data in the buffer list.   */

{

struct  buffer  *link

= (struct buffer *) xmalloc(sizeof (struct buffer));

link->buffer  =  buffer;

link->size  =  success;

/*  Add the new link to the end of the list.   */

if  (tail)

tail->next  =  link;

else

list  =  link;

tail  =  link;

}

}

}

11.9  데이터그램 소켓 명령

이번 절에서는 어떻게 연결없는 통신스타일(SOCK_DGRAM과 SOCK_RDM스타일)을 사용하는지에 대해 서술한다. 이러한 스타일을 사용하여 패킷안에 데이터를 묶어 넣고, 각 패킷은 독립적인 통신을 하게 된다. 그리고 각 패킷마다 목적지를 명시해야 한다.

데이터그램 패킷은 편지와 같다. 당신은 개별적으로 하나씩 목적지를 명시하여 보낸다. 그것들은 순서가 바뀌어 도착할 수도 있다.

listen과 accept함수는 비연결 통신 스타일을 사용하는 소켓에서 사용할 수 없다.

11.9.1  데이터그램 전송

데이터그램소켓으로 데이터를 보내는 보통 방법은 'sys/socket.h'에 선언되어 있는 sendto함수를 사용하는 것이다. 데이터그램 소켓에 connect를 호출할 수 있지만, 나중에 데이터전송을 위해 기본경로를 한 번 명시한다. 소켓이 기본경로를 가지면, send를 사용할 수 있다.(11.8.5.1절 [데이터 전송],169쪽을 보라) 또한 패킷을 전송하기 위해 write를 사용할 수 있다.(8.2절 [I/O 추상], 105쪽을 보라) addr인수에 AF_UNSPEC의 주소포맷을 사용하여 connect를 호출함으로써 기본경로를 취소할 수 있다. connect함수에 관한 더 많은 정보는 11.8.1절 [연결하기] 166쪽을 보라.

int sendto (int socket, void *buffer size_t size, int flags, struct sockaddr *addr, size_t length) 함수

sendto함수는 목적지가 addr과 length인수로 명시되어 있는 socket을 통하여 버퍼에 있는 데이터를 전송한다. size인수는 전송할 바이트 수를 말한다.

flags는 send에서과 같은 방법으로 해석된다. 11.8.5.3절  [소켓 데이터 옵션], 170쪽을 보라.

반환치와 에러상태도 역시 send와 비슷하다. 그러나 시스템이 에러를 검출하게 할 수는 없다. 대부분의 에러는 패킷이 없어지거나 수신할 주소가 명시되어 있지 않은 것이다. 그래서 당신의 OS는 보통 에러가 난 것을 알지 못할 것이다. 예전의 호출에 관련된 문제 때문에 sendto는 에러가 났다고 할 수도 있다.

11.9.2  데이터그램 수신

recvfrom함수는 데이터그램 소켓으로부터 패킷을 읽고 또한 어디서부터 온 것인지 알려준다.  이 함수는 'sys/socket.h'에 선언되어 있다.

int recvfrom(int socket,void *buffer,size_t size,int flags, struct sockaddr *addr, size_t *length_ptr)함수

recvfrom함수는 socket소켓으로부터 패킷하나를  읽어 buffer라는 버퍼에 넣는다. size인수는 한 번 읽을 때 최대의 바이트 수이다. 만약 패킷이 size바이트보다 더 크면, 패킷의 앞부분만 얻을수  있고 specify flags를 원하지 않으면 사용된다.(8.2절[I/O Primitives]을 보라, 105쪽).

만약 (어디서부터 올 것인지 아는 경우, 누가 보냈던지 똑같이 취급하기 때문에) 누가 패킷을 보냈는지 알 필요가 없다면  recvfrom대신에 평탄한 recv를 쓸 수 있다. (11.8.5.2절 [데이터 수신], 169쪽을 보라)

11.9.3  데이터그램 소켓 예제

여기에 파일이름공간의 데이터그램 스트림을 통해 메시지를 보내는 예제프로그램의 세트가 있다. 클라이언트와 서버프로그램들 모두가 소켓을 만들고 이름짓기 위하여, 11.4절 [파일 이름공간]에 설명되어 있는 make_named_socket 함수를 사용한다.

먼저 여기 서버프로그램이 있다. 그것은 메시지가 도착할 때까지 계속 기다리고 메시지가 오면 다시 송신자에게 되돌려주는 것을 반복한다. 분명히 특별히 유용한 프로그램이 아니지만, 일반적인 아이디어를 보여준다.

#include  <stdio.h>

#include  <errno.h>

#include  <stdlib.h>

#include  <sys/socket.h>

#include  <sys/un.h>

#define  SERVER   "/tmp/serversocket"

#define  MAXMSG   512

int

main  (void)

{

int  sock;

char  message[MAXMSG];

struct  sockaddr_un  name;

size_t  size;

int  nbytes;

/*  Make the socket, then loop endlessly.  */

sock  =  make_named_socket  (SERVER);

while  (1)

{

/*  Wait for a datagram. */

size  =  sizeof  (name);

nbytes  =  recvfrom  (sock,  message,  MAXMSG,  0,

(struct  sockaddr  *)  &  name,  &size);

if  (nbytes  <  0)

{

perror  ("recfrom  (server)");

exit  (EXIT_FAILURE);

}

/*  Give a diagnostic message.  */

fprintf (stderr, "Server: got message:  %s\n",  message);

/*  Bounce the message back to the sender.  */

nbytes  =  sendto (sock,  message,  nbytes,  0,

(struct  sockaddr  *)  &  name,  size);

if  (nbytes  <  0)

{

perror  ("sendto  (server)");

exit  (EXIT_FAILURE);

}

}

}

11.9.4  데이터그램 읽기 예제

여기 위에 있는 서버프로그램에 맞는 클라이언트 프로그램이 있다.

그것은 서버에게 데이터그램을 송신하고나서 응답을 기다린다. 클라이언트를 위한 소켓에는 (서버와 같이)  주어진 이름을 가지고 있음을 주시하라. 그것은 서버가 메시지를 바로 클라이언트에게 되돌리게 한다. 소켓이 연계된 연결상태를 가지지 않기 때문에, 서버는 클라이언트의 이름을 참조하여 그렇게 할 수 있는 것이다.

#include  <stdio.h>

#include  <errno.h>

#include  <unistd.h>

#include  <stdlib.h>

#include  <sys/socket.h>

#include  <sys/un.h>

#define  SERVER   "/tmp/serversocket"

#define  CLIENT   "/tmp/mysocket"

#define  MAXMSG   512

#define  MESSAGE  "Yow!!!  Are  we  having  fun  yet?!?"

int

main  (void)

{

externint  make_named_socket  (const  char  *name);

int  sock;

char  message[MAXMSG];

struct  sockaddr_un  name;

size_t  size;

int  nbytes;

/*  Make the socket.  */

sock  =  make_named_socket  (CLIENT);

/*  Initialize the server socket address.  */

name.sun_family  =  AF_UNIX;

strcpy  (name.sun_path,  SERVER);

size  = strlen (name.sun_path) + sizeof  (name.sun_family);

/*  Send the datagram.  */

nbytes  =  sendto  (sock,  MESSAGE,  strlen  (MESSAGE)  +  1,  0,

(struct  sockaddr  *)  &  name,  size);

if  (nbytes  <  0)

{

perror  ("sendto  (client)");

exit  (EXIT_FAILURE);

}

/*  Wait for a reply.  */

nbytes = recvfrom (sock, message,  MAXMSG,  0,  NULL,  0);

if  (nbytes  <  0)

{

perror  ("recfrom  (client)");

exit  (EXIT_FAILURE);

}

/*  Print a diagnostic message.  */

fprintf  (stderr,  "Client:  got  message:  %s\n", message);

/*  Clean up.  */

remove  (CLIENT);

close  (sock);

}

데이터그램소켓 통신은 비신뢰성임을 명심하라. 이 예제에서,  메시지가 서버에 도달하지 못하거나 서버가 응답을 하지 않을 지라도, 클라이언트 프로그램은 막연히 기다린다. 만약 원한다면, 그것을 종료시키고 리셋시키는 프로그램을 유저가 수행하여야 한다. 자동적인 해결법은 select(8.6절 [I/O기다리기],111쪽을 보라)을 사용하여 응답의 시간제한을 설정하여, 시간제한이 초과되면 메시지를 재전송하거나 소켓을 끝내고 종료하는 것이다.

11.10  inetd 데몬

위에서 듣기를 하는 서버프로그램을 작성하는 법을 설명했다. 어떤 서버는 그것에 접속하기 위해 미리 수행되고 있어야 한다. 인터넷포트를 위한 서비스를 제공하는 다른 방법은 inetd 데몬프로그램을 이용하여 듣기를 하도록 하는 것이다. inetd는 명시된 포트의 세트에서의 메시지를 (select를 사용하여) 기다리며 수행되는 프로그램이다. 그것이 메시지를 받았을 때, (만약 소켓스타일이 연결이 필요하면) 연결을 수락하고나서, 해당 서버프로그램을 수행하도록 자식프로세스를 만들어 낸다. '/etc/inetd.conf'파일내에 포트들와 프로그램들을 명시한다.

11.10.1  inetd 서버들

inetd에 의해 수행되는 서버프로그램을 작성하는 것은 매우 간단하다. 누군가 해당 포트로 연결을 연결하려는 때마다, 새로운 서버 프로세스가 시작된다. 연결은 단지 이 순간에만 존재한다. 소켓은 서버프로세스에서 표준 입력 서술자와 표준 출력 서술자 (서술자 0과 1)처럼 된다. 가끔은 프로그램이 일반적인 I/O기능을 필요로 할 때도 있다.; 실제로, 소켓에 대해 아무것도 모르는 범용 필터 프로그램도 inetd에 의해 바이트 스트림 서버처럼 동작할 수 있다.

비연결성 통신 스타일의 서버에도 inetd를 사용할 수 있다.연결이 필요없으므로, 이러한 서버들을 위해  inetd는 연결을 수락하려는 시도를 하지 않는다.그것은 바로 서술자 0으로부터 수신된 데이터그램패킷을 읽을 수 있는 서버프로그램을 시작한다.서버프로그램은 한 요구를 다룰 수 있고 종료할 수 있다. 또는 더 이상의 도착이 없을 때까지 요구를 읽는 것을 계속하고나서 종료할 수도 있다. inetd를 설정할 때, 위의 두가지 기술 중 어떤 것을 사용할 것인지 명시해야 한다.

11.10.2  inetd 설정

'/etc/inetd.conf'파일은 inetd에게 들을 포트번호와 수행될 서버프로그램이 무엇인지 말해준다. 보통 이 파일의 각 요소는 한 줄씩이다. 그러나, 그것을 화이트스페이스로 시작하는 요소의 첫 번째 줄을 제외한 모든 줄로 나눌 수 있다.  '#'로 시작하는 줄은 주석문이다.

여기 '/etc/inetd.conf'안의 두 기본적인 요소가 있다.

ftp  stream  tcp  nowait  root  /libexec/ftpd  ftpd

talk  dgram  udp  wait  root  /libexec/talkd  talkd

한 요소는 다음 형식을 가진다.

서비스 스타일 프로토콜 대기 사용자명 프로그램 인수

서비스 부분은 이 프로그램이 어떤 서비스를 제공하는 지를 말한다. 그것은 '/etc/services'안에 서비스의 이름이 정의되어 있을 것이다. inetd는 이 요소를 위해 들을 어떤 포트를 사용할 것인가 결정하는데 서비스를 사용한다.

스타일과 프로토콜 부분은 통신스타일과 소켓을 듣는데 사용되는 프로토콜을 명시한다. 스타일은 'SOCK_'이 빠지고, 소문자로 변환된 통신스타일의 이름이다. 예를 들면 stream 또는 dgram이다. 프로토콜은 '/etc/protocols'에 나열된 프로토콜 중 하나이다. 전형적인 프로토콜이름은 바이트 스트림 통신을 위한 'tcp'와 비신뢰성 데이터그램을 위한 'udp'가 있다.

대기 부분은 'wait' 또는 'nowait' 중 하나이다. 만약 통신스타일이 비연결성이고, 서버가 시작시에만 많은 다중 요구를 다룬다면 'wait'을 사용하라. 만약 inetd가 각 메시지나 요구가 들어 올 때마다 새로운 프로세스를 시작할 경우 'nowait'를 사용하라. 만약 스타일이 연결성이면 대기 부분은 'nowait'를 써라.

사용자명은 서버를 수행하는 사용자의 이름이다. inetd는 root로서 수행된다. 그래서 마음대로 그 자식의 사용자ID를 세트할 수 있다. 가능하면 사용자명에 'root'를 피하는 것이 좋다.; 그러나 telnet과 ftp같은 어떤 서버는 사용자 이름과 암호를 읽는다. 이러한 서버들은, 네트워크로부터 오는 데이터에 의한 명령 같이 로그인을 할 수 있도록 처음에 root가 될 필요가 있다.

프로그램 부분과 인수 부분은 서버를 시작할 때 수행되는 명령어를 명시한다. 프로그램은 수행가능한 파일의 절대 경로의 파일이름이어야 한다. 인수는 화이트스페이스로 구분된 단어로 구성되어, 프로그램의 명령어라인 인수가 된다. 인수의 첫 번째 단어는 인수 0으로 (디렉토리 없이) 프로그램의 이름 그자체로 된다.

만약 '/etc/inetd.conf'를 수정하면, inetd프로세스에게 SIGHUP신호를 inetd에게 파일을 다시 읽고, 새로운 내용을 얻게 하여야 한다. ps를 사용하여 inetd의 프로세스ID가 바뀌었는지 확인하면 된다.

11.11  소켓 옵션

이 장에서는 소켓의 행동과 그 밑에 있는 통신 프로토콜을 변경하는 여러 가지 옵션을 설정하고 읽는 방법을 서술한다. 소켓옵션을 다룰 때, 옵션이 속하는 레벨을 명시해 주어야 한다. 이것은 옵션이 소켓인터페이스에 적용되는 지, 저수준 통신 프로토콜 인터페이스에 적용되는 지를 서술한다.

11.11.1  소켓 옵션 함수

여기에 소켓옵션을 변경하고 시험하는 함수가 있다. 'sys/socket.h'안에 그것들이 선언되어 있다.

int getsockopt (int socket, int level, int optname, void *optval, size_t *optlen_ptr)  함수

getsockopt함수는 socket소켓의 level레벨의  optname 옵션의 옵션값에 대한 정보를 얻는다.

옵션 값은 optval이 가리키는 버퍼에 저장된다. 호출 전에, 이 버퍼의 크기인 *optlen_ptr안에 제공해야 한다. 반환할 때,  실제로 버퍼에 저장된 정보의 바이트 수를 담고 있다.

대부분의 옵션은 optval버퍼를 정수값으로 해석된다. 성공시 getsockopt의   실제반환값은 0이고  실패시 -1이다.   다음에 errno에러조건이 정의되어 있다.

EBADF	socket인수가 유효한 파일 서술자가 아니다.

ENOTSOCK	서술자 socket이 소켓이 아니다.

ENOPROTOOPT	optname이 주어진 레벨에 맞지 않다.

int setsockopt  (int socket, int level, int optname, void *optval, size_t optlen) 함수

이 함수는 socket소켓의 level레벨에 소켓옵션 optname을 설정하는데 사용된다. 옵션의 값은 optlen크기를 가진 optval버퍼에 전달된다. setsockopt의 반환치와 에러코드는 getsockopt과 같다.

11.11.2  소켓-레벨 옵션

int SOL__SOCKET                                      상수

이 절에 서술되어 있는 소켓레벨 옵션을 다루려면, getsockopt 나 setsocket의 level인수로 이 상수를 사용하라. 여기에 socket-level 옵션 이름의 표가 있다. 모두 'sys/socket.h'헤더 파일에 정의되어 있다.

SO_DEBUG

하위 프로토콜 모듈의 디버깅  정보의 저장할 것인가 여부를  설정한다. 그값은 int형을 가진다. 0이 아닌 값은 저장하겠다는 것을 의미한다.

SO_REUSEADDR

이 옵션은 bind( 11.3.2절 [주소 설정], 152쪽을 보라)가 이 소켓의 지역주소를 재사용하는 것을 허락할 것인지  여부를 제어한다. 만약 이 옵션을 가능하도록 한다면, 같은 인터넷  포트로부터 두 소켓을 가질수 있게 된다; 그러나, 시스템은 인터넷을 혼란시키는 두 별개의 이름을 가진 소켓을 사용하는  것을 허락하지 않는다. 이 옵션이 있는 이유는 FTP같은 상위 프로토콜이 같은 소켓번호의 재사용을 유지하기를 원하기 때문이다. 값은 int형을 가지며, 0이 아닌 수는 "그렇다(yes)"를 의미한다.

SO_KEEPALIVE

이 옵션은 하위 프로토콜이 주기적으로 메시지를 연결된 소켓으로 전송할 것인지 여부를 제어한다. 만약 서로 이러한 메시지의 응답을 실패하면 연결이 끊어지도록 한다. 값은 int형이고 0이 아닌 값은 "그렇다"를 의미한다.

SO_DONTROUTE

이 옵션은 송신하는 메시지가 일반 메시지 라우팅 기능을  우회할 것인지 여부를 제어한다. 만약 설정되면, 메시지는 직접  네트워크 인터페이스로 보내진다. 값은 int형이며 0이  아닌값은 "그렇다"의 의미이다.

SO_LINGER

이 옵션은 신뢰도 있는 전달을 보장하는 어떤 형태의 소켓이 닫힐 때 여전히 전송되지 못한 메시지들을 가지고 있을 때, 무엇이 일어날 것인지를 명시한다. 11.7.2절  [소켓 닫기],  165쪽을 보라. 값은  구조체 linger이다.

struct linger                                        자료형

이 구조체는 다음 멤버를 가진다:

int l_onoff

이 부분은 참거짓으로 해석된다. 만약  0이 아니면, close는 데이터가 전달되거나 시간제한이 경과될 때까지  프로그램의 수행이 중단된다.

int l_linger

이것은 초단위로 시간제한을 명시한다.

SO_BROADCAST

이 옵션은 데이터그램이 소켓으로부터 브로드캐스트될 것인지 여부를 제어한다. 값은 int형이고 0이 아닌 값은 "그렇다"를 의미한다.

SO_OOBINLINE

만약 이 옵션이 설정되면, 소켓에 수신된 out-of-band 데이터가 보통 입력 큐에 위치하게 된다. 이것은  MSG_OOB플랙 명시 없이 read나 recv를 사용하여 그것을 읽을수 있도록 한다. 11.8.8절  [Out-of-Band 데이터], 173쪽을 보라.  값은 정수형이며 0이  아닌 값은 "그렇다"를 의미한다.

SO_SNDBUF

이 옵션은 출력버퍼의 크기를 얻거나 설정한다. 값은 size_t형이고, 바이트 크기이다.

SO_RCVBUF

이 옵션으 입력버퍼의 크기를 얻거나 설정한다. 값은 size_t형이고, 바이트 크기이다.

SO_STYLE

SO_TYPE

이 옵션은 getsockopt에만 사용될 수 있을  것이다. 그것은 소켓의 통신 스타일을 얻는데 사용된다. SO_TYPE은 전통적인 이름이고 SO_STYLE은 GNU에서  채택한 이름이다.  값은  int형이고, 통신스타일을 나타낸다. 11.2절 [통신 스타일], 150쪽을 보라.

SO_ERROR

이 옵션은 getsockopt에만 사용할 수  있을 것이다. 소켓의 에러상태를 리셋하는데 사용된다. 값은 int형이고 전번의 에러  상태를 나타낸다.

11.12  네트워크 데이터베이스

많은 시스템은 시스템 개발자들에게 알려진 네트워크 리스트를 기록하는 데이터베이스를 가지고 있다. 이것은 보통 '/etc/networks' 또는 네임서버의 같은 파일을 유지한다. 이 데이터베이스는 route같은 라우팅 프로그램에서 유용하다. 그러나 네트워크를 통해 단순히 통신하는 프로그램에게는 별 필요가 없다. 우리는 'netdb.h'에 선언된 이 데이터베이스 접근 함수를 볼 것이다.

struct netent                                         자료형

이 자료형은 네트워크 데이터 베이스의 요소에 관한 정보를 표현하는데 사용된다. 그것은 다음 멥버를 가진다.

char *n_name

이것은 네트워크의 공식적인 이름이다.

char **n_aliases

네트워크의 또다른 이름들이 문자열의 벡터처럼 표현되어 있다. 널포인터로 배열이 끝난다.

int n_addrtype

이것은 네트워크 번호의 종류이다.; 이것은 인터넷 네트워크에 대해 항상 AF_INET이다.

unsigned long int n_net

이것은 네트워크 번호이다.  네트워크 번호는  호스트 바이트 순서로 반환된다. 11.5.5절 [바이트 순서], 161쪽을 보라.

특정 네트워크에 관한 정보를 위해 네트워크 데이터베이스를 검색하기 위해 getnetbyname 또는 getnetbyaddr함수를 사용하라. 정보는 정적으로 할당된 구조체에 정보가 반환된다.; 만약 그것을 저장할 필요가 있으면 그 정보를 복사해두어야 한다.

struct netent * getnetbyname  (const char *name)        함수

getnetbyname함수는 name이라는 네트워크에 관한 정보를 반환한다. 만약 그와 같은 네트워크가 없으면 널포인터를 반환한다.

struct netent * getnetbyaddr  (long net, int type)         함수

getnetbyaddr함수는 type라는 종류와 net라는 숫자를 가진 네트워크에 관한 정보를 반환한다. 인터넷 네트워크의 종류를 위해서 AF_INET값을 명시해야 한다. getnetbyaddr은 만약 그러한 네트워크가 없으면 널포인터를 반환한다. 또한 setent, getnetent 와 endnetent를 사용하여 네트워크 데이터 베이스를 일일이 살필수 있다. 이러한 함수를 사용할 때는 주의하라. 그것들은 재진입성이 없기 때문이다.

void setnetent  (int stayopen)                            함수

이 함수는 네트워크 데이터베이스를 맨앞부터 읽을수 있도록 한다. 만약 stayopen인수가 0이 아니면, 플랙을 세트하여 추후의 getnetbyname이나 getnetbyaddr 호출이 (보통 그 함수들이 닫으므로)데이터베이스를 닫지 않도록 한다. 만약 각 호출시 데이터베이스를 다시 여는 것을 피하여 그러한 함수를 여러번 호출할 때 매우 유용하다.

struct netent * getnetent  (void)                          함수

이 함수는 네트워크 데이터베이스의 다음  요소를 반환한다. 만약 더 이상의 요소가 없으면 널포인터를 반환한다.

void endnetent  (void)                                   함수

이 함수는 네트워크 데이터 베이스를 닫는다.

나는 길가에 앉았습니다.

집이라고야 할 수 있으리까마는

엇 가가 하나를 치고

오가는 행인들 뉘시나 들어와

쉬셔도 좋습니다.

비좁고 누추하나마

곧 가셔도 좋고

얼마를 묵으셔도 좋습니다.

나는 이야기를 하오리다.

그리고 노래를 부르오리다. 있는 대로 다.

듣기 싫은 분은

안 들으셔도 좋습니다.

나는 사는 날까지

이 길가에 앉아 있으렵니다.

그리고 이야기를 하고

노래를 하렵니다.

누구시나 행인은

들어오셔도 좋습니다.

가고 싶으면 아모 때에 가셔도 좋습니다.

나  는 □이 광수

12  저수준 터미날 인터페이스

12장 저수준 터미널 인터페이스

이 장은 터미날 디바이스의 여러가지를 정하는 함수를 설명하고 있다.  당신은 이 함수들을 통해서 입력의 반향(echoing)을 없애는 것과 같은 일을 할 수 있고; 속도와 흐름제어와 같은 직렬통신 문자들을 설정할 수도 있고; 파일의 끝, 코멘드-라인 편집, 신호 보내기, 그리고 유사한 신호 함수들을 위해 사용된 문자들을 변경할수도 있다.

이 장에 있는 함수들의 대부분은 파일 기술자 상에서 동작한다.  8장[Low-Level I/O], 103 페이지에 파일 기술자가 무엇이고, 터미날 디바이스를 위해 파일 기술자를 어떻게 개방하는지에 대한 자세한 정보가 있다.

12.1  터미날 확인하기

이 장에서 설명하고 있는 함수들은 터미날 디바이스에 해당하는 파일에서만 동작한다.  당신은 isatty 함수를 사용해서 터미날과 연관된 파일기술자인지 어떤지를 알아낼 수 있다. isatty 와 ttyname 함수들을 위한 프로토타입은 헤더파일 'unistd.h'에 선언되어 있다.

함수 :  int isatty (int filedes)

이 함수는 만일 filedes가 터미날 디바이스와 연관된 파일 기술자이면 1을 리턴하고, 그렇지 않으면 0을 리턴한다. 만일 파일기술자가 터미날과 연관되어 있다면, 당신은 ttyname함수를 사용해서 그 연관된 파일 이름을 얻을 수 있다.  또한 ctermid함수를 참고하라, 그것은 24.7.1절 [Identifying the Termina.], 349 페이지에 있다.

함수 :  char  *ttyname(int filedes)

만일 파일 기술자 filedes 가 터미날 디바이스와 연관되어 있으면, ttynaem함수는 정적으로 할당되고, 널 문자로 끝나는 문자열에 터미날 파일의 파일이름을 저장하여 그것을 가리키는 포인터를 리턴한다.  만일 파일 기술자가 터미날과 연관되지 않거나, 또는 그 파일이름을 알아낼수 없다면, 널 포인터를 리턴한다.

12.2  입/출력 큐

이 절에 있는 많은 함수들은 터미날 디바이스의 입력과 출력 큐(queues)를 조회한다.  이들 큐는 입/출력 스트림들에 의해 실행된 버퍼링의 커널안에 있는 버퍼링의 한 형식이다. ( 7장 [I/O on Streams], 63 페이지 참조. )

터미날 입력 큐는 또한 선행입력(typeahead) 버퍼로써 사용되어진다. 그 큐는 터미날로부터 받아들여 졌지만, 아직 어느 프로세스에 의해서도 읽혀지지 않은 문자들은 저장한다.

-- 역자주: typeahead : 어떠한 이유로 인하여 입력의 속도가 프로그램의 작업처리 속도보다 빠를 경우 아직 처리되지 못한 입력들은 잠시 내부에 있는 기억장치에 저장해두고 나중에 처리하는 방법

터미날의 입력 큐의 크기는 _POSIX_MAX_INPUT 과 MAX_INPUT 파라미터로 표현된다; 27.6절 [Limits for Files], 374 페이지 참조. 만일 IXOFF 입력 모드 비트가 설정되어서 입력 흐름제어가 가능하다면( 12.4.4절 [Input Modes], 189 페이지 참조), 터미날 드라이버는 큐가 오버플로우가 나는 것을 방지하기 위해, 필요할 때 터미날로 STOP 와 START 문자들을 전송한다.  그렇지 않다면, 터미날로부터 너무 많은 입력이 쇄도할 경우 그 입력을 잃어버릴지도 모른다.  ( 이런상황은 손으로 타이핑을 통해 입력하는것으로는 불가능하다. )

터미날 출력 큐는 입력큐와 같지만, 출력을 위해 쓰인다.  출력큐는 프로세스에 의해 출력되어 졌지만, 아직 터미날로 전송되지 않은 문자들을 저장하고 있다.  만일 IXON 입력 모드 비트( 12.4.4절 [Input Modes], 189페이지 참조)가 설정되어서 출력 흐름 제어가 가능하다면, 터미날 드라이버는 멈춤을 지시하기 위해 터미날에서 보낸 STOP 문자들과 출력의 재전송에 따른다.

터미날의 입력큐의 소거(Clearing)란 받아들이기는 했지만 아직 읽혀지지 않은 문자들을 버리는 것을 의미한다.  유사하게, 터미날 출력큐를 소거하기란 출력됐지만, 아직 전송되지 않은 문자들을 버리는 것을 의미한다.

12.3  입력의 두가지 스타일: Canonical 또는 Not

POSIX시스템은 입력의 두가지 기본 모드를 제공한다:정규와비정규(canonical and noncanonical)

정규(canonical) 입력 프로세싱 모드에서, 터미날 입력은 새줄문자('\n'), EOF, 또는 EOL 문자들로 종료되는 한 라인으로 처리된다.  어떤 입력도 사용자에 의해 한 라인 전체의 입력이 종료되기 전에 읽혀질 수 없고, read 함수는( 8.2절 [I/O Primitives], 105 페이지 참조), 얼마나 많은 바이트가 요청되었는지에 상관없이, 많아야 오직 한줄의 입력을 리턴할 뿐이다.

정규입력 모드에서, 운영체제는 입력 편집 도구를 제공한다:  ERASE 와 KILL 문자들은 텍스트의 현재의 줄에서 편집 명령을 수행하기 위해 특별하게 해석되어진다.

상수 _POSIX_MAX_CANON 과 MAX_CANON는 정규 입력의 한줄에 나타낼 수 있는 최대 바이트수를 한정한다. 27.6절 [Limits for Files] 374 페이지 참조.

비정규입력( noncanonical input ) 프로세싱 모드에서, 문자들은 라인들로 묶여지지 않고, ERASE 와 KILL 프로세싱은 수행되지 않는다.  비정규입력에서 읽혀진 바이트들은 MIN 과 TIME을 설정함으로 인해서 제어된다. 12.4.10절 [Noncanonical Input], 198 페이지 참조.

대부분의 프로그램들이 정규입력을 사용하는 것은, 정규입력의 방법이 사용자에게 라인으로 입력을 편집할수 있는 방법을 제공하기 때문이다. 비정규입력을 사용하는 보통의 이유는 프로그램이 단일-문자 명령들을 받아들이거나 또는 프로그램 자체가 편집도구를 제공할 때 사용된다.

정규 혹은 비정규의 선택은 구조체 struct termios의 멤버인 c_lflag에 ICANON 플로그에 의해 제어된다.  12.4.7절 [Local Modes], 192 페이지 참조.

12.4  터미널 모드

이 절은 어떻게 입력과 출력이 수행되어지는지를 제어하는 다양한 터미널   속성들은 설명하고 있다.  이곳에서 설명한 함수, 자료구조, 그리고 기호 상수들은 모두 헤더파일 'termios.h'에 선언되어 있다.

12.4.1  터미날 모드 데이터 타입들

터미날 속성의 전부는 구조체 struct termios 에 저장되어 있다.  이 구조체는 속성들을 읽고, 설정하기 위한 함수 tcgetattr 과 tcsetattr에서 사용된다.

데이터 타입 :  struct termios

터미날의 모든 입출력 속성을 기록하는 구조체.  이 구조체는 적어도 다음과 같은 멤버들을 포함하고 있다.

tcflag_t c_iflag

입력 모드를 위한 플래그들을 정하는 비트 마스크; 12.4.4절 [Input Modes] 189 페이지 참조.

tcflag_t c_oflag

출력모드를 위해 플래그들을 정하는 비트마스크; 12.4.5절 [Output Modes] 190 페이지 참조.

tcflag_t c_cflag

제어모드를 위해 플래그들을 정하는 비트마스크; 12.4.6절 [Control Modes], 191 페이지 참조.

tcflag_t c_lflag

로컬모드를 위해 플래그들은 정하는 비트마스크;12.4.7절 [Local Modes] 192페이지참조.

cc_t c_cc[NCCS]

다양한 제어 함수들과 연관된 문자들을 정하는 배열; 12.4.9절 [Special Characters] 195 페이지 참조.

구조체 struct termios 는 또한 입력과 출력 전송 속도를 부호화(encode)한 멤버들을 갖고 있지만, 아직 설명하지 않았다.  12.4.8절 [Line Speed]193 페이지를 참조로해서 어떻게 속도값을 시험하고 저장하는지를 살펴보라.

다음절에서 구조체 struct termios 의 멤버들에 대한 자세한 설명을 할 것이다.

데이터 타입 :  tcflag__t

unsigned integer 형으로, 터미날플래그들을 위한 다양한 비트마스크를 나타내는데 사용한다.

데이터 타입 :  cc__t

unsigned integer 형으로 다양한 터미날 제어 함수들과 연관된 문자들을 나타내기 위해 사용한다.

매크로 :  int  NCCS

이 매크로의 값은 c_cc 배열에 있는 요소들의 개수이다.

12.4.2  터미날 모드 함수들

함수 :  int tcgetattr(int filedes, struct termios *termios_p)

이 함수는 파일기술자 filedes와 연관된 터미날 디바이스의 속성을 시험하는데 사용된다.  그 속성은 구조체 termios_p가 가리키는 곳으로 리턴된다. 만일 성공하면, tcgetattr 은 0을 리턴하고, 실패하면 -1을 리턴한다. 다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EBADF	filedes 인수가 유용한 파일기술자가 아니다.

ENOTTY	filedes 가 터미날과 연관이 없다.

함수 :  int tcsetattr(int filedes, int when, const struct termios *termios_p)

이 함수는 파일기술자 filedes와 연관된 터미날 디바이스의 속성을 설정한다.  새로운 속성들은 termios_p가 가리키고 있는 구조체로부터 가져온다.

when 인수는 이미 큐된( 큐에 저장되어 있는 ) 입력과 출력을 어떻게 취급할 것인지를 정하는 것으로 다음값들중 하나를 사용할 수 있다.

TCSANOW	즉시 속성을 변경시켜라.

TCSADRAIN	큐에 저장된 출력이 쓰여질때까지 기다린후에 속성을 변경하라.  당신은 변경하는 파라미터가 출력에 영향을 미칠때 이 옵션을 사용한다.

TCSAFLUSH	이것은 TCSADRAIN과 같지만, 큐에 저장된 입력을 버린다.

TCSASOFT	이것은 위에 있는 어떤것과도 덧붙여 사용할 수 있는 플래그 비트이다.  이것은 터미날 하드웨어에 대한 상황의 변경을 금지하기위한 것이다.  이것은 BSD 확장이다;  BSD가 아닌시스템에서는 아무련 영향을 받지 않는다.

만일 이 함수가 터미날을 제어하고 있는 배경 프로세스로부터 호출된다면, 보통 프로세스 그룹안에 있는 모든 프로세스들은 터미날에 쓰기를 시도하는 프로세스가 있을때와 같은 방법으로, SIGTTOU 시그날을 보낸다.  만일 함수를 호출한 프로세스 자신이 SIGTTOU 신호를 무시하거나 블록하고 있다면 이 명령은 수행되어 지고, 아무런 신호도 보내지 않는다. 24장 [Job control] 335 페이지 참조. 만일 성공하면, tcsetattr 은 0을 리턴하고, 실패하면 -1을 리턴한다. 다음의 errno는 이 함수를 위해 정의된 에러 상황이다.

EBADF	filedes 인수는 유용한 파일기술자가 아니다.

ENOTTY	filedes는 터미날과 아무런 연관이 없다.

EINVAL	when 인수값이 유용하지 않거나, termios_p인수에 잘못된 데이터가 잘못되었거나.

tcgetattr 과 tcsetattr 이 filedes를 터미날과 연관된 파일기술자로 정했다고 할지라도, 그 속성은 파일기술자의 것이 아니라 터미날 디바이스 그 자체의 속성이다.  이것은 변경한 터미날 디바이스의 속성들이 불변함을 의미한다;  만일 나중에 다른 프로세스가 터미날 파일을 개방하면, 그것은 심지어 개방한 파일 기술자로 아무일도 하지 못할지라도 변견된 속성들을 보일 것이다. 유사하게, 만일 단일 프로세스가 동일한 터미날 디바이스에 다중 또는 복제된 파일기술자들을 가졌다면, 터미날 속성 변경은 이 파일기술자 모두의 입력과 출력에 영향을 미친다. 이 의미는, 예를 들어, 당신은 한 개의 파일 기술자만을 개방할 수 없거나, 또는 반향 모드(echoed mode)로 보통의 라인 버퍼의 입력을 터미날로부터 읽기위한 스트림을 개방할 수 없다; 그리고 동시에 당신은 비반향 모드로 동일한 터미날로부터 단일 문자를 읽기위해 사용되는 다른 파일기술자를 가진다.  대신에 당신은, 두 개의 모드 사이에 어떤 것이 전면이고, 어떤 것이 후면인지 정확이 해야한다.

12.4.3  적당하게 터미날 모드 설정하기

당신이 터미날 모드를 설정할 때, 당신은 첫째로 특별한 터미날 디바이스의 현재의 모드를 얻기위해 tcgetattr을 호출하고, 그다음 실제로 당신이 관심을 가진 모드들을 갱신하고, tcseattr에 그 결과를 저장한다.

속성들의 설정을 선택하기 위해 구조체 struct termios를 간단히 초기화하고 직접적으로 tcsetatt에 인수로 주는 것은 나쁜방법이다.  당신의 프로그램은 이 매뉴얼에 문서화되지 않은 멤버들을 지원하는 시스템에서 지금으로부터 수년후에 실행되어질지도 모른다.  이해할수 없는 값들로 이들 멤버들은 설정하는 것을 피하기 위한 방법은 그들을 변경하는 것을 피하는 것이다. 즉, 다른 터미날 디바이스는 적당하게 설정된 다른 모드를 필요로 한다. 그래서 당신은 하나의 터미날 디바이스로부터 다른 터미날 디바이스로 그냥 속성들을 카피하는 것은 피해야한다.

한 멤버가 c_iflag, c_oflag 그리고 c_cflag 등의 독립적 플래그들의 집합일 때, 그들 전체 멤버들을 설정하려하는 것은 잘못된 생각이다, 왜냐하면, 특별한 운영체제는 그들 자신만의 플래그들을 갖고 있기 때문이다.  대신에, 당신은 멤버의 현재의 값들을 얻어서, 당신의 프로그램에서 다룰수 있는 플래그만 변경하고, 다른 플래그들을 변경하지 않도록 해라.  이곳에 구조체 struct termios의 다른 데이터를 그대로 유지하고, 오직 한 플래그(ISTRIP)만을 어떻게 변경하는지에 대한 예가 있다.

int

set_istrip  (int  desc,  int  value)

{

struct  termios  settings;

int  result;

result  =  tcgetattr  (desc,  &settings);

if  (result  <  0)

{

perror  ("error  in  tcgetattr");

return  0;

}

settings.c_iflag  &=  ~ISTRIP;

if  (value)

settings.c_iflag  |=  ISTRIP;

result  =  tcgetattr  (desc,  &settings);

if  (result  <  0)

{

perror  ("error  in  tcgetattr");

return;

}

return  1;

}

12.4.4  입력 모드들

이 절은 입력 프로세싱의 저수준 관점을 완전히 제어하는 터미날 속성 플래그들은 설명하고 있다: 에러 핸들링, 멈춤 신호들, 제어 흐름, 그리고 RET 와 LFD 문자들.

이들 플래그 모드는 구조체 struct termios 의 c_iflag안의 비트들이다.그 멤버는 정수이고, 당신은 오퍼레이터 &, | 그리고 ^ 를 사용해서 플래그들은 변경할 수 있다.  c_iflag_instead를 위한 전체값을 정하여 시도하지 말고, 나머지는 손대지 말고 남겨둬라.( 12.4.3절 [Setting Modes] 188 페이지 참조.)

INPCK

만일 이 비트가 설정되면, 입력 패리티 체크가 가능하다. 만일 설정되지 않으면, 입력에서 패리티 에러가 났는지 체크하지 않는다;  즉 그문자들은 그 어플리케이션에 간단히 주어진다. 입력 프로세싱에서 패리티 체크는 패리티 검출의 여부에 독립하여 있고, 터미날 하드웨어에서의 패리티 발생은 가능하다;  12.4.6절 [Control Modes] 191 페이지 참조. 예를 들어, 당신은 INPCK 입력 모드 플래그를 소거하고 입력에서 패리티 에러들은 버리기위해 PARENB 제어 모드 플래그를 설정할 수 있지만, 여전히 출력에서 패리티 에러는 발생한다.

만일 이비트가 설정되면, 패리티 에러가 발생했을 때 IGNPAR 이나 PARMRK 비트가 설정되었는지에 의존하여 무슨일이 발생한다. 이비트를 설정하지 않는다면, 패리티 에러를 위한 한 바이트를 '\0'로 응용프로그램에 주어야 한다.

IGNPAR

만일 이 비트가 설정되면, 구성(framing) 이나 패리티 에러를 위한 바이트가 무시되어진다.  이것은 INPCK 가 설정되어야지만 유용하다.

PARMRK

만일 이 비트가 설정되면, 패리티나 구성 에러가 있는 입력 바이트들이 프로그램에 표시된다.  이 비트는 INPCK 가 설정되고 IGNPAR이 설정되지 않았을 때 유효하다.  잘못된 바이트를 표시하는 방법은 두 개의 선행 바이트, 377과 0을 사용하는 것이다.  그래서, 프로그램은 터미날로 부터 받은 잘못된 한 개의 바이트로부터 세 개의 바이트들을 읽게되는 것이다.  만일 유용한 바이트가 0377의 값을 가지고, ISTRIP( 밑을 보라.) 이 설정되지 않는다면, 그 프로그램은 그것을 패리티 에러 표시와 혼동할 것이다.  그래서 유용한 바이트 0377은 두 개의 바이트로 프로그램에 주어진다, 즉 이경우에는 0377 0377로...

ISTRIP

만일 이비트가 설정되면, 유용한 입력 바이트들이 일곱 비트로 구성되어 있다; 그렇지 않다면, 여덟비트 모두가 읽혀서 프로그램에서 사용된다.

IGNBRK

만일 이 비트가 설정되면, 멈춤(break)의 상황이 무시된다.

멈춤(break) 상황은 한 바이트보다 긴 0-값을 가진 비트들의 열들을 비동기적 직렬 데이터 전송의 방법으로 정의된다.

BRKINT

만일 이 비트가 설정되고, IGNBRK 가 설정되지 않는다면, 멈춤(break)의 상황은 터미날 입력과 출력의 큐를 소거하고, 터미날과 연관있는 전면 프로세스 그룹을 위해서 SIGINT 신호를 발생한다.  만일 BRKINT 나 IGNBRK 가 설정되지 않았다면, 멈춤(break) 상황은 만일 PARMRK가 설정되지 않으면, 단일 문자 '\0'로 응용프로그램에 주어진다, 그렇지않으면,

세 개의 문자열 '\377', '\0', '\0'로 주어진다.

IGNCR

만일 이 비트가 설정되면, 캐리지리턴 문자('\r')는 입력에서 버려진다.  버려진 캐리지리턴은 당신이 RET 키를 칠 때 캐리지리턴과 라인피드(linefeed) 이둘을 보낸 터미널에서 유용하게 될 것이다.

ICRNL

만일 이 비트가 설정되고 IGNCR 이 설정되지 않으면, 입력으로 받은 캐리지리턴문자를('\r') 새줄문자('\n')로해서 응용프로그램에 주어진다.

INLCR

만일 이 비트가 설정되면, 입력으로 받은 새줄문자('\n')는 캐리지리턴문자('\r')로 응용프로그램에 주어진다.

IXOFF

만일 이 비트가 설정되면, 입력의 정지/시작의 제어가 가능하다.  즉, 컴퓨터가, 프로그램이 처리하는 속도보다 더 빠르게 데이터가 도착하는 것을 방지하기 위해 STOP과 START 문자들을 보낸는것이다. 입력 데이터를 생성하는 실제 터미날 하드웨어에서 STOP 문자에 응답하여 전송을 중단하고, START 문자에 응답하여 다시 전송을 재개한다.  12.4.9.4절 [Strat/Stop Characters], 197 페이지 참조.

IXON

만일 이 비트가 설정되면, 출력의 시작/정지의 제어가 가능하다.  즉, 만일 컴퓨터가 STOP 문자를 받으면, START 문자를 받을때까지 출력을 중단한다.  이 경우, STOP 과 START 문자들은 결코 응용프로그램에 주어지는 것이 아닌다.  만일 이 비트가 설정되지 않는다면그러면 START와 STOP는 원래의 문자들로 읽혀진다. 12.4.9.4절 [Start/Stop Characters], 197 페이지 참조.

IXANY

만일 이 비트가 설정되면,  출력이 STOP를 통해 정지 되어져 있을 때, 어느문자를 가지고도 출력을 재개할수 있다.  즉, 오직 START 문자만 출력을 재개하는 것이 아니라는 것이다.

IMAXBEL

만일 이 비트가 설정되면, 벨이 울리도록 터미날에 BEL문자( code 007)를 보내어 터미날의 입력 버퍼를 가득 채운다.

12.4.5  출력 모드들

이 절은 출력 문자들을 어떻게 해석하고 화면을 채울것인지를 제어하는 터미날 플래그와 필드들에 대해 설명한다.  이들 모두는 구조체 struct termios의 c_oflag 멤버에 들어있다.

c_oflag 멤버 자신은 정수형이고, 당신은 그 플래그들과 필드들을 오퍼레이터 &, |, 그리고 ^를 사용해서 갱신할 수 있다.  c_oflag의 전체의 값을 변경하려 시도하지 말고_대신에 오직 정해진 하나의 플래그만 변경하고 나머지는 손대지 말고 남겨둬라 ( 12.4.3절 [Setting Modes], 188페이지 참조.

매크로 :  int OPOST

이 비트가 설정되면, 출력 데이터는 터미날 디바이스에 적당하게 표시 되는 그런 특별히 정해지지 않은 방법으로 처리된다.  이것은 새줄문자 ('\n')를 캐리지리턴과 라인피드의 쌍으로 대치시켜 포함한다.  만일 이 비트가 설정되지 않는다면, 그 문자들은 그대로 전송된다.

다음 세 개의 비트들은 BSD를 위한 것으로, BSD 가 아닌 시스템에는 아무런 영향을 주지 않는다.  모든 시스템에서, 그들은 OPOST가 설정 되어있어야 효과를 발휘한다.

매크로 :  int ONLCR

만일 이 비트가 설정되면, 출력에 나타난 새줄문자를 문자의 쌍(pair)인 캐리지리턴과 라인피드로 변환한다.

매크로 :  int OXTABS

만일 이 비트가 설정되면, 출력에 나타난 탭문자들을 8칼럼의 탭을 구현하는 적당한 공백으로 변환한다.

매크로 :  int ONOEOT

만일 이 비트가 설정되면, 출력에 나타나는 C-d 문자(code 004)들을 버린다. 이들 문자들은 dial-up 터미날의 연결을 단절시키기 때문이다.

12.4.6  제어 모드들

이 절은 비동기 직렬 데이터 전송에 관계된 제어 파라미터인 터미날 플래그와 필드들을 설명한다.  이들 플래그들은 터미날 포트(네트웍에 연결된 가상-터미날 처럼)의 다른 종류에는 통하지 않을지도 모른다. 이들 모두는 구조체 struct termios의 c_cflag 멤버에 존재한다.

c_cflag 멤버 자체는 정수형으로, 당신은 그 플래그와 필드들을 오퍼레이터 &, |, 그리고 ^ 을 사용해서 갱신할 수 있다. c_cflag의 전체값들을 정하려 시도하지 말고, 대신에 오직 정해진 플래그들만 변경하고, 나머지는 손대지 말고 남겨둬라(12.4.3절 [Setting Modes] 188페이지 참조.)

CLOCAL

만일 이 비트가 설정되면, 터미날이 "국부적으로" 연결되어 있고, 모뎀 상태 라인들은( 캐리어 검출과 같은)무시됨을 의미한다. 만일 이 비트가 설정되지 않고 당신이 O_NONOBLOCK 플래그를 설정하지 않고 open을 호출하면, open은 모뎀이 연결될때까지 블록된다.

만일 이 비트가 설정되지 않고, 모뎀 연결이 끊어지면, SIGHUP 신호를 터미날(만일 그것이 하나를 가진다면)을 위한 제어 프로세스 그룹에 보낸다.  보통, 이것은 탈출(exit) 하려는 프로세스에 때문이다;  21장 [Signal Handling]271 페이지 참조.  연결이 끊어진 후에 터미날로부터 읽기는 파일끝 상황을 발생하고, 쓰기는 리턴될 EIO 에러를 발생한다.  터미날 디바이스는 반드시 닫혀져야 하고, 그 상황을 소거하기 위해 재개방되여져야 한다.

HUPCL

만일 이 비트가 설정되면, 모든 프로세스가 폐쇠된 파일인 터미날 디바이스를 갖거나, 빠져나갈 때 모뎀 단절(disconnect)이 발생한다.

CREAD

만일 이 비트가 설정되면, 입력이 터미날로부터 읽혀질 수 있다.  그렇지않다면, 입력은 그것이 도착했을 때 버려진다.

CSTOPB

만일 이 비트가 설정되면, 두 개의 stop 비트가 사용된다. 그렇지 않다면 오직 한 개의 stop 비트가 사용된다.

PARENB

만일 이 비트가 설정되면, 패리티 비트의 생성과 검출이 가능하게 된다.  12.4.4절 [Input Modes], 189 페이지를 참조로 입력 패리티 에러가 어떻게 다루어지는지를 보아라.

만일 이 비트가 설정되지 않으면, 출력 문자들에 패리티 비트가 더해지지 않고, 입력 문자들에서는 패리티에러를 체크하지 않는다.

PARODD

이 비트는 PARENB가 설정됐을때만 유용하다.  만일 PARODD가 설정되면, 홀수 패리티가 사용되고, 그렇지 않으면 짝수 패리티가 사용된다.

제어 모드 플래그들은 문자당 비트들의 개수를 나타내는 필드를 갖고 있다.  당신은 그 값을 추출하기 위해서 CSIZE 매크로를 사용할 수 있다.  이처럼 : settings.c_cflag & CSIZE

CSIZE

이것은 문자당 비트들의 개수를 위한 마스크이다.

CS5

바이트당 다섯비트를 정한다.

CS6

바이트당 여섯비트를 정한다.

CS7

바이트당 일곱비트들을 정한다.

CS8

바이트당 여덟비트들을 정한다.

CCTS_OFLOW

이 비트가 설정되면, CTS와이어(RS232 프로토콜)에 기반한 출력의 흐름제어가 가능하다.

CRTS_IFLOW

이 비트가 설정되면, RTS 와이어(RS232 프로토콜)에 기반한 입력의 흐름제어가 가능하다.

MDMBUF

이 비트가 설정되면, 출력의 캐리어-기반 흐름제어가 가능하다.

12.4.7  국소 모드들

이 절은 구조체 struct termios 의 c_lflag 멤버의 플래그들을 설명한다. 이들 플래그들은 일반적으로 12.4.4절 [Input Modes] 189 페이지에서 설명한, 모드 플래그들보다는 반향, 신호들, 그리고 정규와 비졍규입력의 선택등과 같은 입력 프로세싱의 고-수준 관점을 제어한다.

c_flag 멤버 그자체는 정수형이고, 당신은 오퍼레이터 &, |, 그리고 ^를 사용해서 그 플래그들과 필드를 변경할 수 있다.  c_lflag의 전체값들을 정하려 시도하지 말고 대신에, 정해진 플래그들만 변경하고, 다른 나머지 것들에는 손대지 말라.

ICANON

이비트가 설정되면, 정규입력 프로세싱 모드로 된다.  그렇지않다면, 입력은 비정규 모드로 처리된다. 12.3절 [Canonical or Not], 186 페이지 참조.

ECHO

이 비트가 설정되면, 입력문자가 반향된다.

ECHOE

이 비트가 설정되면, 스크린의 현재의 라인에 있는 마지막 문자를 지우는 역할을 하는 ERASE 문자를 사용 할 때 그 마지막 문자를 화면상에서 실제로 없앰으로서 사용자에게 문자가 실제로 지워졌음을 확인시킨다. 그렇지않다면 지워진 문자는 다시 반향된다( 프린팅 터미날에 적당한). 이비트는 오직 앞에 표시된 것 만을 제어한다.  ECHOE를 사용하지 않고도, ICANON 비트는 ERASE문자를 실제로 인식하고 입력을 지우는 것을 그 자체에서 제어하고 있다.

ECHOK

이 비트는 KILL 문자에 대한 표시를 가능하게 한다. 이것을 할수 있는 두가지 방법이 있다.  KILL 문자가 눌려진곳의 전체 라인을 화면에서 지우는 것이 좀더 나은 방법이다.  좀더 안좋은 방법은 KILL 문자를 반향한 후에 새줄로 옮기는 것이다.  어떤 시스템은 한가지를 허용하고, 어떤 시스템은 다른 하나를 허용하고, 어떤 것은 당신에게 두가지중 하나를 선택하도록 허용한다. 이 비트가 설정되지 않으면, KILL 문자는 KILL 문자가 존재하지 않는것처럼 단지 그것을 반향한다. 그러면 전의 입력을 지웠던 KILL 문자를 기억하는 것은 사용자의 몫이다;  화면에서 아무런 표시가 없기 때문이다. 이 비트는 오직 전의 표시된 것을 제어한다.  ICANON 비트는 그자체로 KILL 문자를 실제로 인식하고, 입력을 지운다.

ECHONL

이 비트가 설정되고 ICANON 비트가 또한 설정되면 새줄 ('\n') 문자는 심지어 ECHO 비트가 설정되지 않았을때도 반향된다.

ISIG

INTR, QUIT, 그리고 SUSP 문자들을 인식하는지의 여부에 대한 제어비트이다.  이 문자들과 연관된 함수들은 이 비트가 설정됐을때만 수행된다.  규정이나 비규정 입력에 대한 것은 이들 문자들을 해석하는데 아무런 영향이 없다. 당신은 이들 문자들의 인식을 불가능할때 주의해서 사용해야한다.  사용자에 의해 인터럽트될수 없는 프로그램은 사용자에게 매우 불친절한 것이다.  만일 당신이 이 비트를 소거하면, 당신의 프로그램은 사용자에게 이들 문자들과 연관있는 신호를 보내도록 허용하거나, 그 프로그램으로부터 탈출하는 인터페이스를 제공해야한다.  12.4.9절 [Signal Characters] 196 페이지 참조.

IEXTEN

이 비트는 ISIG와 유사하지만, 특별한 문자들에 정의된 제어실행에 대한 것이다.  만일 이 비트가 설정되면, ICANON 과 ISIG 국소 모드 플래그와 IXON과 IXOFF 입력모드 플래그를 위한 디폴트 동작을 무효로 할 것이다.

NOFLSH

보통, INTR, QUIT, 그리고 SUSP 문자들은 터미널의 입력과 출력 큐를 소거하게 한다.  만일 이 비트가 설정되면 그 큐들은 소거되지 않는다.

TOSTOP

이 비트가 설정되고 시스템이 작업 제어를 지원하면, SIGTTOU신호가 터미날에 쓰기를 시도하는 배경 프로세스에 의해 발생되어진다.  24.4절 [Access to the Terminal]336 페이지 참조.

다음 비트들은 BSD 확장이다.  GNU 라이브러리는 당신이 그들을 요청하는 어느 시스템상에서도 사용하도록 이들 심볼들을 정의했지만, BSD 시스템과 GNU시스템을 제외한 다른 시스템에는 아무런 영향을 미치지 않는 비트설정이다.

ECHOKE

BSD 시스템에서, 이 비트는 ECHOK를 설정할 때, KILL 문자를 표시하는 두가지 방법중 하나를 선택하게 한다.  만일 ECHOKE 가 설정되면, KILL문자는 화면에서 라인 전체를 지우고 그렇지않으면 KILL문자는 화면의 다음 라인으로 옮긴다.  ECHOKE의 설정은 ECHOK가 설정됐을때만 유효하다.

ECHOPRT

이 비트는 하드카피 터미날을 조정하는 방법으로 ERASE 문자의 표시를 가능하게 한다.

ECHOCTL

만일 이 비트가 설정되면, 해당하는 텍스트 문자가 따르는 control문자를 '^'표시로 반향한다.  그래서 control-A 는 '^A'처럼 나타난다.

ALTWERASE

이 비트는 WERASE 문자가 지울것인지 결정한다.  단어의 시작점에서 뒤쪽으로 문자를 지운다.  어디에서  단어를 시작할것인지 의문이 생긴다.  만일 이 비트가 설정되면, 단어의 시작점은 공백문자 다음의 비공백 문자가 된다.  만일 이 비트가 설정되지 않으면, 단어의 시작점은 영숫자 문자이거나, 또는 그들이 없는 문자 다음의 underscore이다.

FLUSHO

이 비트는 사용자가 DISCARD 문자를 타입할 때 토글 된다.  이 비트가 설정되어 있는 동안에, 모든 출력은 버려진다.  12.4.9.52JF [Othr Special], 198 페이지 참조.

NOKERNINFO

이 비트의 설정은 STATUS 문자의 처리가 불가능하도록 한다.  12.4.9.5절 [Other Special] 198 페이지 참조.

PENDIN

이 비트가 설정되면, 다시 프린트할 필요가 있는 입력 라인이 있음을 알린다.  REPRINT문자를 치면 이 비트가 설정된다.  그 비트는 재프린트가 종료될 때 까지 설정상태가 지속된다.  12.4.9.2절 [BSD Editing]196 페이지 참조.

12.4.8  라인 속도

터미날 라인 속도는 터미날상에서 얼마나 빨리 데이터를 읽고 쓸수 있는지를 컴퓨터에게 알린다.  만일 터미날이 직렬 라인으로 연결되면, 터미날 속도를 그 라인의 실제 속도에 맞도록 지정해야지, 터미날 자신의 임의대로 그 속도를 지정할 수 없고, 만약 그렇게 했다면 통신은 단절된다. 실제 직렬 포트는 오직 어떤 표준 속도만을 받아들인다.  어떤 특별한 하드웨어는 심지어 모든 표준 속도를 지원하지 않을 것이다.  제로(zero)로 속도를 정하는 것은 연결된 상태를 끊고 모뎀 제어 신호를 끄는 것이다.

만일 터미날이 직렬 라인이 아니라면(예를 들어, 그것이 네트웍 연결이라면), 라인의 속도는 실제 데이터 전송속도에 영향을 받지 않지만, 어떤 프로그램에서는 필요한 채워넣기(padding)의 양을 결정하기 위해 그것을 사용할 것이다.  그것은 실제 터미날의 속도에 대응되는 라인 속도 값을 정하기에 가장 좋지만, 당신은 채워넣기(padding)의 다양한 양을 다양한 값으로 안전하게 시험해야 한다.

각 터미날을 위한 두 개의 라인 속도가 있는데, 입력을 위한것과 출력을 위한 것이다.  당신은 그들을 독립적으로 정할 수 있지만, 대부분의 터미날에서는 동일한 속도를 사용한다.

속도값은 구조체 struct termios 에 저장되어 있지만, 직접적으로 구조체 struct termios안에 있는 그들을 직접적으로 억세스하려 시도하지 말라. 대신에, 당신은 그들을 읽고 저장하기 위해서 다음 함수를 사용하라.

함수 :  speed_t cfgetospeed(const termios *termios_p)

이 함수는 구조제 *termios`p안에 저장되어 있는 출력 라인 속도를 리턴한다.

함수 :  speed_t cfgetispeed (const struct termios *termios_p)

이 함수는 구조체 *termios`p안에 저장되어 있는 입력 라인 속도를 리턴한다.

함수 :  int cfsetospeed (struct termios *termios_p, speed_t speed)

이 함수는 출력 속도로 *termios`p 에 speed를 리턴한다.  보통의 리턴값은 0이고, 에러가 발생하면 -1을 리턴한다.  만일 speed가 속도값이 아니면, cfsetospeed는 -1을 리턴한다.

함수 :  int cfsetispeed (struct termios *termios_p, speed_t speed)

이 함수는 입력 속도로 *termios`p에 speed를 저장한다.  보통의 리턴값은 0이고 에러가 발생하면 -1을 리턴한다.  만일 speed가 속도값이 아니라면, cfsetopeeed는 -1을 리턴한다.

함수 :  int cfsetspeed (struct termios *termios_p, speed_t speed)

이 함수는 입력과 출력의 속도 둘을 위해 *termios`p로 speed를 저장한다. 보통 리턴값은 0이고, 에러가 발생하면 -1을 리턴한다.  만일 speed가 속도값이 아니라면, cfsetspeed는 -1을 리턴한다.  이 함수는 4.4 BSD 확장이다.

데이터타입:  speed_t

speed_type은 unsigned integer형으로 라인의 속도를 나타내기 위해 사용된다.

cfsetospeed 와 cfsetispeed 함수는 그 시스템이 간단히 취급할 수 없는 속도값일 경우에만 에러를 표시한다.  만일 당신이 기본적으로 받아들일 수 있는 속도값을 지정하면, 이 함수는 성공할 것이다.  그러나 그들은 어떤 특별한 하드웨어가 정해진 속도를 실제로 지원할 수 있는지를 체크할 수 없고, 실제로 그들은 당신이 속도를 설정하려 계획하는 디바이스를 알지 못한다.  만일 당신이 처리될수 없는 값으로 특별한 디바이스의 속도를 설정하려고 tcsetattr을 사용하면, tcsetattr은 -1을 리턴한다.

이식성 노트:  GNU 라이브러리에서, 함수들은 입력과 출력으로 초(second)당 비트로 계산된 속도를 받아들인다.  다른 라이브러리들은 속도를 특별한 코드로 지정해준다.  POSIX.1과 이식성을 위해서는, 당신은 속도를 나타내기 위한 다음의 심볼들 중의 하나를 사용해야만 한다;  그들의 정확한 숫자적 값들은 시스템_의존적이지만, 각 이름은 정해진 의미를 갖고 있다.  B110은 110 bps를 위한것이고, B300은 300 bps를 위한것이고.. 등등..이것은 속도를 나타내기 위한 다른 방법들과 전혀 이식성이 없지만, 그들은 특별한 직렬 라인을 지원할 수 있는 속도이다.

B0   B50   B75   B110   B134   B150   B200

B300   B600   B1200   B1800   B2400   B4800

B9600   B19200   B38400

BSD는 유사이름으로 두 개의 부가적 속도 심볼들은 정의한다. EXTA 는 B19200와 같고, EXTB는 B38400과 같다. 이들 유사어는 오래된 것이다.

함수 : int cfmakeraw (struct termios *termios_p)

이 함수는 BSD에서 전통적으로 원래의 모드"로 불리워지는 모드로 *termios`p를 설정하는 쉬운 방법을 제공한다.  그것은 정확히 이런 일을 한다.

termios_p->c_iflag  &=  ~(IGNBRK|BRKINT|PARMRK|ISTRIP

|INLCR|IGNCR|ICRNL|IXON);

termios_p->c_oflag  &=  ~OPOST;

termios_p->c_lflag  &=  ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);

termios_p->c_cflag  &=  ~(CSIZE|PARENB);

termios_p->c_cflag  |=  CS8;

12.4.9  특별 문자들

정규입력에서, 터미날 구동기는 다양한 제어 함수들을 수행하는 특별문자들의 수를 인식한다.  이들은 편집입력을 위한 ERASE 문자(보통은 DEL)와 그리고 다른 편집문자들이 포함된다.  SIGINT 신호를 보내기 위한 INTR 문자( 보통 C -c)와 다른 신호-발생 문자들은, 다른 정규입력이나 비정규 입력 모드에서 유용할 것이다.  모든 이들 문자들은 이 절에 설명되어 있다.

특별한 문자들은 구조체 struct termios의 c_cc 멤버에 정해져있다. 이 멤버는 배열이다; 각 요소는 특별한 규칙을 가진 문자를 정한다. 각 요소는 그 요소의 인덱스를 위한 심볼 상수를 갖는다_예를 들어 INTR은 INTR 문자를 정하는 요소의 인덱스이다, 그래서 INTR 문자로 '='을 정하여 termios.c_cc[INTR]에 '='을 저장한다.

다른 시스템에서, 당신은 _POSIX_VDISABLE를 정하여 특별 문자 함수들을 불가능하게 할 수 있다.  이 값은 어느 가능한 문자 코드와 같지 않다.  27.2절 [Options for Files] 375 페이지를 참고로, 운영체제가 _POSIX_VDISABLE를 당신에게 지원하는지의 여부를 어떻게알수있는지를 보아라.

12.4.9.1  입력 편집을 위한 문자들

이들 특별 문자들은 오직 정규(canonical) 입력 모드에서만 작동된다. 12.3절 [Canonical or Not], 186 페이지 참조.

매크로 :  int VEOF

이것은 특별 제어 문자 배열에 있는 EOF 문자를 위한 첨자(subscript)이다. termios.c_cc[VEOF] 는 그 문자 자체를 저장하고 있다.  EOF 문자는 오직 정규입력 모드에서만 인식된다.  그 문자는 새줄 문자와 같은 방법으로 라인의 종료자(terminator)로서 동작하지만, 만일 EOF 문자가 라인의 처음에 존재된다면, 0바이트를 리턴하여, 파일의 끝임을 지적한다.  EOF 문자 그 자체는 버려진다.  보통, EOF 문자는 C-d 이다.

매크로 :  int VEOL

이것은 특별 제어 문자배열에 있는 EOL 문자를 위한 첨자이다.  termios.c_cc[VEOL] 은 문자 그 자체를 저장하고 있다.  EOL 문자는 오직 정규입력 모드에서만 인식된다.  그것은 새줄 문자처럼 라인 종료자(terminator) 로서 동작한다.  입력 라인에서 마지막 문자로 읽혀진 EOL문자는 버려진다.  한 라인의 끝인 RET을 만들기 위해서 EOL 문자를 사용할 필요가 없다. 단지 ICRNL 플래그를 설정하라.  실제로, 이것이 디폴트 상황이다.

매크로 :  int VERASE

이것은 특별 제어문자 배열에 있는 ERASE문자를 위한 첨자이다.  termios.c_cc[VERASE] 는 그 문자 자체를 저장한다. ERASE문자는 오직 정규입력 모드에서만 인식된다.  사용자가 erase 문자를 타입할 때, 전에 타입된 문자가 버려진다. ( 만일 그 터미날이 다중바이트 (multibyte)문자열을 발생시킨다면, 이것은 입력에서 버려진 것이 한 바이트보다 더 많을 수도 있다.)  이것은 텍스트의 현재 라인보다 앞의것을 지울수 없다.  ERASE 문자 그 자체는 버려진다.  보통 ERASE문자는 DEL 이다.

매크로 :  int VKILL

이것은 특별 제어문자 배열에 있는 KILL 문자를 위한 첨자이다. termios.c_cc[VKILL] 은 문자 그자체를 저장하고 있다.  KILL 문자는 오직 정규입력 모드에서만 인식된다.  사용자가 kill 문자를 타입할 때, 입력의 현재라인의 전체의 내용이 버려진다.  kill 문자도 버려진다.  KILL 문자는 보통 C-u 이다.

12.4.9.2  편집 문자의 BSD 확장

이들 특별 문자들은 오직 정규입력 모드에서만 동작한다.  12.3절 [Canonical of Not], 186 페이지 참조.  그들은 BSD 확장이다; GNU 라이브러리는 그들을 요청하면, 어느 시스템 상에서든지 그 심볼들을 사용하게 하지만, 그문자들은 BSD 시스템을 제외하고는 아무데서도 동작하지 않을 것이다.

매크로 :  int VEOL2

이것은 특별 제어문자 배열에 있는 EOL2 문자를 위한 첨자이다. termios.c_cc[VEOL2] 는 문자 그자체를 저장한다.  EOL2문자는 EOL문자처럼 동작하지만( 위를 보라 ) 다른 문자가 될 수 있다.  그래서, 당신이 입력 라인을 끝내기 위해서 두 개의 문자들을 정할수 있지만,그들중 하나를 위해서 EOL 문자를 설정하고 다른 것을 위해서 EOL2 문자를 설정하라.

매크로 :  int VWERASE

이것은 특별 제어문자 배열에 있는 WERASE 문자를 위한 첨자이다.  termios.c_cc[VWERSE] 는 문자 그자체를 저장한다.  WERASE 문자는 오직 정규입력 모드에서만 동작한다.  그것은 이전 입력의 전체 단어를 지운다.

매크로 :  int VREPRINT

이것은 특별 제어문자 배열에 있는 REPRINT문자를 위한 첨자이다. termios.c_cc[BREPRINT]는 문자 그자체를 저장한다.  REPRINT 문자는 오직 정규입력 모드에서만 인식된다.  그것은 현재의 입력라인을 다시 프린트한다.

매크로 :  int VLNEXT

이것은 특별 제어문자 배열에 있는 LNEXT 문자를 위한 첨자이다. termios.c_cc[VLNEXT]는 문자 그자체를 저장한다.  LNEXT 문자는 오직 IEXTEN이 설정되었을때만 인식된다.  사용자가 타입한 다음문자의 편집을 불가능하게 한다.  이것은 Emacs에서 C-q 와 유사하다.  "LNEXT"는 "literal next"를 나타낸다.  LNEXT문자는 보통 C-v 이다.

12.4.9.3  신호를 발생시키는 문자들

이들 특별문자들은 정규모드나 비정규모드를 상관하지 않고 동작하지만, ISIG 플래그가 설정되었을때만 동작한다.( 12.4.7절 [Local Modes], 192페이지 참조.)

매크로 :  int VINTR

이것은 특별 제어문자 배열에 있는 INTR 문자를 위한 첨자이다. termios.c_cc[VINTR] 은 문자 그자체를 저장한다.  INTR(interrupt)문자는 터미날과 연관된 전면 작업에 있는 모든 프로세스 를 위해 SIGINT 신호를 발생시킨다.  INTR 문자 그자체는 버려진다. 21장 [Signal Handling], 271 페이지를 참조로, 신호에 대한 정보를 보아라. 특별히, INTR 문자는 C-c 이다.

매크로 :  int VQUIT

이것은 특별 제어문자 배열에 있는 QUIT문자를 위한 첨자이다. termios.c_cc[VQUIT] 는 문자 그자체를 저장한다.  QUIT 문자는 터미날과 연관된 전면작업에 있는 모든 프로세스를 위한 SIGQUIT 신호를 발생시킨다.  QUIT 문자 그자체는 버려진다.  21장[Signal Handling], 271 페이지에서 신호에 대한 더 많은 정보를 참조하라. 특별히, QUIT 문자는 C-\이다.

매크로 :  int VSUSP

이것은 특별 제어문자 배열에 있는 SUSP 문자를 위한 첨자이다. termios.c_cc[VSUSP]는 문자 그자체를 저장한다. SUSP(suspend) 문자는 작업제어를 지원하는 동작에서만 인식된다( 24장 [Job Control] 335 페이지 참조).  그것은 터미날과 연관된 전면 작업에 있는 모든 프로세스들에게 보내기위한 SIGTSTP 신호를 발생시킨다. SUSP 문자 그자체는 버려진다.   신호에 대한 자세한 정보는 21장 [Signal Handling] 271 페이지를 참조하라.  몇몇 응용프로그램에서는 SUSP 문자에 대한 해석을 불가능하게 한다. 만일 당신의 프로그램이 그렇게 한다면, 사용자가 그 작업을 멈출수 있게 하기 위한 다른 메카니즘을 제공해야 할 것이다.  사용자가 이 메카니즘을 불렀을 때, 프로그램은 단지 그 프로세스 자신이 아닌, 프로세스의 프로세스그룹에게 SIGTSTP 신호를 보낸다.  21.6.2절 [Signaling Another Process] 295 페이지 참조.

매크로 :  int VDSUSP

이것은 특별 제어문자 배열에 있는 DSUSP 문자를 위한 첨자이다.  termios.c_cc[VDSUSP] 는 문자 그자체를 저장한다.  DSUSP(suspend) 문자는 작업 제어를 지원한는 동작에서만 인식된다(24장 [Job Control] 335 페이지 참조).  그것은 SUSP 문자처럼 SIGTSTP 신호를 보내지만, 프로그램이 입력으로 그것을 읽으려 시도하는, 잘못된 행동을 취할 때 발생한다.  작업제어를 지원하는 모든 시스템에서 DSUSP가 지원되는 것이 아니라 오직 BSD 시스템에서만 지원된다.  신호에 대한 자세한 정보는 21장 [Signal Handling], 271 페이지를 참조하라.  특별히, DSUSP 문자는 C-y 이다.

12.4.9.4  흐름 제어를 위한 특별 문자들

이들 특별 문자들은 정규입력 모드나 비정규입력모드에 상관없이 동작되지만, 그들의 사용은 IXON과 IXOFF 플래그에 의해 제어된다. ( 12.4.4절 [Input Modes], 189 페이지 참조.)

매크로 :  int VSTART

이것은 특별 제어문자 배열에서 START 문자를 위한 첨자이다. termios.c_cc[VSTART] 는 문자 그자체를 저장한다.  START 문자는 IXON 과 IXOFF 입력 모드를 지원하기 위해서 사용된다. 만일 IXON이 설정되고, START 문자를 받으면 보류된 출력을 다시 시작한다.  이때 START 문자는 버려진다.  만일 IXOFF 가 설정되면, 시스템은 터미날에 START 문자를 전송할 것이다.  START 문자의 보통의 값은 C-q 이다. 당신이 무엇으로 정하든지에 상관없이 하드웨어에 C-q로 정해져 있다면 당신은 이값을 변경할 수 없을 것이다.

매크로 : int VSTOP

이것은 특별 제어문자 배열에 있는 STOP 문자를 위한 첨자이다. termios.c_cc[VSTOP] 는 문자 그 자체를 저장한다.  STOP 문자는 IXON 과 IXOFF 입력 모드를 지워하기 위해 사용된다. 만일 IXON이 설정되고, STOP 문자를 받으면 출력을 보류시킨다; 이때 STOP 문자 자체는 버려진다.  만일 IXOFF 가 설정되면, 시스템은 입력큐에서 오버플로우가 발생하는 것을 방지하기 위해 터미날에 STOP 문자를 전송할 것이다.  STOP를 위해서 보통 사용되는 값은 C-s 이다.  당신은 당신이 무엇으로 이값을 변경하든지에 상관없이 하드웨어에서 C-s로 고정되어 있다면, 이값을 변경할 수 없다.

12.4.9.5  다른 특별 문자들

이곳에서는 BSD 시스템에서 의미가 있는 두 개의 부가적 특별 문자들을 설명한다.

매크로 :  int VDISCARD

이것은 특별 제어문자 배열에 있는 DISCARD 문자를 위한 첨자이다. termios.c_cc[VDISCARD] 는 문자 그자체를 저장한다. DISCART문자는 IEXTEN이 설정되었을때만 인식된다.  그 영향은 discard-output 플래그를 토글하게된다.  이 플래그가 설정되면, 모든 프로그램 출력은 버려진다.  플래그 설정은 또한 출력 버퍼안에 현재 존재하는 모든 출력을 버린다.

매크로 :  int VSTATUS

이것은 특별 제어문자 배열에 있는 STATUS 문자를 위한 첨자이다. termios.c_cc[VSTATUS] 는 문자 그 자체를 저장한다.  STATUS 문자의 영향은 현재 프로세스가 어떻게 동작하고 있는지에 대한 상황 메시지를 출력하기 위한 것이다.  STATUS 문자는 오직 정규 입력 모드에서만 인식된다.

12.4.10  비정규입력

비정규입력 모드에서, ERASE 와 KILL 과 같은 특별 편집 문자들을 무시된다.  입력 편집을 위해 사용자에게 부여된 시스템 도구들이 비정규입력 모드에서는 불가능하게 된다.  그래서 모든 입력 문자들( 만약 그들이 신호나 흐름제어 목적을 가진 것이 아니라면)은 응용 프로그램에 정확이 친대로(typed) 인식된다.  응용 프로그램에서 사용자에게 입력을 편집하는 방법을 제공하는 것이 좋다.

비정규입력 모드는 유용한 입력이 있을때까지 기다릴 것인지와 얼마나 기다릴것인지를 제어하기 위한 MIN 과 TIME 이라 불리는 파라미터가 있다.  당신은 유용한 입력이 있을 때, 또는 없을 때, 즉시 리턴되도록 해서 무작정 기다리는걸을 피하는데도 그들을 사용할 수 있다.

MIN과 TIME은 구조체 struct termios의 멤버인 c_cc 배열의 요소로서 저장되어진다. 이 배열의 각 요소는 특별한 규칙을 가지고 있고, 각 요소는 그 요소의 인덱스로 대표되는 심볼 상수를 가지고 있다. VMIN 과 VMAX는 MIN과 TIME 슬롯의 배열의 인덱스들을 위한 이름들이다.

매크로 :  int VMIN

이것은 c_cc 배열안의 MIN 슬롯을 위한 첨자이다.  그래서 termios.c_cc[VMIN]은 그값 자체이다.  MIN 슬롯은 비정규입력 모드에서만 유용하다;  read가 리턴되기 전에 입력 큐에서 받아들여야만 하는 유용한 바이트의 최소 개수를 정하는데 사용된다.

매크로 :  int VTIME

이것은 c_cc 배열에 있는 TIME 슬롯을 위한 첨자이다.  그래서, termios.c_cc[VTIME] 는 그 값 자체이다.  TIME 슬롯은 비정규입력 모드에서만 유용하다; 그것은 0.1초의 단위로 리턴하기전에 입력을 얼마나 기다릴것인지를 정한다.  MIN 과 TIME값은 read가 리턴할때를 위한 표준을 정하는데 영향을 미친다; 그들의 정확한 의미는 그들이 가진 값에 따른다(0이냐 0이 아니냐..) 4개의 가능한 경우가 있다.

□  MIN 과 TIME 둘다 영일 때...

이 경우, read는 큐에 요청된 개수를 넘어서는, 유용한 입력이 있을 때 즉시 리턴한다.  만일 아무런 입력이 없어도 즉시 리턴하는데, 이때 read는 0의 값을 리턴한다.

□  MIN 은 0이지만 TIME은 0이 아닐 때.

이 경우, read는 유용한 입력이 될때까지 TIME 시간동안 기다린다; 단 한 개의 바이트도 요청한 read를 만족시키기에 충분하고, read는 리턴한다.  그것이 리턴할 때 요청된 개수의 유용한 문자를 리턴한다. 만일 시간이 다할때까지 유용한 입력이 없으면 read는 0을 리턴한다.

□  TIME 이 0이지만 MIN은 0이 아닐 때.

이 경우, read는 적어도 큐에 유용한 입력이 MIN 바이트가 될 때까지 기다린다.  그 시간동안, read는 요청된 개수의 유용한 문자들을 리턴한다.  read는 만일 큐에 MIN보다 더 많은 문자가 발생했다면 MIN 문자보다 더 많은 문자를 리턴할 수 있다.

□  TIME 과 MIN 둘다 영이 아닐 때.

이 경우, TIME은 만일 입력이 도착하면, 그 첫 번째 도착한 입력부터 얼마동안 기다릴것인가를 정한다.  read는 MIN 바이트의 입력이 도착하거나, 또는 TIME이 더 이상 아무런 입력없이 경과 되었을 때까지 기다림을 유지한다.  read는 TIME이 첫 번째 입력이 도착하기전에 경과되면 입력없이 리턴할 수 있다.  read는 MIN 보다 더 많은 입력이 큐안에 발생하면 그것을 리턴할 수 있다 만일 MIN이 50이고 당신이 단지 10 바이트만 읽기를 요청하면 무슨일이 발생할것인가?  보통, read는 버퍼에 50바이트가 찰때까지 기다린다(또는 더 일반적으로는, 위에 설명된 기다림의 상황이 만족된다.), 그리고나서, 그들중 10개를 읽고, 나머지 버퍼에 있는 40개는 read의 연속적 호출에서 사용하기 위해서 운영체제안에 남겨둔다.

이식성 노트:  어떤 시스템에서, MIN 과 TIME 슬롯은 실제로 EOF 와 EOL 슬롯과 같다.  MIN 과 TIME은 비정규 입력에서만 사용되고, EOF와 EOL은 정규입력에서만 사용되기 때문에 심각한 문제는 없지만, 완전한 것은 아니다.  GNU 라이브러리는 이렇게 사용하기 위해서 슬롯들을 분리해서 할당한다.

12.5  라인 제어 함수들

이들 함수들은 터미날 디바이스상에서 갖가지 제어 동작을 수행한다. 터미날 억세스에 관하여, 그들은 출력을 하는것처럼 취급된다:  만일 그들 함수중 어떤 것이 터미날을 제어중인 배경 프로세스에서 사용된다면, 보통, 프로세스 그룹의 모든 프로세스들은 SIGTTOU 신호를 받는다.  예외적으로 호출한 프로세스 자신이 무시되거나, SIGTTOU 신호에 의해 블록되어 있다면, 그 경우 명령은 수행되고 아무런 신호를 받지않는다.  24장 [JobControl] 335 페이지 참조.

함수 : int tcsendbreak( int filedes, int duration)

이 함수는 파일 기술자 filedes와 연관된 터미날에 0 비트의 스트림을 전송함으로써 멈춤(break) 상황을 발생시킨다.  멈춤의 존속시간은 duration인수에 의해 제어된다.   만일 duration 이 0이면, 존속시간은 0.25 와 0.5초 사이이다.  0이 아닌값의 의미는 운영체제에 의존된다.  이 함수는 만일 그 터미날이 비동기적 직렬 데이터 포트가 아니면 아무일도 하지 않는다.  리턴값은 보통 0이고, 에러가 발생하면 -1을 리턴한다. 다음의 errno는 이 함수를 위해 정의된 에러 상황이다.

EBADF           filedes 가 유용한 파일 기술자가 아니다.

ENOTTY          filedes 가 터미날 디바이스와 연관이 없다.

함수 :  int tcdrain (int filedes)

tcdrain 함수는 큐에 저장된 모든 출력이 터미날 filedes에 모두 전송되어 질때까지 기다린다.  리턴값은 보통 0이고, 에러가 발생하면 -1을 리턴한다. 다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EBADF	filedes 가 유용한 파일 기술자가 아니다.

ENOTTY	filedes는 터미날 디바이스와 연관이 없다.

EINTR	그 명령은 신호에 의해 인터럽트되어졌다.

21.5절 [Interrupter Primitives], 293 페이지참조.

함수 :  int tcflush (int filedes, int queue)

tcflush 함수는 터미날 파일 filedes와 연관된 입력 그리고/또는 출력큐를 소거하기위해 사용된다.  queue 인수는 소거할 큐를 정하고, 다음값들중 하나를 사용할 수 있다.

TCIFLUSH	받았지만, 아직 읽지않은 입력 데이터를 소거하라

TCOFLUSH	쓰여졌지만, 아직 전송되지 않은 출력데이타를 소거하라.

TCIOFLUSH	큐에 저장된 입력과 출력을 모두 소거하라. 리턴값은 보통 0이고 에러가 발생하면 -1을 리턴한다.  다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EBADF	filedes가 유용한 파일 기술자가 아니다.

ENOTTY	filedes는 터미날 디바이스와 연관이 없다.

EINVAL	적당하지 못한 값이 queue인수로써 공급되었다. 이 함수의 이름이 tcflush라고 지어진 것은 유감스러운데, 왜냐하면 한정 "flush"는 보통 모든 출력이 전송되고, 다른 명령을 사용하기 전에 혼동될 입력이나 출력을 버리는데 사용된다.  유감스럽게도,tcflush는 POSIX로부터 유래됐고,우리는 그것을 변경할 수 없다.

함수 : int tcflow (int filedes,int action)

tcflow 함수는 filedes로 정해진 터미날 파일에서 XON/XOFF 흐름제어에 해당하는 명령을 수행하기 위해 사용된다.  action인수는 무슨 명령을 수행할것인지를 정하고, 다름값들중 하나를 가질수 있다.

TCOOFF          출력의 전송을 중단하라.

TCOON           출력의 전송을 재시작하라.

TCIOFF           STOP 문자를 전송하라.

TCION            START 문자를 전송하라.

STOP 와 START에 대한 자세한 정보를 12.4.9절 [Special Characters] 195 페이지를 참조하라.  리턴값은 보통 0이고 에러가 발생하면 -1이 리턴된다.  다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EBADF	filedes 가 유용한 파일 기술자가 아니다.

ENOTTY	filedes 가 터미날 디바이스와 연관이 없다.

EINVAL	적당하지 못한 값이 action인수로 주어졌다.

12.6  비정규 모드의 예

이곳의 예는 비정규입력모드에서 반향없이 단일 문자들을 읽기 위해서 터미날 디바이스를 어떻게 맞출것인지를 보여주고 있다.

#include  <unistd.h>

#include  <stdio.h>

#include  <stdlib.h>

#include  <termios.h>

/* 원래의 터미날 속성들을 기억하기 위해서 이 변수를 사용하라  */

struct  termios  saved_attributes;

void

reset_input_mode  (void)

{

tcsetattr  (STDIN_FILENO,  TCSANOW,  &saved_attributes);

}

void

set_input_mode  (void)

{

struct  termios  tattr;

char  *name;

/* stdin이 터미날인지 확인하라   */

if  (!isatty  (STDIN_FILENO))

{

fprintf  (stderr,  "Not  a  terminal.\n");

exit  (EXIT_FAILURE);

}

/* 그들을 나중에 재저장 할 수 있도록 터미날 속성들을 저장하라. */

tcgetattr  (STDIN_FILENO,  &saved_attributes);

atexit  (reset_input_mode);

/*  재미있는(?) 터미날 모드를 설정하라. */

tcgetattr  (STDIN_FILENO,  &tattr);

tattr.c_lflag  &=  ~(ICANON|ECHO);  /*  Clear ICANON and ECHO.  */

tattr.c_cc[VMIN]  =  1;

tattr.c_cc[VTIME]  =  0;

tcsetattr  (STDIN_FILENO,  TCSAFLUSH,  &tattr);

}

int

main  (void)

{

char  c;

set_input_mode  ();

while  (1)

{

read  (STDIN_FILENO,  &c,  1);

if  (c  ==  '\004')             /*  C-d  */

break;

else

putchar  (c);

}

return  EXIT_SUCCESS;

}

이 프로그램은 신호와 함께 빠져나가거나 종료되기 전에 원래의 터미널   모드를 재저장하도록 주의를 기울여야 한다.  이것을 확실하게 하기 위해서는 atexit 함수를( 22.3.3절 [Cleanups on Exit] 321 페이지 참조.) 사용하라.  쉘은 한 프로세스가 멈추거나 지속될 때 터미날 모드의 재설정에 조심한다.  24장 [Job Control] 335 페이지 참조.  그러나 어떤 쉘들은 실제로 이것을 하지 않는다, 그래서 당신은 터미날 모드를 재설정하는 작업 제어 신호를 위한 핸들러를 만들어야 할 것이다. 위의 예는 그렇게 한다.

13  수학 함수

13장 수학 함수

이장은 삼각함수와 같은, 수학적 계산을 수행하는 함수들에 대해서 설명한다.  이들 함수들중 대부분은 헤더파일 'math.h'에 선언되어 있다.

이 함수들 모두는 플로팅-포인트 수를 인수로 받아들여 계산하고, double형으로 결과를 리턴한다. 즉, float 와 long double 값들을 계산하는 부가적함수가 될 것이다. 예를들어, cosf 와 cosl은 float 와 long double 인수들로 수행하는, cos 함수의 변형이다.  동시에, 당신이 그들 예약어를 당신이 임의의 목적으로 사용하는 것을 피하라.  1.3.3절 [Reserved Names] 4 페이지참조.

13.1  정의구역과 범위 에러들

이 절에서 설명된 함수들의 대부분은 실수(read nunbers)의 부분집합인 정의구역(domain)에 관하여 수학적으로 정의되어 있다.  예를 들어, acos 함수는 -1 과 1 사이의 정의구역으로 정의된다.  만일 당신이 그 함수에서 정의되어 있는 정의구역 밖의 값을 인수로 사용한다면, 그 함수는 errno를 EDOM으로 설정해서 정의구역 에러가 났음을 지적한다.  IEEE floating point 를 지원하는 컴퓨터에서, error 로 EDOM을 보고하는 함수들은 또한 NaN 도 리턴한다.

이들 함수중 어떤 것은 그들의 정의구역에서 복잡한 값으로 결과를 낸다. 이것과 가장 유사한 예는 음수에 루트를 씌운것의 결과값이다.  이 장에 있는 함수들은 오직 실수 인수만을 취하고, 실수값만을 리턴한다. 그래서 만일 실수가 아닌 값이면, 이것을 정의구역 에러로 취급한다.

연관성 있는 문제로 함수의 결과를 플로팅 포인트로 나타낼 수 없는 것이 있다.  만일 정확하게 나온 결과값의 크기가 나타내기에 너무 크면,  그 함수는 범위 에러임을 지적하기 위해서 ERANGE로 errno를 설정하고, 특별하게 매우 큰 값을 리턴하거나( 매크로 HUGE_VAL ), 그것의 부정(negation)의 값을(-HUGE_VAL)을 리턴한다.  만일 결과의 크기가 너무 작으면, 0의 값을 대신 리턴한다.  이 경우, ERANGE의 에러가 날수도, 또는 나지 않을 수도 있다.

정의구역과 범위에러를 체크하기 위한 좋은 방법은 우선 당신이 수학적함수를 호출하기 전에 errno를 0으로 설정한다음, 나중에 errno를 테스트 해보는 것이다.  errno를 이같이 사용한 결과로, 당신이 에러들을 체크하면, 그 수학함수를 재진입해서 사용할 수 없다.

수학적 함수들의 어떤것도 정의구역이나 범위에러로 인해서 결코 신호를 발생시키지 않는다.  이 특별함이 의미하는 것은 이들 함수들의 사용으로는 SIGEPE신호들의 발생을 당신이 볼 수 없다는 것이다. ( 21장 [Signal Handling] 271 페이지, 신호에 대한 정보 참조.)

매크로 :  double HEGE__VAL

특별히 매우 큰값을 나타내는 표현.  IEEE 플로팅 포인트 형신을 지원하는 기계에서, 그 값은 "무한대"이다.  다른 기계들에서는, 그것은 나타낼 수 있는 가장 큰 양의숫자를 의미한다.  이 매크로의 값은 여러 가지 수학적 함수들이 발생시킬수 있는 오버플로우 상황에서 리턴값을 나타내기 위해서 사용된다.

플로팅 포인트 표현과 그 제한에 대한 자세한 정보는, A.5.3.2절 [Floating Point Parameters] 390 페이지를 참조하라.  매크로 DBL_MAX는 수학적 함수들로 에러를 테스트하기 보다는 다른 용도로 많이 사용한다면 HUGE_VAL보다는 사용하기에 더 적당할 것이다.

13.2  삼각 함수

이들은 sin, cos, 과 tan 함수들과 유사하다. 이들 함수에 주어질 인수들은 라디안(radians : 호도)의 단위로 주어진다. pi 라디안은 180도와 같다는 것을 상기하라.  math 라이브러리에서 pi를 위한 심볼 상수를 정의하지 않았지만, 당신이 만일 필요하다면 당신 스스로 정의할 수 있다.

#define  PI  3.14159265358979323846264338327

당신은 또한 acos(-1.0)의 표현식으로 pi의 값을 계산할 수 있다.

함수 :  double sin (double x)

이 함수는 라디안의 값으로 주어진 x의 sine값을 리턴한다.리턴값은 -1과 1의 범위안에있다.

함수 :  double cos (double x)

이 함수는 라디안의 값으로 주어진 x 의 cosine 값을 리턴한다. 리턴값은 -1 과 1 의 범위안에 있다.

함수 :  double tan (double x)

이 함수는 라디안의 값으로 주어진 x 의 tangent 값을 리턴한다.

다음의 errno는 이 함수를 위해 정의된 에러 상황이다.

ERANGE	수학적으로, tangent 함수는 pi/2 의 홀수배( odd multiples ) 의 특성들을 가진다.  만일인수 x 가 이들 특성들중 하나에 너무 근접해 있다면, tan은 errno를 ERANGE로 설정하고 음이나 양인 HUGE_VAL 중의 하나를 리턴한다.

13.3  역 삼각 함수들

이 절에서는 sine, cosine, tangent 함수들의 역함수인 arc sine, arc cosine, arc tangent 함수들을 설명하고 있다.

함수 :  double asin (double x)

이 함수는 x(sine x 의 결과값인 )의 arc sine의 값을 계산한다.  결과값은 라디안의 단위이다.  수학적으로, 무한대의 많은 값들이 존재한다; 실제로는 -pi/2 와 pi/2(포함해서) 사이의 하나가 리턴된다.  만일 x가 범위밖에 존재하면, asin은 실패하고, errno는 EDOM으로 설정된다.  arc sine 함수는 -1 에서 1 사이의 정의구역을 가지도록 정의되었다.

함수 :  double acos (double x)

이 함수는 x(cosine x의 결과값인 )의 arc cosine의 값을 계산한다. 이 값은 라디안의 단위이다.  수학적으로 무한대의 값이 존재한다; 실제로 0 과 pi(포함해서) 사이의 하나가 리턴된다.  만일 x 가 범위밖에 존재하면, acos는 실패하고, errno를 EDOM으로 설정한다.  arc cosine 함수는 오직 -1에서 1사이의 정의구역을 가지도록 정의됐다.

함수 :  double atan (double x)

이 함수는 x(tangent x 의 결과값인 )의 arc tangent의 값을 계산한다. 이 값은 라디안의 단위이다.  수학적으로 무한대의 값이 존재한다; 실제로는 -pi/2 와 pi/2(포함해서) 사이의 값중 하나가 리턴된다.

함수 :  double atan2 (double y, double x)

이 함수는 두 개의 인수를 가진 arc tangent 함수이다.  이것은 두 인수의 부호가 결과값의 4분구간(quadrant)을 결정하는데 사용되고, x의 값으로 0의 사용이 허용된다는 것을 제외하고는,  y/x의 arc tangent 값을 계산하는것과 유사하다.  리턴값은 라디안으로 주어진고, -pi 와 pi를 포함한 그 사이의 범위를 가진다.

만일 x 와 y 가 평면에 위치한 좌표라면, atan2는 x 를 축으로 하고, 원점으로부터 그 점까지 사이의 부호화된 각도를 리턴한다.  그래서 atan2는 데카르트 좌표를 극 좌표로 변경하는데 유용하다. ( radial 좌표를 계산하기 위해서는, hypot를 사용하라; 13.4절 [Exponents and Logarithms] 205 페이지 참조.)  atan2 함수는 x 와 y 가 둘다 0이면 EDOM으로 errno를 설정한다; 리턴값은 이 경우에 정의되지 않았다.

13.4  지수함수와 대수

함수 :  double exp (double x)

exp 함수는 e( 자연대수에 기초한 )의 x 승의 값을 리턴한다.  만일 결과의 크기가 너무 커서 나타낼 수 없으면, 그 함수는 실패하고, errno를 ERANGE 로 설정한다.

함수 :  double log (double x)

이 함수는 x 의 자연로그를 리턴한다.  수학적으로 정확하게, C로는 대략적으로 exp (log (x)) 는 x 와 같다.  다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EDOM	인수 x 가 음수이다.  log 함수는 양의 인수가 주어져야지만 실수 결과값을 리턴하도록 정의되어있다.

ERANGE	인수가 0이다.  0의 로그값은 정의되지 않았다.

함수 :   double log10 (double x)

이 함수는 10을 밑으로 하는 x의 로그값을 리턴한다.  밑의 값이 다르다는 것을 제외하고는, log 함수와 유사하다.  실제로, log10 (x)는 log (x) / log (10) 과 같다.

함수 :   doule pow (double base, double power)

이것은 base의 power승을 리턴하는 일반적 지수 함수이다.  다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EDOM	base 인수가 음수이고 power은 정수값이 아니다. 수학적으로, 이 경우에 결과가 복잡하게 나올 것이다.

ERANGE	결과에서 언더플로우 나 오버플로우 상황이 검출되었다.

함수 :  double sqrt (double x)

이 함수는 x의 음이아닌 루트의 값을 리턴한다.  sqrt 함수는 만일 x가 음수이면 실패하고, errno를 EDOM으로 설정한다.  수학적으로, 루트값은 복잡한 수이다.

함수 :   double cbrt(double x)

이 함수는 x 의 세제곱근의 값을 리턴한다.  이 함수는 실패할 수 없다; 모든 표현 가능한 실수의 값은 표현 가능한 세제곱근의 값을 가질 수 있다.

함수 :   double hypot (double x, double y)

hypot 함수는 sqrt ( x*x + y*y )의 값을 리턴한다.  (이것은 x 와 y길이의 두변을 가진 직각 삼각형의 빗변의 길이이거나, 원점으로부터 점(x, y) 까지의 거리이다.)  cabs함수에 대한 것은 14.3절 [Absolute Value] 210 페이지 참조.

함수 :   double expml (double x)

이 함수는 exp (x) - 1 과 동일한 값을 리턴한다.  그것은 심지어 x가 0에 근접한 값이라 할지라도 정확하게 계산되어진다._ exp (x) -1은두수의 차이가 거의 같다면 부정확하게 되어질 것이다.

함수 :   double loglp (double x)

이 함수는 log (1 + x)와 동등하다.  이 함수는 심지어 x의 값이 0에 근접해 있을지라도 정확하게 계산되어진다.

13.5  쌍곡선 함수들

이 절에 있는 함수들은 지수 함수들과 연관이 있다; 13.4절 [Exponents and Logarithms] 205 페이지참조.

함수 :   double sinh (double x)

sinh 함수는 수학적으로 exp(x) - exp(-x)/2 로 정의된,x의 쌍곡선 sine을 리턴한다. 만일 x의 값이 너무 크면 그 함수는 실패하고, errno를 ERANGE 로 설정한다; 그것을 오버플로우의 발생이라고 한다.

함수 :  double cosh (double x)

cosh 함수는 수학적으로 exp(x)-exp(-x)/2 로 정의된, x의 쌍곡선 cosine을 리턴한다.  만일 x의 값이 너무 크면, 함수는 실패로 끝나고, errno를 ERANGE로 설정한다; 그것을 오버플로우의 발생이라고 한다.

함수 :   double tanh (double x)

이 함수는 sinh(x)/cosh(x)이라는 수학적 정의를 가진, x 의 쌍곡선 tangent x를 리턴한다.

함수 :   double asinh (double x)

이 함수는 쌍곡선 sine이 x 인 값_x의 역 쌍곡선 sine의 값을 리턴한다.

함수 :   double acosh (double x)

이 함수는 쌍곡선 cosine 이 x 인 값_x의 역 쌍곡선 cosine의 값을 리턴한다.  만일 x 가 1보다 작으면, acosh는 HUGE_VAL을 리턴한다.

함수 :   double atanh (double x)

이 함수는 쌍곡선 tangent가 x인_x의 역 쌍곡선 tangent의 값을 리턴한다.  만일 x의 절대값이 1 과 같거나, 1보다 크면, atanh는 HUGE_VAL을 리턴한다.

13.6  의사-난수

이 절은 의사-난수를 발생시키는 GNU 함수들을 설명하고 있다. 그 숫자들은 실제로는 난수가 아니다. 특별히, 그들은 정해진 주기로 반복적으로 숫자열을 형성하는데, 그 주기는 당신이 원래의 목적을 위해서는 무시 가능한, 그렇게 큰 주기이다.  난수 발생기는 다음 난수를 계산하기 위해 사용할 seed를 항상 기억하고 또한 새로운 seed를 계산하기 위해 작업한다.

비록, 발생된 숫자가 프로그램의 처음 실행에서는 예측할수 없게 보이지만, 숫자들의 열(sequence)은 다음실행에서도 처음과 정확히 동일한 값이 나온다.  이것은 처음 seed값이 항상 동일하기 때문이다.  이것은 프로그램을 디버깅할 때 편리하지만, 만일 당신이 예측불가능한 행동을 하도록 프로그램을 만들기 원한다면 전혀 도움이 되지 못한다.  만일 당신이

단지 의사-난수가 아닌, 진짜 난수를 원한다면, 현재의 시간을 기반으로 해서 seed를 정하라

당신이 난수를 발생시키기 위해서는 동일한 seed 값으로 정해서 다른 컴퓨터 상에서도 반복적인 임의의 숫자열을 얻을 수 있다. seed값으로는 아무런 표준이 없다. 동일한 seed를 다른 C 라이브러리나, 또는 다른 CPU 타입상에서 사용하면 당신은 다른 난수들을 얻을 것이다.

GNU 라이브러리는 표준 ANSI C 난수 함수와 더불어 BSD시스템에서 기원된 다른 함수들도 지원한다.  우리는 표준 함수인 rand 와 srand를 사용하기를 권한다.

13.6.1  ANSI C 난수 함수들

이 절은 ANSI C 표준의 일부분인 난수 함수들을 설명한다. 이들을 사용하기 위해서, 당신은 당신의 프로그램에 헤더파일 'stdlib.h'를 포함시켜야 한다.

매크로 :   int RAND__MAX

이 매크로의 값은 rand 함수에 의해 리턴된 최대 가능한 값을 표현하기 위한 정수 상수 표현이다.  GNU 라이브러리에서, 그것은 32비트에서 표현 가능한 가장 큰 양의 정수값인 037777777이다.  다른 라이브러리에서는 그것은 32767로 작을 것이다.

함수 :   int rand ()

rand함수는 차례로 다음 의사-난수를 리턴한다. 그 값은 0과 RAND_MAX 사이의 범위이다.

함수 :  void srand (unsigned int seed)

이 함수는 의사-난수들의 새로운 열(series)을 위해서 seed로 seed를 만든다.  만일 당신이 srand로 seed를 정하기 전에 rand를 호출하면, 그것은 디폴트값 seed인 1을 사용한다.  실제로 난수를 만들기 위해서는 (단지 의사-난수가 아닌), srand(time(0)) 을 사용하라.

13.6.2  BSD 난수 함수들

이절은 BSD에서 기인된 난수 발생 함수들에 대해서 설명한다.  GNU C 라이브러리에서 이들 함수를 사용하는 것은 아무런 이득이 없다;  우리는 오직 BSD와의 호환성을 위해서 그들을 지원할 뿐이다. 이들 함수들을 위한 프로토타입은 'stdlib.h'이다.

함수 :  long int random ()

이 함수는 차례로 다음 의사-난수를 리턴한다. 리턴된 값의 범위는 0과 RAND_MAX사이다.

함수 :  void srandom (unsigned int seed)

srandom 함수는 정수 seed를 기초로 하는 현재의 난수를 위해서 seed를 설정한다.  만일 당신이 1의 값으로 seed를 공급하면, 이것은 디폴트 난수들을 집합을 재생산하는 결과를 낳는다.  진짜 난수(단지 의사 난수가 아닌)를 만들기 위해서는, sramdom(time(0))을 사용하라.

함수 :  void *initstate(unsigned int seed, void *state, size_t size)

initstate 함수는 난수 발생기 상황을 초기화 하기 위해서 사용된다. state 인수는 상황에 대한 정보를 저장하고 있는 size 바이트의 배열이다. size는 적어도 8바이트를 가져야만 하고,  최상의 크기는 8, 16, 32, 64, 128, 그리고 256 이다.  배열은 클수록 좋다.  리턴값은 상황 정보 배열의 전의 값이다.  당신은 그 상황을 재저장하기 위해서 setstate의 인수로서 나중에 이 값을 사용할 수 있다.

함수 :  void *setstate (void *state)

stetstate 함수는 난수 상황에 대한 정보 state 를 재저장한다.  인수는 initstate나 또는 setstate의 호출로 얻은 결과를 사용해야만 한다. 리턴값은 상황 정보 배열의 기존의 값이다.  당신은 그 상황을 재저장하기 위해서 stestate에 나중에 이값을 인수로 사용할 수 있다.

동천

내 마음속 우리 님의 고운 눈썹을

즈믄 밤의 꿈으로 맑게 씻어서

하늘에다 옮기어 심어 놨더니

동지 섣달 나르는 매서운 새가

그걸 알고 시늉하며 비끼어 가네.

서 정주

14  저 수준 연산 함수들

14장 저 수준 연산 함수들

14.1  "숫자가 아닌" 값들

대부분의 현대적 컴퓨터들에서 사용되는 IEEE 플로팅 포인트 형식은 "숫자가 아닌" 값들도 지원한다.  이들 값들은 NaNs 라고 불린다.  "숫자가 아닌" 값들은 0을 0으로 나누거나 또는 무한대를 무한대로 나누는 것과 같이, 아무런 의미없는 숫자 결과를 갖는 어떤 명령들로부터의 결과이다.

NaNs의 주목할 만한 특성중의 하나가 그들은 그들 자신과 동등하지 않다는 것이다.  그래서, x == x 식에서 만일 x가 NaN 이라면 그 결과는 0이 될 수 있다.  당신은 어떤 값이 NaN 인지 아닌지를 테스트하기 위해서 이것을 사용할 수 있다:  만일 그것이 그 자신과 같지 않다면, 그것은 NaN이다.  그러나 NaN 인지를 테스트 하기위한 권장할 만한 방법으로는 isnan 함수가 있다.  ( 14.2절 [Predicates on Floats], 209 페이지 참조.)

NaN을 한 인수로 사용한 거의 모든 연산 명령들에서는 역시 NaN이 리턴된다.

매크로 :   double NAN

"숫자가 아닌" 값들은 표현하는데 사용.  이 매크로는 GNU 확장으로, IEEE 플로팅 포인트를 지원하는 모든 기계상에서, 그들이 말하는, 즉, "숫자가 아닌" 값들은 지원하는 기계상에서만 유용하다. 당신은 그 기계가 NaNs 를 지원하는지의 여부를 알아보기 위해서 '#ifdef NAN'을 사용할 수 있다.  (물론, 당신은 _GNU_SOURCE로 정의해서, GNU 확장으로 정리를 해야 하고, 또한 'math.h'를 포함해야만한다.)

14.2  플로트의 술어 ( Predrcates )

이 절은 doubles 의 잡다한 테스트 함수들을 설명한다.  이 함수들을 위한 프로토타입은 'math.h'에 있다.  그것들은 BSD 함수로 당신이 _BSD_SOURCE 또는 _GNU_SOURCE라고 정의했을 때 유용하다.

함수 :  int isinf (double x)

이 함수는 x 가 무한대의 음수라면 -1을 리턴하고, x 가 무한대의 양수이면 1을 리턴하고, 그렇지 않으면 0을 리턴한다.

함수 :  int isnan (double x)

이 함수는 만일 x가 "숫자가 아닌" 것이라면 0이 아닌값을 리턴하고, 그렇지 않으면 0을 리턴한다. ( 당신은 같은 결과를 얻기위해서 x != x 도 사용할 수 있다.)

함수 :   int finite (double x)

이 함수는 만일 x 가 한정된 값이거나, "숫자가 아닌" 값이라면 0이 아닌 값을 리턴하고, 그렇지 않다면 0을 리턴한다.

함수 :   double infnan (int error)

이 함수는 BSD 와의 호환성을 위해 제공되었다.  다른 수학적 함수들이 에러가 무엇 때문에 발생했는지를 알기위해서 infan을 사용한다. 인수로는 EDOM 이나 ERANGE 같은 에러 코드를 사용하고;  이때 infan은 이 에러에 적당한 값을 리턴한다.

-ERANGE	이것 또한 인수로써 받아들여지고, -HUGE_VAL에 해당한다.  BSD라이브러리로는, 어떤 기계상에서, infnan 함수는 모든 경우에 심각한 신호를 발생시킨다.  GNU 라이브러리는 ANSI C 규격에 맞지 않기 때문에 이러한 일을 하지 않는다.

이식성 노트 :  이 절에서 설명된 함수들은 BSD 확장이다.

14.3  절대값

이들 함수들은 한 숫자의 절대값( 크기가 아닌 )을 얻기위해서 제공된다. 실수 x 의 절대값은 x 가 양의 수라면 x 이고, x 가 음의 수라면 -x 가 된다.  복소수 z 에서, 그 수의 실수부 x 와 허수부 y 가 있다면, 그때 그 수의 절대값은  sqrt(x*x + y*y) 이다.

abs 와 labs 는 'stdlib.h'의 프로토타입을 갖고 fabs 와 cabs는 'math.h'에 선언되어 있다.

함수 :   int abs (int number)

이 함수는 수의 절대값을 리턴한다.  대부분의 컴퓨터는 2의 보수(two's complement) 정수 표현을 사용한다.  하지만 2의 보수는 INT_MIN ( 가능한 가장작은 int )의 절대값은 표현 할 수 없기 때문에; 그래서, abs (INT_MIN)은 정의되지 않았다.

함수 :  long int labs (long int number)

이 함수는 abs가 int 형을 사용하는 대신, 인수와 결과값에 long int 형을 사용한다는 점을 제외하고는 abs와 유사하다.

함수 :  double fabs (double number)

이 함수는 플로팅-포인트 수의 절대값을 리턴한다.

함수 :   double cabs (struct { double real, imag; } z)

cabs 함수는 실수부로 z.read 과 허수부로 z.imag를 갖고 있는, 복소수 z 의 절대값을 리턴한다.  ( 13.4절 [Exponents and Logarithm] 205 페이지에서 hypot 함수를 참조하라. )

그 값은 :

sqrt (z.real*z.real + z.imag*z.imag)

14.4  표준화 함수들

이 절에서 설명된 함수들은, 내부적으로 2진 기수(binary radix)를 사용하여 표현되는, 플로팅 포인트에서 일어나는 저-수준 동작들을 효율적으로 수행하기 위한 방법을 제공한다; A.5.3.1절 [Floating Point Comcepts] 389 페이지 참조. 이들 함수들은 만일 어떤 표현이 2진수를 사용하지 않을 때라도 같은 동작을 하도록 할 필요가 있지만, 물론 그들은 그 경우에 특별히 효율적이게 보이지는 않는다. 이들 함수들 모두는 'math.h'에 선언되어 있다.

함수 :  double frexp (double value, int *exponent)

frexp 함수는 숫자를 가수부과 지수부로 분리하는데 사용된다. 만일 그 인수가 0 이 아니면, 리턴값은 가수부의 값이 리턴되고, 그것은 항상 1/2 (포함)에서 1 (제외)의 범위안에 있다.  지수부는 *exponent 에 저장된다. 리턴값에 이 2 의 지수승을 곱하면 하면 원래의 숫자값과 같다.

예를들어, frexp (12.8, &exponent) 는 0.8 을 리턴하고 exponent 에  4를 저장한다.  만일 값이 0이면, 리턴값은 0이고 *exponent에 0이 저장되어 진다.

함수 :   double ldexp (double value, int exponent)

이 함수는 2의 exponent승의 값에다, 플로팅 포인트값인 value를 곱한 결과를 리턴한다.  ( 그것은 frexp에 의해 떨어지게 되었던 플로팅-포인트의 값을 붙이는데 사용될 수 있다. )

예를 들어, ldexp (0.8, 4) 는 12.8 을 리턴한다.

다음 함수들은 BSD를 위한 함수들로 ldexp 와 frexp 와 동등하다.

함수 :   double scalb (double value, int exponent)

scalb 함수는 BSD용 ldexp이다.

함수 :   double logb (double x)

이 BSD 함수는 2를 밑으로 하는 로그 x 값에서 정수 부분을 double형으로 리턴한다.  이것은 x 에 포함된 2의 가장 높은 정수 승이다. x의 부호는 무시된다. 예를 들어, logb (3.5) 는 1.0 이고 logb(4.0)은 2.0 이다.

역자주 : 즉 3.5는 2 + 1.5 가 되므로... 2의 가장 높은 정수승이라면 1.0이 되는것이고, 4.0 은 2의 2승이므로 2.0이 된답니다.

그 결과값은 2의 지수로 한 값이 x 로 나뉘어질 때, 그것은 1 (포함)과 2 (제외) 사이의 값이 주어진다.  만일 x 가 0이면, 그값은 음의 무한대이고,(만일 그 기계가 그와같은 값을 지원한다면 ), 아니면, 매우 작은 값이된다.  만일 x 가 무한대이면, 그 값도 무한대이다. logb에 리턴된 값은 frexp가 *exponent에 저장한 값보다 하나가 적다.

함수 :   double copysign (double value, double sign)

copysign 함수는 value의 절대값을 리턴하고, sign은 그 수의 부호와 일치시킨다.  이것은 BSD 함수이다.

14.5  라운딩과 나머지 함수들

역자주: Rounding(라운딩): 라운딩이란 원래 수학에서 수치를 표현 하기 위해서 유효자리 숫자의 크기를 유지하는 방법인데..이곳에서는 플로팅-포인트를 정수화 하는 여러 가지 방법들을 통틀어 라운딩이라고 표현했습니다.

이곳에서 설명된 함수들은 플로팅 포인트의 나누기에서, 라운딩, 절단, 그리고 나머지와 같은 동작을 수행하는 함수들이다.  이들 함수중 어떤 것은 플로팅 포인트 숫자들은 정수값으로 변경한다.  그들은 모두 'math.h'에 선언되어 있다.

당신은 또한 플로팅-포인트 숫자를 int 로 간단히 캐스트(casting) 함으로써 정수로 변경할 수 있다.  이것은 소수부를 버린다. 하지만, 이것은 그 결과값이 실제로 int 로써 표현가능할 때 작업하고 아주 매우 큰 수의 경우에는 불가능하다.  이 절에 있는 함수들은 이러한 문제를 유도하는 대신에 double형으로 리턴한다.

함수 :  double ceil (double x)

ceil 함수는 근접한 정수로 x를 올림을 하고 그 값을 double형으로 리턴한다.  그래서, ceil (1.5)는 2.0이다.

함수 :   double floor (double x)

ceil 함수는 근접한 정수로 x를 내림을해서, 그 값을 double형으로 리턴한다.  그래서, floor (1.5)는 1.0 이고, floor(-1.5)는 -2.0이다.

함수 :   double rint (double x)

이 함수는 현재의 라운딩의 모드에 따라서 x를 정수값으로 만든다. A.5.3.2절 [Floating Point Parameters] 390 페이지에서 다양한 라운딩모드들에 대한 정보를 참조하라.  디폴트 라운딩 모드는 가장 근접한 수로 만드는 것이다.  어떤 기계에서는 다른 모드를 지원하지만, 가장 근접한수로의 라운딩(round-to-nearest)은 당신이 명시적으로 다른 것을 선택할지라도 항상 사용된다.

함수 :   double modf (double value, double *integer_part)

이 함수는 인수 value를 정수부분과 소수부분( -1과 1을 제외한 그 사이의 값)으로 분해한다.  그들의 합은 value와 같다.  그 각 부분들은 value와 같은 부호를 갖기 때문에, 정수부분의 라운딩은 0을 올림과 같다.  modf는 *integer`part에 정수부분을 저장하고, 소수부분을 리턴한다.  예를 들어, modf (2.5, &intpart) 는 0.5를 리턴하고, intpart에 2.0을 저장한다.

함수 :   double fmod (double numerator, double denominator)

이 함수는 denominator로 numerator를 나눈것의 나머지값을 계산한다. 특별히, n이 denominator 로 numerator을 나눈 몫일 때, 그 몫을 정수로 소수점 뒤를 버리고, 그 리턴값은 numerator-n*denominator 이다.  그래서, fmod(6.5, 2.3)의 리턴값은 6.5에서 4.6을 뺀, 1.9가 된다.

역자주:즉...6.5-(6.5/2.3)*2.3 = 6.5-2*2.3 = 6.5-4.6이 된다 이거죠..왜 이렇게 어렵게 할깡~

그 결과는 numerator과 같은 부호를 갖고 denominator의 magnitude 보다 적은 magnitude 를 갖는다.  만일 denominator이 0이면, fmod는 실패하고 errno를 EDOM으로 설정한다.

함수 :   double drem (double numerator, double denominator)

drem 함수는 소수점 이하의 수를 버리는 대신 가장 가까운 정수로 몫 n을 라운딩을 한다는 것을 제외하고는 fmod와 같다.  예를들어, drem ( 6.5, 2.3) 은 6.5-6.9 이므로 -0.4 를 리턴한다.  결과의 절대값은 denominator의 절대값의 반과 같거나 더 적다. fmod (numerator, denominator) 과 drem (numerator, denominator) 과의 차이가 denominator, minus denominator, 또는 0 이거나 항상 존재한다. 만일 denominator 이 0이면, drem 은 실패하고, errno를 EDOM으로 설정한다.

14.6  정수 나누기

이 절은 정수 나눗셈을 수행하기 위한 함수들을 설명한다.  GNU C 라이브러리에 있는 함수들에서 '/' 연산자는 항상 소수점 이하를 버리는 방식으로 버림(round)을 행하지만, 다른 C 에서 '/' 는 음의 인수에는 다르게 버림(round)를 행할 것이다.  div 와 ldiv 는 어떻게 몫에 버림(round)을 할것인지를 정하기 때문에 유용하다: 소수점 이하를 버린다. 나머지는 numeraotr 과 같은 부호를 갖는다.

이들 함수들은 r.quot*denominator + r.rem 은 numerator 과 같도록 리턴값 r 을 정한다.  이들을 사용하기 위해서, 당신은 당신의 프로그램에 헤더파일 'stdlib.h'를 포함시켜야 한다.

데이터 타입 :   div__t

이것은 div 함수에 의해 리턴된 결과를 저장하기 위해 사용되는 구조체형이다.  이것은 다음과 같은 멤버들을 갖는다.

int quot	나눗셈의 몫

int rem	나눗셈의 나머지

함수 :   div_t div (int numerator, int denominator)

div함수는 numerator 을 denominator 로 나눈것에서 몫과 나머지를 구하고, 그 결과를 구조체 div_t에 저장하여 리턴한다. 만일 그 결과가 표현될 수 없다면( 영으로 나누는 것 처럼), 그 결과는 정의되지 않았다.

유용한 것은 아니지만, 하나의 예를 들면...

div_t result;

result = div (20, -6);

이것의 결과로 나온 result.quot 는 -3 이고 result.rem 은 2이다.

데이터 타입 :   ldiv__t

이것은 ldiv 함수에 의해 리턴된 결과를 저장하기 위해 사용되는 구조체이다.  이것은 다음과 같은 멤버들을 갖는다.

long int quot	나눗셈의 몫

long int rem	나눗셈의 나머지

( 이것은 구조체 요소들이 int 형이 아니라 long int 라는 점을 제외하면, div_t 와 동일하다. )

함수 :   ldiv_t ldiv (long int numerator, long int denominator)

ldiv 함수는 인수가 long int 형이고, 그 결과가 ldiv 형의 구조체에 리턴된다는 점을 제외하고는, div 함수와 유사하다.

14.7  숫자의 파싱 ( 구문해석 : parsing )

이 절은 문자열로부터 정수와 플로팅 포인트 숫자를 "읽기" 위한 함수들을 설명한다. 그것은 sscanf 또는 그 함수와 연관된 어떤 것보다,어떤 경우에는 사용하기 더 편리할 것이다. 7.11절 [Formatted Input] 85페이지참조.당신이 직접 문자열에서 토큰을 분리해내는것이 더 안정된 프로그램을 만드는 경우도 있겠지만, 그러면 그때는 하나하나씩 그 토큰을 숫자로 변경해야만 한다.

14.7.1  정수의 파싱

이들 함수들은 'stdlib.h'에 선언되어 있다.

함수 :   long int strtol (const char *string, char **tailptr, int base)

strtol ("스트링에서 long 으로 ") 함수는 문자열의 처음부분을 long int형으로 리턴된 부호화된 정수로 바꾼다.  이 함수는 다음과 같이 문자열을 분해하려 시도한다.

□ 공백문자(whitespace characters)의 열.  공백문자는 isspace 함수로 알아낼 수 있다( 4.1절 [Classification of Characters] 41 페이지 참조.).  이들 공백문자는 버려진다.

□ 임의의 플러스 또는 마이너스 부호('+' 또는 '-').

□ 기수가 정해진, 아무런 공백이 없는 숫자들의 열.  만일 기수가 0이면, '0'( 8진수로 정하기 ) 또는 '0x' 나 '0X'( 16진수 ) 로 시작하는 숫자열이 아니한, 십진수로 가정한다.  즉, 같은 구문을 C에서 정수 상수를 위해 사용한다. 그렇지 않다면 기수는 반드시 2와 35 사이이여야만 한다.  만일 기수가 16이면, 그 숫자는 '0x' 나 '0X' 로 시작될 것이다.

□ 문자열에서 남은 나머지 문자들.  만일 tailptr이 널 포인터가 아니라면, strcol은 *tailptr에 tail을 가리키는 포인터를 저장한다.

만일 그 문자열이 오직 공백으로만 되어 있거나, 정해진 기수의 정수가 가져야하는 처음의 부문자열을 포함하지 않는다면, 아무런 변환이 수행되지 않는다.  이 경우, strcol은 0을 리턴하고, *tailptr에 저장된 값은 스트링의 값이다.

표준 "C" 지역이 아닌 다른 지역에서, 이 함수는 그 지역의 구문에 의존되는 부가적인 동작을 인식할 것이다.  만일 그 문자열이 정수에 맞는 구문을 갖고 있지만 오버플로우 때문에 표현할 수 없다면, strtol은 그 값의 부호에 알맞는 LONG_MAX 또는 LONG_MIN을 리턴한다(A.5.2절 [Range of Type], 388 페이지 참조.) 또한 오버플로우가 났음을 알리기 위해서 errno를 ERANGE로 설정한다.  이것에 대한 예는 이 절의 마지막에 있다.

함수 :   unsigned long int strtoul (const char *string, char **tailptr,  int base)

strtoul (" 문자열을 unsigned long 으로 ") 함수는 unsigned long int 형으로 그 값을 리턴한다는 것을 제외하고는 strtol함수와 같다. 오버플로우가 발생할 경우에 리턴되는 값은 ULONG_MAX 이다(A.5.2 절 [Range of Type] 388 페이지 참조.)

함수 :   long int atol (const char *string)

이 함수는 오버플로우 에러를 검출할 필요가 없다는 점을 제외하고, base 인수로 10을 사용한 strtol 함수와 유사하다.   atol 함수는 현존하는 코드와의 호환성을 위해서 제공되었다; strtol이 더 안정적이다.

함수 :   int atoi (const char *string)

이 함수는 long int 가 아닌 int 형으로 값을 리턴한다는 점을 제외하고, atol 함수와 같다.  atoi 함수는 또한 오래된 함수로 간주되므로 strtol 을 대신 사용하라.

이것은 정수로 이어진 문자열을 분석하고 그들의 합을 구하는 함수이다.

int sum_ints_from_string  (char  *string)

{

int  sum  =  0;

while  (1)  {

char  *tail;

int  next;

/* 앞이 공백으로 되어 있는 것을 건너뛴다.  */

while  (isspace  (*string))  string++;

if  (*string  ==  0)

break;

/* 공백이 제거되었으므로 숫자로 변경하자 */

errno  =  0;

/* 그것을 분석한다. */

next  =  strtol  (string,  &tail,  0);

/* 만일 오버플로우가 발생하지 않았다면, 그것을 더해라 */

if  (errno)

printf  ("Overflow\n");

else

sum  +=  next;

/* 다음 분석을 위해서 지금 현재까지 분석한 위치를 저장해놓는다.  */

string  =  tail;

}

return  sum;

}

14.7.2  플로트의 파싱

이곳에 있는 함수들은 'stdlib.h'에 선언되어 있다.

함수 :   double strtol (const char *string, char **tailptr)

strtol ("문자열을 double 로") 함수는 문자열의 처음부분을 double형을 가진 플로팅-포인트 수로 변환한다.  이 함수는 다음의 경우에 문자열을 분해하려 시도한다.

□ 연속적인 공백문자들. 공백문자(whitespace characters)들은 isspace 함수( 4.1절 [Classification of Characters] 41 페이지 참조. ) 의 사용으로 알아낼 수 있다.  공백은 버려진다.

□ 임의의 플러스나 마이너스 부호 ( '+' 또는 '-')

□ 소수점(보통 '.')을 포함하고 있는 공백없는 숫자열, 그러나 소수점의 위치에 의존한다. (19.6절 [Numeric Formatting] 262페이지 참조.)

□ 문자 임의의 부호 'e' 또는 'E', 그리고 숫자들의 열로 구성된 지수부분.

□ 문자열의 나머지 문자.  만약 tailptr이 널 포인터가 아니라면, 스트링의 tail 포인터를 *tailptr에 저장된다.

만일 그 문자열이 비어있거나, 오직 공백만 있거나, 또는 플로팅-포인트로 판단할만한 문자열을 포함하지 않다면, 아무런 변환이 일어나지 않는다.  이 경우, strtod는 0을 리턴하고, *tailptr에 리턴된 값은 문자열의 값이다.

표준 "C" 와는 다른 지역이라면, 이 함수는 그 지역의 구문에 의존하는 부가적 작동들을 인식할 것이다.

만일 그 문자열이 플로팅-포인트 숫자를 이루기 위한 유용한 구문을 갖고 있지만, 그 값을 오버플로우 때문에 표현할 수 없다면,  strtod는 그 값의 부호에 의존해서, 음이나 양의 HUGE_VAL을 리턴한다. ( 13장. [Mathematics] 203 페이지 참조.).  유사하게, 만일 그 값이 언더플로우(underflow) 때문에 표현할 수 없다면, strtod는 0을 리턴한다.  그것은 또한 오버플로우나 언더플로우의 경우에 errno를 ERANGE 로 설정한다.

함수 :   double atof (const char *string)

이 함수는 오버플로우와 언더플로우 에러를 검출할 필요가 없다는 점을 제외하고는, strtod 함수와 유사하다.  atof 함수는 현존하는 코드와의 호환성을 위해서 제공된다;  strtod를 사용하는 것이 더 안전성이 있다.

내 마음의 어딘 듯 한 편에 끝없는 강물이 흐르네.

돋쳐 오르는 아침 날빛이 뻔질한 은결을 돋우네.

가슴엔 듯 눈엔 듯 또 핏줄엔 듯

마음이 도른도른 숨어 있는 곳

내 마음의 어딘 듯 한 편에 끝없는 강물이 흐르네.

김 영랑의 □끝없는 강물이 흐르네□ 에서

15  탐색과 정렬

15장 탐색과 정렬

이 장은 배열을 탐색하고 정렬하기 위한 함수들을 설명하고 있다. 당신은 배열에 있는 objects의 크기와 요소의 전체갯수와 함께, 인수로써 적당한 비교 함수를 줄수 있다.

15.1  비교 함수 정의하기

배열을 정렬하는 라이브러리 함수를 사용하기 위해서, 당신은 어떻게 그 배열의 요소들을 비교할것인지 알려줘야만 한다. 이것을 하기위해, 당신은 배열의 두 개의 요소를 비교하기 위한 비교함수를 공급한다.  그 라이브러리는 비교하기 위한 두 개의 배열 요소들을 가리키는 포인터가 인수로써 주어지면, 이 함수를 호출할 것이다.  당신의 비교함수는 strcmp( 5.5절 [String/Array Comparison] 49 페이지 참조)가 하는 방법으로 값을 리턴하는데, 그 방법이란 첫번째 인수가 두 번째 인수보다 작으면 음수를 리턴하고, 만일 같으면 0을 리턴하고, 첫번째가 크면 양수를 리턴한다. 이곳에서는 double형의 숫자들로 이루어진 배열을 가지고 작업하는 비교함수의 예가 있다.

int

compare_doubles  (const  double  *a,  const  double  *b)

{

return  (int)  (*a  -  *b);

}

헤더파일 'stdlib.h'는 비교함수들의 데이터 타입을 위한 이름을 정의하고 있다.  이 타입은 GNU 확장이다.

int comparison_fn_t (const void *, const void *);

15.2  배열 탐색 함수

정렬된 배열에서 키(key)값과 같은 요소를 탐색하기 위해서는, bsearch함수를 사용하라.  이 함수를 위한 프로토타입은 헤더파일 'stdlib.h'에 있다.

함수 :  void *bsearch (const void *key, const void *array, size_t count, size_t size,	comparison_fn_t compare)

bsearch 함수는 key 와 동등한 오브젝트를 정렬된 배열 array에서 찾는다.  그 배열은 size 바이트의 크기를 가진, count 개의 요소를 갖는다. compare 함수는 비교를 수행하는데 사용된다.  이 함수는 두 개의 포인터 인수로 호출되고 첫 번째 인수가 두 번째 인수보다 적거나, 같거나, 또는 큼에 해당하는 정수를 리턴한다.  배열의 요소들은 이 비교함수에 의하여 오름차순으로 미리 정렬되어져야만 한다.

리턴값은 key값과 같은 배열의 요소를 가리키는 포인터 이거나, 또는 같은 요소를 발견하지 못하면 널 포인터를 리턴한다.  만일 그 배열이 key값과 같은 것을 한 개 이상 가지고 있다면,  리턴된 것은 정해지지 않았다.  이 함수는 바이너리 탐색 알로리즘을 사용해서 동작한다는것에 기인하여 bsearch라는 이름이 유래됐다.

15.3  배열 정렬 함수

비교 함수를 사용하여 배열을 정렬하기 위해서는, qsort 함수를 사용하라. 이 함수를 위한 프로토타입은 'stdlib.h'에 있다.

함수 : void qsort (void *array, size_t count, size_t size, comparison_fn_t compare)

qsort 함수는 배열 array를 정렬한다.  그 배열은 size의 크기를 가진 count개의 요소를 갖는다. compare 함수는 배열 요소들의 비교를 수행하는데 사용된다.  이 함수는 두 개의 포인터 인수로 호출되고 첫 번째 인수가 두 번째 인수와 비교해서 큰지, 같은지, 적은지를 알 수 있는 값을 리턴한다.

주의: 만일 두 개의 오브젝트가 같다면, 정렬된 후의 그들의 순서는 예측할 수 없다.  그것은 이 정렬이 안정적이지 못하다는 것을 말하고 있다.  배열의 단지 한부분만을 가지고 비교를 할 때 두 개의 요소가 같다고 나오는 경우, 그들은 단지 key 값만 같은 뿐이지 다른 점에 있어서는 차이가 있을 것이다.

만일 당신이 안정적인 정렬을 원한다면, 당신은 두 개의 요소사이에 부족한 다른 측면의 차이나, 그들의 주소로 그들을 비교하는 것과 같은 비교 함수를 써서 이 결과를 얻을 수 있다.

이곳은 위에 정의된 비교 함수를 사용해서, 숫자 순서로 double형의 배열을 정렬하는 예를 보여주고 있다. ( 15.1 [Comparison Functions] 217 페이지 참조.)

{

double  *array;

int  size;

. . .

qsort (array,  size,  sizeof  (double),  compare_doubles);

}

qsort 함수는 "퀵 소트(quick sort)" 알고리즘을 사용해서 동작한다는 사실에 기인하여 qsort라는 이름이 유래되었다.

15.4  탐색과 정렬의 예

이곳에서는 구조체의 배열에 qsort 와 bsearch를 사용하는 예를 보여주고 있다.  배열의 오브젝트들은 strcmp 함수를 사용해서 그들의 이름을 비교함으로써 정렬되어졌다.

그러면, 우리는 그들의 이름에 기초한 개개의 오브젝트들을 살펴볼 수 있다.

#include  <stdlib.h>

#include  <stdio.h>

#include  <string.h>

/* 정렬할 critter들의 배열을 정의하라.  */

struct  critter

{

const  char  *name;

const  char  *species;

};

struct  critter  muppets[]  =

{

{"Kermit",  "frog"},

{"Piggy",  "pig"},

{"Gonzo",  "whatever"},

{"Fozzie",  "bear"},

{"Sam",  "eagle"},

{"Robin",  "frog"},

{"Animal",  "animal"},

{"Camilla",  "chicken"},

{"Sweetums",  "monster"},

{"Dr.  Strangepork",  "pig"},

{"Link  Hogthrob",  "pig"},

{"Zoot",  "human"},

{"Dr.  Bunsen  Honeydew",  "human"},

{"Beaker",  "human"},

{"Swedish  Chef",  "human"}

};

int  count  =  sizeof  (muppets)  /  sizeof  (struct  critter);

/* 이것은 정렬과 탐색을 위해 사용하는 비교함수이다. */

int

critter_cmp (const struct critter *c1, const struct critter *c2)

{

return  strcmp  (c1->name,  c2->name);

}

/* critter에 대한 정보를 프린트하라. */

void

print_critter  (const  struct  critter  *c)

{

printf  ("%s,  the  %s\n",  c->name,  c->species);

}

/*  정렬된 배열을 살펴보라 */

void

find_critter  (const  char  *name)

{

struct  critter  target,  *result;

target.name  =  name;

result = bsearch(&target, muppets, count, sizeof (struct critter),

critter_cmp);

if  (result)

print_critter  (result);

else

printf  ("Couldn't  find  %s.\n",  name);

}

/*  Main 함수 */

int

main  (void)

{

int  i;

for  (i  =  0;  i  <  count;  i++)

print_critter  (&muppets[i]);

printf  ("\n");

qsort (muppets, count,  sizeof  (struct  critter),  critter_cmp);

for  (i  =  0;  i  <  count;  i++)

print_critter  (&muppets[i]);

printf  ("\n");

find_critter  ("Kermit");

find_critter  ("Gonzo");

find_critter  ("Janice");

return  0;

}

이 프로그램의 출력은 다음과 같다.

Kermit,  the  frog

Piggy,  the  pig

Gonzo,  the  whatever

Fozzie,  the  bear

Sam,  the  eagle

Robin,  the  frog

Animal,  the  animal

Camilla,  the  chicken

Sweetums,  the  monster

Dr.  Strangepork,  the  pig

Link  Hogthrob,  the  pig

Zoot,  the  human

Dr.  Bunsen  Honeydew,  the  human

Beaker,  the  human

Swedish  Chef,  the  human

Animal,  the  animal

Beaker,  the  human

Camilla,  the  chicken

Dr.  Bunsen  Honeydew,  the  human

Dr.  Strangepork,  the  pig

Fozzie,  the  bear

Gonzo,  the  whatever

Kermit,  the  frog

Link  Hogthrob,  the  pig

Piggy,  the  pig

Robin,  the  frog

Sam,  the  eagle

Swedish  Chef,  the  human

Sweetums,  the  monster

Zoot,  the  human

Kermit,  the  frog

Gonzo,  the  whatever

Couldn't  find  Janice.

16  패턴 매칭 ( Pattern Matching )

16장 패턴 매칭

GNU C 라이브러리는 두종류의 형태(pattern)를 조화시키 위한 도구를 제공한다: 보통의 표현들과 파일-이름 와일드카드.  라이브러리는 또한, 쉘이 하는 방법대로 변수를 확장하고 명령을 참조하고, 단어로 텍스트를 구문분석하는 그러한 도구들을 제공한다.

16.1  와일드카드 매칭 ( Winldcard Matching )

이 절은 특별한 문자열에 대응하여 와일드카드(wildcard) 패턴과 어떻게 매치할것인지를 설명한다.  그 결과는 예, 또는 아니오 의 대답이다: 그 문자열이 패턴과 맞는지 틀린지...  여기에 설명된 모든 기호(sysbols)들은 모두 'fnmatch.h/에 선언되어 있다.

함수 :  int fnmatch (const char *pattern, const char *string, int flags)

이 함수는 문자열 string 이 패턴 pattern 과 맞는지, 어떤지를 시험한다.  만일 그들이 서로 맞으면 0을 리턴하고; 그렇지 않으면 영이 아닌 값 FNM_NOMATCH 를 리턴한다.  pattern 과 string인수들은 둘다 문자열이다.  flags 인수는 세밀한 매칭(matchin)을 하기위한 플래그 비트들의 조합니다.  정의된 플래그들은 아래를 참조하라.

GNU C 라이브러이에서, fnmatch는 "에러"를 발생시킬수 없다. 그것은 항상 그 둘이 서로 맞는지에 대한 대답을 리턴하기 때문이다.  그렇지만, fnmatch에서 파생된 다른 함수들은 때때로 "에러"를 보고할 것이다.  그들은 FNM_NOMATCH 과는 다른 0이 아닌값을 리턴함으로써 에러를 보고 할 것이다. 이들은 flags 인수에서 사용되는 유용한 플래그들이다.

FNM_FILE_NAME

파일이름과 맞추는 동안, '/' 문자를 특별히 취급하라. 만일 이 플래그가 설정되면, 패턴안의 와이드카드 구성들은 문자열에 있는 '/'과 매치할 수 없다.  그래서, '/'과 매치하기위한 유일한 방법은 패턴안에 '/'를 명시하는 것이다.

FNM_PATHNAME

이것은 FNM_FILE_NAME 의 다른 이름으로; POSIX.2로부터 왔다.  우리는 파일 이름으로 "경로이름"을 사용하지 않기 때문에 이 이름을 쓰는 것을 권장하지 않는다.

FNM_PERIOD

만일 문자열의 처음에 '.' 문자가 나타나면 특별하게 취급하라.  만일 이 플래그가 설정되면, 패턴안의 와일드카드 구성은 첫 번째 문자가 '.'로 시작되는 문자열에서 '.'문자와 매치할 수 없다.  만일 당신이 FNM_PERIOD 와 FNM_FILE_NAME 을 모두 설정해 놓았다면, 문자열의 처음에 나타나는 '.' 은 물론 '.'문자 다음에 나타나는 '/'문자도 특별한 취급을 한다.  (쉘은 FN,_PERIOD 와 FNM_FILE_NAME 플래그를 파일이름과 매치하기 위해서 함께 사용한다.)

FNM_NOESCAPE

패턴안에 있는 '\'문자를 특별하게 취급하지 말아라.  보통, '\'가 바로 뒤에 따르는 문자를 인용하면, 원래 그 문자가 갖는 특별한 의미가 무효화된다.  인용이 가능했을 때, 패턴 '\?'는 패탄안의 의문부호가 보통의 문자처럼 행동하기 때문에 '?'과 매치된다.  만일 당신이 FNM_NOESCAPE을 사용하면, '\'도 보통의 문자가 된다.

FNM_LEADING_DIR

문자열에서 '/'뒤에 나타나는 문자들의 열을 무시하라; 이것은, 패턴과 매치되는 디렉토리 이름으로 시작하는 문자열인지를 시험함을 말한다.  만일 이 플래그가 설정되면, 패턴이 'foo*' 이거나 'foobar' 이면, 문자열 'foobar/frobozz' 과 매치될 것이다.

FNM_CASEFOLD

패턴에 비교되는 문자열의 대,소문자 구분을 무시하라.

16.2  Globbing

와일드카드의 전형적인 사용은 디렉토리 안에 있는 파일들과 맞추어서 매치되는 모든 파일들의 리스트를 만들기 위함이다.  이것은 globbing이라고 부른다.

당신은 fnmatch 를 사용해서 하나씩 디렉토리 엔트리들(entries)을 읽고, 테스트 함으로써 이런일을 할 수가 있다. 그렇지만, 속도가 느리다. (그리고 직접 서브디렉토리들(subdirectories)을 다루기 때문에 복잡하다.)

라이브러리는 편리한 와일드카드를 특별하게 사용하도록 하기 위해 glob 함수를 제공한다.  glob 과 이절에 있는 다른 심볼들은 'glob.h'에 선언되어 있다.

16.2.1  glob 호출하기

globbing의 결과는 파일 이름들(문자열)의 벡터(vector)이다.  이 벡터를 리턴하기 위해서, glob은 구조체인 golb_t 라는 특별한 데이터타입을 사용한다.  당신이 glob에게 구조체의 주소를 주면, glob은 그 구조체의 각 필드를 채워서 당신에게 그 결과를 알린다.

데이터타입 :   glob__t

이 데이터타입은 벡터를 가리키는 포인터를 저장한다.  더 자세하게 말하자면, 그것은 벡터의 주소와 그 크기를 갖고있는 레코드이다.

gl_pathc	벡터안에 있는 요소들의 개수

gl_pathv	벡터의 주소. 이 필드는 char ** 형을 갖는다.

gl_offs	gl_pathv 필드안에 있는 명목상의 주소로부터 구한, 벡터의 첫 번째 실 요소의 offset.  다른 필드들과 달리, 이것은 glob으로부터 나온 출력이 아니라, 항상 glob에서 입력으로 사용된다. 만일 당신이 0이 아닌 offset을 사용하면, 벡터의 시작점으로부터 다른 요소들은 비어있는 채로 왼쪽에 존재한다. ( glob 함수는 널 포인터로 그들을 채운다.) gl_offs 필드는 당신이 GLOB_DOOFFS 플래그들 사용할때만 의미가 있다.  그렇지 않다면, offset은 이 필드안에 무엇이 있던지 상관없이 항상 0이고, 첫 번째 실 요소는 벡터의 시작점에 있다.

함수 :  int glob (const char *pattern, int flags, int (*errfunc) (const char  *filename, int error-code),	glob_t *vector_ptr)

glob 함수는 현재의 디렉토리에서 패턴 pattern을 사용해서 globbing을 한다.  그것은 새로이 할당된 벡터에 그 결과를 넣고, *vector'ptr에 이 벡터의 주소와 크기를 저장한다.  flags인수는 비트 플래그들의 조합이다;  플래그들에 대한 상세한 정보는 16.2.2절 [Flags for Globbing] 223 페이지를 참조하라.

globbing의 결과는 파일이름들의 문자열이다.  glob 함수는 각 결과를 위해서 워드(word) 단위의 문자열을 할당하고, 이들 문자열의 주소를 저장하기 위해서 char ** 형의 벡터를 할당한다.  벡터의 마지막 요소는 널 포인터이다.  이 벡터는 워드 벡터(word vector)라고 불린다. 이 벡터를 리턴하기 위해서, glob는 *vector'ptr에 그 주소와 길이를 (널 포인터로 끝나는 것을 세지않은, 요소들의 개수) 저장한다. 보통, glob는 그들을 리턴하기 전에 알파벳순으로 파일 이름들을 정렬한다.  당신은 만일 당신이 가능한한 빨리 그 정보를 얻기를 원한다면 GLOB_NOSORT 플래그를 사용해서 이 기능을 이용하지 않을 수 있다.  만일 당신이 알파벳 순으로 파일들을 처리한다면, 보통은 glob가 정렬하도록 하는 것이 좋고, 당신이 만든 응용프로그램을 사용하는 사용자들은 프로그램에 대한 가치를 느낄 것이다. glob 함수가 성공하면 0을 리턴하고, 그렇지 않다면 그것은 다음 에러코드중 하나를 리턴한다.

GLOB_ABORTED

디렉토리 개방에서 에러가 있었고, 그리고 당신이 GLOB_ERR 플래그를 사용했거나 errfunc가 0이 아닌 값을 리턴했다. GLOB_ERR 플래그와 errfunc 에 대한 설명은 아래를 참조하라.

GLOB_NOMATCH

pattern이 현존하는 파일들과 아무것도 맞지않는다.  만일 당신이 GLOB_NOCHECK 플래그를 사용하면, 이 에러코드는 결코 나오지 않는다. 왜냐하면 이 플래그는 적어도 한 개의 파일이 패턴과 맞는 것이 있는것처럼 glob이 가장하도록 하기 때문이다.

GLOB_NOSPACE

그 결과를 저장하기 위한 메모리 할당이 불가능하다. 에러가 발생하면, glob는 지금까지 발견한 pattern과 매치되는 것을 *vector`ptr에 정보를 저장한다.

16.2.2  Globbing 을 위한 플래그들

이 절은 glob 에게 flags 인수로 지정할 수 있는 플래그들을 설명한다. 당신이 원하는 플래그를 선택하고, 비트별 OR 연산자 | 을 사용해서 그들을 조합하라.

GLOB_APPEND

앞서서 호출한 glob이 만들어낸 위드 벡터에 새로운 워드벡터를 붙인다.  이것은 그들사이를 공백으로 연결해서 여러 워드들을 효율적으로 확장할 수 있다. 붙이는 작업을 하려면, glob 호출동안 구조체 워드 벡터의 내용을 변화시켜서는 안된다.  그리고 만일 당신이 glob의 처음 호출에서 GLOB_DOOFFS 를 설정하면, 당신이 그 결과들을 붙일때도 반드시 그것을 설정해야만 한다. gl_pathv 에 저장한 포인터는 당신이 두 번째 glob을 호출한 이후에는 더 이상 유용한 정보가 아님을 기억하라. 왜냐하면, glob이 그 벡터의 위치를 바꾸기 때문이다. 그러므로 항상 매번 glob을 호출한 후에 바로 구조체 glob_t 에서 gl_pathv를 추출하라; 호출을 거쳐 그 포인터를 결코 저장하지 말아라.

GLOB_DOOFFS

워드 벡터의 시작점에 빈 슬롯을 남겨라.  gl_offs는 얼마나 많은 슬롯을 남겨야 하는지를 알린다.  빈 슬롯은 널 포인터를 저장한다.

GLOB_ERR

즉시 포기하고 순서대로 읽혀져야만 하는 디렉토리를 읽는데 어떤 어려움이 있다면, 그 에러를 보고하라.  그와 같은 어려움들은 당신이 필요한 접근을 갖지못한 디렉토리를 포함했기 때문일 것이다.  보통, glob는 그 디렉토리가 무엇이든지, 어떤 에러에도 불구하고, 계속 실행하려 시도한다. 당신은 glob를 호출할 때 에러-처리 함수 errfunc를 정함으로 해서 이것보다 더 많은 제어를 실행할 수 있다. 만일 errfunc 가 널포인터가 아니라면, glob는 디렉토리를 읽을 수 없을 때 즉시 실행을 멈추지 않고; 대신 다음처럼 두 개의 인수를 사용해서 errfunc 함수를 호출한다:

(*errfunc)  (filename,  error-code)

filename인수는 glob이 개방할 수 없거나, 읽을수 없었던 디렉토리의 이름이고, error-code는 glob에서 보고된 에러값이다.  만일 에러처림 함수가 영이 아닌값을 리턴하면, glob는 즉시 멈춘다.  그렇지 않다면 계속 실행한다.

GLOB_MARK

만일 pattern이 디렉토리 이름과 매치되면, 그것을 리턴할 때, 디렉토리의 이름에 '/'를 덧붙여라.

GLOB_NOCHECK

만일 pattern이 어떤 파일 이름과도 매치가 되지 않으면, 매치되는 파일 이름이 있는것처럼 pattern 그 자체를 리턴한다. (보통, pattern이 어느것과도 매치가 안될 때, glob는 매치되는 것이 아무것도 없음을 리턴한다. )

GLOB_NOSORT

파일 이름들을 정렬하지 말아라;  특별한 순서없이 그들을 리턴하다. ( 실제로, 그 순서는 디렉토리에 있는 엔트리의 순서에 의존할 것이다. )  정렬하지 않는 유일한 이유는 시간을 절약하기 위함이다.

GLOB_NOESCAPE

pattern들에 있는 '\' 문자를 특별하게 취급하지 말아라. 보통, '\'가 그 다음에 나타나는 문자들을 인용해서, 그 문자들이 갖는 특별한 기능을 없앤다.  인용(quoting)이 가능할 때, 패턴 '\?'은 오직 문자열 '?'로 매치되는데, 그 이유는 pattern에 있는 의문부호가 보통의 문자처럼 행동하기 때문이다.

만일 당신이 GLOB_NOESCAPE를 사용하면, '\'은 보통의 문자가 된다.  glob가 반복적으로 fnmatch 함수를 호출함으로써 그 작업을 한다.  fnmatch의 호출에서 FNM_NOESCAPE가 켬으로 해서 GLOB_NOESCAPE 플래그를 취급한다.

16.3  정규식 매칭 ( Matching )

GNU C 라이브러리는 정규식을 매치하기 위한 두 개의 인터페이스를 제공한다.  하나는 표준 POSIX2 인터페이스이고, 다른 하나는 GNU 시스템이 오랬동안 가지고 있었던 것이다.

두 개의 인터페이스는 헤더파일 'regex.h'에 선언되어 있다.  만일 당신이 _POSIX_C_SOURCE 라고 정의하면, 오직 POSIX.2의 함수들, 구조체, 그리고 상수들로만 선언되어 진다.

16.3.1  POSIX 정규식 컴파일

당신이 실제로 정규식을 매치하기 전에, 당신은 그것을 컴파일 해야만 한다.  이것은 실제로 우리가 생각하고 있는, 기계 명령어로 바꾸는 그런 실제의 컴파일이 아니라 특별한 데이터 구조체를 생성하는 것을 말한다. 그러나 그것은 당신은 그 패턴을 "실행"이 가능하도록 만들려 하는 목적을 갖고 있다는 점에서 보통의 컴파일과 같다. (컴파일된 일반 표현식을 어떻게 매치하는지에 대한 정보는 16.3.3절 [Matching POSIX Regexps], 226 페이지 참조.)

컴파일된 정규식을 위한 특별한 데이터 타입은 아래와 같다.

데이터타입 :   regex__t

이것은 컴파일된 정규식을 저장하는 오브젝트의 타입이다. 그것은 실제로 구조체이다.  당신은 당신의 프로그램에서 밑에 보여진 오직 하나의 필드만 가진다.

re_nsub	이 필드는 컴파일된 정규식에 존재하는 괄호로 묶여진 부정규식(subexpressions)의 개수를 저장한다.

그 구조체에는 여러 가지 다른 필드들이 있지만, 우리는 여기서 그들을 설명하지 않겠다. 왜냐하면 다른 필드들은 우리가 임의대로 건드릴 수 없고, 오직 라이브러리 함수에서만 그들을 사용하기 때문이다. 당신이 regex_t 오브젝트를 만든후에, 당신은 regcomp를 호출함으로써 정규식을 컴파일할 수 있다.

함수 :   int regcomp (regex_t *compiled, const char *pattern, int cflags)

regcomp 함수는 문자열과 매치시키는데 사용하는 regexec를 사용할 수 있도록 정규식을 데이터 구조체로 "컴파일"한다.  컴파일된 정규식의 형식은 매칭(matching)에 효율적이게 만들어졌다. regcomp는 *compiled에 그것을 저장한다.

당신은 regex_t 타입의 오브젝트를 할당한 다음 그 주소를 regcomp에게 주어라.  인수 cflags는 정규식의 구문과 의미들을 제어하는 다양한 옵션을 정할 수 있도록 허용한다.  16.3.2절 [Flags for POSIX Regexps] 225 페이지 참조.  만일 당신이 REG_NOSUB플래그를 사용한다면, regcomp는 어떻게 부정규식이 실제로 매치되는지를 기록하기위해 필요한 정보를 컴파일된 정규식에서 생략한다.  이 경우, 당신은 regexec를 호출할 때 matchptr 과 nmatch인수로 0을 사용하는 것이 좋을 것이다. 만일 당신이 REG_NOSUB 를 사용하지 않는다면, 컴파일된 정규식은 어떻게 부정규식을 매치하는지를 기록하는 용량을 갖는다.  또한, regcomp는 얼마나 많은 부정규식 패턴을 가졌는지를 compiled->re_nsub 에 저장하여 당신에게 알린다.  당신은 그것을 부정규식 매치에 대한 정보를 저장할곳을 할당하기 위해 얼마나 긴 배열을 할당할지를 정하기위한 값으로 사용할 수 있다.

regcomp는 정규식을 컴파일 하는데 성공하면 0을 리턴하고; 그렇지 않으면 0이 아닌 에러코드( 밑에 설명된 )를 리턴한다. 당신은 그 에러코드의 발생이유를 설명할 에러메세지를 만들기 위해서는 regerror을 사용할 수 있다;  16.3.6절 [Regexp Cleanup] 227 페이지 참조. 이것들은 regcomp가 리턴할 수 있는 0이 아닌값들이다.

REG_BADBR

정규식안에 유용하지 않은 `\{. . .\}` 구성이 있었다. 유용한 `\{. . .}\` 구성은 단일한 숫자, 또는 콤마로 분리된 오름차순으로 된 두 개의 숫자중 하나를 포함해야만 한다.

REG_BADPAT

정규식에 구문에러가 있었다.

REG_BADRPT

`?'나 `*'와 같은 반복 연산자가 나쁜 위치에 있다(선행하는 아무런 부표현식도 없이 ).

REG_ECOLLATE

정규식이 유용하지 않은 대조(collating) 요소를 참조하였다. (문자열 대조를 위해서 현재의 지역에서 정의되지 않은 것.) 19.3절 [Locale Categories] 260 페이지 참조.)

REG_ECTYPE

정규식이 유용하지 않은 클래스 이름을 참조하였다.

REG_EESCAPE

정규식이 `\' 으로 끝났다.

REG_ESUBREG

`\digit' 구성에 유용하지 않은 숫자가 있었다.

REG_EBRACK

정규식 안에 균형이 맞지 않는 sqrare brackets([,])이 있었다.

REG_EPAREN

연장된 정규식이 균형이 맞지 않는 괄호를 갖었거나, 기본 정규식이 균형이 맞지 않는 `\(' 와 `\)'를 가졌다.

REG_EBRACE

정규식이 균형이 맞지 않는 `\{' 와 `\}'을 가졌다.

REG_ERANGE

범위 표현식에서 끝점의 하나가 유용하지 않다.

REG_ESPACE

regcomp가 메모리를 다 써버렸다.

16.3.2  POSIX 정규식을 위한 플래그들

regcomp로 정규식을 컴파일할 때 피연산자 cflags에서 사용할 수 있는 비트플래그들에 대한 설명이다.

REG_EXTENDED

기본 정규식이 아닌, 연장된 정규식으로 패턴을 취급하라.

REG_ICASE

문자들을 매치할 때 대,소문자 구분을 무시하라.

REG_NOSUB

match_ptr 배열에 저장한 내용들을 건드리지 말아라.

REG_NEWLINE

문자열에 있는 새줄문자를 여러개의 라인으로 문자열을 나누는 역할을 하는 것 처럼 취급한다, 그래서 `$'은 새줄 문자 전에 매치할 수 있고, `^'은새줄문자 후에 매치할 수 있다.  또한, 새줄문자와 매치하기 위해 `.'을 허용하지 않고, `[^. . .]'을 허용하지 않는다.  그렇지 않다면 새줄문자는 다른 보통의 문자들처럼 작용한다.

16.3.3  컴파일된 POSIX 정규식을 매칭하기

일단 당신이 16.3.1절 [POSIX Regexp Compilation] 224 페이지에서 설명된것처럼 컴파일된 정규식을 가졌다면, 당신은 regexec를 사용해서 문자열과 그것을 매치할 수 있다.  만일 정규식이 `^' 나 `$' 와 같은 고정문자들을 표함하고 있지 않다면, 문자열안에 어디에서든지 매치되는 것은 성공으로 센다.

함수 : int regexec(regex_t *compiled, char *string, size_t nmatch, regmatch_t matchptr [], int eflags)

이 함수는 문자열을 컴파일된 정규식*compiled와 매치하려 시도한다.  regexec 는 만일 정규식이 매치되면 0을 리턴하고; 그렇지 않다면 0이 아닌값을 리턴한다.  0이 아닌값들이 무엇을 의미하는지 밑 테이블에 설명해 놓았다.  당신은 0이 아닌값이 발생하는 이유를 설명할 에러 메시지를 생성하도록 regerror 함수를 사용할 수 있다; 16.3.6절 [Regexp Cleanup] 227 페이지 참조. 인수 eflags는 다양한 옵션들을 가능하도록 하는 비트플래그들의 집합인 워드이다.  만일 당신이 문자열이 정규식과 매치된곳이 실제로 어느부분인지 또는 그 부표현식은 무엇인지를 알기를 원한다면 인수 matchptr 과 nmatch를 사용하라.  그렇지 않다면 nmatch에는 0을 주고 matchptr에는 NULL을 주어라.  16.3.4절 [Regexp Subexpressions] 226 페이지 참조. 당신이 정규식을 컴파일했을 때 그 컴파일된 정규식은 실제로 그것이 현재 위치한 지역에 맞도록 정규식을 매치해야만 한다.  regexec 함수는 eflags 인수에서 다음 플래그들을 받아들인다.

REG_NOTBOL

한 라인의 시작이 정해진 문자열로 시작하는지를 신경쓰지 말아라;  즉, 어떤 텍스트의 앞에 무엇이 선행해야만 한다는 어떤 가정도 만들지 말아라.

REG_NOTEOL

한 라인의 끝이 정해진 문자열로 끝나는지를 신경쓰지 말아라;  즉, 어떤 텍스트의 뒤에 무엇이 따라와야만 한다는 어떤 가정도 만들지 말아라.

regexec가 리턴할 수 있는 0이 아닌값들에 대한 설명.

REG_NOMATCH

패턴이 문자열과 매치되는 것이 없다.  이것은 사실상 에러는 아니다.

REG_ESPACE

regexec가 메모리를 다 써버렸다.

16.3.4  부표현식 ( Subexpressions ) 과 매치한 결과

regexec함수가 패턴의 괄호로 묶인 부문자열을 매치할 때, 그것은 매치할려는 문자열의 부분들을 기록한다. 그것은 구조체 regmatch_t 형인 요소를 가진 배열안의 offsets 에 저장하여 그 정보를 리턴한다.  그 배열(index 0)의 첫 번째요소는 전체 정규식과 매치된 문자열의 부분을 기록한다.  배열의 다른 요소들은 단 하나의 괄호로 묶인 부표현식과 매치된 부분의 처음과 끝을 기록한다.

데이터타입 :   regmatch__t

이것은 당신이 regexec 함수에서 사용하기 위한 배열 matcharray의 데이터타입이다.  그것은 다음과 같은 두 개의 필드들을 갖고 있다.

rm_so	문자열에 있는 부문자열의 시작점의 offset. 그 부분의 주소를 얻기위해서는 문자열에 이 값을 더하라.

rm_eo	문자열에 있는 부문자열의 끝점의 offset

데이터타입 :   regoff__t

regoff_t는 signed integer 형의 다른 이름이다.  regmatch_t의 필드들은 regoff_t의 형을 갖는다. regmatch_t 요소들은 부표현식의 위치에 대한 것이다; 첫 번째 요소( index 1)는 매치된 첫 번째 부표현식이 어디에 있는지를 기록하고, 두 번째 요소는 두 번째 부표현식을 기록하고, 등등.  부표현식의 순서는 그들이 나타난 순서이다.

당신이 regexec를 호출할 때, 얼마나 긴 matchptr 배열이 있는지, nmatch 인수를 가지고 정해야한다.  이것은 regexec함수에게, 저장하기 위한 얼마나 많은 요소들이 있는지를 알린다.  만일 실제로 정규식이 nmatch보다 더 많은 부표현식을 갖는다면, 당신은 그들의 나머지에 대한 offset정보를 제대로 얻지못한 것이다.  그러나 이것은 그 pattern이 어떤 특별한 문자열과 매치되는지 또는 매치되지 않는지에 대한 사실을 변경하지 않는다.

만일 당신이 regexec 함수가 매치된 부표현식에 대한 어떤 정보도 리턴하기를 원하지 않는다면, 당신은 nmatch에 0을 넣거나, 또는 regcomp로 pattern을 컴파일할 때 REG_NOSUB 플래그를 사용하라.

16.3.5  부표현식 매치하기의 복잡함

때때로 부표현식은 아무 문자도 없는 부문자열(substring)을 매치한다. 이것은 'f\(o*)\'와 문자열 `fum'을 매치할 때 발생한다. (그것은 실제로는 단지 `f'를 매치한다. )  이 경우, offsets의 양쪽은 널 부문자열이 발견된 곳을 가리킨다.  이 예에서, offsets은 둘다 1이다.

때때로, 전체 정규식은 전혀 부표현식을 사용하지 않고 매치할 수 있다. 예를들어, `ba\(na\)*'이 'ba'와 매치할 때, 괄호안에 있는 부표현식은 사용되지 않는다.  이런일이 발생할 때, regexec는 부표현식을 위한 요소의 필드에 -1을 저장한다.

때때로 전체 정규식을 매치하기는 한 번보다 더 많은 특별한 부표현식을 매치할수 있다.  예를들어, `ba\(na\)*'이 문자열 `bananana'와 매치할 때, 괄호안의 부표현식은 세 번 매치된다.  이런일이 발생할 때, regexec는 보통, 부표현식과 매치된 문자열의 마지막 부분의 offsets을 저장한다. `bananana'의 경우에 이들의 offsets은 6과 8이다.

그러나 마지막으로 매치된 것은 항상 문자열의 마지막에 있는 매치된 것이 아니다.  그것은 매치하기위한 마지막 기회에서 우선권을 가진것이라고 하는 것이 더 정확하다.  이것이 의미하는 것은 하나의 부표현식이 다른것에서 발견됐을 때, 내부 부표현식을 위해 보고된 결과는 밖의 부표현식과의 마지막 매치에서 무슨일이 일어났는지에 대해 영향을 받는다.  예를들어, `\(ba\(na\)*s \)*' 을 문자열 `bananas bas '와 매치하는 것을 고려해보자. 첫 번째, 내부 표현식과 실제로 매치된 것은첫 번째 단어의 끝에 가까이 있다.  그러나 다시 두 번째 단어를 고려해보면, 그곳에는 매치되는 것이 아무것도 없다.  regexec는 "na" 부표현식이 사용되지 않은 것으로 보고한다.

다른예로 `\(ba\(na\)*s \|nefer\(ti\)* \)*'을 'bananas nefertiti'와 매치할 때 이 규칙이 적용되는지를 알아보자.  "na" 부표현식은 첫 번째 단어에서 매치되는 것이 있지만, 두 번째 단어에서는 매치되는 것이 없다.  다시한번, 밖의 부표현식의 두 번 반복은 첫 번째를 무시하고, 두 번째 반복에서 "na" 부표현식은 사용되지 않았다,  그래서 regexec는 "na" 부표현식이 사용되지 않았음을 보고한다.

16.3.6  POSIX Regexp 매치하기 소거

당신이 컴파일된 정규식을 다 사용했을 때, 당신은 regfree를 사용해서 그 저장공간을 해제할 수 있다.

함수: void regfree (regex_t *compiled)

regfree를 호출해서 *compiled가 가리키고 있는 모든 저장영역을 해제할 수 있다.  이것은 이 매뉴얼에서는 설명하지 않았지만, 구조체 regex_t의 다양한 내부적 필드들을 포함하고 있다. regfree는 오브젝트 *compiled 자체를 해제하지 않는다.

당신은 다른 정규식을 컴파일하기위해 그 구조체를 사용하기 전에 regfree를 사용해서 구조체 regex_t 안에 있는 공간을 항상 해제해야한다. regcomp나 regexec에서 에러가 났을 때, 당신은 에러메세지를 출력하기 위해서 regerror함수를 사용할 수 있다.

함수: size_t regerror (int errcode, regex_t *compiled, char *buffer, size_t length)

이 함수는 에러코드 errcode를 위한 에러메세지 문자열을 생성하고, buffer에서 시작하는 메모리의 length 바이트안에 그 문자열을 저장한다.  컴파일된 인수를 위해서, regcomp나 regexec에서 작업되었던 같은 컴파인된 정규식 구조체를 공급한다.  선택적으로, 당신은 compiled를 위해서 NULL을 공급할 수 있다;  당신은 여젼히 의미있는 에러메세지를 얻을 것이다, 그렇지만, 그것은 상세하지 않을 것이다.

만일 그 에러메세지가 length 바이트의 길이에 맞을수 없다면(널 종료문자를 포함해서), 그러면 regerror은 그것은 자른다.  regerror 이 저장한 문자열은 심지어 그것이 잘렸을 때라도 널 종요문자를 저장한다. regerror의 리턴값은 전체 에러메세지를 저장하기위해 필요한 최소 길이이다.  만일 이것이 length보다 적다면, 에러메세지는 잘리지 않고, 당신은 그것을 사용할 수 있다. 그렇지 않다면, 당신은 더큰 버퍼를 잡아서 다시 regerror을 호출해야한다. 이곳에 regerror을 사용하는 함수가 있는데, 에러메세지를 위한 버퍼를 항상 동적으로 할당한다.

char  *get_regerror  (int errcode,  regex_t  *compiled)

{

size_t length = regerror (errcode, compiled, NULL, 0);

char  *buffer  =  xmalloc  (length);

(void)  regerror  (errcode,  compiled,  buffer,  length);

return  buffer;

}

16.4  쉘-스타일 단어 확장

단어 확장은 단어들로 문자열을 분리하고, 그것을 쉘이 하는것처럼 변수, 명령, 그리고 와일드카드로 해석하는 것을 의미한다.  예를들어, 당신이 `ls -l foo.c'라고 쓸대, 이 문자열은 세단어,`ls', '-l', `foo.c'로 분리된다. 이것은 단어 확장의 가장 기본적인 함수이다.

당신이 `ls *.c'라고 쓸대, 이것은 단어 `*.c'가 어느 수의 파일이름들과 대치될수 있기 때문에 많은 단어들이 될 수 있다.  이것은 와일드카드 확장이 호출되는데, 그것은 또한 단어 확장의 일부분이다.  당신이 당신의 경로를 프린트하기 위해서 `echo $PATH'를 사용할 때, 이것도 또한 단어 확장의 일부분인 변수 치환이 이용된다.  프로그램들은 라이브러리 함수 wordexp를 호출하여서 쉘처럼 단어 확장을 수행할 수있다.

16.4.1  단어 확장의 단계

단어 확장이 단어들의 열(suquence)에 적용될 때, 그것은 여기에 보여진 순서를 따라서 변환을 수행한다.

1. 틸드(~) 확장: `~foo'는 `foo'의 홈 디렉토리로 대치된다.

2. 다음, 세 개의 다른 변환들은 왼쪽에서 오른쪽으로, 동등한 결합순서로 적용된다.

□ 변수 치환: 환경변수들은 `$foo'처럼 참조를 위해서 대치된다.

□ 명령 치환: ``cat foo`' 와 그와 동등한 `$(cat foo)'와 같은 것들은 내부 명령을 통해서 출력  으로 대치된다.

□ 산술적 확장 : `$(($x-1))'와 같은 것은 산술적 계산의 결과로 대치된다.

3. 필드 분리하기 : 텍스트를 단어로 분리한다.

4. 와일드카드 확장 : `*.c'와 같은 구석은 `.c'로 끝나는 파일이름들의 리스트로 대치된다.  와일드카드 확장은 동시에 전체 단어로 적용되고, 그들 단어들이 있는 0개의 파일, 또는 많은 파일들로 대치한다.

5. 인용 제거: 문자열-인용의 제거, 그들은 적당한때에 변환을 금지함으로써, 그들의 작업이 수행된다.

이들 변환들에 대한 상세한 정보, 그리고 그들을 사용한 구성들을 어떻게 쓸것인가에 대한 것들은 BASH 매뉴얼을 참조하라.

16.4.2  wprdexp 호출하기

단어 확장을 위한 모든 함수들, 상수들, 그리고 데이터타입들은 헤더파일 'wordexp.h'에 선언되어 있다.  단어 확장은 단어들(문자열)의 벡터를 생성한다.  이 벡터를 리턴하기 위해서, wordexp는 구조체인 wordexp_t라는 특별한 데이터타입을 사용한다.  당신이 그 구조체를 wordexp함수에 주면, 그것은 그 결과를 알리기 위해서 그 구조체의 필드를 채운다.

데이터타입 :   wordexp_t

이 데이터타입은 워드 벡터를 가리키는 포인터를 저장한다.  좀 더 자세하게 말하면워드 벡터의 주소와 그 크기를 기록한다.

we_wordc	벡터에 있는 요소들의 개수

we_wordv	벡터의 주소. 이 필드는 char ** 타입을 갖는다.

we_offs	we_wordv 필드에 있는 명목상의 주소로부터 구한 벡터의 첫번째 실 인수의 offset. 다른 필드들과 달리, 이 필드는 wordexp함수에서 항상 입력으로 사용된다. 만일 당신이 0이 아닌 offset을 사용하면, 벡터의 시작점에서 많은 요소들은 비어있는 왼쪽에 있다. (wordexp함수는 널 포인터로 그들을 채운다.) we_offs 필드는 당신이 WRDE_DOOFFS 플래그를 사용했을때만 유용하다. 그렇지않다면, offset은 이 필드안에 무엇이 있는지 상관없이 항상 0이고, 첫번째 실인수는 벡터의 시작점에 있다.

함수:  int wordexp (const char *words, wordexp_t *word-vector-ptr, int flags)

단어들로 이루어진 문자열에서 단어 확장을 수행하고, 그 결과를 새로이 할당된 벡터에 넣고, 그리고 이 벡터의 크기와 주소를 *word-vector-ptr에 저장한다.  인수 flags는 비트플래그들의 조합이다;  플래그들에 대한 상세한 정보는 16.4.3절 [Flags for Wordexp], 230 페이지를 참조하라. 당신은, 그들이 인용되지 않았다면 문자열안에 `|&;<>' 문자들을 사용하지 못한다; 새줄도 마찬가지. 만일 당신이 인용없이 이들 문자들을 사용한다면, 당신은 WRDE_BADCHAR 에러코드를 얻을 것이다.  그들이 인용되지 않았거나 또는 단어 확장 구성의 부분이 아니라면 괄호나, 쌍으로 이루어진것들을 사용하지 말아라. 만일 당신이 인용 문자들 `'"`' 을 사용한다면 그들은 균형이 맞는 한 쌍이 되어야 한다. 단어 확장의 결과는 단어들의 열이다.  wordexp함수는 결과로 나온 단어를 위한 문자열을 할당하고, 이들 문자열의 주소를 저장하기 위해서 char ** 타입의 벡터를 할당한다.  벡터의 마지막 요소는 널 포인터이다.  이 벡터는 워드벡터라 불린다. 이 벡터를 리턴하기 위해서, wordexp는 그 주소와, 그 길이(종료 널 포인터를 셈하지 않은 요소들의 개수)를 *word-vector-ptr에 저장한다. 만일 wordexp가 성공하면 0을 리턴하고, 그렇지 않으면 다음 에러코드중 하나를 리턴한다.

WRDE_BADCHAR

입력 문자열 단어들이 인용이 없는 `|'와 같이 유용하지않은 문자를 포함하고 있다.

WRDE_BADVAL

입력 문자열이 정의되지 않은 쉘 변수를 참조 하였고,  당신이 그와 같은 참조를 금하기 위해서 WRDE_UNDEF 플래그를 사용하였다.

WRDE_CMDSUB

입력 문자열이 명령 치환을 사용했는데, 당신이 명령 치환을 금하기 위해서 WRDE_NOCMD 플래그를 사용하였다.

WRDE_NOSPACE

그 결과를 저장하기위한 메모리를 할당하기가 불가능하다.  이 경우, wordexp는 할당할 수 있는 만큼 그 결과의 일부를 저장할 수 있다.

WRDE_SYNTAX

입력 문자열에 구문에러가 있었다. 예를 들어 매치되지 않는 인용 문자가 구문에러이다.

함수:   void wordfree (wordexp_t *word-vector-ptr)

*word-vector-ptr이 가리키고 있는 문자열과 벡터를 위한 저장공간이 해제된다. 이것은 *word-vector-ptr 자체를 해제하지않고, 단지 그것이 가리키고 있는 데이터만을 해제한다.

16.4.3  단어 확장을 위한 플래그들

이 절은 wordexp에서 사용되는 flags인수로 지정할 수 있는 플래그들을 설명한다.  당신이 원하는 플래그들을 선택하고, 그들을 연산자 |을 사용해서 조합하라.

WRDE_APPEND

바로전에 호출한 wordexp가 생성한 단어의 벡터에 이번 확장으로부터 생성된 단어들을 붙여라. 이것은 그들 사이가 공백으로 분리되어져서, 여러 단어들을 효율적으로 확장할 수 있는 방법이다. 덧붙이기 위한 일련의 작업에서, 당신은 wordexp의 호출사이에 워드 벡터 구조체의 내용을 갱신하지 말아야만 한다. 그리고, 만일 당신이 wordexp의 첫 번째 호출에 WRDE_DOOFFS를 설정하면 당신은 그 결과들을 붙일때에도 또한 그것을 설정해야만 한다.

WRDE_DOOFFS

단어들의 벡터의 시작점에 빈 슬롯들을 남겨라.  we_offs 필드는 얼마나 많은 슬롯들을 남겨야하는지를 알린다. 빈 슬롯들은 널 포인터를 포함한다.

WRDE_NOCMD

명령 치환을 하지 말아라.  만일 입력이 명령 치환을 요청하면, 에러를 출력한다.

WRDE_REUSE

앞서서 호출한 wordexp에서 만들어진 워드 벡터를 재사용 하라.  새로운 벡터를 할당하는 대신에 wordexp는 이미 존재하고 있는( 필요한만큼 그것을 크게 만들어서) 벡터를 사용할 것이다.  그 벡터를 이동할지도 모른다는 것을 기억 하라, 그래서 오래된 포인터를 저장하고, wordexp를 호출한 후에 그것을 다시 사용하는 것은 안정적이지 않다. 당신은 매번 호출마다 새로운 we_pathv를 추출해야만 한다.

WRDE_SHOWERR

명령 치환에의해 만들어진 명령으로 실행된 결과로 나온 프린트된 에러메세지를 보여라.  더 정확히, 현재 프로세스의 표준 에러 출력 스트림을 계승하도록 이들 명령들에게 허용한다.  디폴트로, wordexp는 모든 출력을 버린 표준 에러 스트림을 이들 명령들에게 준다.

WRDE_UNDEF

만일 입력이 정의되지 않은 쉘 변수를 참조한다면 에러가 출력된다.

16.4.4  wordexp Example

wordexp를 사용해서 여러 문자열을 확장하고, 그 결과들을 쉘 명령을 실행하기 위해 사용하는 예제가 있다.  확장들을 분리하기위해 사용한 WRDE_APPEND와 wordexp에 의해 할당된 공간을 해제하기 위한 wordfree의 사용도 보여주고 있다.

int

expand_and_execute (const char *program, const char *options)

{

wordexp_t  result;

pid_t  pid

int  status,  i;

/* 그 프로그램을 실행하기 위해서 문자열을 확장하라 */

switch  (wordexp  (program,  &result,  0))

{

case  0:   /*  Successful.   */

break;

case  WRDE_NOSPACE:

/* 만일 그 에러가 WRDE_NOSPACE라면 아마도 결과의 일부분이 할당되어진다. */

wordfree  (&result);

default:

return  -1;

/* 어떤 다른 에러 */

}

/* 인수를 위해 정해진 문자열을 확장하라.  */

for  (i  =  0;  args[i];  i++)

{

if  (wordexp  (options,  &result,  WRDE_APPEND))

{

wordfree  (&result);

return  -1;

}

}

pid  =  fork  ();

if  (pid  ==  0)

{

/* 이것은 자식 프로세스이다.  그 명령을 실행하라.  */

execv  (result.we_wordv[0],  result.we_wordv);

exit  (EXIT_FAILURE);

}

else  if  (pid  <  0)

/*  fork가 실패하였다,  실패를 보고하라 */

status  =  -1;

else

/* 이것은 부모 프로세스이다.  자식이 완수하도록 기다려라  */

if  (waitpid  (pid,  &status,  0)  !=  pid)

status  =  -1;

wordfree  (&result);

return  status;

}

실제로, wordexp는 서브쉘(subshell)로 실행되기 때문에, 문자열에서 그들 사이를 공백으로 분리하고 쉘 명령 `sh -c'를 사용해서 실행하는것보다는 빠르다.

가을의 노래

흐느낌 소리

길게 끄는

가을의 비올롱

나른하게도

단조롭게

내 마음을 아프게 한다

때를 알리는

종소리에

가슴 미어 파리하게

지난 날의

추억에

나는 운다

아아 나는 간다

사나운 바람에

불리어

이리저리

흩날리는

가랑잎처럼

베르네르

17  날짜와 시간

17장 날짜와 시간

이 절은 현재의 시간과, 다르게 시간을 표현하는 방법들 사이의 전환을 결정하는 함수들을 포함하여, 날짜와 시간들을 위한 함수들을 설명하고 있다.

시간 함수들은 세가지 주요한 범주로 나뉘어진다.

□ 경과된 CPU 시간을 계산하는 함수들은 17.1절 [Processor Time], 233 페이지에서 논의되었다.

□ 절대 시각이나 달력 시간을 계산하는 함수들은 17.2절 [Calendar Time]235 페이지에서 논의되었다.

□ 알람과 타이머를 설정하는 함수들은 17.3절 [Setting an Alarm] 243페이지에서 논의되었다.

17.1  프로세서 타임

만일 당신이 당신의 프로그램을 최적화하거나, 또는 그 효율성을 계산하려한다면, 어느 주어진 지점이후에, 얼마나 많은 프로세서 시간과 CPU 시간이 흘렀는지를 알수있도록 하는 유용한 방법이 있다.  프로세서 시간은 벽시계와는 다른데, 그것은 벽시계가 입/출력이나 다른 어떤 프로세스가 동작하고 있는 동안 얼마나 시간이 흘렀는지를 알리지 않기 때문이다.  프로세서 시간은 데이터 타입 clock_t로 표현되고, 단일한 프로그램의 시작점에서 표시된 어떤 기준 시간과 연관된 시간의 흐름을 숫자로 표현한다.

17.1.1  기준 CPU 시간 조사

프로세스에 의해 사용된, 경과된 CPU 시간을 얻기 위해서는, 당신은 clock함수를 사용할 수 있다.  이 함수는 헤더파일 'time.h'에 선언되어 있다.

특별한 사용예로, 당신이 시작점과 끝점 사이의 시간의 간격을 구할려면,clock함수를 호출하고, 그 값들을 나중값에서 처음값을 빼고, 그리고 CLOCKS_PER_SEC로 나누어라( 초당 횟수(clock ticks)의 숫자를 얻기 위해서 ), 다음 프로그램처럼.

#include  <time.h>

clock_t  start,  end;

double  elapsed;

start  =  clock();

. . .

/* 먼저 일을 시작하기 전에 clock함수를 호출한 다음 당신이 경과된 시간을 얻기 원하는 그 일을 시작하라 */

end  =  clock();

elapsed  =  ((double)  (end  -  start))  /  CLOCKS_PER_SEC;

다른 컴퓨터와 운영체제에서 어떻게 프로세서 시간을 기억하는지는 매우 광법위한 문제이다.  100분의 1초나, 100만분의 1초 사이의 내부프로세스 시계를 갖는 것이 보통이다.

GNU 시스텝에서 clock_t는 long int 와 동등하고, CLOCKS_PER_SEC는 정수 값이다.  그러나 다른 시스템에서는, clock_t는 정수이고, 매크로 CLOCKS_PER_SEC는 플로팅-포인트 형으로 서로 다르다.  어떤 시스템하에 놓여있든지 상관없이 적당하게 작업을 수행하고, 산술적으로 그 명령을 확인하기 위해서는 아래의 예처럼 double형으로 프로세스 타임을 cast연산을 통해서 형변환을 하라.

매크로 :   int CLOCKS__PER__SEC

이 매크로는 clock 함수에 의해 계산된 초당 시간주기(clock ticks)의 수를 나타내는 값이다.

매크로 :   int CLK__TCK

이것은 옜날에 CLOCKS_PER_SEC 대신에 쓰이던 것이다.

데이터타입 :   clock__t

이것은 clock함수에 의해 리턴된 값의 형으로 시간주기의 단위이다.

함수 :   clock_t clock(void)

이 함수는 경과된 프로세서 시간을 리턴한다.  기준 시간은 항상 다르지만, 단일 프로세스 안에서는 변경되지 않는다.  만일 그 프로세서 시간이 유용하지 않거나 표현될 수 없다면, clock는 (clock_t)(-1)의 값을 리턴한다.

17.1.2  자세하게 경과된 CPU 시간에 대한 조사

times 함수는 경과된 프로세서 시간에 대해서 struct tms오브젝트로 더 상세한 정보를 리턴한다.  이 함수는 헤더파일 `sys'times.h'에 선언되어 있다.

데이터 타입 :   struct tms

tms구조체는 프로세스 시간들에 대한 정보를 리턴하는데 사용된다. 그것은 적어도 다음과 같은 멤버들을 포함하고 있다.

clock_t tms_utime

이것은 호출된 프로세스의 명령들을 수행하는데 사용된 CPU 시간이다.

clock_t tms_stime

이것은 호출된 프로세스 때문에 시스템에서 사용된 CPU 시간이다.

clock_t tms_cutime

이것은 wait 또는, waitpid에 의해 부모 프로세스에게 보고된 상황으로, 호출된 프로세스의 모든 종료된 자식 프로세스들의 tms_utime 과 tms_cutime의 값들을 합한 값이다;  23.6절 [Process Completion] 329 페이지 참조.  즉, 호출한 프로세스의 모든 종료된 자식 프로세스 의 명령을 수행하는데 사용된 총 CPU 시간을 나타낸다.

clock_t tms_cstime

이것은 tms_cutime과 유사하지만, 호출된 프로세스의 모든 종료된 자식 프로세스들을 위하여 시스템에서 사용된 총 CPU 시간을 나타낸다.

모든 시간들은 시간주기(clock ticks)로 주어진다.  이들은 절대값이다;  새로이 만들어진 프로세스에서, 그들은 모두 0이다. 23.4 [Creatina a Precess] 326 페이지 참조.

함수 :   clock_t times (struct tms *buffer)

times 함수는 호출된 프로세스를 위한 프로세서 시간 정보를 buffer에 저장한다.  리턴값은 clock()의 값과 동일하다:  어떤기준 시간에서 실제로 경과된 시간.  기준 시간은 어떤 특별한 프로세스 상에서는 변하지 않는 상수값이고, 시스템 시작시간과 연괸된 시간으로 표현한다.  실패를 하면 (clock_t)(-1)의 값을 리턴하다.

이식성 노트: 17.1.1절 [Basic CPU Time] 페이지 233에서 설명된 clock 함수는, ANSI C 표준함수이고, times 함수는 POSIX.1의 함수이다. GNU 시스템에서는, clock함수로 표현된 값은 times에 의해 리턴된 tms_utime 과 tms_stime의 합계와 동등한 값이다.

17.2  달력 시간

이절은 그레고리오 력에 의한 날짜와 시간들을 기억하기 위한 도구들을 설명하고 있다.

날짜와 시간 정보를 위한 세가지 표현방법이다.

□ 달력 시간( 데이터 타입 time_t)은 간단히 어떤 정해진 기준시간 이후 몇초가 흘렀는지를 숫자로 표현한다.

□ 또한 초를 미세하게 나누어서 표현하는 고해상도 시간 표현이 있다 (데이터 타입 struct timeval).  당신이 큰 정밀도를 필요로 할 때 보통의 달력 시간대신 이 시간 표현을 사용하라.

□ 지역 시간이나 broken-down time( 데이터타입 struct tm)은 정해진 시간 구역에서 정해진 년, 달, 등 등의 구성요소들의 집합으로 날짜와 시간을 표현한다.   이 시간 표현은 보통 형식화된 날짜와 시간 값들을 결합하는데 사용된다.

17.2.1  간단한 달력 시간

이 절은 달력 시간을 표현하기 위한 데이터 타입 time_t와 달력 시간 오브젝트에서 동작하는 함수들을 설명한다.  이 도구들은 헤더파일 `time.h'에 선언되어 있다.

데이터 타입 :   time_t

이것은 달력 시간을 표현하기 위해서 사용하는 데이터 타입이다. GNU C 라이브러리와 다른 POSIX-계열에서, time_t는 long int 와 같다.  절대시간값으로 해석될 때, 그것은 협정 세계 시간 1970년 1월 1일 00:00:00 시간 이후 경과된 초의 수를 표현한다. ( 이 날짜는 때때로 시대(epoch)로써 사용된다. )  다른 시스템 에서, time_t는 정수이거나 플로팅-포인트이거나 할 것이다.

함수 :   double difftime (time_t time1, time_t time0)

difftime 함수는 double 형으로 time1과 time2 사이의 경과된 시간을 초로 리턴한다.  GNU 시스템에서, 당신은 간단히 time_t 값들을 빼는 것으로 그 값을 구할 수 있지만, 다른 시스템에서는 time_t 데이터 타입은 직접적으로 빼기를 할 수가 없게, 기호와 되있을 것이다.

함수 :   time_t time (time_t *result)

time 함수는 time_t의 형으로 표현된 값으로 현재의 시간을 리턴한다.  만일 인수 result가 널 포인터가 아니라면, time값은 *result에 저장되어진다.  만일 달력 시간이 유용하지 않다면, (time_t)(-1)이 리턴된다.

17.2.2  고해상도 달력

time_t 데이터 타입은 오직 1초 사이의 해상도를 갖는 달력 시간들을 표현하기 위해 사용한다.  어떤 응용프로그램은 더 정밀할 필요가 있다.

그래서, GNU C 라이브러리 에서는 1초 보다 더 높은 고해상도의 달력시간을 표현할 용량이 있는 함수들을 갖고 있다.  이 절에서 설명하고 있는 함수들과 연관된 데이터 타입들은 `sys/time.h'에 선언되어 있다.

데이터 타입 :   struct timeval

구조체 struct timeval은 달력 시간을 표현한다.  그것은 다음과 같은 멤버들을 갖고 있다.

long int tv_sec

이것은 epoch 이후를 초(second)로 표현한다. 이것은 보통 time_t 값과 동일하다.

long int tv_usec

이것은 마이크로초(microsecond)로 표현된 시간값이다.  어떤 시간 struct timeval 값들은 시간 간격들을 위한 사용자이고, 그러면 tv_sec 멤버는 간격에서 경과된 초(second) 이고, tv_usec는 부가적으로 흐른 시간 마이크로초의 수이다.

데이터 타입 :   struct timezone

구조체 struct timezone는 지역 시간 대에 대한 최소의 정보를 저장하기위해 사용된다.  그것은 다음의 멤버들을 갖는다.

int tz_minuteswest

이것은 그리니치 표준시(GMT)의 minutes west의 수이다.

int tz_dsttime

만일 0이 아니라면, 그 해(year)의 어떤 기간동안 일광 절약시간을 적용한다.

struct timezone 형은 절대값이고 결코 사용할일은 없을것이다.대신에 17.2.6절 [Time Zone Functions] 241 페이지에서 설명된 도구들에서 사용한다.

struct timeval형의 두값들을 빼는 것이 종종 필요하다.이곳에는 이런일을 하는 가장 좋은 방법이 있다.  그것은 tv_sec 멤버가 unsigned 형인 어떤 기묘한 운영체제하에서 조차도 돌아간다.

/* `struct timeval'의 형을 가진 값 X 와 Y를 빼고, 그 결과는 RESULT에 저장하라.  그 차이가 음수이면 1을 리턴하고, 그렇지 않으면 0을 리턴하라 */

int

timeval_subtract  (result,  x,  y)

struct  timeval  *result,  *x,  *y;

{

/* 갱신된 y로 나중에 빼기를 하기위해, y값을 가져와라  */

if  (x->tv_usec  <  y->tv_usec)  {

int  nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;

y->tv_usec  -=  1000000  *  nsec;

y->tv_sec  +=  nsec;

}

if  (x->tv_usec  -  y->tv_usec  >  1000000) {

int  nsec  =  (y->tv_usec  -  x->tv_usec)  /  1000000;

y->tv_usec  +=  1000000  *  nsec;

y->tv_sec  -=  nsec;

}

/* 기다림을 위한 시간을 계산하라. tv_usec는 어떤 양의 값이다.    */

result->tv_sec  =  x->tv_sec  -  y->tv_sec;

result->tv_usec  =  x->tv_usec  -  y->tv_usec;

/* 만일 결과가 음수이면 0을 리턴하라 */

return  x->tv_sec  <  y->tv_sec;

}

함수 :   int gettimeofday (struct timeval *tp, struct timezone *tzp)

gettimeofday 함수는 tp 가 가리키고 있는 구조체 struct timeval에서 현재의 날짜와 시간을 리턴한다.  그 시간대에 대한 정보는 tzp가 가리키고 있는 구조체 안에 리턴된다.  만일 tzp 인수가 널 포인터라면, 시간 대 정보는 무시된다. 리턴값이 0이면 성공이고, 실패면 -1이다.  다음의 errno는 이 함수를 위해 정의된 에러상황이다.

ENOSYS	운영체제가 시간대 정보 얻는 것을 지원하지 않고, tzp가 널포인터가 아니다.  GNU 운영체제는 시간대 정보를 표현하기 위해서 struct timezone를 사용하는 것을 지원하지 않는다; 그것은 예전에 4.3 BSD에서 사용되던 것이다.  대신에 17.2.6절[Time Zone Functions] 241 페이지에서 설명된 도구들을 사용하라.

함수 : int settimeofday(const sruct timeval *tp, const struct timezone *tzp)

settimeofday 함수는 인수들에 따라서 현재의 날짜와 시간을 설정한다.  gettimeofday를 통해서, 만일 tzp가 널 포인터라면, 시간대 정보는 무시된다.  당신이 settimeofday를 사용할려면 특권이 부여된 사용자이어야만 한다.  성공하면 리턴값은 0이고, 실패하면 -1이다.  다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EPERM	이 프로세스가 특권이 없기 때문에 시간을 설정할 수 없다.

ENOSYS	운영체제가 시간대 정보를 설정하는 것을 지원하지 않고 tzp가 널포인터가 아니다.

함수 :  int adjtime (const struct timeval *delta, struct timeval *olddelta)

현재의 시간을 점차적으로 조정하기 위해서 시스템 시계를 빠르게, 또는 느리게 하는 함수이다.  만일 간단히 현재의 시간을 설정할 수 없는, 항상 천편일률적으로 증가하기만 하는 시스템 시계라면, 이것은 그 시간을 맞출 수가 있다.

delta 인수는 현재의 시간을 위한 조정을 지정한다.  만일 음수라면 시스템 시계는 잃어버린 많은 시간을 복구할때까지 천천히 내려가고, 만일 양수라면, 시스템 시계는 천천히 올라간다.  만일 olddelta 인수가 널 포인터가 아니라면, adjtime함수는 조정이 아직 수행되기 전의 앞의 시간에 대한 정보를 리턴한다. 이 함수는 특별히 로컬 네트웍상의 컴퓨터들의 시간을 동기화 하는데 사용된다.  당신은 그것을 사용하기 위해서는 특권이 부여된 사용지가 되어야만 한다.  성공하면 0을 리턴하고, 실패하면 -1을 리턴한다.  다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EPERM	당신은 시간을 설정할 수 있는 특권을 갖고 있지 않다.

이식성 노트 : gettimeofday, settimeofday, 그리고adjtime 함수들은 BSD로부터 왔다.

17.2.3  Broken-down 시간

달력 시간은 초단위로 표현된다.  이것은 계산하기는 편리하지만, 사람이 보통 날짜와 시간을 표현하는 방법과는 다르다.  그와 달리, broken-down 시간은 연도, 달, 날짜 등을 분리하여 2진으로 표현한다.  Broken down 시간값은 계산에는 불편하지만, 그들은 인간이 읽을 수 있는 시간으로 유용하게 사용된다.

broken-down 시간 값은 지역시간대의 선택과 항상 연관되어 있고, 그것은 항상 어떤 시간대가 사용되었음을 지적한다.  이 절에 있는 심볼들은 헤더파일 `time.h'에 선언되어 있다.

데이터 타입 :   struct tm

이것은 broken-down 시간을 표현하기 위해 사용되는 데이터 타입이다.  그 구조체는 어느 순서로 나타날 수 있는, 적어도 다음과 같은 멤버들을 포함하고 있다.

int tm_sec

이것은 0에서 59까지의 범위를 갖는 초(second)를 표현하고 있다.(실제로는 "윤초"를 허용해서, 61까지가 제한이다.)

int tm_min

이것은 0에서 59까지의 범위를 갖는 분(minute)을 나타낸다.

int tm_hour

이것은 0에서 23까지의 범위를 갖는 시(hours)를 나타낸다.

int tm_mday

이것은 1에서 31까지의 범위를 갖는 달중의 그날을 나타낸다.

int tm_mon

이것은 0에서 11까지의 범위를 갖는, 1월 이후의 달의 수를 표현한다.

int tm_year

이것은 1900년 이후의 년(year)의 수이다.

int tm_wday

이것은 0에서 6의 범위를 갖는, 일요일을 기점으로 해서 지난날의 수를 나타낸다.

int tm_yday

이것은 0에서 365의 범위를 갖는, 1월 1일 이후 지난날의 수를 나타낸다.

int tm_isdst

이것은 시간을 표한하는데, 일광절약 시간이 영향을 주는지(있었거나, 또는 있을 예정이거나)를 지적하는 플래그이다.  그 값은 만일 일광 절약시간이 영향을 미치면 양수이고, 만일 그렇지 않으면 0이고, 그 정보가 음수이면 유용하지 않다.

long int tm_gmtoff

이 필드는 broken-down 시간값을 계산하는데 사용되어지는 시간대를 나타낸다; 그것은 초의 단위로 그리니치 표준시(GMT)를 얻어서, 그것에 지역시간을 얻기 위해서 더해져야만 하는 양( amount)이다. 그 값은 변화 가능한 시간대( 17.2.6절 [Time Zone Functions] 241 페이지 참조.)와 같은 것이다. 당신은 또한 그리니치 표준시의 "seconds west의 수"라고 생각할 수도 있다. tm_gmtoff 필드는 GNU 라이브러리 확장이다.

const char *tm_zone

이것은 broken-down 시간값을 계산하는데 사용 되어지는 시간대를 위한 세-문자 이름이다. 이것은 GNU 라이브러리 확장이다.

함수 :   struct tm *localtime (const time_t *time)

localtime 함수는 time이 가리키고 있는 달력시간을 사용자가 정한 시간대와 맞추어서, broken-down 시간으로 변환한다. 리턴값은 어느 날짜와 시간 함수들을 호출해서 얻은 정적 broken-down 시간 구조체를 가리키고 있는 포인터이다. (그러나 어느 다른 라이브러리 함수도 이 오브젝트의 내용에 덧씌우기 (overwrite)를 할수없다.) localtime을 호출하는 것은 다른 하나의 영향이 있다: 그것은 변수 tzname를 현재 시간대에 대한 정보로 설정한다.  17.2.6절 [Time Zone Functions] 241 페이지 참조.

함수 :   struct tm *gmtime(const time_t *time)

이 함수는 broen-down이 세계 협정시간으로 표현된다는 것을 제외하고, localtime과 유사하다._그리니치 표준시가 지역 시간대와 더 연관이 있다.  달력 시간들은 항상 세계 협정시로 표현된다는 것을 기억하라.

함수 :   time_t mktime(struct tm *brokentime)

mktime 함수는 broken-down 시간 구조체를 달력시간 표현으로 변환시키기 위해서 사용된다.  그것은 다른 날짜에 기초하여 연도(year), 주(week)의 날수와 시간 요소들을 채워서 broken-down 구조체의 내용을 "일반화"한다.

mktime함수는 broken-down 시간 구조체의 멤버인 tm_wday와 tm_yday의 정해진 내용들을 무시한다.  그것은 달력 시간을 계산하기 위해서 다른 구성요소의 값들을 사용한다; 그것은 그들의 보통 범위의 밖에 있는 비일반화된 값들을 갖는 그들 구성요소들을 위해서 허용되었다.  mktime이 마지막으로 하는일은 brokentime 구조체의 구성요소들을 조정하는 것이다( tm-wday 와 tm-yday를 포함해서 ). 만일 정해진 broken-down 시간이 달력 시간으로 표현될 수 없다면, mktime은 (time_t)(-1)의 값을 리턴하고 brokentime의 내용을 갱신할 수 없다.  호출된 mktime은 또한 현재의 시간대에 대한 정보로 변수 tzname 를 설정한다.  17.2.6절 [Time Zone Functions] 241 페이지 참조.

17.2.4  형식화된 날짜와 시간

이 절에 설명된 함수들은 문자열로 시간 값들을 형식화 한다.  이들 함수들은 헤더파일 'time.h'에 선언되어 있다.

함수 :   char *asctime(const struct tm *brokentime)

asctime 함수는 brokentime이 가리키고 있는 broken-down 시간을 표준 형식을 가진 문자열로 변환한다.

"Tue  May  21  13:46:22  1991\n"

주안에 있는 요일의 약자는: `Sun', `Mon', `Tue', `Wed', `Thu' `Fri' 그리고 `Sat' 이다.

달들의 약자는: '`Jan', `Feb', 'Mar', 'Apr', 'May', Jun', 'Jul', 'Aug', 'Sep', 'Oct', Nov', 그리고'Dec' 이다.

리턴값은 어떤 날짜와 시간 함수들의 호출에 의한 결과로 덮어쓰기가 된, 정적으로 할당된 문자열을 가리킨다. (그러나 어느 다른 함수도 이 문자열의 내용에 덧씌우기를 할 수 없다.)

함수 :   char *ctime(const time_t *time)

ctime함수는 시간값이 달력시간(지역시간) 형식으로 지정되었다는 것을 제외하고는, asctime과 유사하다.  그것은  asctime(localtime (time))과 같다.  ctime는 localtime으로 하기때문에 변수 tzname을 설정한다.  17.2.6절 [Time Zone Function] 241페이지 참조.

함수 : size_t strftime(char *s, size_t size, const char *template, const struct tm *brokentime)

이 함수는 sprintf 함수(7.11절 [Formatted Input] 85페이지 참조)와 유사하지만, 형식 템플릿 template안에 나타날 수 있는 변환지정은 시간 변환을 위해서 정해진 현재의 지역시간에 따른 brokentime의 날짜와 시간의 요소들을 프린트하도록 특별화되었다. (19장 [Locales], 259 페이지 참조.) template안에 나타난 보통의 문자들은 출력 문자열 s에 복사되어 진다; 이것은 다중바이트 문자들로 이루어진 열들을 포함할 수 있다.  변환지정자는 `%'문자 다음에 나타나고, 다음처럼 출력 문자열을 조정한다.

%a	현재의 지역에 맞는 약자로 표현된 요일이름.

%A      현재지역에 맞는 완전한 요일이름.

%b	현재지역에 맞는 약자인 달 이름.

%	현재지역에 맞는 완전한 달 이름.

%	현재 지역을 위해 선택된 날짜와 시간 표현.

%d	십진수로 표현된(01에서 31까지의 범위) 한달 안의 날짜.

%H	24시간 시계를 사용해서(00에서 23까지의 범위), 십진수로 나타낸 시간.

%I	12시간 시계를 사용해서(01에서 12까지의 범위), 십진수로 나타낸 시간.

%j	십진수(001에서 366까지의 범위)로 표현된 일년안의 날수.

%m	십진수(01에서 12까지의 범위)로 표현된 달.

%M	십진수로 표현된 분.

%p	주어진 시간값에 맞는, `am'또는 `pm' 또는 현재지역에 맞는 연관된 문자열.

%S	십진수로 표현된 초.

%U	첫 번째주의 첫 번째 날인 첫 번째 일요일을 기점으로 해서, 십진수로 표현된 올해의 주의수

%W	첫 번째주의 첫 번째날인 첫 번째 월요일을 기점으로 해서, 십진수로 표현된 올해의 주의 수

%w	일요일을 0으로 한, 십진수로 표현된 요일.

%x	시간 없이, 현재지역을 위해 예약된 날짜 표현.

%X	날짜없이, 현재지역을 위해 예약된 시간 표현.

%y	세기(century)없이(00에서 99까지의 범위), 십진수로 표현된 연도.

%Y	세기를 포함해서, 십진수로 표현된 연도.

%Z	시간대나 이름 또는 약자(시간대가 결정될 수 없다면 비워라)

%%	문자 `%'

size 인수는 널 종료문자를 포함해서, 배열 s에 저정된 문자들의 최대 개수를 나타내기 위해서 사용될 수 있다.  만일 형식화된 시간이 size 크기보다 더 많은 문자들이 필요하다면, 초과되는 문자들은 버려진다.  strtime을 통한 리턴값은 널 종료문자를 포함하지 않은, 배열 s에 저장된 문자들의 개수이다.  만일 value가 size와 같다면, 그 배열이 너무 작았음을 의미한다;  당신은 배열을 크기를 크게하고, strtime를 재 호출하라. 만일 s가 널 포인터라면, strftime은 실제로 아무것도 쓰지 않지만, 대신에 쓰여졌던 문자들의 개수를 리턴한다.  strftime에 대한 예는 17.2.7절 [Time Functions Example] 242 페이지 참조.

17.2.5  TZ으로 시간대를 정하기

GNU 시스템에서, 사용자는 TZ 환경변수로 시간대를 정할 수 있다. 환경변수를 어떻게 설정하는가에 대한 정보는, 22.2절 [Environment Variables] 317 페이지 참조.  시간대를 억세스하기 위한 함수는 'time.h'에 선언되어 있다.  TZ 변수의 값은 세가지 형식중에 하나가 된다. 첫 번째 형식은 지역시간대에서 일광절약시간(또는 썸머타임)이 없는 곳에서 사용된다:

std offset

std 문자열은 시간대의 이름을 정한다.  그것은 세 개 이상의 문자열의 길이를 가져야만 하고, 콜론이 선행되거나, 숫자, 콤마, 또는 플러스나 마이너스 부호가 들어가 있으면 안된다.  지역시간대 이름 사이에 공백이 없어야한다.  이러한 제한들은 정확하게 그 지정을 분석하기위해 필요하다.  offset는 협정 세계 시간을 얻기위해서 지역시간에 더해야만 하는 시간값을 정한다. 그것은 [+|-]hh[:mm[:ss]]와 같은 구문이다.  이것은 만일 지역시간대가 그리니치 자오선(Prime Meridian)의 서쪽에 있다면 양수이고, 동쪽에 있다면 음수이다.  시간은 0과 24의 사이에 있어야만 하고, 분과 초는 0과 59의 사이에 있어야만 한다.

예를 들어, 일광절약시간을 택하지 않고, 동부표준시간을 어떻게 정하는지는 다음과 같다.

EST+5

일광절약시간이 있는 곳에서 사용되는 두 번째 형식:

std offset dst [offset],start[/time],end[/time]

처음의 std 와 offset은 위에 설명된것처럼 표준 시간 대를 정한다. dst 문자열과 offset은 일광절약시간의 시간대에 맞는 name과 offset을 정한다;  만일 offset을 생략하면, 디폴트로 표준시간보다 한시간 앞으로 정해진다.

지정자의 나머지는 일광절약시간이 있는곳에서의 설명이다.  일광절약 시간이 영향을 미칠때는 시작필드에 있고, 다시 표준시간으로 변환되었을때는 끝 필드에 있다.  다음 형식들은 이들 필드를 위한 설명이다.

Jn

이것은 1과 365사이에 있는 어떤 날 n으로, 율리우스력의 날짜를 정한다.  윤년에 있는 2월 29일은 더해지지 않는다.

n

0과 365사이에 있는 어떤날 n으로, 율리우스력의 날짜를 정한다.  윤년에 있는 2월 29일은 더해진다.

Mm.w.d

m달의 w주의 d날.  날 d는 0(일요일)와 6사이의 값이어야한다.  주 w는 반드시 1과 5사이이다; 주 1은 그달에서 날 d가 발생한 첫 번째 주이고, 주 5는 달에서 마지막날 d가 있는 마지막주이다.  달 m은 1과 12사이이다.  time필드들은 지역시간을 다른 시간표현으로 변환할 때 지정한다.  만일생략되면, 디폴트값은 02:00:00이다.  예를 들어, 미국에서 적당한 일광절약시간이 포함된 날짜로 동부시간대를 지정하는 것이 있다.  보통 GMT로부터의 offset는 5시간이다;  그리니치 자오선으로부터 서쪽으로 있기때문에 양수값이다.  썸머타임은 4월 첫 번째 일요일 오전 2시에 시작되고, 10월 마지막 일요일 오전 2시에 끝난다.

EST+5EDT,M4.1.0/M10.5.0

특별한 어느 지역에서 일광절약시간의 예정은 해마다 변경된다.  정확하게 하려면, 그 지역에 맞는 일광절약시간의 예정에 기초하여 날짜와 시간을 변경해야한다.  그렇지만, 시스템은 그 예정이 어떻게 변경되었는지를 당신이 지정할 도구들을 가지고 있지 않다.  당신이 이것을 할 수 있는 가장좋은 방법은 하나의 특별한 예정을 지정해서 하는 것이다. 보통 현재의 시간을 표시하고 아무런 문제가 없을 때, 이것은 다른 날짜로 변경하기위해 사용된다.

세 번째 형식은 이와 같다.

:characters

각각의 운영체제마다 이 형식을 다르게 해석한다. GNU C 라이브러리에서는, 문자들은 시간대를 표현하는 파일의 이름으로 해석한다. 만일 TZ 환경변수가 아무런 값도 가지고 있지 않다면, 그 오퍼레이션은 디폴트로 시간대를 정한다. 각 운영체제는 디폴트시간대를 정하기 위한 자신나름대로의 규칙을 갖고 있고, 그래서 우리가 그들에 대해서 말할수 있는 것은 적다.

17.2.6  시간대를 위한 함수와 변수들

변수 :   char *tzname[2]

tzname 배열은 사용자가 선택한 표준 시간대와 일광절약시간대의 이름들을 표준 세-문자로 가진 두 개의 문자열을 저장한다.  tzname[0]은 표준시간대(예를 들어 "EST")의 이름이고, tzname[1]은 일광절약시간이 사용될 때 시간대를 위한 이름이다(예를들어, "EDT").  이들은 TZ 환경변수로부터 std 와 dst문자열과 같다. tzname 배열은 tzset, ctime, strftime, mktime, 또는 localtime이 호출될때마다 TZ 환경변수로 초기화된다.

함수 :   void tzset(void)

tzset함수는 TZ 환경변수의 값으로 tzname변수를 초기화한다. 이것은 시간대에 의존하는 다른 시간 변환 함수들에서 사용될 때 자동적으로 호출되기 때문에 당신이 직접 당신의 프로그램을 통해서 이것을 부를필요가 없을 것이다.

다음의 변수들은 유닉스 시스템 V와의 호환성을 위해서 정의되었다. 이들 변수들을 localtime 함수를 호출함으로써 설정된다.

변수 :   longint timezone

이것은 그리니치 표준시와 지역 표준 시간사이의 차이를 초단위로 저장한다.  예를 들어, 미국에서 동부시간대의 값은 5*60*60이다.

역자주 : 아까 동부시간대가 그리니치 표준시와 5시간의 차이가 난다고 했으니까... 5*60*60.

변수 :   int daylight

이 변수는 만일 표준 미국 일광절약시간 규칙이 적용된다면 0이 아닌값을 가진다.

17.2.7  시간 함수들의 예제

지역시간과 달력시간 함수들의 몇가지를 사용하는 예를 보여주는 프로그램이다.

#include  <time.h>

#include  <stdio.h>

#define  SIZE  256

int

main  (void)

{

char  buffer[SIZE];

time_t  curtime;

struct  tm  *loctime;

/* 현재의 시간을 얻어라 */

curtime  =  time  (NULL);

/* 지역시간 표현으로 변환하라   */

loctime  =  localtime  (&curtime);

/* 표준 형식으로 날짜와 시간을 출력하라. */

fputs  (asctime  (loctime),  stdout);

/* 보기좋은 형식으로 그것을 출력하라 */

strftime (buffer, SIZE, "Today is %A, %B %d.\n", loctime);

fputs  (buffer,  stdout);

strftime (buffer, SIZE, "The time is %I:%M %p.\n", loctime);

fputs  (buffer,  stdout);

return  0;

}

다음과 같은 출력을 만들어낸다.

Wed  Jul  31  13:02:36  1991

Today  is  Wednesday,  July  31.

The  time  is  01:02  PM.

17.3  알람을 설정하기

alarm 과 setitimer 함수들은 어떤 미래의 시간에 프로세스 그 자체에 인터럽트를 거는 메카니즘을 제공한다.  그들은 타이머를 설정함으로써 이루어진다.  타이머가 끝나면, 프로세스는 신호를 받는다.

각 프로세스는 유용한 세가지 독립 간격 타이머를 갖는다.

□ 시계 시간을 계산하는 실제-시간 타이머. 이 타이머는 그 시간이 경과되면 프로세스에게 SIGALRM 신호를 보낸다.

□ 프로세스에 의해 사용된 CPU시간을 셈하는 가상 타이머. 이 타이머는 그 시간이 경과되면 프로세스에게 SIGVTALRM 신호를 보낸다.

□ profiling 타이머는 프로세스에 의해 사용된 CPU 시간과 프로세스를 위한 시스템 호출에서 사용된 CPU 시간 양쪽을 계산한다.

이 타이머는 그 시간이 경과되었을 때, 프로세스에게 SIGPROF 신호를 보낸다.

당신은 어떤 종류로 설정된 어느 주어진 시간에 한가지의 타이머만을 사용할 수 있다.  만일 당신이 아직 경과되지 않은 시간을 가진 타이머를 설정하면, 그 타이머는 새로운 값으로 간단히 재설정된다. 당신은 setitimer 이나 alarm을 호출하기 전에 signal이나 sigaction을 사용해서 적당한 알람 신호를 위한 핸들러를 만들어야 할 것이다. 그렇지않다면, 시간이 경과되었을 때 발생할 수 있는 사건들의 연결은 알람 신호들에 대한 디폴트 동작으로, 프로그램의 종결을 발생시킬 것이다. 21장. [Signal Handling] 271페이지 참조.

setitimer 함수는 알람을 설정하기 위해서 주요한 방법이다.  이 도구는 헤더파일 `sys/time.h'에 선언되어 있다.  실제-시간 타이머를 설정하기 위해서 간단한 인터페이스를 제공하는 alarm 함수는 `unistd.h'에 선언되어 있다.

데이터타입 :   struct itimerval

이 구조체는 타이머가 경과되는 때는 정하기 위해서 사용되어진다.  그것은 다음과 같은 멤버들을 포함하고 있다.

struct timeval it_interval

이것은 연속적인 타이머 인터럽트들 사이의 간격이다.  만일 0이면, alarm은 오직 한 번만 보내질것이다.

struct timeval it_value

이것은 첫 번째 타이머 인터럽트 간격이다. 만일 0이면, 알람은 불가능하다. 데이터타입 struct timeval은 17.2.2절 [High-Resolution Calendar] 235 페이지에 선언되어 있다.

함수 : int setitimer(int which, struct itimerval *old, struct itimerval *new )

setitimer 함수는 new에 따른것에 의해 정해진 타이머를 설정한다.  which 인수는 ITIMER_REAL, ITIMER_VIRTUAL, 또는, ITIMER_PROF 중의 한값을 가질 수 있다. 만일 old가 널 포인터가 아니라면, setitimer 은 그것이 가리키고 있는 구조체에 같은 종류의 전에 경과되지 않았던 어느 타이머에 대한 정보를 리턴한다.  성공하면 리턴값은 0이고, 실패하면 -1이다. 다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EINVAL	타이머의 간격이 너무 길었다.

함수 :   int getitimer(int which, struct itimerval *old)

getitimer 함수는 old가 가리키고 있는 구조체에의해 정해진 타이머에 대한 정보를 저장한다.  리턴값과 에러상황들은 setitimer과 같다.

ITIMER_REAL

이 상수는 실제-시간 타이머로 정하기 위해서, setitimer과 getitimer 함수들에서 which 인수로써 사용될 수 있다.

ITIMER_VIRTUAL

이 상수는 가상 타이머로 정하기 위해서, setitimer과 getitimer 함수들에서 which 인수로써 사용될 수 있다.

ITIMER_PROF

이 상수는 profiling 타이머로 정하기 위해서, setitimer과 getitimer 함수들에서 which 인수로써 사용될 수 있다.

함수 :   unsigned int alarm(unsigned int seconds)

alarm 함수는 초단위로 seconds가 경과되도록 실제-시간 타이머를 설정한다.만일 당신이 현존하는 alarm을 취소하길 원한다면, 당신은 seconds인수를 0으로 해서 alarm을 호출하면 된다.리턴값은 미리 호출됐던 alarm에서 몇초가 남았는지를 가리킨다. 만일 미리 호출된 alarm이 없다면, alarm은 0을 리턴한다. alarm함수는 이처럼 setitimer에 의하여 정의될 수 있다.

unsigned  int alarm  (unsigned  int  seconds)

{

struct  itimerval  old,  new;

new.it_interval.tv_usec  =  0;

new.it_interval.tv_sec  =  0;

new.it_value.tv_usec  =  0;

new.it_value.tv_sec  =  (long  int)  seconds;

if  (setitimer  (ITIMER_REAL,  &new,  &old)  <  0)

return  0;

else

return  old.it_value.tv_sec;

}

alarm 함수를 사용하는 예는 21.4.1절 [Handler Returns] 285 페이지에서 보여주고 있다.  만일 당신이 주어진 시간(초단위의)동안 당신의 프로세스가 기다리기를 원한다면 sleep 함수를 사용할 수 있을 것이다.  17.4절[Sleeping] 244 페이지 참조.

당신은 타이머가 경과되었을 때, 정확하게 도착된 신호를 셀수없을 것이다. 멀티프로세싱 환경에서는 어느정도의 지연이 있기 때문이다.

이식성 노트:setitimer 와 getitimer 함수들은 BSD Unix로부터 왔고, alarm 함수는 POSIX.1 표준으로 정해져있다.  setitimer 은 alarm보다는 더 강력하지만, alarm이 더 광범위하게 쓰인다.

17.4  Sleeping

sleep 함수는 짧은 시간동안 그 프로그램을 기다리게 하는 간단한 방법을 제공한다.  만일 당신의 프로그램이 신호들을 사용하지 않는다면( 종료를 제외하고), 당신은 sleep가 정해진 시간동안에 확실하게 기다린다는 것을 기대할 수 있다.  그렇지 않다면, sleep는 만일 신호가 도착한다면 곧바로 리턴될 수 있다;  만일 당신이 신호에 상관없이 주어진 시간동안 기다리기를 원한다면 select( 8.6절 [Waiting for I/O], 111 페이지 참조.)를 사용하고, 기다리는 어떠한 기술자도 지정하지 말아라.

함수 :   unsigned int sleep (unsigned int seconds)

sleep 함수는 seconds동안 기다리거나, 첫 번째 발생한 신호가 도착될때까지 기다린다.  만일 sleep 함수가 요청된 시간이 경과되었기 때문에 리턴했다면, 리턴값은 0이다.  만일 신호가 도착되어서 리턴했다면, 그 리턴값은 아직 경과되지 않고 남은 시간의 값이다. sleep 함수는 `unistd.h'에 선언되어 있다. sleep가 0이 아닌값을 리턴했을 때, 그 리턴값을 사용해서 정해진 시간동안 다시 기다리도록 sleep를 재호출하려는 유혹에 저항하라.  이것은 빈번하게 도착한 신호들만큼 정확하게 길어진 어떤 시간동안 작업할 것이다.  그러나 각 신호는 그 부가적인 시간(초)으로 인해서 원래 정해진 기다림의 시간보다 길어지게 할 것이다.  기다림이 얼마나 길어지거나, 짧아질수 있는지에 대한 아무런 제한이 없는 어떤 신호들이 불행하게도 빠르게 연속적으로 발생한 다고 가정하라. 대신에, 그 프로그램이 기다림을 멈추게 된 그 시간을 계산하고, 다시 남아있는 시간동안 기다림을 다시 시도하도록 하라.  이것은 1초보다도 더 많이 시간차이가 나지 않을 것이다.  더 적은 시간동안 작업하려면, 당신은 select를 사용하고, 꽤 정확한 시간동안 기다리게 할 수 있다. ( 물론, 멀티유저 시스템에서 그 기계가 단지 한 응용프로그램을 위해서 사용되지 않는다면, 피할수 없는 부가적인 지연을 발생될 것이다, 당신이 그것을 피할 수 있는 방법은 아무것도 없다. ) 어떤 시스템상에서, 만일 당신의 프로그램에서 SIGALRM을 사용한다면 sleep는 이상하게 동작할수 있다.  심지어 sleep가 호출되었을 때 SIGALRM 신호가 무시되거나 블록된다면, sleep는 SIGALRM 신호 때문에 시기상조적으로 리턴될 것이다.  만일 당신이 프로세스가 기다리고 있는(sleeping)동안 배달된 SIGALRM 신호와 SIGALRM 신호를 위한 핸들러를 만들었다면, 그 동작은당신이 만든 신호 핸들러에 그 처리를 맡기는 대신에 sleep의 리턴을 발생시킬 것이다.  그리고 만일 sleep가 alarm을 요청하거나 SIGALRM의 처리를 선택하는 핸들러가 가진 신호에 의해 인터럽트된다면, 이 핸들러와 sleep는 방해될것이다. GNU 시스템에서는, sleep가 SIGALRM에 의하여 작업하지 않기 때문에, sleep와 SIGALRM을 같은 프로그렘에서 사용하는 것이 안전한다.

17.5  자원 사용

함수 getrusage 와 데이터타입 struct rusage는 프로세스의 사용형태를 시험하기 위해서 사용된다.  그들은 `sys/resource.h'에 선언되어 있다.

함수 :   int getrusage(int processes, struct rusage *rusage)

이 함수는 processes에 의해 정해진 그 프로세스를 위해 사용된 것을 *rusage에 정보를 저장하여, 보고한다.  대부분의 시스템에서, processes는 오직 두 개의 유용한 값을 가진다:

RUSAGE_SELF

단지 현재의 프로세스.

RUSAGE_CHILDREN

이미 종료된 모든 자식 프로세스(직접 과 간접).

GNU 시스템에서, 프로세스 ID를 정함으로 해서, 특정한 자식 프로세스에 대해서 조사할 수 있다.  getrusage의 리턴값은 성공하면 0이고, 실패하면 -1이다.

EINVAL

processes 인수가 유용하지 않다.

특정한 자식 프로세스를 위한 사용형태를 얻는 방법은 wait4 함수를 통해서 할 수 있는데, 그 함수는 자식 프로세스가 종료되었을 때 자식 프로세스를 위한 모든 것을 리턴한다.  23.8절 [BSD Wait Functions] 332 페이지 참조.

데이터타입 :   struct rusage

이 데이터타입은 다양한 종류의 자원에 대한 사용량을 기록한다. 이것은 다음과 같은 멤버들을 갖으며, 다른것도 가능하다.

struct timeval ru_utime	사용된 사용자 시간.

struct timeval ru_stime	사용된 시스템 시간.

long ru_majflt	페이지 폴트의 개수.

long ru_inblock	블록된 입력 명령의 개수.

long ru_oublock	블록된 출력 명령의 개수.

long ru_msgsnd	보냈던 메시지의 개수.

long ru_msgrcv	받았던 메시지의 개수.

long ru_nsignals	받았던 시그날의 개수.

사용형태를 시험하는 함수로는 vtimes도 있지만 여기서 설명하지는 않는다.그것은 `sys/vtimes.h'에 선언되어 있다.

17.6  제한된 자원 사용

당신은 프로세스의 자원 사용에 대해서 제한을 가할 수 있다.  그 프로세스가 제한을 넘어서려 시도할 때, 제한에 따라서 그 프로세스는 신호를 받거나, 아니면 시도했던것의 실패로 인한 시스템 호출이 될 것이다.  각 프로세스는 그 부모 프로세스로부터 처음에는 제한을 상속받지만, 그후에는 그들을 변경 할 수 있다. 이 절에 있는 심볼들은 `sys/resource.h'에 정의되어 있다.

함수 :   int getrlimit(int resource, struct rlimit *rlp)

자원 resource의 현재값과 최대값을 읽고, 그들을 *rlp에 저장한다. 성공하면 리턴값은 0이고 실패하면 -1이다.  발생 가능한 에러 상황은 EFAULT뿐이다.

함수 :   int setrlimit(int resource, struct rlimit *rlp)

*rlp에 있는 것으로 자원 resource의 현재값과 최대값을 설정한다. 성공하면 리턴값은 0이고 실패하면 -1이다.  다음의 errno는 가능한 에러상황이다.

EPERM	당신이 허용된 최대 제한값을 변경하려 시도했지만, 당신은 그렇게 할만한 특권을 부여받지 않았다.

데이터타입 :   struct rlimit

이 구조체는 제한값들을 받기위해서 getrlimit에서 사용되고, 제한값들을 정하기 위해서 setrlimit에서 사용된다.  두 개의 필드를 갖는다.

rlim_cur	질문에 대한 제한의 현재값.

rlim_max	질문에 대한 제한의 최대 허용값.  당신은 이 최대값 보다 큰수로 제한의 현재값을 설정할 수 없다.  오직 슈퍼 유저만이 최대 허용값을 변경할 수 있다.

getrlimit에서 그 구조체는 출력으로서, 현재값들을 받는다.  setrlimit에서 그 구조체는 새로운 값들을 정하는데 사용된다. 당신이 제한을 정할 수 있는 자원의 리스트이다.  그들은 바이트로 계산된 크기이다.

RLIMIT_CPU

그 프로세스가 사용할 수 있는 cpu time의 최대량.  만일 이것보다 더 길게 실행한다면, SIGXCPU라는 신호가 발생한다.  값은 초단위로 계산된 것이다.  21.2.7절 [Nonstandard Signals] 278 페이지 참조.

RLIMIT_FSIZE

그 프로세스가 만들 수 있는 파일의 최대 크기.  이것보다 큰 파일을 만들려하면 SIGXFSZ라는 신호가 발생된다. 21.2.7절 [Nonstandard Siganls] 278 페이지 참조.

RLIMIT_DATA

프로세스가 사용할 수 있는 데이터 메모리의 최대크기. 만일 그 프로세스가 이 양을 초과하는 데이터 메모리를 할당하려 시도하면 그 할당은 실패한다.

RLIMIT_STACK

프로세스를 위한 최대 스텍 크기.  만일 프로세스가 이 크기보다 크게 스택을 확장하려 시도한다면, SIGSEGV 신호가 발생된다.  21.2.1절 [Program Error Signals] 273 페이지 참조.

RLIMIT_CORE

이 프로세스가 만들 수 있는 최대 크기 코아 파일.  만일 프로세스가 종료되고 코어파일이 만들어졌고, 이 최대크기가 충분하지 않다면, 코아파일은 잘려진다.

RLIMIT_RSS

이 프로세스가 얻을 수 있는 물리적 메모리의 최대량. 이 수치는 시스템 스케쥴러와 메모리 할당자를 위한 정보가 된다;  시스템은 메모리가 남아있는 양이 있을 때 그 프로세스에게 더 많은 메모리를 줄 수도 있다.

RLIMIT_OPEN_FILES

프로세스가 개방할 수 있는 파일의 최대개수.  만일 이것보다 더 많이 개방하려 한다면, EMFILE라는 에러코드가 발생된다. 2.2절 [Error Codes] 12 페이지 참조.

RLIM_NLIMITS

다른 자원 제한의 개수.  어느 유용한 자원 피연산자는 RLIM_NMLIMITS보다 적을것임이 틀림없다.

상수 :   int RLIM_INFINITY

이 상수는 setrlimit에서 제한값으로 사용될 때 "무한대"의 값을 나타낸다.

자원 제한을 설정하는 함수는 ulimit와 vlimit가 있지만 여기서 설명되지 않았다.  vlimit는`sys/vlimit.h'에 선언되어 있고 BSD로부터 왔다.

17.7  프로세스 우선권

여러개의 프로세스들이 실행하려 시도될 때, 그들 각각이 가진 우선권이 그 프로세스가 가져갈 CPU의 분할을 결정한다.  이 절은 당신이 어떻게 프로세스의 우선권을 알아내고 설정할 수 있는지를 설명한다.  이들 함수들과 매크로 모두는 `sys/resource.h에 선언되어 있다.

유용한 우선권값들의 범위는 운영체제에 의존되지만, 일반적으로 -20에서 20까지에서 실행된다.  낮은 우선권값은 그 프로세스가 더 자주 실행됨을 의미한다.  이들 상수들은 우선권 값들의 범위를 설명한다.

PRIO_MIN

유용한 우선권의 가장 작은값.

PRIO_MAX

유용한 우선권의 가장 큰값.

** 역자주 :  원서에는 PRIO_MAX에 The smallest라고 되어 있었습니다. 하지만 저는 틀렸다는 판단을 갖고 largest라고 고쳤음을 알려 드립니다.

함수 :   int getpriority(int class, int id)

프로세스들 class의 우선권을 읽는다; class와 id는 밑에 있는 것 중에 한가지로 정한다.  성공하면 리턴값을 우선권값이고, 실패하면 -1을 리턴한다.  다음의 errno는 이 함수에서 가능한 에러상황이다.

ESRCH

class와 id의 조합이 현존하고 있는 어떤 프로세스와도 맞지가 않는다.

EINVAL

class의 값이 유용하지 않다.

리턴값이 -1일 때, 그것은 실패했음을 알리거나, 또는 그것이 우선권의 값이 될 수 있다.  이럴때를 대비한 정확한 방법은 getpriority를 호출하기 전에 errno = 0으로 설정을 해놓으면, 실패를 판단하는 조건으로 errno != 0 을 사용할 수 있다.

함수 :   int setpriority(int class, int id, int priority)

프로세스들 class의 우선권을 읽는다(설정한다.);  class 와 id는 밑에 것중에 하나로 설정한다.  성공하면 리턴값은 0이고 실패하면 -1이다. 다음의 errno는 이 함수를 위해 정의된 에러상황이다.

** 역자주 : 역시 이곳에서도 문맥상 Read가 맞지가 않습니다.  책에는 Read라고 나와있지만, 함수 이름이나, 다음 문장들을 참고로 보건데 저는 Set이 맞다고 생각합니다.

ESRCH	class와 id의 조합이 현존하는 어떤 프로세스와도 맞지가 않는다.

EINVAL	class의 값이 유용하지 않다.

EPERM	당신이 다른 사용자의 프로세스의 우선권을 설정하려 시도하였고, 당신은 그것을 위한 특권을 가지지 않았다.

EACCES	당신이 어떤 프로세스의 우선권을 낮추려 시도했고, 당신은 그것을 할만한 특권이 부여되지 않았다.

인수 class와 id는 함께 당신이 우선권을 읽거나, 설정할 프로세스의 집합을 설정한다.  이들은 class에서 사용할 수 있는 값들이다.

PRIO_PROCESS

한 프로세스의 우선권을 읽거나 설정하라.  인수 id는 프로세스의 ID이다.

PRIO_PGRP

한 프로세스 그룹의 우선권을 읽거나 설정하라.  인수 id는 프로세스 그룹 ID 이다.

PRIO_USER

한 사용자의 프로세스들의 우선권을 읽거나 설정하라. 인수 id는 사용자 ID이다.

만일 인수 id가 0이면, class에 따라서 현재 프로세스, 현재 프로세스 그룹 또는 현재 사용자를 나타낸다.

함수 :   int nice(int increment)

increment로 현재 프로세스의 우선권을 증가시킨다.  리턴값은 의미가 없다.  다음은 nice함수와 같은 일을 하는 함수이다. 즉.. nice의 정의이다.

int nice  (int  increment)

{

int  old  =  getpriority  (PRIO_PROCESS,  0);

setpriority  (PRIO_PROCESS,  0,  old  +  increment);

}

18  확장된 문자들

18장 확장된 문자들

다수의 언어들이 char의 형태로 문자들을 표현할 수 없다.  일본어와 중국어가 이와 가장 유사한 예이다. GNU C 라이브러리는 확장된 문자 집합들을 취급하기 위해서 두 개의 메카니즘을 지원한다:  다중바이트 문자들과 와이드 캐릭터(wide characters) 이 장은 이들 메카니즘과 그들을 변환하는 함수들을 어떻게 사용할 것인지를 설명한다. 이 장에 있는 함수들의 동작은 문자 분류 LC_CTYPE 범주로 나타낸 현재의 지역에 의해 영향을 받는다; 19.3절 [Locale Categories] 260 페이지 참조.  지역을 선택하는 것은 다중바이트 코드가 사용되는지, 와이드 캐릭터 코드가 사용되는지를 선택하는 것이다.

18.1  확장된 문자들에 대한 소개

당신은 두가지 방법중에 하나로 확장 문자들을 나타낼 수 있다.

□ 다중바이트 문자들은 char 오브젝트의 배열인 원래의 문자열에 포함 될 수 있다.  이것의 이득은 많은 프로그램들과 운영제체가 아무런 변환없이, 원래의 ASCII 문자들 사이에 흩어져 있는 다중바이트 문자들을 다룰수 있다는 것이다.

□ 와이트 캐릭터는 더 많은 비트들을 점유한다는 것을 제외하고는 원래의 문자들과 같다.  와이트 캐릭터 데이터 타입, wchar_t는 ASCII 코드들 은 물론 확장된 문자 코드들을 다루기위해서 충분히 큰 범위를 갖는다. 와이드 캐럭터의 이득은 각 문자들이 원래의 ASCII 문자들처럼 단일한 데이터 오브젝트라는 것이다.  하지만 몇개의 불편이 있다.

□ 어떤 현존하는 프로그램이 와이트 캐릭터를 사용하도록 하려면 갱신되고 재컴파일 되어야만 한다.

□ 와이드 캐릭터의 파일들은 보통의 문자들을 제외하고는 프로그램에 의해 읽혀질 수 없다.

일반적으로, 당신은 파일에 텍스트를 읽거나 쓰기처럼, 외부 프로그램 인터페이스의 부분으로 다중바이트 문자 표현을 사용한다.  그렇지만, 일정한 표현은 편집 명령들을 가장 쉽게 구현하도록 하기 때문에, wchar_t 오브젝트의 배열로 확장된 문자들을 포함하는 문자열을 내부적인 처리를 수행하는 것이 보통은 더 쉽다.  만일 당신이 파일에서는 다중바이트 문자들을 사용하고 내부 동작에는 와이트 캐릭터를 사용한다면, 당신은 데이터를 읽거나 쓸 때, 그들 사이에 변환을 할 필요가 있다. 만일 당신의 시스템이 확장된 문자들을 지원한다면, 다중바이트 문자들과 와이드 캐릭터 문자들을 둘다 지원한다.  라이브러리에 포함된 함수를 통해 당신은 그 두 표현 사이의 변환을 수행할 수 있다.  이들 함수들은 이 장에 설명되어 있다.

18.2  지역과 확장된 문자

컴퓨터 시스템은 한 개의 다중바이트 문자 코드보다 많이, 그리고 한 개의 와이트 캐릭터 코드보다는 많이 지원할 수 있다.  사용자는 문자 분류에 있는 현재의 지역코드를 선택함으로써 제어한다( 19장 [Locals] 259 페이지 참조.)  각 지역은 특정한 다중바이트 문자 코드와 특정한 와이드 캐럭터 코드를 정한다.  지역의 선택은 라이브러리에 있는 변환 함수들의 동작에 영향을 끼친다. 어떤 지역들에서는 와이드캐릭터나 혹은 다중바이트 문자들중 하나만 지원한다.  이들의 지역에서, 라이브러리의 변환 함수들은 비록 하찮은 일들이지만 여전히 작업한다.

만일 당신이 문자분류를 위해서 새로운 지역을 선택한다면, 그들 함수들에 의하여 유지되고 있던 내부 shift 상황은 혼란하게 될 수도 있다, 그러므로 당신이 문자열을 처리하는 도중에 지역을 변경하는 것은 좋은 생각이 아니다.

18.3  다중바이트 문자들

보통의 ASCII 코드에서 연속된 문자들은 바이트가 연속하고 있음을 의미하고 각 문자는 한 바이트이다.  이것은 매우 간단하지만, 오직 256개의 독립문자들만을 허용한다.

다중바이트 문자 코드에서도, 연속된 문자들은 바이트가 연속하고 있음을 의미하지만, 각 문자는 한 개 또는 더 많은 연속적인 바이트들을 점유하고 있을 것이다.

다중바이트 코드를 설계하는 많은 다른 방법들이 있다;  다른 시스템들은 다른 코드들을 사용한다.  특정한 코드를 설계하기 위해서는 특정한 문자를 대표하는 한 문자를 몇바이트(기본 바이트 시퀀스)로 나타낼것인지를 정해야 한다.  컴퓨터에서 한 코드는 실제로 기본 시퀀스들의 제한된 개수가져야만 하고, 그들중 어떤것도 이것보다 길어서는 안된다.

이들 시퀀스들이 모두 같은 길이를 가질 필요는 없다.  실제로, 그들중 대부분은 단지 한바이트 길이를 가진다.  0에서 0177의 범위에 있는 기본 ASCII 문자들이 중요하기 때문에, 그래서 그들을 모두 다중바이트 코드로 나타낸다.  0에서 0177의 범위에 있는 한 바이트는 그 자체로 한 문자를 표현하고 있다.   한 바이트보다 긴 문자들은 0200에서 0377 범위에 있는 한바이트로 항상 시작되어야만 한다. 바이트 값 0은 문자열을 끝내기 위해서 사용될 수 있고, 종종 ASCII 문자들의 문자열에 사용된다.

단일한 문자들을 표현하는 기본 바이트 시퀀스를 정하는 것은 자동적으 로긴 바이트 시퀀스에 의미를 주게된다.  예를 들어, 두 개의 바이트 시퀀스 0205 049가 그리스 문자 알파를 의미한다면,  0205 049 065는 알파 뒤에 'A'(ASCII 코드 065)가 따라옴을 나타내고, 0205 049 0205 049는 일렬로 두 개의 알파가 있음을 나타낸다.

만일 어느 바이트 시퀀스가 여러 가지의 의미로 해석될 수 있다면 그 다중바이트 코드는 좋은 것이 아니다. 시스템에서 코드는 실제로 모두 단일한 의미로 사용된다. 대부분의 코드에서 어떤 바이트들의 시퀀스는 문자로써 아무런 의미를 가지지 않는다.그들을 유용하지 않다라고 한다.

가장 간단하고 가능한 다중바이트 코드에 대한 한가지 :

단일한 바이트들로 구성된 기본 시퀀스

특정한 코드는 전혀 다중바이트 코드를 사용하지 않는다.  그것은 유용하지 않은 시퀀스가 없다.  그러나 오직 256개의 문자들만을 취급할 수 있다. 이곳에 9376개의 다른 문자들을 다룰 수 있는 다른 가능한 코드가 있다.

기본 시퀀스의 구성은.

□ 0에서 0237은 단일한 바이트값.

□ 0240에서 0377 범위에 있는 값들을 가진 바이트 두 개로 두 개-바이트 시퀀스

이 코드또는 이와 유사한것들은 일본어 문자들을 표현하기위해서 어떤 시스템에서 사용된다.  유용하지 않은 시퀀스는 0240부터 0377의 범위에 있는 연속된 바이트들의 개수가 홀수라면 유용하지 않은 것이다.

여기에 더 많은 확장된 문자들을 다룰 수 있는 다중바이트 코드가 있다. 이것은 실제로 거의 3000만 까지 된다.

기본 시퀀스 구성은.

□  0에서 0177의 범위로는 단일한 바이트.

□ 네 개의 바이트로 구성된 시퀀스에서 첫 번째 바이트는 0200에서 0237의 범위를 갖고, 나머지는 0240에서 0377의 범위를 갖는다.

이 코드에서, 어느 시퀀스가 0240에서 0377의 범위의 한바이트로 시작하지 않으면 유용하지 않다. 그리고 마지막 바이트를 제거함으로써 어떤 이득을 갖거나, 한 개의 유용한 문자를 대표하는 바이트들을 가지고 결코 다른 유용한 문자을 만들어낼 수 없는 문자코드가 있다. (이런 특성은 당신이 특정한 문자들이 있는 문자열을 탐색하고자 할 때 편리하다. )

기본 시퀀스 구성은.

□ 0에서 0177의 범위로는 단일한 바이트.

□ 두 개-바이트 시퀀스에서 첫 번째 바이트는 0200에서 0207의 범위를 갖고, 두 번째는 0240에서 0377의 범위를 갖는다.

□ 세 개-바이트 시퀀스에서 첫 번째 바이트는 0210에서 0217의 범위를 갖고, 나머지 바이트들은 0240에서 0377의 범위를 갖는다.

□ 네 개-바이트 시퀀스에서 첫 번째 바이트는 0220에서 0227의 범위를 갖고, 나머지 바이트들은 0240에서 0377의 범위를 갖는다.

이 코드로 구성된 유용한 시퀀스들은 길다;  유용한 시퀀스의 예로 0240 과 0220 0300 065가 있다. 가능한 다중바이트 코드들의 개수는 셀수가 없다.  그러나 주어진 컴퓨터 시스템은 고작 몇 개의 다른 코드들을 지원할 것이다. ( 그들 코드들중 하나는 수천개의 다른 문자들을 허용할 것이다. ) 다른 컴퓨터 시스템은 한 개의 완전한 다른 코드를 지원할 것이다.  이 장에서 설명된 라이브러리도구들은 당신의 프로그렘에서 그들을 알 필요가 없이 특정 컴퓨터 시스템의 다중바이트 코드에 대한 세심한 지식들로 꾸려져있기 때문에 유용하다.

당신이 현재 선택된 다중바이트 코드에서 한문자를 표현하는 바이트들의 최대 가능 개수를 찾기 위해서는 표준 매크로 MB_CUR_MAX를 사용하고, 당신의 컴퓨터에서 지원되는 어느 다중바이트 코드를 위한 최대 개수를 위해서는 MB_LEN_MAX를 사용하라.

매크로 :   int MB__LEN__MAX

이것은 어느 지원된 지역을위한 한 다중바이트 문자의최대 길이이다. `limits.h'에 정의되어 있다.

매크로 :   int MB__CUR__MAX

이 매크로는 현재의 지역에서 한 다중바이트 문자를 표현하는 최대 바이트의 개수를 양의 정수표현으로(가능하게비-상수)확장한다. 그 값은 결코  MB_LEN_MAX보다 길 수가 없다.

MB_CUR_MAX는 `stdlib.h'에 정의되어 있다.

보통, 특정 문자 코드안에 있는 각각의 기본 시퀀스는 한 문자를 나타낸다. 같은 다중 바이트 문자 코드들은 한 shift 상황을 갖는다;  어떤 코드들에서는, 쉬프트 시퀀스라고 불리는 다른 쉬프트 상황으로 변경하고, 어떤 또는, 모든 기본 시퀀스들의 의미는 현재의 쉬프트 상황에 따른다.  실제로, 기본 시퀀스의 집합은 심지어 현재의 쉬프트 상황에 의존하여 달라질 것이다. 이 코드의 종류를 다루는것에 대한 정보는 18.9절 [Shift State] 256 페이지를 참조하라.

포함된 다중바이트 문자열을 그들에 대해서 알지 못하는 함수에게 주려 시도할 때 무슨일이 일어날것인가?  보통, 함수는 바이트들의 시퀀스로 문자열을 취급하고, 특별한 어떤 바이트값으로 해석한다;  모든 다른 바이트 값들은 "보통"이다.  다중바이트의 길이를 가진 문자가 어느 특별한 바이트 값들을 포함하지 않으면, 그 함수는 몇 개의 보통 문자처럼 그들을 취급할 것이다.

예를들어, 만일 당신이 파일의 이름으로 다중바이트 문자들을 사용한다면 무엇이 발생할지를 생각해보자.  open과 unlink와 같은 함수들은 특별한 값으로 `/'와 함께, 연속된 바이트들의 시퀀스로 그 이름을 취급하여 함수이름을 가지고 동작한다. 어느 다른 바이트 값들이 시퀀스로 복사되거나 비교되면, 모든 바이트 값들은 유사하게 취급된다. 그러면, 당신은 연속된 바이트들로 구성된 파일이름으로 생각하거나, 또는 다중바이트 문자들이 포함된 문자열로 생각할 것이다;  그 같은 동작은 `/'를 포함하지 않은 다중바이트 문자에도 같은 방법중 하나로 이해될 것이다.

18.4  와이드 캐릭터에 대한 안내

와이드 캐릭터는 다중바이트 문자보다는 많이 간단하다.  그들은 8비트 이상을 가지고 문자들을 표현하기 때문에, 256개보다는 많은 문자들을 표현할 수 있다. 와이드 캐릭터 데이터 타입, wchar_t는 규격화된 ASCII 코드는 물론 확장된 문자 코드들을 저장하기에 충분한 범위를 갖고 있다. 와이드 캐럭터의 사용으로 갖는 이득은 각각의 문자가 보통의 ASCII 문자들 처럼, 단일한 데이터 오브젝트라는 것이다.  그렇지만 와이드 캐럭터들은 몇가지 불편함을 내포하고 있다.

□ 프로그램이 와이드 캐릭터를 사용하려면 수정되고 재컴파일 되어야만 한다.

□ 와이드 캐릭터로 이루어진 파일들은 보통의 문자들을 제외하고는 프로그램에 의해 읽혀질 수 없다.

와이드 캐럭터 값, 0에서 0177 사이는 항상 ASCII 문자 코드와 동일하다. 와이드 캐럭터값 0은 , 0의 값을 가진 단일한 바이트가 보통의 문자들로 이루어진 문자열을 종료하는데 사용되는것처럼, 와이드 캐릭터들로 이루어진 문자열을 종료하는데 사용된다.

데이터 타입 :   wchar__t

이것은 지원된 지역들에서 사용되는 확장된 문자 값들을 모두 표현하기에 충분히 큰 범위를 가진 정수형으로 "와이드 캐럭터"의 데이터 형이다.  지역에 대한 정보는 19장 [Locales] 259 페이지를참조하라.  이 형은 헤더파일 'stddef.h'에 정의되어 있다.

만일 당신의 시스템이 확장된 문자들을 지원한다면, 각각의 확장된 문자들은 한 개의 와이드 캐릭터 코드와 그에 해당하는 다중바이트 기본 시퀀스를 둘다 갖는다.

18.5  확장된 문자열의 변환

mbstowcs함수는 다중바이트 문자들로 이루어진 문자열을 와이드 캐릭터 배열로 변환한다.  wcstombs 함수는 그 역변환을 한다.  이들 함수들은 헤더파일 'stdlib.h'에 선언되어 있다.

대부분의 프로그램에서, 이들 함수들은 와이드 문자열과 다중바이트 문자열 사이의 변환을 위해서 당신이 필요한 것들이다. 그러나 그들은 제한들을 갖는다. 만일 당신의 데이터가 널문자로 끝나지 않거나, 동시에 모두 코어 안에 있지 않다면, 당신은 아마도 한 번에 한 문자를 변환하는 저-수준 변환함수를 사용해야 할것이다.  18.7절 [Converting One Char], 253 페이지 참조.

함수 : size_t mbstowcs (wchar_t *wstring, const char *string, size_t size)

mbstowcs("다중바이트 문자열을 와이드 캐릭터 문자열로")함수는 널 종료 문자로 끝나는 다중바이트 문자들로 이루어진 문자열을 와이드 캐릭터 코드들의 배열로 변환한다.  그 배열은 wstring에서 시작하는 배열에 와이드 문자들을 저장하는데,  그 배열의 크기가 size보다는 클 수 없다.  size의 크기에는 널 종료문자가 포함되지 않았다, 그래서 만일 size가 이 함수의 실행 결과인 문자열에서 와이드 캐럭터의 실제 개수보다 작았다면, 널 종료문자는 저장되지 않게된다. 문자열을 가지고 문자들을 변환하는 것은 초기 쉬프트 상황으로 시작한다.  만일 유용하지 않은 다중바이트 문자 시퀀스가 발견되면,  이 함수는 -1의 값을 리턴하고, 그렇지않다면, 배열 wstring에 저장 된 와이드 캐럭터의 개수를 리턴한다.  이 개수는 널 종료문자가 포함되지 않은 것으로 만일 개수가 size보다 작으면 널 종료문자가 저장된다.

다음은 결과를 저장하기에 충분한 공간을 할당해서, 다중바이트 문자열을 어떻게 변환하는지를 보여주는 예제이다.

wchar_t  *

mbstowcs_alloc  (const  char  *string)

{

size_t  size  =  strlen  (string)  +  1;

wchar_t *buf = xmalloc (size * sizeof (wchar_t));

size  =  mbstowcs  (buf,  string,  size);

if  (size  ==  (size_t)  -1)

return  NULL;

buf = xrealloc (buf, (size + 1) * sizeof (wchar_t));

return  buf;

}

함수 : size_t wcstombs  (char *string, const wchar_t wstring, size_t size)

wcstombs("와이드 캐럭터 문자열을 다중바이트 문자열로")함수는 널 종료문자로 끝나는 와이드 캐럭터 배열 wstring을 다중바이트 문자열로 변환하는 함수로, string에서 시작하여 size 바이트보다 크지않게 저장하는데, 그 size의 크기가 변환된 문자열을 저장하기에 충분하다면 널 종료문자도 저장된다.  문자들의 변환은 초기 쉬프트 상황으로 시작한다.

널 종료문자는 size의 크기에 포함되지 않기 때문에, 만일 size의 크기가 wstring에서 필요로하는 바이트의 개수와 같거나 더 적다면, 널 종료문자는 저장되지 않는다.  만일 유용한 다중바이트 문자에 해당되지 않은 코드가 발견되면, 이 함수는 -1의 값을 리턴한다. 그렇지않다면, 리턴값은 배열 string에 저장된 바이트들의 개수이다. 이 개수는 널 종료문자가 포함되지 않은 것으로, 그 개수가 size 보다 적다면 널 종료문자가 저장된 것이다.

18.6  다중바이트 문자 길이

이 절은 다중 바이트 문자들로 이루어진 문자열을 한 번에 한문자씩 어떻게 조사하는지를 설명하고 있다.  이런일을 하는데 따르는 어려움은 각각의 한 문자가 몇 개의 바이트로 이루어졌는지를 알아야하는 것이다.  당신은 이것을 판단하는데 mblen 함수를 사용할 수 있다.

함수 :   int mblen  (const char *string, size_t size)

널 종료문자가 없는 문자열을 가지고 mblen함수를 사용하면, string에서 시작하여 다중바이트 문자를 구성하는 바이트의 개수를 리턴하는데, size바이트 만큼만 시험한다.  ( 여기서 size는 당신이가지고 있는 데이터의 바이트의 개수로 공급한다.) mblen의 리턴값은 세가지 가능성으로 구분된다: string의 첫 번째 size 바이트들이 유용한 문자로 시작한는지, 유용하지 않은 바이트 시퀀스로 시작하거나 단지 한 문자의 일부인지, 비어있는 문자열인지. 유용한 다중바이트 문자에서는, mblen은 그 문자(적어도 한 개 이상이고, 결코 size보다 커서는 안된다)의 바이트들의 개수를 리턴 한다.  유용하지 않은 바이트 시퀀스에서는, mblen은 -1을 리턴한다. 비어있는 문자열에서는, 0을 리턴한다. 만일 다중바이트 문자 코드가 쉬프트 문자들을 사용한다면, mblen은 그것을 조사해서 쉬프트 상황을 갱신하고 유지한다.  만일 당신이 string으로 널 포인터를 사용하여 mblen을 호출한다면, 표준 초기 값으로 쉬프트 상황을 초기화한다.  그것은 만일 실제로 사용된 다중바이트 문자 코드가 쉬프트 상황을 갖고 있다면 0이 아닌 값을 리턴한다.  18.9절 [Shift State] 256 페이지를 참조하라. mblen 함수는 `stdlib.h'에 선언되어 있다.

18.7  일 대 일로 확장된 문자들의 변환

당신은 mbtowc 함수를 사용해서 다중바이트 문자를 한번에 한자씩 와이드 캐릭터로 변환할 수 있다.  wctomb 함수는 그 반대 작용을 한다. 이들 함수들은 `stdlib.h'에 선언되어 있다.

함수 : int mbtowc  (wchar_t *result, const char *string, size_t size)

mbtowc( 다중바이트 문자를 와이드 캐릭터로 ) 함수는 널 종료 문자가 없는 문자열을 가지고 호출했을 때, string의 처음에 있는 첫 번째 다중바이트 문자를 그에 해당하는 와이드 캐릭터 코드로 변환한다.  그것은 결과를 *result에 저장한다.  mbtowc는 size 바이트 만큼만 시험한다.( size는 당신이 가지고 있는 데이터의 바이트의 개수로 공급한다. ) 널 종료문자가 없는 string으로 호출된 mbtowc는 세가지 가능성으로 구분된다:  string에 있는 첫 번째 size 바이트들이 유용한 다중바이트 문자로 시작하는지, 그들이 유용하지 않는 바이트 시퀀스로 시작하거나 단지 한 문자의 일부분인지, 또는 string이 비어있는( 한 개의 널 문자) 문자열을 가리키는지. 유용한 다중바이트 문자라면, mbtowc 는 그것은 와이드 캐릭터로 변환하여 *result에 저장하고, 그 문자의 바이트 개수를 리턴한다 ( 적어도 한 개 이상이고, 결코 size보다 커서는 안된다.).  유용하지 않은 바이트 시퀀스라면, mbtowc는 -1을 리턴한다.  비어있는 문자열이라면, 0을 리턴하고, 또한 *result에도 0을 저장한다. 만일 다중바이트 문자 코드가 쉬프트 상황을 사용한다면, mbtowc는 그것을 검사하여 쉬프트 상황을 유지하고 갱신한다.  만일 당신이 string에 널 포인터를 사용하여 mbtowc를 호출하면, 그것은 표준 초기값으로 쉬프트 상황을 초기화 한다.  그것은 또한 만일 사용하고 있는 다중바이트 코드가 쉬프트 상황을 갖고 있다면 0이 아닌값을 리턴한다.  18.9절 [Shift State] 256 페이지 참조.

함수 :  int wctomb  (char *string, wchar_t wchar)

wctomb("와이드 캐릭터를 다중바이트로") 함수는 와이드 캐럭터 코드 wchar을 그에 해당하는 다중 바이트 시퀀스로 변환하고, 그 결과를 string의 주소에서 시작하는 바이트들에 저장한다.  아무리 많아도 MB_CUR_MAX 문자들이 저장된다. 널 종료문자가 없는 문자열을 가지고 호출된 wctomb 함수는 세가지 가능성으로 구분한다:  유용한 와이드 캐릭터 코드( 다중 바이트 문자로 변환될 수 있는 것.), 유용하지 않은 코드, 그리고 0. 유용한 코드가 주어진 wctomb는 그것은 다중바이트 문자로 변환하고 string을 시작으로 바이트들을 저장한다.  그리고 그것은 그 문자의 바이트들의 개수를 리턴한다( 적어도 한 개 이상하고, 결코 size보다 커서는 안된다.). 만일 wchar이 유용하지 않은 와이드 캐릭터 코드라면, wctomb는 -1을 리턴한다.  만일 wchar이 0이라면, 그것은 0을 리턴하고, 또한 *string에도 0을 저장한다. 만일 다중바이트 문자 코드가 쉬프트 문자들을 사용한다면, wctomb는 그것을 조사해서 쉬프트 상황을 갱신하고 유지한다.  만일 당신이 string으로 널 포인터를 사용해서 wctomb를 호출한다면, 표준 초기값으로 쉬프트 상황을 초기화 한다.  만일 사용하고 있는 다중바이트 문자 코드가 실제로 쉬프트 상황을 갖는다면 0이 아닌값을 리턴한다.  18.9절 [Shift State] 256 페이지 참조. wchar에 0을 넣고, string이 null이 아닌 것을 가지고 이 함수를 호출하면, 다중바이트 문자 0을 저장하고 0을 리턴하는것은 물론이고 저장된 쉬프트 상황을 다시 초기화 하는 부작용을 갖는다.

18.8  문자 대 문자 변환의 예제

기술자 input 으로부터 다중바이트 문자 텍스트를 읽어서 그에 해당하는 와이드 캐럭터들을 기술자 output에 쓰는 예제 프로그램이다.  우리가 이 예제에서 일대일로 문자들을 변환하려한 것은 mbstowcs 함수가 널 문자가 있다면 변환을 계속하지 않고, 읽어들인 많은 입력중에 부분적으로 유용하지 않은 문자가 있다면 이것에 완전하게 대처할 수 없기 때문이다.

int

file_mbstowcs  (int  input,  int  output)

{

char  buffer[BUFSIZ  +  MB_LEN_MAX];

int  filled  =  0;

int  eof  =  0;

while  (!eof)

{

int  nread;

int  nwrite;

char  *inp  =  buffer;

wchar_t  outbuf[BUFSIZ];

wchar_t  *outp  =  outbuf;

/* 입력 파일로부터 버퍼를 채운다.  */

nread  =  read  (input,  buffer  +  filled,  BUFSIZ);

if  (nread  <  0)

{

perror  ("read");

return  0;

}

/*  만일 파일의 끝에 도달하면, 더 이상 읽지않도록 기록하라*/

if  (nread  ==  0)

eof  =  1;

/*  filled는 버퍼에 있는 바이트의 개수이다.  */

filled +=  nread;

/*  그들 바이트들을 우리가 할수 있는 한 많이 와이드 캐릭터로 변환하라. */

while  (1)

{

int  thislen  =  mbtowc  (outp,  inp,  filled);

/*  유용하지 않은 문자에서 변환을 멈춰라;  이것은 유용한 문자의 단지 첫 번째 부분을 읽었음을 의미한다.  */

if  (thislen  ==  -1)

break;

/* 널 문자를 다른 문자처럼 취급하라, 그러나 또한 쉬프트 상황을 재설정하라.  */

if  (thislen  ==  0)  {

thislen  =  1;

mbtowc  (NULL,  NULL,  0);

}

/* 이 문자를 지나서 진행하라.  */

inp  +=  thislen;

filled  -=  thislen;

outp++;

}

/*  우리가 만들었던 와이드 캐럭터들을 기록하라 */

nwrite  =  write  (output,  outbuf, (outp - outbuf) * sizeof (wchar_t));

if  (nwrite  <  0)

{

perror  ("write");

return  0;

}

/*  만일 우리가 실제 유용하지 않은 문자를 가졌는지 보자.  */

if((eof && filled > 0) || filled >= MB_CUR_MAX)

{

error  ("invalid  multibyte  character");

return  0;

}

/*  만일 어느 문자들이 앞에서 온것임에 틀림없다면, 버퍼의 처음에 그들을 저장하라.  */

if  (filled  >  0)

memcpy  (inp,  buffer,  filled);

}

}

return  1;

}

18.9  쉬프트 시퀀스들을 사용한 다중바이트 코드들

어떤 다중바이트 문자 코드에서, 어느 특정한 바이트 시퀀스의 의미가 고정되지 않은 것이 있다. 그 의미는 동일한 문자열의 처음에 있는 어떤 시퀀스에 의존한다.  전형적으로 그것은 단지 몇 개의 시퀀스로서 다른 시퀀스들의 의미를 변화시킬수 있다; 그들을 쉬프트 시퀀스라고 부르고 우리는 그 다음에 나타나는 다른 시퀀스를 위해서 쉬프트 시퀀스를 설정한다고 말한다.

쉬트프 상황과 쉬프트 시퀀스를 설명하기 위해서, 우리는 시퀀스 0200이 0240에서 0377의 범위를 갖는 바이트의 쌍으로 한 문자를 나타내는 일본어 모드를 시작함을 나타내고, 시퀀스 0201이 0240에서 0377의 범위를 갖고 ISO Latin-1의 문자 집합에 따라 해석되는 Latin-1 모드를 시작함을 나타낸다고 가정하자.  이것은 두개의 선택가능한 쉬프트 상황을 가진 다중바이트 코드("일본어 모드"와 "Latin-1 모드")를 가지고, 두 개의 쉬프트 시퀀스는 특정한 쉬프트 상황들을 설정한다. 사용하고 있는 다중바이트 문자 코드가 쉬프트 상황들을 사용할 때, mblen, mbtowc 그리고 wctomb는 문자열을 조사해서 현재의 쉬프트 상황을 갱신하고 유지한다.  이러한 작업을 적당하게 만들기 위해서, 당신은 다음의 규칙들을 따라야만 한다.

□ 문자열에 대한 조사를 시작하기 전에, 다중바이트 문자 주소를 널 포인터로 해서 그 함수를 호출하라_예를들어, mblen(NULL, 0). 이것은 표준 초기값으로 쉬프트 상황을 초기화한다.

□ 한 번에 한문자씩, 순서대로 문자열을 조사하라.  "뒤로"가지 말고 이미 조사했던 문자들을 다시 조사하지 말고, 다른 문자열을 처리하는 동안 방해하지 말아라.

이들 규칙들을 따라 mblen을 사용하는 예제가 있다.

void

scan_string  (char  *s)

{

int  length  =  strlen  (s);

/* 쉬프트 상황을 초기화하라.  */

mblen  (NULL,  0);

while  (1)

{

int  thischar  =  mblen  (s,  length);

/* 문자열의 끝과 유용하지 않은 문자들을 처리하라 */

if  (thischar  ==  0)

break;

if  (thischar  ==  -1)

{

error  ("invalid  multibyte  character");

break;

}

/* 이 문자를 넘어서 진행하라 */

s  +=  thischar;

length  -=  thischar;

}

}

함수 mblen, mbtowc 그리고 wctomb들은 사용하고 있는 다중바이트 코드가 쉬프트 상황을 사용할 때 재진입하지 않는다. 그렇지만, 어느 다른 라이브러리 함수도 이들 함수들을 호출하지 않기 때문에 당신은 그 쉬프트 상황이 당신이 이해할 수 없게 변할것이라고 걱정할 필요가 없다.

19  지역과 세계화

19장 지역과 세계화

다른 나라와 문명은 소통하기 위한 다양한 관습을 갖는다.  이들 관습들은 날짜와 시간을 표현하기 위한 형식처럼 아주 간단한 것에서부터 매우 복잡한 것까지 소통되는 언어의 범위를 갖는다.

소프트웨어의 세계화는 사용자가 좋아하는 관습들에 적응하도록 프로그램 하는 것을 의미한다.  ANSI C에서, 세계화는 지역을 가지고 작업된다. 각 지역은 관습들의 집합에서 각 목적을 위해 선택된 한가지 관습을 지정한다. 사용자는 지역을 선택함으로써(환경변수를 거쳐서) 관습의 집합을 선택한다.

모든 프로그램들은 그들의 환경의 일부로써 선택된 지역을 상속받는다. 주어진 프로그램은 선택된 지역에 따라서 만들어졌고, 그들은 사용자에 의해 선택된 관습을 따를 것이다.

19.1  지역이 가진 효과

각 지역은 다음의 것을 포함해서, 여러 가지 목적을 위한 관습들을 정한다.

□ 무슨 다중바이트 문자 시퀀스가 유용한것이고, 그들이 어떻게 해석되는가 ( 18장 [Extended Characters] 249 페이지 참조.)

□ 지역 문자 집합안에 있는 문자들의 분류가 알파벳처럼 되어있는지, 그리고 대, 소문자 변환 관습이 있는지.

□ 지역 언어와 문자 집합을 위한 대조 시퀀스(5.6절 [Collating Functions]51 페이지 참조.

□ 수(numbers)와 통화량(currency amounts)의 형식 (19.6절 [Numeric Formatting] 262페이지 참조.

□ 날짜와 시간의 형식 ( 17.2.4절 [Formatting Date and Time] 239페이지 참조.

□ 에러 메시지를 포함한, 출력을 위해서 사용하는 언어는 무엇인가.( C 라이브러리는 당신이 이것을 행하도록 아직은 도움을 주지않는다. )

□ 예/아니오 의 질문들에 사용자의 답변을 위해 사용하는 언어는 무엇인가.

□ 훨씬 복잡한 사용자 입력을 위해 사용하는 언어는 무엇인가. ( C 라이브러리는 아직까지 이것에 대한 도움을 주지 못하고 있다.)

정해진 지역에 적응하는 어떤 것은 라이브러리 서브루틴들에 의해 자동적으로 다루어진다.  예를들어, 선택된 지역의 대조시퀀스를 사용하여 순서대로 맞출 필요가 있는 모든 프로그램들은 비교 문자열에 strcoll 이나 strxfrm을 사용할 수 있다.

지역의 다른것들은 라이브러리의 이해력 밖에 있다.  예를들어, 라이브러리는 당신 프로그램의 출력 메시지를 다른 언어로 자동적으로 해석할 수 없다.  사용자가 좋아하는 언어로 출력을 지원하는 유일한 방법은 직접 프로그램 하는 것이다.( 우리는 이것을 더 쉽게 하기위한 도구들이 주어지기를 희망한다. )

이 장은 현재의 지역을 갱신할 수 있고, 그것의 메카니즘을 설명한다. 정해진 라이브러리 함수들상에서 현재 지역의 효과들은 각각의 함수들에 대한 설명에서 좀더 상세하게 설명되어진다.

19.2  지역 선택하기

사용자가 한 지역을 선택하기 위한 가장 간단한 방법은 환경변수 LANG를 설정하는 것이다.  이것은 모든 목적을 위해서 사용자에게 단일한 지역을 지정한다.  예를들어, 한 사용자가 스페인에서 가장 표준인 관습을 사용하기 위해서 `espana-castellano'라고 이름이어진 가상 지역을 선택할 수 있다.

지역들의 집합은 사용자가 사용하고 있는 운영제체에 의존하여 지원되고, 그리고 그들의 이름들을 가지고 작업한다.  우리는 `C' 또는 `POSIX'라고 불리는 표준 지역을 위한 것을 제외하고는, 무슨 지역들이 존재할것인지에 대해서 어느 약속도 할 수 없다.  사용자는 다른 목적으로 다른 지역들을 지정하기위한 옵션들을 가진다._실제로, 여러개의 지역들의 혼합하여 선택한다.

예를들어, 사용자가 여러 가지 목적을 위해서 지역 `espana-castellano'를 선택하였지만, 통화의 형식을 위해서는 지역 'usa-english'를 선택할 수 있다.  이것은 통화량이 미국 달러로 표현되지만 만일 그 사용자가 스페인에서 일하고 있고, 스페인어를 사용하는 미국인이라면 뜻이 통할 것이다.

모든 지역들처럼 `espana-castellano' 와 `usa-english' 의 두 지역은 어떤 지역들에 적용하기 위한 모든 목적들을 위한 관습을 포함한다. 그렇지만, 사용자는 그들 목적중에서 특정한 몇가지 목적을 위해서는 다른 지역을 사용하도록 선택할 수 있다.

19.3  지역이 미치는 활동의 범주

지역을 제공하는 목적은 범주안에 묶어지므로, 사용자나 프로그램은 독립적으로 각 범주를 위해서 그 지역을 선택할 수 있다. 다음은 범주들의 리스트이다; 각각의 이름은 사용자가 설정할 수 있는 환경변수, 그리고 setlocale에 인수로서 사용할 수 있는 매크로 이름이다.

LC_COLLATE

이 범주는 문자열의 대조를 위해서 제공된다(함수 strcoll과 strxfrm); 5.6절 [Collation Functions] 51 페이지 참조.

LC_CTYPE

이 범주는 문자들의 분류와 관습, 그리고 다중바이트와 와이드 캐릭터들에게 제공된다; 4장 [Characters Handling]41페이지와 18장 [Extended Characters] 249 페이지 참조.

LC_MONETARY

이 범주는 통화의 값들을 형식화하는데 제공된다. 19.6절 [Numeric Formatting] 262 페이지 참조.

LC_NUMERIC

이 범주는 통화가 아닌 숫자값들을 형식화하는데 제공된다; 19.6절 [Numeric Formattin] 262 페이지 참조.

LC_TIME

이 범주는 날짜와 시간값들을 형식화하는데 제공된다; 17.2.4절 [Formattin Date and Time] 239 페이지 참조.

LC_ALL

이것은 환경변수가 아니다; 그것은 모든 목적들을 위해서 단일한 지역을 설정하는 setlocale에서 사용할 수 있는 매크로이다.

LANG

만일 이 환경변수가 정의되면, 그 값은 위의 변수들에 의해 무시되는 것을 제외하고는 모든 목적들을 위해 사용자에게 지역을 지정한다.

19.4  프로그램은 지역을 어떻게 설정하는가

C 프로그램은 그 프로그램이 시작할 때 지역 환경변수들을 상속받는다. 이것은 자동적으로 발생한다.  그렇지만, 그들 변수들이 라이브러리 함수들에 의해 사용된 지역은 자동적으로 제어하지 않기 때문에, ANSI C는 모든 프로그램을 디폴트로 표준 `C'지역으로 시작하게한다. 환경변수에 의해 정해진 지역들을 사용하기 위해서는, 당신은 setlocale을 호출해야만 한다.  그것을 다음과 같이 호출하라.

setlocale  (LC_ALL,  "");

적당한 환경변수들에 기초한 지역을 선택하도록 한다.

당신은 또한 일반적으로 사용하거나 또는 정해진 범주를 위한, 특정한 지역을 선택하기 위해서 setlocale를 사용할 수 있다.  이 절에 있는 심볼들은 헤더파일 `locale.h'에 정의되어 있다.

함수 :   char *setlocale (int category, const char *locale)

setlocale 함수는 지역에 대한 범주 category를 위해서 현재의 지역을 설정한다.  만일 범주가 LC_ALL 이면, 이것은 모든 목적을 위해서 그 지역을 지정함을 말한다.  범주의 다른 가능한 값들은 개별적 목적으로 지정한다. (19.3절 [Locale Categories] 260 페이지 참조. )

당신은 locale인수에 널 포인터를 사용함으로서, 현재의 지역을 알아내도록 이 함수를 사용할 수 있다.  이 경우에, setlocale는 범주 category를 위해서 선택된 현재의 지역의 이름을 가진 문자열을 리턴한다. setlocale에 의해 리턴된 문자열은 다음에 이어지는 호출에 의해 덧씌워질 수 있으므로, 만일 당신이 전에 호출된 setlocale의 결과를 저장하기를 원한다면 당신은 그 문자열의 복사본을 만들어야 할 것이다( 5.4절 [Copying and Concatenation] 46 페이지 참조.)( 표준 라이브러리는 결코 setlocale 그 자체를 호출하지 않도록 보증된다. )

당신은 setlocale에 의해 리턴된 문자열을 갱신하지 말아야 한다. 그것은 전에 호출된 setlocale에서 인수로써 사용했던 문자열과 동일하게 될 것이다.

당신이 범주 LC_ALL을 위해서 현재의 지역을 읽을 때, 그 값은 모든 범주를 위해서 선택된 지역들의 전체조합으로 기호화한다. 이 경우, 그값은 단지 단일한 지역이름이 아니다.  실제로, 그것이 무엇일지에 대해서 우리는 말할 수 없다.  그러나 만일 당신이 setlocale의 다음 호출에 LC_ALL과 함께 같은 "지역이름"을 지정하면, 그 함수는 선택된 지역과 같은 조합을 저장한다.  locale인수가 널 포인터가 아닐 때, setlocale에 의해 리턴된 문자열은 새로인 갱신된 지역을 반영한다. 만일 당신이 loclae에 비어있는 문자열을 지정하면, 이것은 적당한 환경변수를 읽고 그 값을 범주를 위한 지역을 선택하는데 사용한다는 의미이다.  만일 당신이 유용하지 않은 지역이름을 지정하면, setlocale는 널 포인터를 리턴하고 현재의 지역은 변화되지 않는다. 다음은 당신이 새로운 지역으로 일시적인 변환을 위해서 setlocale를 어떻게 사용할것인지에 대한 예제이다.

#include  <stddef.h>

#include  <locale.h>

#include  <stdlib.h>

#include  <string.h>

void

with_other_locale(char *new_locale, void (*subroutine)(int), int argument)

{

char  *old_locale,  *saved_locale;

/*  현재 지역의 이름을 얻어라.   */

old_locale  =  setlocale  (LC_ALL,  NULL);

/*  setlocale에 의해 변경되지 않은 이름을 복사하라  */

saved_locale  =  strdup  (old_locale);

if  (old_locale  ==  NULL)

fatal  ("Out  of  memory");

/* 이제 그 지역을 변경하고, 그것으로 어떤 자료를 하라  */

setlocale  (LC_ALL,  new_locale);

(*subroutine)  (argument);

/*  원래의 지역을 다시 저장하라 */

setlocale  (LC_ALL,  saved_locale);

free  (saved_locale);

}

이식성 노트 : 어떤 ANSI C 시스템들은 부가적인 지역 범주들을 정의할 수 있을 것이다.  이식성을 위해서, `LC_'로 시작하는 어느 심볼은 `locale.h'에 정의되었다고 생각하자.

19.5  표준 지역들

모든 운영체제상에서 당신이 발견할 수 있는 지역의 이름은 오직 다음의 세가지 뿐이다:

"C"

이것은 표준 C 지역이다.  그것이 제공하고 있는 속성과 행동은 ANSI C 표준에 의해 지정되었다.  당신의 프로그램이 시작될 때, 그것은 처음에 디폴트로 이 지역을 사용한다.

"POSIX"

이것은 표준 POSIX 지역이다.  일반적으로 이것은 표준 C 지역을 위한 다른으로 여겨지고 있다.

□ □

이 빈(empty) 이름은 환경변수들에 기초한 지역을 선택함을 말한다.  19.3절 [Locale Categories] 260 페이지 참조.

이름을 가진 지역들을 정의하고 구동하는 것은 보통 당신이 속한 시스템의 시스템관리자의 책임이다(또는 GNU C 라이브러리를 구동했던 사람. ) 어떤 시스템들은 사용자가 지역을 만들도록 허용할 수도 있지만, 우리는 이것에 대해서 논의하지 않겠다.

만일 당신의 프로그램이 `C'지역과는 다른 어떤 것을 사용할 필요가 있다면, 명백하게 어떤 비-표준 지역을 이름으로 지정하기 보다는, 환경변수를 통해서 사용자가 정한 지역을 따르도록 한다면 좀 더 이식성이 있는 프로그램이 될 것이다.  다른 기계에서는 자신이 구동한 지역과는 다른 지역들의 집합을 가질지도 모른다는 것을 기억하라.

19.6  숫자의 형식화

당신이 현재 지역의 관습을 사용해서 숫자나, 통화량을 형식화하길 원한다면, 그것을 어떻게 형식화할것인지에 대한 정보를 얻도록 localeconv 함수를 사용할 수 있다.  localeconv 함수는 헤더파일 `locale.h'에 선언되어 있다.

함수 :   struct lconv * localeconv  (void)

localeconv 함수는 현재의 지역에서 숫자와 통화값이 어떻게 형식화되는지에 대한 정보가 포함된 요소를 가진 구조체를 가리키는 포인터를 리턴한다. 당신은 그 구조체나 내용을 변경할 수 없다.  구조체는 localecnov의 다음 호출에 의해 덧씌워지거나, 호출된 setlocale에 의해 변경되지만, 라이브러리에 있는 어느 다른 함수들은 이 값을 변경할 수 없다.

데이터 타입 :   struct lconv

이것은 localeconv에 의해 리턴된 값의 데이터타입이다.

만일 구조체 struct lconv의 멤버가 char 형을 가진다면, 그리고 그 값이 CHAR_MAX 라면, 그것은 현재의 지역을 위한 파라미터에 아무런 값이 없음을 의미한다.

19.6.1  일반적인 숫자 형식화 파라미터.

다음은 struct lconv의 표준 멤버들이다;다른 것이 있을 수도 있다.

char *decimal_point

char *mon_decimal_point

통화량이 아닌것과 통화량을 형식화 하는데 사용되는 소수점 분리자(separators)이다.  `C' 지역에서는, 소수점의 값은 "."이고, mon_decimal_point의 값은 ""이다.

char *thousands_sep

char *mon_thousands_sep

통화량이 아닌것과 통화량을 형식화할 때, 소수점 왼쪽의 십진수의 한계그룹에 사용되는 분리자(separators)이다. `C' 지역에서, 이 둘 멤버들은 ""(빈 문자열)의 값을 갖는다.

char *grouping

char *mon_grouping

grouping은 통화량이 아닌것에 적용되고 mon_grouping은 통화량에 적용해서 소수점왼쪽에 있는 숫자들을 어떻게 묶을것인지를 정하는 문자열이다.  십진수 그룹들 분리하는데는 thousnads_sep나 mon_thousands_sep중에 하나를 사용하라.

각 문자열은 세미콜론에 의해 분리된 십진수를 구성한다. 연속적인 숫자들(왼쪽부터 오른쪽으로)은 연속적인 그룹들( 소수점에서 시작하여, 오른쪽에서부터 왼쪽으로)의 크기를 준다.  문자열에서 마지막 숫자는 모든 남겨진 그룹들을 위하여 되풀이해서 사용되어진다. 만일 마지막 정수가 -1이라면, 그것은 더이상 묶을것이 없거나_또는 남겨진 숫자들이 분리자가 없는 하나의 큰 그룹을 구성하고 있는, 다른 방법으로 놓여져 있음을 의미한다.

예를들어, 만일 묶음(grouping)이 "4;3;2"라면, 숫자 123456787654321을 위한 정확한 그룹은 `12', `34', `56', `78', `765', `4321' 이다.  이것은 2 숫자그룹과 3 숫자 그룹에 의해 선행된, 마지막에 있는 4 숫자그룹을 사용한다.  그 숫자는 분리자 `,'를 사용하여, `12,34,56,78,765,4321'. 와 같이 출력되어진다.

"3"의 값은 보통 미국에서 사용되어, 세 개 숫자들의 반복된 그룹들을 지적한다.   표준 `C' 지역에서, grouping 과 mon_grouping이 모두 ""의 값을 가지면, 이 값은 전혀 묶음을 하지 않음을 의미한다.

char int_frac_digits

char frac_digits

이것은 국제와 지역 형식 각각에서 통화값을 나타내는데 있어서 얼마나 많은 소수부(소수점의 오른쪽)를 사용할 것인가를 나타낸다.  (아주 자주, 두 개의 멤버들은 같은 값을 갖는다. )

표준 `C'지역에서, 이들 멤버 둘은 모두 "미지정"이라는 의미를 가진 CHAR_MAX 값을 갖는다.  ANSI 표준은 당신이 이것을 발견했을 때 그 값으로 무엇을 할것인지 말하지 않는다.; 우리는 아무런 소수부가 출력되지 않는 것을 추천한다.  ( 이 지역은 또한 mon_decimal_point를 위해 빈 문자열을 지정하는데, 그것은 어느 소수부 숫자들의 출력도 거부됨을 의미한다. )

19.6.2  통화 기호 ( Currency Symbol ) 출력하기

구조체 struct lconv의 멤버들은 통화값을 확인하는 심볼들을 어떻게 출력할 것인지를 정한다_미국달러의 표시는 `$'으로 세계적으로 인정한다.

각 나라는 두 개의 표준 통화기호들을 가지고 있다.  지역 통화기호는 그 나라안에서 통상적으로 사용되는것이고, 세계 통화기호는, 명백하게 그 나라를 지정할 필요가 있을 때 그 나라의 통화기호로 나타내기 위해서 국제적으로 사용된다.

예를들어, 많은 나라들은 그들의 통화단위로 달러를 사용하고, 국제적으로 다른 나라들과 거래를 할 때 미국 달러대신에 캐나달 달러로 거래할것인지 또는 오스트레일이라 달러로 거래할 것인지를 정하는 것은 중요하다. 그러나 그 구문이 캐나다가 되도록 정해져있을 때, 달러의 양들은 캐나다 달러로 된다고 가정되므로 이것을 명백하게 만들 필요가 없다.

char *currency_symbol

선택된 지역을 위한 통화기호.  표준 `C' 지역에서, 이 멤버는 "미지정"을 의미하는, ""(빈 문자열)의 값을 갖는다.  ANSI 표준은 당신이 이것을 발견했을 때, 그 값으로 무엇을 하는지를 말하지 않는다.  우리는 당신이 적당한 멤버에서 발견한 어느 문자열을 출력하는 것 처럼, 빈 문자열을 간단히 출력하도록 권장한다.

char *int_curr_symbol

선택된 지역을 위한 국제 통화기호.  int_curr_symbol의 값은 국제 표준 ISO 4217 코드들에 의해 결정된 세 개-문자의 약어로 구성된다.  국제표준 ISO 4217코드는 한-문자 분리자의 다음에 나타나는(거의 공백을 사용.), 통화와 자금의 표현을 위한 것이다. 표준 `C'지역에서, 이 멤버는 "미지정"을 의미하는 ""(빈 문자열)의 값을 갖는다.  우리는 적당한 멤버에서 발견된 어느 다른 문자열을 출력하는것처럼 빈 문자열을 간단히 출력하기를 권장한다.

char p_cs_precedes

char n_cs_precedes

이들 멤버들은 만일 currency_symbol 문자열이 통화량의 값보다 선행하면 1이고, 그 문자열이 값보다 뒤에 나타나면 0이다.  p_cs_precedes 멤버는 양의 값(또는 0)에 적용하고, n_cs_precedes 멤버는 음의 값에 적용한다. 표준 `C' 지역에서, 이 멤버 둘 모두는 "미지정"을 의미 하는 CHAR_MAX의 값을 갖는다.  ANSI 표준은 당신이 이 값을 발견할 때 무엇을 하는지를 말한지 않지만, 우리는 그 값 앞에 통화기호를 출력하기를 권장한다. 대부분의 나라에서 그렇게 사용한다.  즉 그들 멤버안에 있는 모든 0이 아닌값들을 유사하게 취급하라.

POSIX 표준은 그들 두 개의 멤버들을 currency_symbol은 물론 int_curr_symbol에도 적용한다.  ANSI C 표준은 그들은 오직 currency_symbol에 적용됨을 내포하고 있는 것처럼 보여서_int_curr_symbol은 항상 나타내려는 양(amount)에 선행할 것이다.  우리는 그들이 국제통화기호를 출력하기 위해서 보통의 관습들과 매치될것이라고 짐작할 수 있다.  우리의 나타내려는 양(amount)에 항상 선행할것이라고 추측한다.  만일 우리가 이해가능한 답을 발견한다면, 우리는 그것을 그렇게 할 것이다.

char p_sep_by_space

char n_sep_by_space

그들 멤버들은 currency_symbol 문자열과 양(amount) 사이에 공백이 있으면 1이고 없으면 0이다.  p_sep_by_space 멤버는 양의값(또는 0)에 적용되고, n_sep_by_space 멤버는 음의 값에 적용된다.표준 `C' 지역에서, 이들 멤버 둘 모두는 "미지정"을 의미하는 CHAR_MAX의 값을 갖는다.  ANSI 표준은 당신이 이 값들을 발견할 때 당신이 무엇을 할것인지를 알리지 못한다; 우리는 공백을 출력하는 것 처럼 그것을 취급하기를 제안한다.  즉, 그들 멤버안에 있는 모든 0이 아닌값들은 유사하게 최급하라.  그들 멤버들은 오직 currency_symbol에 적용된다.  당신이 int_curr_symbol을 사용할때, 당신은 결코 부가적인 공백을 프린트하지 않는다, 왜냐하면, int_curr_symbol 그자체는 적당한 분리자를 포함하고 있기 때문이다.

POSIX 표준은 그들 두 개의 멤버들을 currency_symbol 처럼 int_curr_symbol에도 적용한다. 그러나 ANSI C 표준에 있는 예제들은 적당한 분리자를 표함한 int_curr_symbol로 currency_sumbol만이 오직 적용됨을 보여주고 있다_그래서 당신은 결코 부가적인 공백을 프린트하지 않을 것이다.  우리가 현재 알고있는것에 기초하여, 우리는 당신이 국제 통화기호를 프린트하고 아무런 공백을 프린트 하지않을 때, 그들 멤버들을 무시하기를 권장한다.

19.6.3  금전( Money ) 부호를 프린트하기

구조체 struct lconv의 멤버들은 통화값의 부호를 어떻게 프린트할 것인지를 지정한다.

char *positive_sign

char *negative_sign

그들은 양의값(또는 0) 과 음의 통화량을 알리기위해 사용되는 문자열이다.  표준 `C' 지역에서, 그들 멤버 둘 모두는 "미지정"을 의미하는 ""(빈 문자엶)의 값을 갖는다.

ANSI 표준은 당신이 이 값을 발견할 때 무엇을 하도록 말하지 않는다; 우리는 당신이 그것을 발견했을 때, 심지어 만일 그것이 비어있을때라고, positive_sign을 프린트 하기를 권장한다.  음의 값을 위해서는, nugative_sign과 positive_sign이 모두 비어있지 않다면 당신이 발견할 것으로negative_sign을 프린트하라, 이 경우에는 `-'이다. ( 전혀 어떤 부호라도 지시하는 것을 실패하면 나중에 이해할 수 없는 값이 되고만다.)

char p_sign_posn

char n_sign_posn

0               통화기호와 통화량이 괄호가 쳐져있다.

1               통화기호와 통화량 앞에 부호 문자열을 출력하라.

2               통화기호와 통화량 뒤에 부호문자열을 출력하라.

3               통화기호 앞에 적당한 부호 문자열을 출력하라.

4               통화기호 후에 적당한 부호 문자열을 출력하라.

CHAR_MAX

"미지정"  두 개 멤버 모두는 표준 `C' 지역에서 이 값을 갖는다.

ANSI 표준은 CHAR_MAX 값이 있을 때 당신이 무엇을 해야하는지 말해주지 않는다.  우리는 통화기호 뒤에 부호를 출력하도록 권장한다.

국제적 통화 형식이거나 아닌것에 이들 멤버들의 적용이 당신에게 허용 될것인지가 명백하지 않다.  POSIX는 당신이 그렇게 하도록 하지만, ANSI C 표준에 있는 예제들은 당신이 그렇제 하지않기를 제안한다. 우리는 통화량의 형식화를 위한 관습을 잘 알고 있는 누군가가 우리가 무엇을 권장할지를 우리에게 말해주기를 희망한다.

닭소래

그대만 없게 되면

가슴 뛰노는 닭소래 늘 들어라.

밤은 아주 새여울 때

잠은 아주 달아날 때

꿈은 이루기 어려워라.

저리고 아픔이여

살기가 왜 이리 고달프냐.

새벽 그림자 산란한 들풀우를

혼자 거닐어라.

20  비-지역 탈출

20장 비-지역 탈출

때때로 당신의 프로그램이 호출한 함수의 깊게 내포된 곳에서 평소와는 다른 상황을 검출할 때, 당신은 제어수준의 밖으로 즉시 리턴이 가능하게

되는 것이 좋을 것이다.  이 절은 그와같은 비-지역 탈출의 상황에서

어떻게 setjmp 와 longjmp를 사용할 수 있는지를 설명한다.

20.1  비-지역 탈출들에 대한 안내

비-지역 탈출이 유용할 수 있는 상황에 대한 예제로서, 프롬프트 상에서 명령문으로 실행하는 "메인 루프"를 가진, 즉 상호작용하는 프로그램이 있다고 가정하자.  그리고 "read"라는 명령문으로는 파일로부터 입력을 읽어서, 그것이 처리되는 동안 입력의 어휘와 구문을 분석한다고 가정하자.그때 만일 저-수준 입력 에러가 검출되면, 어휘와 구문분석 각각을 만들고, 내포된 호출에 의해 검출된 에러상황을 명백하게 취급하는 모든 처리상황을 만들기보다는 "메인 루프"로 즉시 리턴 가능하도록 하는 것이 유용할 것이다. ( 다른 한편으로, 만일 파일들을 닫거나, 버퍼나 다른 데이터 구조체를 해제하는 등의 그와같은 exit의 상황일 때 그들 상황에서 그들에게 할당된 실제적인 양을 소거한다면_보통 리턴을 하고 그 자체가 소거되는 것이 더 적당할 수 있다, 왜냐하면 비-지역 탈출은 방해되는 상황들을 무시하고 그들에 연관된 전체 코드를 소거하기 때문이다.  선택적으로 당신은 "메인 루프"의 뒤나 또는 앞에서 명백하게 소거를 할 수 있다.)

어떤 방법에서, 비-지역 탈출은 함수로부터 리턴을 하는 `return' 구문을 사용하는것과 유사하다.  그러나 `return'은 오직 단일하게 호출된 함수로 부터 빠져나와서 그것이 호출되었던 지점의 뒤로 호출을 넘기지만, 비-지역 탈출은 내포된 함수 호출의 여러단계를 빠져나올 수 있다.

당신은 setjmp 함수를 호출해서 비-지역 탈출을 위한 리턴지점을 확인하라. 이 함수는 object의 타입 jmp_buf안에 실행환경에 대한 정보를 저장한다. setjmp의 호출후에도 프로그램의 실행은 정상적으로 계속되지만, 만일 한 exit가 그것에 부합되는 jmp_buf 오브젝트를인수로 하여 longjmp를 호출함으로써 이 리턴 지점보다 나중에 만들어졌다면 제어는 setjmp가 호출되었던 그 지점의 뒤로 넘겨진다.  setjmp로 부터의 리턴값은, 보통의 리턴과 longjmp를 호출함으로 해서 만들어진 리턴과의 구별하기위해 사용되므로, setjmp는 보통 `if'문안에 나타난다.

위에 설명된 예제 프로그램이 어떻게 만들어지는지를 보여준다.

#include  <setjmp.h>

#include  <stdlib.h>

#include  <stdio.h>

jmp_buf  main_loop;

void

abort_to_main_loop  (int  status)

{

longjmp  (main_loop,  status);

}

int

main  (void)

{

while  (1)

if  (setjmp  (main_loop))

puts("Back at main loop....");

else

do_command();

}

void

do_command(void)

{

char  buffer[128];

if  (fgets  (buffer,  128,  stdin)  ==  NULL)

abort_to_main_loop  (-1);

else

exit  (EXIT_SUCCESS);

}

함수 abort_to_main_loop는 그것이 어디로부터 호출되었는지에 상관없이, 프로그램의 메인루프로 제어를 즉시 돌려준다.

메인 함수 안의 제어의 흐름은 처음에 조금 이상한일이 발생할지도 모르지만, 그러나 그것은 실제로 setjmp에서 사용하는 보통의 관용이다. setjmp는 보통 0을 리턴하므로, 그래서 조건절 "else"가 실행되어진다. 만일 abort_to_main_loop가 do_command의 실행중에 어느곳에서 호출되어지면, 그것은 메인에서 setjmp를 호출하는것처럼 나타난다. 그래서 setjmp를 사용하는 일반적 양식은 다음과 같다:

if  (setjmp  (buffer))

/*  때이른 리턴후에 소거하려는 코드  */

. . .

else

/* 리턴 지점을 설정한 후에 일반적으로 실행되어지는 코드 */

. . .

20.2  비-지역 분기의 상세한 설명

여기에서는 비-지역 분기를 수행하기 위해 사용되는 함수들과 데이터 구조체에 대해서 설명한다. 이들 도구들은 `setjmp.h'에 선언되어 있다.

데이터 타입 :   jmp__buf

오브젝트의 타입 jmp_buf는 비-지역 분기에의해 되돌려질 상황 정보를 저장한다.  jum_buf의 내용들은 리턴하기 위한 지정된 장소를 알린다.

매크로 :   int setjmp (jmp_buf state)

보통 호출되었을 때, setjmp는 state안에 현재 실행중인 프로그램의 상황에 대한 정보를 저장하고 0을 리턴한다.  만일 longjmp가 이 state로 비-지역 분기를 수행하기위해 나중에 사용된다면, setjmp 는 0이 아닌값을 리턴한다.

함수 :   void longjmp (jmp_buf state, int value)

이 함수는 state에 저장된 상황으로 현재 실행을 되돌리고, 리턴 지점을 만들었던 setjmp를 호출한 지점부터 실행을 계속한다.  longjmp에 의한 setjmp로부터의 리턴은 longjmp에 주어졌던 0이 아닌 인수값을 리턴한다.  (그러나 만일 값이 0으로 주어지면, setjmp는 0을 리턴한다. )

setjmp와 longjmp의 사용에는 많이 모호하지만 중요한 제한들이 있다. 이들 제한의 대부분은 비-지역 분기가 C 컴파일상에서 상당한 양의 신비한 힘(?)을 요구했고 이상한 방법으로 언어의 다른 부분들과 함께 영향을 끼칠수 있기 때문에 발생한다.

setjmp함수는 함수정의가 없는 매크로이다, 그래서 당신은 `#undef를 사용하거나 또는 그것의 주소를 취하려 할 수가 없다.  그것에 더하여, setjmp를 호출하는 것은 오직 다음과 같은 경우에만 안전하다.

□ 선택이나 반복구문을 시험하기위한 표현으로(`if' 또는 `while'와 같은)

□ 선택이나 반복구문을 시험하기위한 표현으로 사용한 동등 또는 비교 연산자에서 사용된 피연산자의 하나로써.  그때 다른 피연산자는 정수 상수 표현이어야만 한다.

□ 선택이나 반복구문을 시험하기 위한 표현으로 사용한 단항연산자`!'의 피연산자로 사용할때.

□ 표현식으로 자체로 사용할 때.

리턴지점은 그들을 만들기 위해서 setjmp를 호출했던 그 함수가 계속 작동하고 있을때만 유용하다.  만일 당신이 이미 리턴되어진 함수에서 만들었던 리턴지점으로 longjmp를 호출하면, 예측할 수 없고 불행한 일들이 발생될 것이다.

당신은 longjmp의 인수로써 0이 아닌값을 사용수도 있다.  longjmp는 setjmp로부터의 리턴값인 0 인수를 되돌려주기를 거부하므로, 갑자기 발생하는 실수들에 대항한 안전망으로써의 역할을 할 수도 있지만 좋은 프로그래밍 스타일은 아니다.

당신이 비-지역 분기를 수행할 때, 검색가능한 오브젝트들은 longjmp가 호출되었을 때 그들이 가졌던 값이 무엇이든지 일반적으로 계속 유지한다. 그러나 자동 지역변수의 경우에는 setjmp가 호출된 이후에 변경된다. 즉 그들을 휘발성으로 선언하지만 않으면 그들은 변경되지 않는다.

20.3  비-지역 분기와 신호

BSD 유닉스 시스템에서, setjmp와 longjmp는 블록된 신호들을 저장하고 반환한다;  21.7절 [Blocking Signals] 298 페이지 참조. 그렇지만, POSIX.1 표준은 setjmp와 longjmp가 블록된 신호들을 변경하지 않을 것을 요구하고, BSD처럼 행동하기 위한 함수들(sigsetjmp와siglongjmp) 두 개를 부가적으로 제공한다.

GNU 라이브러리에서 setjmp와 longjmp의 행동은 테스트 매크로에의해 제어된다;  1.3.4절 [Feature Test Macros] 6페이지 참조.  GNU 시스템에서 디폴트로 사용하는 것은 BSD가 아니라 POSIX.1 이다.  이절에 있는 도구들은 헤더파일 `setjmp.h'에 선언되어 있다.

데이터 타입 :   sigjmp__buf

이것은 블록된 신호들에 대한 상황 정보를 저장할 수 있다는 것을 제외하고는 jmp_buf와 유사하다.

함수 :   int sigsetjmp (sigjmp_buf state, int savesigs)

이것은 setjmp와 유사하다.  만일 savesigs가 0이 아니면, 블록된 신호들의 집합은 state에 저장되고 만일 siglongjmp가 나중에 이 state를 가지고 수행되면 반환되어질 것이다

함수 :   void siglingjmp (sigjmp_buf state, int value)

이것은 state인수의 타입이 다르다는 것을 제외하고는 longjmp와 유사하다.  만일 0이 아닌 savesigs 플래그를 사용했던 state를 설정하고 sigsetjmp를 호출하면, siglongjmp는 또한 블록된 신호들의 집합을 반환한다.

타향에서

한곳에서 한곳으로 쫓기어 간다□

웬지는 자네도 모른다□

바람속에서 상냥한 목소리가 울린다□

자네는 놀라서 돌아본다□

뒤에 남은 여인이

부드럽게 자네를 부르는 것이다□

□돌아와 주세요. 나는 당신을 사랑하고 있어요□

당신은 나의 단 하나의 행복입니다□□

그러나 앞으로 앞으로 쉬는 일없이

자네는 멈추어 설 수는 없다□

자네가 그토록이나 사랑하던 것을

자네는 다시는 볼 수가 없다□

21  시그날 처리

21장 시그날 처리

시그날(앞으로 신호라 해석하지 않고 시그날이라고 하겠습니다.  그것이 더 좋을 것 같아서...)은  프로세스에게 배달된 소프트웨어 인터럽트이다. 운영체제는 실행하고 있는 프로그램에 예외적인 상황을 보고하기 위해서 시그날을 사용한다.  어떤 시그날들은 유용하지 않은 메모리 주소를 참조하는것과 같은 에러를 보고하고; 다른 것은 전화선의 단절과 같은, 비동기적 사건을 보고한다.

GNU C 라이브러리는 각각의 특정한 사건들의 종류에 따라, 다양한 시그날의 형태를 정의한다. 사건들의 어떤 종류들은 보통 프로그램의 계속된 진행을권장할 수 없거나 불가능하게 하고, 그에 해당하는 시그날들은 보통 그 프로그램을 중지시킨다.,  유해하지 않은 사건들을 보고한 다른 종류의 시그날들은 보통 무시된다.

만일 당신이 시그날이 발생한 사건을 예상한다면, 당신은 시그날 처리 함수를 정의할 수 있고 특정한 형태의 시그날이 도착했을 때 운영체제가 그 시그날 처리함수를 실행하게 할 수 있다.  최종적으로, 하나의 프로세스는 다른 프로세스에게 한 개의 신호를 보낸다; 이것은 부모 프로세스가 자식 프로세스를 중지시키는 것을 허용하거나, 또는 두 개의 연관된 프로세스가 통신하거나 동기하도록 하는 것을 허용한다.

21.1  시그날들의 기본 원칙

어떻게 시그날들이 발생되고, 시그날이 도착된 이후에 무슨일이 발생할 것이며, 어떻게 프로그램이 시그날을 처리할 수 있는지에 대한 기본 원칙들을 설명한다.

21.1.1  시그날들의 종류

신호는 예외적인 사건의 발생을 보고한다.  다음은 시그날을 발생시키는 어떤 예외적인 사건들이다.

□ 프로그램이 0으로 나누는 일을 하거나, 또는 유용한 범위를 넘어서는 주소를 억세스하려는것과 같은 에러.

□ 사용자가 프로그램을 인터럽트 또는 중지하도록 요청한다.  대부분의 환경들은 사용자가 C-z를 타이핑하면 일시 중지하거나, C-c를 타이핑하면 종료를 허용하도록 만들어졌다.  키 시퀀스( key sequence)에 무엇이 사용되었던지, 운영체제는 프로세스를 인터럽트 하기위한 적당한 시그날을 보낸다.

□ 자식 프로세스의 종료.

□ 타이머나 알람의 경과.

□ 같은 프로세스에 의해 죽이거나 발생한 호출.

□ 다른 프로세스로 부터 죽이기 위한 호출.  시그날들은 프로세스사이의 통신에 유용한 형식이지만 제한을 갖는다.

이들 사건들(죽이거나 발생하기 위해 명백하게 호출한 것을 제외하고 )의 각각은 자신만의 특정한 종류의 신호를 발생시킨다.  다양한 종류의 시그날 들은 21.2절 [Standard Signals] 272 페이지에 상세하게 설명되었다.

21.1.2  시그날 발생의 원칙들

일반적으로, 시그날을 발생시키는 사건들은 세가지로 나눌 수 있다: 에러들. 외부의 사건들과 명백한 요청.

에러는 프로그램이 무언가 유용하지 않을 일을 하고 실행을 계속할 수 없는 것을 의미한다.  그러나 에러들의 모든 종류가 시그날을 발생시키지는 않는다_실제로 그들의 대부분은 시그날을 발생시키지만... 예를들어, 존재하지않는 파일을 개방하기와 같은 것은 에러이지만, 그것은 시그날을 발생시키지 않고; 대신에 open은 -1을 리턴한다.  일반적으로, 에러들은 에러를 지적하는 값을 리턴함으로써 보고되는 어떤 라이브러리 함수들과 연관되어있다.  시그날들을 발생시킨 에러들은 단지 라이브러리 호출뿐만 아니라 프로그램의 어디서든 발생할 수 있다.  그들에는 0으로 나누기를 하고 유용하지 않은 메모리 주소의 참조가 포함된다.

외부에서 발생한 사건은 입/출력이나 다른 프로세스들과 함께 하는 것에서 나온다.  그들에는 입력의 도착, 타이머의 경과, 자식 프로세스의 종료등이 포함된다.

명백한 요청은 kill처럼 특별하게 시그날을 발생하도록 어떤 목적을 가진 라이브러리 함수의 사용을 의미한다.

시그날들은 동기적으로 또는 비동기적으로 발생되어진다.  동기적 시그날 은 프로그램의 어느 정해진 동작과 관계하고, 그 동작을 하는 동안(블록된 것이 아니라면)에 배달 되어진다..  에러들은 동기적으로 시그날을 발생하고, 프로세스가 같은 프로세스를 위하여 시그날을 발생하도록 함으로써 명백하게 요청한다.

비동기적 시그날들은 시그날을 받은 프로세스의 제어의 밖에서 발생한 사건에 의해 발생되어진다.  그들 시그날들은 실행동안에 예측할 수 없는 시간에 도착된다.  외부의 사건들은 비동기적으로 시그날들을 발생하고, 어떤 다른 프로세스에 적용하도록 명백하게 요청한다.

시그날의 주어진 형태는 전형적으로 동기적이거나 또는 비동기적중에 하나가 된다.  예를들어, 에러를 위한 시그날은 에러가 동기적으로 신호를 발생했다면 전형적으로 동기적이다.  그러나 어느 시그날의 형태는 명백한 요청으로는 동기적, 또는 비동기적으로 발생되어질 수 있다.

21.1.3  어떻게 신호들이 배달되는가

시그날이 발생되어졌을 때, 그때는 아직 미해결인 상태가 된다.  일반적으로 아주 짧은 시간동안만 미해결인채로 남아있고 그 다음에는 신호가 프로세스에게 배달되어진다.  그렇지만 만일 시그날의 종류가 블록되어졌다면, 그것은 막연히 미해결인체로 남아있게 될것이다_그 시그날의 블록이 해제될때까지. 일단 블록이 해제되면, 그것은 즉시 배달되어질 것이다.  21.7절 [Blocking Signals] 298 페이지 참조.

시그날이 배달되었을 때, 즉시 또는 긴 지연후에 그 시그날을 위하여 정해진 행동을 하게 된다.  SIGKILL 과 SIGSTOP 와 같은 어떤 시그날들은 그 행동이 정해져있지만, 대부분의 시그날들은 프로그램이 선택하게 된다:  시그날을 무시하거나, 처리 함수를 지정하거나, 또는 시그날의 종류 에 따라 디폴트 동작을 하거나.  프로그램은 signal이나 sigaction과 같은 함수들을 사용해서 선택을 하게 된다(21.3절 [Signal Actions] 279 페이지참조. ).  우리는 때때로 핸들러가 시그날을 잡았다고 얘기를 한다.  핸들러가 실행되고 있는 동안, 특정한 시그날은 일반적으로 블록되어진다. 만일 한 종류의 시그날을 위한 정해진 동작이 시그날을 무시하는것이라면,발생되어진 시그날은 즉시 버려진다  이것은 심지어 시그날이 동시에 블록 되어질지라도 발생한다.  이렇게 버려진 시그날은 비록 프로그램이 연속적 으로 그 종류의 시그날을 위하여 다른 동작을 지정하고 블록을 해제하지 않았을지라도 결코 배달되지 않을 것이다.

프로그램에서 처리되지도 않고 무시하지도 않는 신호가 발생하면, 그것의

디폴트 동작이 일어난다.  시그날의 각 종류들은 밑에 설명된 자신의 디폴트 동작을 갖는다(21.2절 [Standard Signals] 272 페이지 참조.). 대부분의 시그날에서 디폴트 동작은 프로세스를 종료하는 것이다. "유해하지 않은" 사건들에서 발생한 어떤 종류의 시그날들의 디폴트 동작은 아무것도 하지않는 것이다.

한 시그날이 프로세스를 종료할 때, 그것의 부모 프로세스는 wait 또는 waitpid 함수들에 의해 보고된 종료 상황 코드를 조사함으로써 종료가 발생한 원인을 알아낼 수 있다. (이것에 대해서는 23.6절 [Process Completion] 329 페이지에 좀더 자세하게 나와있다.)  시그날의 원인이 된 종료의 요소, 그리고 시그날의 종료를 포함해서 정보를 얻을 수 있다. 만일 쉘로부터 당신이 실행하고 있는 어떤 프로그램이 시그날에 의해 종료가 된다면, 그 쉘은 그것에 해당하는 에러메세지를 프린트 할 것이다.  프로그램의 에러를 표현하는 시그날들은 특별한 속성을 갖는다:  시그날 중의 하나가 프로세스를 종료할 때, 종료한 시간에 프로세스의 상황에 대한  기록을 코아 덤프 파일에 기록한다.  당신은 무슨 에러가 발생했는지 조사 하기 위해서 디버거를 사용해서 코아 덤프 파일을 조사할 수 있다.

만일 당신의 명백한 요청에 의해서 프로세스를 종료하고 "프로그램 에러" 시그날이 발생하면, 직접적으로 에러에 기인하는 시그날로써 코아 덤프 파일을 만든다.

21.2  표준 시그날들

이 절은 다양한 표준 시그날들의 이름과 그것이 어떤 사건을 의미하고 있는지를 설명하고 있다.  각각의 시그날의 이름은 시그날의 종류를 위한 시그날번호로써 양의 정수로 나타낸 매크로이다.  당신은 여기에서 정의한 이름대신에 당신 마음대로 시그날의 번호코드를 결코 가정할 수 없다.  이것은 시그날의 종류에 부여된 번호가 시스템에서 시스템으로는 바꿀수 있지만, 그 이름들의 의미는 표준화되어있고 완전히 단일화 되어있기 때문 이다.  시그날의 이름들은 헤더파일 `signal.h'에 정의되어 있다.

매크로 :   int NSIG

이 심볼 상수의 값은 정의된 시그날의 총 개수이다.  시그날의 번호들은 연속적으로 할당되어 있기 때문에 NSIG는 정의된 시그날의 번호중에서 가장 큰 번호보다 하나가 크다.

21.2.1  프로그램 에러 시그날들

다음의 시그날들은 심각한 프로그램의 에러가 운영체제나 컴퓨터 자체에 의해 검출되었을 때 발생된다.  일반적으로, 이들 시그날 모두는 당신의 프로그램이 심각하게 깨져있고, 에러가 포함된 그 실행을 계속할 아무런 방법이 없음을 지적한다.

어떤 프로그램들은 프로그램의 에러 시그날로 인해서 종료되기 전에 그들을 깨끗하게 처리한다. 예를들어, 터미날 입력의 반향을 끈(tnun off) 프로그램들은 다시 반향을 켤 목적으로 프로그램 에러 시그날들을 처리할 것이다.  핸들러는 시그날을 위한 디폴트 동작을 정하고 그 동작을 함으로써 끝날 것이다;  만일 프로그램이 시그날 핸들러를 가지지 않았다면, 프로그램은 그 시그날로 인해서 종료될 것이다.  ( 21.4.2절 [Termination in Handler] 286 페이지 참조.)

종료는 대부분의 프로그램에 에서 에러에 대응한 이해가능한 최종적인 결과이다.  그렇지만, Lisp과 같은 프로그래밍시스템들은 사용자 프로그램에 에러가 발생했을지라도 컴파일된 사용자 프로그램을 실행시켜야할 필요가 있다면 로드(load)시킬 수 있다.  이들 프로그램은 커멘드 레벨(command level)로 제어를 리턴하는 longjmp를 사용한 핸들러를 갖는다. 모든 시그날의 디폴트 동작은 프로세스를 종료하는 것이다.  만일 당신이 그 시그날들을 블록하거나 무시하거나 시그날을 위한 핸들러를 만든다면, 당신의 프로그램은 아마도 그와 같은 시그날들이 발생했을 때,  그들이  실제 에러대신에 raise나 kill에 의해 발생된 것이 아니라면, 심각하게 파괴될 것이다.

그들 프로그램 에러 시그날중의 하나가 프로세스를 종료할 때, 종료와 같은 시간에 프로세스의 상황기록을 코아덤프파일에 출력한다.  코아덤프 파일은 `core'라고 이름지어졌고 프로세스가 현재 존재하고 있는 디렉토리 에 존재한다. ( GNU 시스템에서, 당신은 환경변수 COREFILE를 통해서 코아덤프파일의 이름을 지정할 수 있다.)  코아덤프파일의 존재 목적은 무슨 에러가 발생했는지 조사하기 위함으로써, 디버거를 사용해서 그들을 시험할 수 있다.

매크로 :   int SIGFPE

SIGFPE 시그날은 심각한 산술적 에러를 보고한다.  그 이름이 "floating-point exception"에서 유래된것이라 할지라도, 이 시그날은 실제로는 모든 산술적 에러들에 작용한다.  만일 어떤 프로그램이 어떤 위치에 정수 데이터를 저장하고 그 데이터에 플로팅-포인트 명령을 사용한다면, 이것은 그 프로세서가 데이터를 플로팅-포인트 수로써 인식할 수 없기 때문에 종종 "유용하지 않은 연산"의 원인이 된다.

플로팅-포인트 예외상황에 대한 것은 아주 민감하게 다른 의미를 지닌 예외상황의 여러종류들이 있기 때문에 아주 복잡한 주제이고, SIGFPE 시그날은 그들을 구분하지 않는다.  이진 플로팅-포인트 연산을 위한 IEEE 표준(ANSI/IEEE Std 754-1985)은 다양한 플로팅-포인트 예외상황에 대해서 정의하고 있고 컴퓨터 시스템이 예외상황의 발생을 보고할 때 따르도록 요구한다.  그렇지만, 이 표준은 그 예외상황이 어떻게 보고되는 지에 대해서는 지정하지 않았고, 또한 운영체제가 제어와 처리의 어떤 종류를 프로그래머에게 제공할 수 있는지를 지정하지 않았다.

BSD 시스템들은 예외상황의 다양한 원인을 구별하는 특별한 인수를 가진 SIGFPE 핸들러를 제공한다.  이 인수를 억세스 하기 위해서, 당신은 두 개의 인수를 받아들이는 핸들러를 정의해야만 한다.  GNU 라이브러리는 이 특별한 인수를 제공하지만, 그 값은 BSD 시스템과 GNU 시스템에서만 오직 의미가 있다.

역자주 : trap(트랩) : 하나의 명령어가 실행될 따마다 자동적으로 발생되는 인터럽트.  이러한 인터럽트는 중앙처리 장치에 의하여 하드웨어 적으로 발생하게 되는데 프로그램에서 하나의 명령어가 실행될 때마다 자동적으로 미리 정의된 트랩 처리 루틴으로 실행의 제어권이 넘어온다.  하드웨어 장치와 밀접한 관련이 있는 시스템 소프트 웨어에서 오류를 찾아내기 위한 수단으로 이용된다.

FPE_INTOVF_TRAP

정수 오버플로우 ( 당신이 하드웨어의 정해진 사양에 따라서 오버플로우의 트랩이 가능하지 않으면 C 프로그램에서는 불가능하다. )

FPE_INTDIV_TRAP

정수를 0으로 나누기.

FPE_SUBRNG_TRAP

아래에 기입한-범위 ( 어떤 C 프로그램은 결코 체크하지 않는다. )

FPE_FLTOVF_TRAP

플로팅 오버플로우 트랩.

FPE_FLTDIV_TRAP

플로팅/정수를 0으로 나눔.

FPE_FLTUND_TRAP

플로팅 언더플로우. 트랩 ( 플로팅 포인트에서 트랩하는 것은 보통 가능하지 않다.)

FPE_DECOVF_TRAP

십진수 오버플로우 트랩. ( 오직 몇 개의 기계에서만 십진수 연산을 갖고 있고 C에서는 결코 그것을 사용하지 않는다. )

매크로 :   int SIGILL

이 시그날의 이름은 "illegal instruction : 비합법적인 명령"에서 유래되었다; 그것은 쓸모없거나 특권이 부여된 명령어를 실행하려 했다는 의미이다.  오직 유용한 명령어만이 발생된 C 컴파일러에서, SIGILL은 전형적으로 실행 가능 파일이 훼손되었거나, 당신이 데이터를 실행하려 시도했다는 것을 지적한다.  후자의 상황이 발생되는 일반적 상황으로는 함수를 위한 포인터가 있을 것이라고 예상된 곳에서 유용하지 않은 오브젝트를 파싱하거나, 자동 배열의 끝을 넘어서 기록을 하고( 또는 자동 변수를 위한  포인터와 유사한문제들) 스텍 프레임의 리턴 어드레스 처럼 스텍에서 다른 데이터의 훼손과 같은 문제들이 있다.

매크로 :  int SIGSEGV

이 시그날은 할당된 메모리의 범위를 벗어나는곳에서 읽거나, 쓰기를 시도할 때 발생된다. ( 실제로, 그 시그날들은 프로그램이 충분한 영역을 할당받지 못할 때 시스템 메모리 보호 메카니즘에 의해서 발생한다.) 그 이름은 "segmentation violation"의 약자이다.  SIGSEGV 상황이 발생되는 가장 일반적인 방법은 비참조되는 널( defeferencing a null) 이나 초기화되지 않은 포인터에 의한 것이다.  널 포인터는 주소 0으로 참조되고, 대부분의 운영체제는 이 주소가 정확하게 유용하지 않음을 확실히하기 때문에 비참조 널 포인터는 SIGSEGV가 발생될 것이다. (어떤 운영체제는 주소가 0인 메모리도 유용하고, 비참조 널 포인터는 그들 시스템 상에서는 시그날을 발생하지 않는다.)  비초기화된 포인터에서는, 유용하지 않거나, 유용하더라도 임의의 주소들을 갖게된다. SIGSEGV 상황이 얻어지는 다른 일반적 방법은 배열에 포인터를 사용했을 때 그 배열의 끝을 체크하기를 실패했을때이다.

매크로 :   int SIGBUS

이 시그날은 유용하지 않은 포인터가 비참조되었을 때 발생된다. SIGSEGV 처럼, 이 시그날은 초기화되지 않은 포인터를 비참조 한것의 결과이다.  두 시그날의 차이점은 SIGSEGV는 유용한 메모리에서 유용하지 못한 억세스를 지적하고, SIGBUS는 유용하지못한 주소를 억세스 하는 것을 지적한다.  특별하게, SIGBUS 시그날은 4개로 나누어지지 않은 주소에 4-단어 정수로 참조하는것처럼, 부적당한 포인터가 비참조됨으로써 발생한다. (각종 시스템은 주소 정렬은 위한 자신만의 필요조건을 갖는다.) 이 시그날의 이름은 "bus error"의 약자이다.

매크로 :   int SIGABRT

이 시그날은 프로그램 그 자체와 abort가 호출되었음을 보고함으로써 발생되는 에러를 지적한다. 22.3.4절 [Aborting a Program] 322 페이지 참조.

21.2.2  종료 시그날

이들 시그날들은 이런 저런 방법으로 프로세스를 종료함을 알리기위해 사용된다.  그들은 완전히 다른 목적을 위해 사용되기 때문에 다른 이름을 가졌고, 프로그램은 그들은 다르게 취급하기를 원할 것이다.

이들 시그날들은 처리하기 위한 이유는 보통 당신의 프로그램이 실제로 종료되기전에 적당하게 처리할 수 있도록 하기 위한 것이다.  예를들어, 당신은 상황정보를 저장하고, 임시 파일들을 지우고, 이전의 터미날 모드를 반환하기를 원할수도 있다.   그와같이 핸들러(handler)는 발생된 시그날을 위한 디폴트 동작을 지정하고 그리고 그 시그날을 다시 발생시킴으로써 종료할 것이다. 이것은 만일 프로그램이 핸들러를 가지지 않았더라도, 그 시그날로 인해서 프로그램이 종료될 것이다.  ( 21.4.2절 [Termination in Handler] 286 페이지 참조.)

이 시그날들을 위한 (명백한) 디폴트 동작은 프로세스가 종료되도록 하는 것이다.

매크로 :   int SIGHUP

SIGHUP ("hang-up") 시그날은 사용자 터미날의 단절을 보고하기 위해 사용되어지는데, 아마도 네트웍이나 전화선 연결이 끊어졌기 때문이다.  이것에 대한 상세한 정보는 12.4.6절 [Control Modes] 191 페이지 참조. 이 시그날은 또한 그 세션과 연관된 작업을 위해서 터미날에서 제어하고 있는 프로세스의 종료를 보고하기 위해 사용되어진다;  이 종료는 제어중인 터미날로부터 그 세션안에 있는 모든 프로세스를 효과적으로 단절한다.  더 상세한 정보는 22.3.5절 [Termination Internals] 322 페이지 참조.

매크로 :   int SIGINT

SIGINT("program interrupt") 시그날은 사용자가 INTR 문자를 (보통 C-c)를 타입했을 때 보내어진다.  터미날 드라이버가 C-c 를 지원하는지에 대한 정보는 12.4.9절 [Special Characters] 195 페이지 참조.

매크로 :   int SIGQUIT

SIGQUIT 시그날은 다른 키_QUIT 문자, 보통 C-\_에 의해서 제어된다는 것을 제외하고는 SIGINT와 유사하고, 그 프로세스가 종료 될 때 프로그램 에러 시그날처럼 코아덤프 파일을 작성한다.  당신은 사용자에 의해 "검출된" 프로그램 에러 상황으로 이들을 생각할 수 있다. 코아 덤프 파일에 대한 정보는 21.2.1절 [Program Erroe Signals] 273 페이지 참조.  지원하는 터미날 드라이버에 대한 정보는 12.4.9절 [Special Characters] 195 페이지 참조.  소거의 어떤 종류들은 SIGQUIT를 처리하는 동안에 생략되어지는 것이 좋다. 예를들어, 만일 프로그램이 임시파일을 만든다면, 그것은 임시파일을 지움으로써 다른 종료 요청을 처리할 것이다.  하지만 사용자가 코아 덤프 파일을 시험할수 있게 하기 위하여, 그들을 지우지 않는 것이 SIGQUIT를 위해서 더 좋다.

매크로 :   int SIGTERM

SIGTERM 시그날은 프로그램을 종료하는데 사용하는 포괄적인 시그날이다.  SIGKILL과 달리, 이 신호는 블록되어진고, 처리되어지고 무시되어질 수 있다.  쉘 코맨드 kill은 디폴트로 SIGTERM을 발생시킨다.

매크로 :   int SIGKILL

SIGKILL 시그날은 즉각적인 프로그램 종료를 일으키기 위해서 사용되어진다.  이 시그날은 처리되거나, 무시되거나 할 수 없고, 그 결과는 항상 치명적이 된다.  이 시그날은 블록하는것도 불가능하다.

이 시그날은 오직 명백한 요청에의해 발생되어진다.  그것이 처리되어질 수 없다면, 당신은 일단C-c 또는 SIGTERM과 같은 덜 격렬한 방법을 시도한 후에, 나중에 마지막 방법으로 오직 그것을발생시킬 것이다.  만일 프로세스가 어느 다른 종료 시그날들에 반응하지 않는다면, SIGKILL시그날을 보내면 거의 항상 그 프로세스가 종료될 것이다.  실제로, SIGKILL이 프로세스를 종료하는데 실패한다면, 운영체제의 버그 때문이다.

12.2.3  알람 시그날

그들 시그날은 타이머의 경과를 지적하는데 사용되어진다.  이들 시그날을 보내는 함수에 대한 정보는 17.3절 [Settin an Alarm] 243 페이지 참조. 그들 시그날을 위한 디폴트 동작은 프로그램을 종료를 일으키는 것이다. 이 디폴트 동작은 거의 유용하지 않다; 그들 시그날을 사용하는 대부분의 방법은 어느 경우에 맞는 핸들러 함수들을 요구하는 것이다.

매크로 :   int SIGALRM

이 시그날은 전형적으로 실제또는 클럭 시간을 계산한 타이머의 경과를 지적한다.  예를들어 alarm 함수에의해 사용되어진다.

매크로 :   int SIGVTALRM

이 시그날은 전형적으로 현재 프로세스에 의해 사용된 CPU시간을 계산하는 타이머의 경과를 지적한다.  그 이름은 "virtual time alarm"의 약자이다.

매크로 :   int SIGPROF

이 시그날은 현재의 프로세스에 의해 사용된 CPU 시간과, 프로세스를 대신하여 시스템에의해 사용된 CPU시간의 둘을 계산한 타이머의 경과를 지적하는데 사용된다.  타이머가 자원의 프로파일링을 위한 도구로써 사용되어지므로, 시그날의 이름이 SIGPROF이다.

역자주 :  profiling: 프로파일링 : 시스템의 성능 및 병목현상을 방지하기 위한 도구라고 생각하시면 될 것 같네요.  정확하지가 않아서..

21.2.4  비동기 입/출력 시그날

이 절에 설명된 시그날들은 비동기 입/출력 도구들과 함께 사용되어진다. 당신은 어떤 특정한 파일 기술자가 그들 시그날을 발생시키도록 하기 위해서 fcntl을 호출함으로써 명백한 동작을 취하도록 해야한다( 8.12절[Interrupt Input] 120 페이지 참조.)  그들 시그날을 위한 디폴트 동작은 그들을 무시하는 것이다.

매크로 :   int SIGIO

이 시그날은 파일기술자가 입력 또는 출력을 수행할 준비가 되어있을 때 보내어진다.  대부분의 운영체제에서, 터미날과 소켓만이 SIGIO를 발생시킬 수 있다;  보통의 파일들을 포함한 다른 종류들은 당신이 그들에게 요청했을지라도 SIGIO신호를 발생시키지 않는다.

매크로 :   int SIGURG

이 시그날은 소켓에 도착한 데이터가 "긴급"하거나 범위를 벗어 났을 때 보내어진다.  11.8.8절 [Out-of-Band Date] 173 페이지 참조.

21.2.5  작업 제어 시그날

이들 시그날은 작업 제어를 지원하기 위해서 사용되어진다.  만일 당신의 시스템이 작업 제어를 지원하지 않는다면 시그날들은 발생되어지거나, 처리될 수는 없지만 매크로들은 정의되어있다.  당신이 실제로 작업이 어떻게 제어되는지를 이해할수 없다면 그들 시그날을 그대로 방치할 것이다.  24장 [Job Control] 335 페이지 참조.

매크로 :   int SIGCHLD

이 시그날은 자식 프로세스들중의 하나라도 종료되거나 멈출때마다 부모 프로세스에게 보내어진다.  이 시그날을 위한 디폴트 동작은 그것을 무시하는 것이다. 만일 당신이 wait 또는 waitpid를거쳐 (23.6절 [Process Completion 329 페이지 참조.) 그들의 상황이 보고되지 않았지만, 종료된 자식 프로세스에서 발생한 시그날을 위한 핸들러를 만든다면, 당신의 새로운 핸들러가 그들 프로세스에 적용이 되던지 또는 특정한 운영체제에 달려있다.

매크로 :   int SIGCONT

당신은 프로세스가 계속되도록 하기 위해서 SIGCONT 신호를 보낼 것이다.  이 시그날을 위한 디폴트 동작은 만일 그 프로세스가 멈추었다면 그 프로세스를 계속하도록 만드는 것이고 그렇지않다면 그것을 무시하는 것이다.  대부분의 프로그램에서는 SIGCONT를 처리할 아무런 이유가 없다;  그들은 전에 멈추었었음을 인식함이 없이 계속 실행되고 있다고 가정한다.  당신은 어떤 특정한 동작을 하는 프로그램을 멈추거나 계속하도록 만들기 위해서 SIGCONT 시그날을 위한 핸들러를 사용할 수 있다_예를들어, 입력을 기다리기 위해서 잠시 멈추었을 때 프롬프트를 다시 프린트 하는것과 같은.

매크로 :   int SIGSTOP

SIGSTOP 시그날은 프로세스를 멈춘다.  그것은 처리되거나, 무시되거나 블록될 수 없다.

매크로 :   int SIGTSTP

SIGTSTP 시그날은 상호작용하는 멈춤 신호이다.  SIGSTOP와는 달리 이 신호는 처리되거나 무시되어질 수 있다.  당신의 프로그램에서 프로세스가 멈추었을 때 파일이나 시스템 테이블을 안전한 상황으로 만들어놓을 특별한 필요가 있다면 이 신호를 처리할 수 있다.  예를들어, 반향이 꺼진 프로그램에서는 멈추기 전에 다시 반향을 켜도록 SIGTSTP 시그날을 처리할 것이다. 이 시그날은 사용자가 SUSP 문자(보통 C-z)를 타입했을 때 발생된다.  지원하는 터미날 드라이버에 대한 자세한 정보는 12.4.9절 [Special Characters] 195 페이지 참조.

매크로 :   int SIGTTIN

한 프로세스가 배경 작업으로써 실행되고 있는 동안 사용자의 터미날로부터 읽을 수 없다.  배경 작업에 속한 어느 프로세스가 터미날로부터 읽으려 시도할 때, 그 작업에 속한 모든 프로세스는 SIGTTIN 신호를 받는다.  이 시그날을 위한 디폴트 동작은 그 프로세스를 멈추는 것이다.  어떻게 터미날 드라이버와 상호작용 하는지에 대한 자세한 정보는 24.4절 [Access to the Terminal] 336 페이지 참조.

매크로 :   int SIGTTOU

SIGTTIN과 유사하지만, 배경 작업에 속한 프로세스가 터미널에 출력하려 시도하거나 그 터미날 모드를 설정하려 시도할 때 발생 된다.  다시 말하면 디폴트 동작은 그 프로세스를 멈추는 것이다.

프로세스가 멈추어있을 동안, SIGKILL 시그날과 SIGCONT시그날을 제외하고는 어느 다른 시그날들은 배달되어질 수 없다.  SIGKILL 시그날은 항상 프로세스의 종료를 유발하고 블록되거나 무시될 수 없다.  당신이 SIGCONT 시그날을 무시하거나 블록할 수 있지만, 그것은 만일 그 프로세스가 멈추어져있다면 프로세스가 계속되도록 한다.  프로세스에게 보낸 SIGCONT 시그날은 아직 미해결인채로 남아있는 멈춤 시그날을 프로세스가 버리도록 한다.  이와 비슷하게, 어떤 프로세스에서 아직 미해결인채로 남아있는 SIGCONT 시그날은 멈춤 시그날이 도착했을 때 버려진다.

고아가 되어버린 프로세스 그룹에 있는 한 프로세스에게 SIGTSTP, SIGTTIN, 또는 SIGTTOU 시그날은 보내면 그것은 처리되지도 않고, 그 프로세스는 멈추어 지지도 않는다.  그것을 계속할 아무런 방법이 없는 부당하게 되어버린 프로세스를 멈추게 하라.  운영체제에 의존하지 말고당신이 무언가를 사용해서 멈추게 하라.  어떤 시스템은 아무런 일도 하지 않을 것이다.  다른 시스템들은 대신에 SIGKILL 또는 SIGHUP와 같은 시그날들을 배달할 것이다.

21.2.6  잡다한 시그날

그들 시그날은 다양한 다른 상활들을 보고하기 위해서 사용되어진다. 이들의 디폴트 동작은 프로세스가 종료되도록 하는 것이다.

매크로 :   int SIGPIPE

만일 당신이 파이프나 FIFO들을 사용한다면, 당신의 어플리케이션에서 다른 것이 출력을 시작하기 전에 한 프로세스가 읽기를 위해서 파이프를 개방하도록 만들어야 한다.  만일 읽기 프로세스가 결코 시작되지 않거나, 급작스럽게 종료된다면 파이프나 FIFO에 출력하기는 SIGPIPE 시그날을 발생시킨다.  만일 SIGPIPE 가 블록되거나, 처리되어지거나, 무시되어지면, 그 손상된 호출은 대신에 EPIPE로 실패한다. 파이프와 FIFO 특별한 파일들은 10장 [Pipes and FIFOs] 145 페이지에서 좀더 자세하게 논의되었다.  SIGPIPE가 발생하는 다른 원인은 당신이 연결되지 않은 소켓에 출력을 시도했을 때 발생한다.  11.8.5.1절 [Sending Data] 169 페이지 참조.

매크로 :   int SIGUSR1

매크로 :   int SIGUSR22

SIGUSR1 과 SIGUSR2 시그날들은 당신이 원하는 어떤 방법을 사용하지 못하도록 한다.  그들은 프로세스간 통신을 위해서 유용하다.  그들 시그날을 보통 심각하기 때문에 당신은 그 시그날을 받은 프로그램에서 그들은 위한 시그날 처리를 해야할 것이다. SIGUSR1 과 SIGUSR2에 대한 사용예는 21.6.2절 [Signaling Another Process] 295 페이지 참조.

21.2.7  비표준 시그날

특정한 운영체제는 위에 설명되지 않은 부가적인 시그날들을 지원한다. ANSI C 표준은 시그날들의 명칭을 `SIG'로 시작하는 대문자로 예약하였다.  당신은 당신의 특정한 운영체제를 위한 헤더파일이나 그 운영체제가 지원하고 있는 시그날을 발견하는 프로세서의 타입 등에 대한 것에 조언을 구할 수 있다.  예를들어, 어떤 시스템은 하드웨어 트랩에 해당하는 여분의 시그날들을 제공한다.  보통 지원되는 어떤 다른 종류의 시그날들은 CPU 시간과 파일 시스템 사용에 대한 제한을 가하거나, 터미날 구성을 비동기적으로 변경하는 것과 같은 것을 위해 사용되어진다.  시스템들은 또한 표준 시그날 이름의 별칭(aliases)이 되는 시그날 이름들을 정의하고 있다.

당신은 당신이 이해하고 있는 정의된 시그날들을 위한 디폴트 동작을 (또는 쉘에의해 작동하는 동작) 가정할 수 있고, 당신은 그들에 대해서는 걱정하지 않는다.  당신이 그 시그날의 의미를 알지못하는것에 대해서 핸들러를 만들려 시도하거나 알지 못하는 시그날을 무시하거나 블록하는 것은 좋지못한 생각이다.  여기에 일반적으로 운영체제에서 사용되고 있는 약간의 다른 시그날에 대한 것이 있다.

SIGCLD

SIGCHLD의 오래된 명칭.

SIGTRAP

기계상의 중단점 명령에 의해 발생된다.  디버거에 의해 사용된다.  디폴트 동작은 코아를 덤프하는 것이다.

SIGIOT

PDP-II "iot" 명령에 의해 발생된다;  SIGABRT와 동등하다.  디폴트 동작은 코아를 덤프하는 것이다.

SIGEMT

에뮬레이터를 트랩한다;  어떤 충족되지 못한 명령으로 부터의 결과이다.  그것은 프로그램 에러 시그날이다.

SIGSYS

좋지못한 시스템 호출;  실행된 운영체제를 트랩하기 위한 명령이지만, 그것을 수행하도록 하는 시스템 호출을 위한 코드 번호가 유용하지 않다.  이것은 프로그램 에러 이다.

SIGPOLL

이것은 SIGIO와 많이 또는 덜 유사한, 시스템 V 시그날 명칭이다.

SIGXCPU

CPU 시간 제한이 초과되었다.  이것은 배치 프로세싱을 위해서 사용되어진다.  디폴트 동작은 프로그램 종료이다.

SIGXFSZ

파일 크기 제한이 초과되었다.  이것은 배치 프로세싱을 위해서 사용되어진다.  디폴트 동작은 프로그램 종료이다.

SIGWINCH

윈도우 크기를 변경한다.  이것은 스크린에서 현재 윈도우의 크기가 변경되었을 때 어떤 시스템에서 발생되어진다. 디폴트 동작은 그것을 무시하는 것이다.

21.2.8  시그날 메세지

우리는 자식 프로세스를 종료한 시그날을 설명하는 메시지를 쉘이 프린트하는것에 대해서는 위에서 잠깐 언급했다. 시그날을 설명하는 메시지를 프린트하는 깨끗한 방법은 strsignal 과 psignal 함수들을 사용하는 것이다. 그들 함수들은 설명하려는 시그날의 종류를 지정하기 위해서 시그날 번호를 사용한다. 시그날번호는 자식 프로세스의 종료상황으로부터(23.6절 [Process Compltion] 329 페이지 참조)오거나 또는 같은 프로세스안에 있는 시그날핸들러러부터 올것이다.

함수 :   char * strsignal  (int signum)

이 함수는 시그날 signum을 설명하고 있는 메시지를 포함하고 있는 정적으로 할당된 문자열에 대한 포인터를 리턴한다. 당신은 이 문자열의 내용을 갱신할 수 없다. 그리고 그것은 연속된 호출에 의해서 덧씌워질 수 있으므로, 당신은 만일 그것을 나중에 참조 할 필요가 있다면 따로 저장해야할 것이다.이 함수는 GNU확장 으로 헤더파일`string.h'에 선언되어 있다.

함수 :   void psignal  (int signum, const char *message)

이 함수는 표준 에러 출력 스트림 stderr에 시그날 signum을 설명하는 메시지를 프린트 한다. 7.2절 [Standard Streams] 63페이지 참조.  만일 당신이 널 포인터이거나 또는 빈 문자열인 message를 가지고 psignal을 호출하면, psignal은 단지 새줄을 하나 덧붙여서 signum에 해당하는 메시지를 프린트 한다. 만일 당신이 널이 아닌 message 인수를 공급하면, psignal은 이 문자열로 그 출력의 앞에 놓는다.  그것은 signum에 해당하는 문자열로부터 메세지를 분리하기 위한 공백이나 콜론을 더한다. 이 함수는 BSD를 위한것이고, 헤더파일 `stdio.h'에 선언되어 있다.  또한 다양한 시그날 코드들을 위한 메시지를 포함하고 있는 배열 sys_siglist가 있다.  이 배열은 strsiganl과 달리 BSD 시스템상에 존재한다

21.3  시그날 동작 정하기

시그날을 위한 동작을 변경하기 위한 가장 간단한 방법은 signal함수를 사용하는 것이다. 당신은 내장된(built-in) 동작을 지정하거나, 핸들러를 만들 수 있다. GNU 라이브러리는 또한 좀더 다양한 기능을 가진 sigaction 도구를 사용한다.  이절은 두 개의 도구들에 대한 설명과 언제 이것을 사용할지에 대한 제안을 한다.

21.3.1  기본 시그날 처리

signal 함수는 특정한 시그날을 위한 동작을 만들기 위한 간단한 인터페이스를 제공한다.  그 함수와 연관된 매크로들은 헤더파일 `signal.h'에 선언되어 있다.

데이터 타입 :   sighandler__t

이것은 시그날 핸들러 함수들의 타입이다.   시그날 핸들러들은 시그날 번호를 지정하기 위해서 정수인수를 하나 취하고, 리턴 타입으로는 void 형을 가진다.  그래서 당신은 다음과 같이 핸들러 함수를 정의할 수 있다.

void  handler  (int  signum)  {  . . .  }

이 데이터 타입을 위한 sinhandler_t는 GNU 확장이다.

함수 :   sighandler_t signal  (int signum, sighandler_t action)

signal 함수는 시그날 signam을 위한 동작을 action으로 만든다. 첫 번째 인수, signum은 당신이 제어하기 원하는 행동을 가진 시그날을 시그날 번호로써 지정한다.  시그날 번호를 지정하기 위한 적당한 방법은 21.2절 [Standard Signals] 272 페이지에서 설명된 심볼릭 시그날 이름들중 하나를 사용하는 것이다_주어진 시그날의 종류를 위한 숫자 코드들은 서로 다른 운영체제에서는 변화할 수 있기 때문에 명백한 숫자를 사용하는 것을 삼가라.

두 번째 인수, action은 시그날 signum을 위해 사용하는 동작을 지정한다.  다음중의 하나가 사용될 수 있다.

SIG_DFL

SIG_DFL 은 특정한 시그날을 위한 디폴트 동작을 지정한다.  다양한 종류의 시그날들을 위한 디폴트 동작들은 21.2절 [Standard Signals] 272 페이지에 나와있다.

SIG_IGN

SIG_IGN은 시그날이 무시되도록 정한다.  당신의 프로그램은 심각한 사건들을 표현하거나, 또는종료를 요청하는데 사용되는 시그날들은 보통무시하지 않을 것이다.  당신은 전혀 SIGKILL 또는 SIGSTOP신호를 무시할 수 없다.  당신은 SIGSEGV와 같은 프로그램 에러 시그날들을 무시할 수 있지만, 에러를 무시하는 것이 실행을 계속하도록 프로그램을 가능하게 만드는 것은 아니다.  SIGINT, SIGQUIT 그리고 SIGTSTP와 같은 사용자의 요청을 무시하는 것은 사용자를 불쾌하게 만든다. 당신이 프로그램의 어떤 부분이 실행되는 동안에 시그날이 배달되는 것을 원하지 않을 때, 그들을 블록하기 위해서 사용하는것이지 그들을 무시하는 것이 아니다.  21.7절 [Blocking Signals] 298 페이지 참조.

handler

어떤 시그날이 배달되었을 때 이 핸들러가 작동하도록 하기 위해서 당신의 프로그램에 핸들러 함수의 주소를 공급한다.  시그날 핸들러 함수를 정의하기에 대한 상세한 정보는 21.4절 [Defining Handlers] 285 페이지 참조. 만일 당신이 SIG_IGN으로 시그날을 위한 동작을 설정했거나, 또는 SIG_DFL로 설정하고 디폴트 동작이 시그날을 무시하는 것이라면 어느 미해결된 시그날들은 버려진다( 심지어 그들이 블록되었을 지라도 ).  미해결인 시그날들을 버리는 것은, 심지어 당신이 그 시그날을 위한 다른 동작을 지정하고 블록을 해제하도록 연속적으로 정할지라도 그들이 결코 배달되어지지 않을 것임을 의미한다.

signal 함수는 정해진 signum 시그날을 위해서 효력이 있었던 동작을 리턴한다.  당신은 이 값을 저장할 수 있고 나중에 다시 signal을 호출함으로써 그것을 다시 반환한다.  만일 signal이 요청을 받아들일 수 없다면, 그것은 대신에 SIG_ERR을 리턴한다. 다음의 errno는 이 함수를 위해 정의된 에러 상황이다.

EINVAL

당신은 유용하지 않은 signum을 지정하였거나; 또는 SIGKILL 이나 SIGSTOP를 위한 핸들러를 제공하거나 무시하려 시도했다.  어떤 심각한 시그날이 발생했을 때 임시파일들을 지우기 위한 핸들러를 설정하는 간단한 예제이다.

#include  <signal.h>

void

termination_handler  (int  signum)

{

struct  temp_file  *p;

for  (p  =  temp_file_list;  p;  p  =  p->next)

unlink  (p->name);

}

int

main  (void)

{

. . .

if  (signal  (SIGINT,  termination_handler)  ==  SIG_IGN)

signal  (SIGINT,  SIG_IGN);

if  (signal  (SIGHUP,  termination_handler)  ==  SIG_IGN)

signal  (SIGHUP,  SIG_IGN);

if  (signal  (SIGTERM,  termination_handler)  ==  SIG_IGN)

signal  (SIGTERM,  SIG_IGN);

. . .

}

주어진 시그날이 무시되도록 미리 설정되어졌다면, 이 코드는 그 설정을 바꾸는 것을 피함을 기억하라. 이것은 비-작업-제어 쉘들이 자식 프로세스가 시작될 때 어떤 시그날들을 종종 무시하기 때문이고, 그리고 이것을 고려하는 것은 자식 프로세스에게는 중요하다. 우리는 이 예제 프로그램이 디버깅을 위해서(코아 덤프 파일) 정보를 제공하도록 만들어졌기 때문에 프로그램 에러 시그날이나 SIGQUIT를 처리하지 않고, 임시 파일들은 유용한 정보를 가질 것이다.

함수 :   sighandler_t ssignal (int signum, sighandler_t action)

ssignal 함수는 signal과 같은 일을 한다;  이것은 오직 SVID와의 호환성을 위해서 제공되어졌다.

매크로:   sighandler_t SIG__ERR

이 매크로의 값은 에러를 지적하는 signal함수로부터의 리턴값으로써 사용되어진다.

21.3.2  진보된 시그날 처리

sigaction 함수는 signal과 같은 기본 효과를 갖는다:  한 시그날이 어떻게 프로세스에 의해 처리될것인지를 정하는. sigaction은 조금은 복잡하지만, 더 많은 제어를 제공한다.  특별하게, sigacion은 시그날이 언제 발생되고 어떻게 그 핸들러가 호출될것인지에 대해 제어를 할 수 있는 부가적인 플래그를 지정하도록 허용한다.  sigaction 함수는 `signal.h'에 선언되어 있다.

데이터 타입 :   struct sigaction

struct sigacion 타입의 구조체들은 어떻게 특정한 시그날을 처리 할것인지에 대한 모든 정보를 지정하기 위해서 sigaction 함수에서 사용된다.  이 구조체는 적어도 다음의 멤버들을 갖고 있다.

sighandler_t sa_handler

이것은 signal 함수의 action인수와 같은 방법으로 사용되어진다.  그 값은 SIG_DFL, SIG_IGN 또는 함수 포인터가 될 수있다.  21.3.1절 [Basic Signal Handling] 280페이지 참조.

sigset_t sa_mask

이것은 핸들러가 작동되고 있는 동안 블록될 시그날의 집합을 설정한다.  블록킹에 대한 것은 21.7.5절 [Blocking for Handler] 301 페이지에 설명되어 있다.  시그날이 배달되었을 때 핸들러가 작동되기전에 디폴트로써 자동적으로 블록됨을 알아둬라; 이것은 sa_mask에 있는 값에 상관하지 않는다.  만일 당신이 어떤 시그날이 핸들러안에서 블록되어지지 않기를 원한다면, 당신은 핸들러 안에 있는 코드에 그것을 블록하지 않을것입을 적어야 한다.

int sa_flags

이것은 시그날의 동작에 영향을 미칠 수 있는 다양한 플래그들을 지정한다.  이들에 대한 것은 21.3.5절 [Flags for Sigaction] 284 페이지에 좀더 자세하게 설명되어 있다.

함수 : int sigaction(int signum, const struct sigaction *action,  struct sigaction *old_action)

action 인수는 시그날 signum을 위한 새로운 동작을 준비하기 위해서 사용되고, old_action 인수는 이 심볼과 연관된 이전의 동작에대한 정보를 리턴하기 위해 사용된다. (즉, old_action은 signal 함수의 리턴값과 같은 목적을 갖는다_당신은 그 시그날에 영향을 미쳤던 이전의 동작이 무엇이었는지를 알 수 있을뿐만 아니라 나중에 만일 당신이 원한다면 그 동작을 다시 반환할 수도 있다.) action 또는 old_action 중의 하나는 널 포인터가 될 수 있다. 만일 old_action 이 널 포인터라면, 이전동작(old action)에 대한 정보를 리턴하는 것이 생략된다.  만일 action이 널 포인터라면, 시그날 signum과 연관된 동작이 변경되지 않는다;  이것은 원래의 시그날이 가진 동작은 변경함이 없이 시그날을 처리할 수 있음을 허용한다.

sigaction으로부터의 리턴값은 만일 성공하면 0이고, 실패하면 -1을 리턴한다.  다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EINVAL

signum인수가 유용하지 않거나, 당신이 SIGKILL 또는 SIGSTOP 시그날을 무시하거나 트랩하려고 시도하였다.

21.3.3  signal 과 sigaction 의 상호작용

한 단일한 프로그램안에서 signal 과 sigaction 함수들을 모두 사용하는 것이 가능하지만, 그들은 완전히 다른 방법들로 서로 영향을 미칠수 있기 때문에 주의해야만 한다. sigaction 함수는 signal 함수보다는 좀더 많은 정보를 지정하기 때문에, signal로부터의 리턴값은 sigaction이 표현할수 있는 범위를 표현할 수 없다.  그렇지만, 만일 당신이 어느 동작을 저장하고 나중에 다시 그 동작을 재건하기위해서 signal을 사용한다면, sigaction을 통해서 만들어졌던 핸들러는 적당하게 재건되어질 수 없을 것이다.  이러한 문제를 피하기 위해서, 핸들러가 전혀 sigaction을 사용하지 않은 프로그램이라고 할지하도 핸들러는 저장하고 반환하기 위해서는 항상 sigaction을 사용하라. sigaction이 좀더 일반적이기 때문에, 원래 siganl 또는 sigaction을 가지고 만들었는지에 상관없이, 어느 동작을 저장하고 재건하기 위해서 sigaction을 사용하는 것이 더 적당할 수 있다.

만일 signal을 사용해서 어떤 동작을 만들었고 그다음 sigaction을 사용해서 그것을 시험한다면, 핸들러 주소는 당신이 signal에서 지정했던 것과 같지 않은 것을 얻게 될 것이다.  그것은 심지어 signal에 action인수로써 사용하기에도 적당하지 않게 될 것이다.  그러나 당신은 sigaction의 인수로써 그것을 사용하고 적용할 수 있다.  그래서, 단일한 프로그램안에서는 시종일관 한가지 또는 다른한가지의 메카니즘을 고수하는 것이 한결 더 낫다.

이식성 노트 : 기본 signal 함수는 ANSI C에서 지원되고, sigaction함수는 POSIX.1 표준에서 지원하고 있다.  만일 당신이 비-POSIX 계열의 시스템 과의 호환성에 관심이 있다면, 당신은 대신에 signal을 사용하라.

21.3.4  sigaction 함수 예제

21.3.1절 [Basic Signal Handling] 280 페이지에서, signal을 사용해서 종료 시그날들을 위한 간단한 핸들러를 만드는 예제를 보여주었다.  다음은 sigaction을 사용하는 같은 예제이다.

#include  <signal.h>

void

termination_handler  (int  signum)

{

struct  temp_file  *p;

for  (p  =  temp_file_list;  p;  p  =  p->next)

unlink  (p->name);

}

int

main  (void)

{

. . .

struct  sigaction  new_action,  old_action;

/*  새로운 동작을 지정할 구조체를 준비하라.  */

new_action.sa_handler  =  termination_handler;

sigemptyset  (&new_action.sa_mask);

new_action.sa_flags  =  0;

sigaction  (SIGINT,  NULL,  &old_action);

if  (old_action.sa_handler  !=  SIG_IGN)

sigaction  (SIGINT,  &new_action,  NULL);

sigaction  (SIGHUP,  NULL,  &old_action);

if  (old_action.sa_handler  !=  SIG_IGN)

sigaction  (SIGHUP,  &new_action,  NULL);

sigaction  (SIGTERM,  NULL,  &old_action);

if  (old_action.sa_handler  !=  SIG_IGN)

sigaction  (SIGTERM,  &new_action,  NULL);

. . .

}

이 프로그램은 요구된 파라미터로 단지 new_action을 로드하고 sigaction을 호출할 때 그것을 인수로써 사용한다.  sigemptyset의 사용에 대한 것은 나중에 설명되었다. 21.7절 [Blocking Signals] 298 페이지 참조.  signal을 사용하는 예제에서, 미리 무시되도록 설정된 시그날들을 처리 하는 것을 피했다.  여기서 새로운 동작을 지정하지 않고 현재의 동작을 시험하는 것을 허용하는 sigaction을 사용해서, 순간적으로 시그날핸들러가 변경되는것을 피할 수 있다.

다음은 다른 예제이다.  이것은 동작을 변경하지 않고 SIGINT를 위해서 현재의 동작에 대한 정보를 구한다.

struct  sigaction  query_action;

if  (sigaction  (SIGINT,  NULL,  &query_action)  <  0)

/* 에러가 발생하면 sigaction은 -1을 리턴한다. */

else  if  (query_action.sa_handler  ==  SIG_DFL)

/* SIGINT는 원래 자신이 가지고 있던 디폴트 동작으로 처리되었다.  */

else  if  (query_action.sa_handler  ==  SIG_IGN)

/*  SIGINT는 무시되었다. */

else

/*  프로그래머가 만든 핸들러가 효력을 발휘하였다.  */

21.3.5  sigaction을 위한 플래그

sigaction 구조체의 멤버 sa_flags는 특별한 기능을 위한 것이다.  대부분의 경우에, SA_RESTART가 이 필드에서 사용하기에 가장 좋은 값이다.  sa_flags의 값은 비트 마스크로써 해석되어진다.  그래서, 당신은 당신이 원하는 플래그를 설정하거나 선택할 수 있고, sigaction 구조체의 sa_flags 멤버안에 그 결과를 저장한다.  각각의 시그날 번호는 자신만의 플래그 설정을 갖는다.  sigaction을 호출하는 것은 특정한 시그날 번호의 영향을 받고, 당신이 지정한 플래그들은 오직 특정한 시그날에만 적용된다.

GNU C 라이브러리에서, signal로 핸들러는 만드는 것은 당신이 siginterrupt로 만들었던 설정에 의존한 값을 갖는 SA_RESTART를 제외하고는 모두 0으로 플래그를 설정한다.  21.5절 [Interrupter Primitives] 293페이지를 참조하라.  이들 매크로들은 헤더파일 `signal.h'에 정의되어 있다.

매크로 :   int SA__NOCLDSTOP

이 플래그는 SIGCHLD 시그날에서만 유용하다.  그 플래그가 설정되었을 때, 종료된 자식 프로세스를 위한 시그날은 배달하지만, 멈추어있는 자식프로세스를 위한 시그날은 배달하지 않는다. 원래 SIGCHLD를 위한 디폴트 설정은 종료된 자식 프로세스와 멈춘 자식 프로세스 둘다를 위한 시그날을 배달하는 것이다.  이 플래그가 설정되면 SIGCHLD를 제외한 다른 시그날에는 아무런 영향을 주지 않는다.

매크로 :   int SA__ONSTACK

만일 어떤 특정한 시그날 번호를 위해서 이 플래그가 설정되면, 시스템은 그런 종류의 시그날이 배달되었을 때 시그날 스택을 사용한다. 21.9절 [BSD Signal Handling] 306페이지 참조

매크로 :   int SA__RESTART

이 플래그는 어떤 기본동작 (open, read, 또는 write와 같은)이 진행되고있는 동안 배달된 시그날을 어떻게 할것인지를 제어하고, 시그날 핸들러는 정상적으로 리턴한다.  두 개의 선택권을 가진다. 라이브러리 함수가 계속될 수 있거나, 또는 에러코드 EINTR을 사용해서 실패를 리턴할 수 있다. 그러한 선택은 배달된 특정한 종류의 시그날에 따라서  SA_RESTART에 의해 제어된다.  만일 그 플래그가 설정되면, 핸들러로부터의 리턴은 라이브러리 함수를 다시 계속한다.  만일 그 플래그의 설정이 해제되면, 핸들러로부터의 리턴은 그 함수가 실패하도록 만든다.  21.5절 [Interrupted Primitives] 293페이지 참조.

21.3.6  초기 시그날 동작들

새로운 프로세스가 만들어질때(23.4절 [Creating a Process] 326 페이지 참조.), 부모 프로세스로부터 시그날들의 처리를 상속받는다.  그렇지만, 당신이 exec 함수(23.5절 [Executing a File] 327 페이지 참조.)를 사용해서 새로운 프로세스 이미지를 로드할 때, 어느 시그날들은 그들의 원래의 동작으로 환원하도록 SIG_DFL을 사용해서 당신 자신의 핸들러를 정의 해야만 한다.  (만일 당신이 이것에 대해서 조금만 생각한다면, 이것을 이해할 수 있다;  원래의 프로그램으로부터 온 처리 함수들은 그 프로그램을 위한 정의이고, 새로운 프로그램 이미지의 주소 공간에는 심지어 존재하지도 않는다.)  물론, 새로운 프로그램은 자신의 핸들러를 만들 수 있다.

어떤 프로그램이 쉘에 의해 실행될 때, 쉘은 보통 적당한 SIG_DFL 또는 SIG_IGN을 사용해서 자식 프로세스를 위한 초기동작을 설정한다. 그것은 당신이 새로운 시그날 핸들러를 만들기전에 쉘이 SIG_IGN으로 초기동작을 준비하지 않은것이 확실한지 체크하는 것은 좋은생각이다.  다음은 현재 무시되지 않고 있는 SIGHUP 시그날을 위한 핸들러를 어떻게 만드는지에 대한 예제이다.

. . .

struct  sigaction  temp;

sigaction  (SIGHUP,  NULL,  &temp);

if  (temp.sa_handler  !=  SIG_IGN)

{

temp.sa_handler  =  handle_sighup;

sigemptyset  (&temp.sa_mask);

sigaction  (SIGHUP,  &temp,  NULL);

}

21.4  시그날 핸들러 정의하기

이절은 signal 이나 sigaction 함수들을 사용해서 만들 수 있는 시그날 핸들러 함수를 어떻게 쓸것인지에 대해서 설명하고 있다.  시그날 핸들러는 프로그램의 다른 부분과 함께 컴파일하는 함수일 뿐이다. 직접적으로 그 함수를 불러내는 대신에, 시그날이 도착했을 때 그 핸들러를 호출하도록 운영체제에게 알리는 signal 이나 sigaction 함수를 사용한다. 이것이 핸들러를 만드는 것이다.  21.3절 [Signal Actions] 279 페이지 참조.  다음은 당신이 시그날 핸들러 함수에서 사용할 수 있는 두 개의 기본 범주들이다.

□ 어떤 전역 데이터 구조체에 의해서 도착된 시그날을 기록해두고 그다음 정상적인 리턴을 하는 핸들러 함수를 가질 수 있다.

□ 프로그램을 종료시키거나 그 시그날의 원인이 된 상황으로부터 회복할 수 있는 지점으로 제어를 옮기는 핸들러 함수를 가질 수 있다.

당신이 핸들러 함수를 작성할 때 핸들러 함수는 비동기적으로 호출되어질 수 있기 때문에 각별한 주의가 필요하다.  즉, 핸들러함수는 예측할수없이, 프로그램의 어느지점에서든지 호출되어질 것이다.  만일 매우 짧은 간격을 두고 두 개의 시그날이 도착한다면, 한 개의 핸들러는 다른 핸들러 안에서 실행할 수 있다.  이절은 당신의 핸들러는 무엇을 하고, 무엇을 피해야 하는지를 설명한다.

21.4.1  리턴하는 시그날 핸들러

정상적인 리턴을 하는 핸들러는 SIGALRM 과 입/출력과 시그날을 사용한 프로세스간 통신과 같은 시그날에 보통 사용되어진다.  그러나 SIGINT를 위한 핸들러는 가능한 시간에 프로그램이 분기하도록 플래그를 설정한 후에 정상적으로 리턴한다.

프로그램 에러 시그날을 위한 핸들러로부터 정상적인 리턴을 하도록 하는 것은 안전하지 않다, 왜냐하면 핸들러 함수가 리턴할때 프로그램의 동작은 프로그램 에러 후에 무엇을 할것인지 정의되지 않았기 때문이다. 21.2.1절 [Program Erroe Signals] 273 페이지 참조.

정상적으로 리턴하는 핸들러들은 어떤 전역 변수를 갱신해야만 한다. 전형적으로, 그 변수는 정상적으로 작동하는 동안에 프로그램에 의해 주기적으로 시험되는 것이다.  데이터타입은 21.4.7절 [Atomic Data Access]292 페이지에서 설명된 sig_atomic_t가 된다.

다음은 한 개의 프로그램과 같은 간단한 예제이다.  이 프로그램은 SIGALRM 시그날이 도착한 것을 발견할때까지 루프의 몸체를 실행한다. 이 기술은 시그날이 도착하면 그 루프를 분기하기 전에 어떤 동작을 완성하도록 진행과정에서 상호작용을 허용하기 때문에 유용하다.

#include  <signal.h>

#include  <stdio.h>

#include  <stdlib.h>

/*  이 플래그는 메인 루프의 종료를 제어한다.  */

volatile  sig_atomic_t  keep_going  =  1;

/* 시그날 핸들러는 단지 그 플래그를 소거하고 그 자체를 다시 가능하게 한다.  */

void

catch_alarm  (int  sig)

{

keep_going  =  0;

signal  (sig,  catch_alarm);

}

void

do_stuff  (void)

{

puts  ("Doing  stuff  while  waiting  for  alarm....");

}

int

main  (void)

{

/*  SIGALRM 시그날을 위한 핸들러를 만든다.  */

signal  (SIGALRM,  catch_alarm);

/*  잠시동안 알람이 멈추도록 설정한다.  */

alarm  (2);

/* 종료할때를 알기위해서 while에서 플래그를 체크한다. */

while  (keep_going)

do_stuff  ();

return  EXIT_SUCCESS;

}

21.4.2  프로세스를 종료시키는 핸들러

프로세스를 종료시키는 핸들러 함수들은 전형적으로 소거 명령이나 프로그램 에러 시그날로부터의 복구와 인터럽트가 원인이 된곳에 사용된다.  프로세스를 종료하도록 하는 핸들러를 만드는 가장 깨끗한 방법은 핸들러 실행의 첫 번째에서 같은 시그날이 발생되도록 하는 것이다.  다음과 같이.

volatile  sig_atomic_t  fatal_error_in_progress  =  0;

void

fatal_error_signal  (int  sig)

{

/* 이 핸들러는 여러개의 시그날에 대하여 처리하는 것이므로, 어떤 종류의 시그날이 배달될때마다 반복적으로 핸들러가 불리워질 것이다.  그것을 기억하도록 정적변수를 사용하라. */

if  (fatal_error_in_progress)

raise  (sig);

fatal_error_in_progress  =  1;

/* 이제 동작들을 정리하자 :

- 터미날 모드를 재설정한다.

- 자식 프로세스를 kill한다.(으~~ 쌀벌해..)

- 록 파일들을 제거하자.  */

. . .

/* 이제 시그날을 다시 일으키자.  그동안 시그날은 블록되어져 있었고, 이제 디폴트 처리로써, 프로세스를 종료하도록 하는 그 시그날을 받을 것이다.  우리는 단지 exit 나 abort만을 호출할 수도 있지만, 시그날을 재발생시키면 프로세스의 정확한 상황으로 리턴을 설정한다.   */

raise  (sig);

}

21.4.3  핸들러 안에서 비지역 제어 이동

당신은 setjmp 와 longjmp 기능을 사용해서 시그날 핸들러의 외부로 제어의 비지역 이동을 할 수 있다( 20장 [Non-Local Exits] 267 페이지참조.) 핸들러가 비지역 제어 이동을 할 때, 실행중에 있던 프로그램의 그부분은 계속되지 않을 것이다.  만일 프로그램의 그 부분이 중요한 데이터 구조체를 갱신중에 있었다면, 그 데이터 구조체는 여전히 완벽하게 처리되지 못한 상태로 남게될 것이다.  프로그램이 종료되지 않는다면, 위와 같은 문제는 나중에 발견될지도 모른다.

이러한 문제를 피하기 위한 두 개의 방법이 존재한다.  한가지는 중요한 테이타를 갱신하는 프로그램 부분을 위해서는 시그날을 블록하는 것이다. 블록된 시그날은 그 블록이 해제된후에 배달되어지고, 그때는 이미 중요한 데이터 갱신은 끝난 상태가 된다.  21.7절 [Blocking Signals] 298 페이지참조.  다른방법은 시그날 핸들러 안에서 중요한 데이터의 구조체들을 재-초기화 하거나, 그들의 값을 모순이 없도록 만드는 것이다. 다음은 한 개의 전역변수의 재초기화를 보여주는 개략적인 예제이다.

#include  <signal.h>

#include  <setjmp.h>

jmp_buf  return_to_top_level;

volatile  sig_atomic_t  waiting_for_input;

void

handle_sigint  (int  signum)

{

/*  우리는 시그날이 도착했을때는 입력을 받기위해서 기다리겠지만, 제어를 옮길때는 더 이상 기다리지 않는다. */

waiting_for_input  =  0;

longjmp  (return_to_top_level,  1);

}

int

main  (void)

{

. . .

signal  (SIGINT,  sigint_handler);

. . .

while  (1)  {

prepare_for_command  ();

if  (setjmp  (return_to_top_level)  ==  0)

read_and_execute_command();

}

}

/* 이것이 여러 명령문에서 사용되는 서브루틴이라고 생각하자.  */

char  *

read_data  ()

{

if  (input_from_terminal)  {

waiting_for_input  =  1;

. . .

waiting_for_input  =  0;

}  else  {

. . .

}

}

21.4.4  핸들러가 실행되고 있는 동안 도착한 시그날들

시그날 핸들러 함수가 실행되고 있을 때 도착한 다른 시그날이 있다면 무슨 일이 발생할까? 한 특정한 시그날을 위한 핸들러가 호출되었을 때, 핸들러가 리턴할 때 까지 그 시그날은 보통 블록된다.  만일 같은 종류의 두 개의 시그날이 서로 가까운 시간에 도착한다면, 두 번째것은 첫 번째것이 처리될때까지 그냥 보유하고 있을 것이다.  ( 만일 당신이 이러한 형태의 더 많은 시그날이 도착하도록 허용하기를 원한다면, 핸들러는 sigprocmask를 사용해서 시그날을 명백하게 블록을 해제할 수 있다;  21.7.3절 [Process Signal Mask] 300페이지 참조.)

그렇지만, 당신의 핸들러는 다른 종류의 시그날의 배달에 의해서는 여전히 인터럽트되어질 수 있다.  이것을 피하기 위해서, 당신은 sigaction에 인수로써 사용하는 action 구조체의 sa_mask 멤버를 사용해서 핸들러가 실행되는 동안 블록되어질 시그날을 명백하게 지정할 수 있다.  그들 시그날은 호출 된 핸들러를 위한 시그날에 더해져 있고, 다른 시그날들은 보통 프로세스에 의해서 블록되어진다.  21.7.5절 [Blocking for Handler] 301 페이지 참조.

이식성 노트 : 만일 당신의 프로그램이 완전히 System V Unix상에서 작업하기를 원할 때, 비동기적인 발생이 예상되는 시그날을 위한 핸들러를 만들려면 항상 sigaction을 사용하라.  다른 시스템에서는, 핸들러에서 하는 시그날의 처리는 SIG_DFL로 시그날이 가진 원래의 동작으로 되돌려지도록 만들어져있고, 핸들러는 실행될때마다 그 자체를 다시 만들어야만 한다.  이 것은 실제로 시그날이 연속적으로 도착할수 없을 때 작업하기는 불편하다.  하지만 다른 시그날이 즉시 도착할 수 있다면, 그것은 다시 핸들러를 다시 정하지 않아도 된다.  그러면 두 번째 시그날은 프로세스를 종료시키는, 디폴트 처리로 받게될 것이다.

21.4.5  한가지로 합병한 서로 밀접한 시그날들

만일 당신의 시그날 핸들러가 전혀 호출될만한 기회도 갖기전에, 당신의 프로세스에 같은 종류의 시그날이 여러개 배달되었다면, 그 핸들러는 오직 한 개의 시그날이 도착한것처럼 호출되어질 것이다. 실제로, 그 시그날들은 한 개로 합병한다.  이 상황은 시그날이 블록되었을때나, 또는 멀티프로세싱 환경에서 시그날이 도착했는데 시스템이 다른 프로세스의 실행 때문에 바쁠 때발생할 수 있다.  이것이 의미하는 것은, 예를들어, 당신은 발생한 시그날의 개수를 세는 시그날 핸들러의 사용을 신뢰할 수 없다.  오로지 당신이 구분할 수 있는 것은 과거의 주어진 시간동안에 적어도 한 개의 시그날이 도착했는지, 또는 도착하지 않았는 지를 구분하는 것만을 신뢰할 수 있다. 다음은 자식 프로세스가 발생시킨 SIGCHLD의 개수와는 같지 않을지도 모르는 SIGCHLD 시그날의 개수를 실제처럼 대치하는 핸들러의 예제이다. 그것은 프로그램이 다음처럼 구조체를 연결하여 자식 프로세스의 모두를 추적하고 있다고 가정한다.

structprocess

{

struct  process  *next;

/*  자식 프로세세의 프로세스 ID  */

int  pid;

/* 자식 프로세스가 출력을 하는 파이프나 가상 터미날의 기술자   */

int  input_descriptor;

/* 만일 이 프로세스가 멈추거나 종료된다면 0이 아니다. */

sig_atomic_t  have_status;

/*  자식 프로세스의 상황; 실행중이면 0, 그렇지 않으면 그 status는 waitpid로부터의 값이다. */

int  status;

};

struct  process  *process_list;

다음 예제는 과거의 어떤 시간동안에 시그날이 도착했는지를 지적하는 플래를 사용한다_그때마다 프로그램은 마지막에 그 플래그를 0으로 소거한다.

/*  0이 아닌값은 process_list의 항목에서 상황이 변환된 자식 프로세스를 발견했음을 의미한다.  */

int  process_status_change;

다음은 핸들러 자체이다.

void sigchld_handler  (int  signo)

{

int  old_errno  =  errno;

while  (1)  {

register  int  pid;

int  w;

struct  process  *p;

/*  정해진 결과를 얻을때까지 상황을 물어보아라    */

do{

errno  =  0;

pid = waitpid (WAIT_ANY, &w, WNOHANG |

WUNTRACED);

}while (pid <= 0 && errno == EINTR);

if (pid <= 0 ) {

/* 실패의 실제 의미는 더 이상 멈추거나 종료될 자식프로세스가 없음을 의미하고, 그래서 리턴한다.  */

errno  =  old_errno;

return;

}

/* 우리에게 신호를 보냈던 프로세스를 찾아서, 그 상황을 기록하라 */

for (p = process_list; p; p = p->next)

if (p->pid == pid) {

p->status  =  w;

/* 보려하는 데이터를 가진 상황 필드를 지적하라. 우리는 그것이 저장된 후에 이것을 한다.  */

p->have_status  =  1;

/* 만일 프로세스가 종료되었다면, 출력을 위한 기다림을  멈추어라  */

if  (WIFSIGNALED  (w)  ||  WIFEXITED  (w))

if  (p->input_descriptor)

FD_CLR  (p->input_descriptor,  &input_wait_mask);

/* 프로그램은 process_list 안에 어떤 새로운 것이 있는지 알아 보기 위해서 주어진 시간동안에 이 플래그를 체크할 것이다.*/

++process_status_change;

}

/* 우리에게 말할 무언가를 가진 모든 프로세스들을 처리 하도록 루프를 돌려라 */

}

}

다음은 process_status_change 플래그를 체크하기 위한 적당한 방법이다.

if  (process_status_change)  {

struct  process  *p;

process_status_change  =  0;

for  (p  =  process_list;  p;  p  =  p->next)

if  (p->have_status)  {

. . .  Examine p->status  . . .

}

}

리스트를 시험하기 전에 플래그를 소거하는 것은 치명적이다;  그렇지 않고, 만일 플래그가 소거되기 전과, 프로세스 리스트의 적당한 요소가 체크된후에 시그날이 배달된다면, 다음 도착한 시그날이 다시 그 플래그를 설정하기 전까지는 그 상황변화를 알아차릴 수 없다.  당신은 물론 그리스트를 조사하는 동안 시그날을 블록함으로써 이러한 문제를 피할 수는 있지만, 올바른 순서로 일들을 처리하는 것이 정확함을 보증하기에는 좀더 좋은 방법이다.

프로세스 상황을 체크하는 루프가 그 상황이 유용하게 저장되어졌음이 확인될때까지 p->status를 조사하는 것을 피한다.  이것은 status가 억세스 되고 있는 도중에 변화될 수 없음을 확실하게 한다.  일단 p->have_status가 설정되면, 그것은 자식 프로세스가 멈추거나 종료했음을 의미하고, 그 어느 경우에도, 프로그램이 주목하고 있는 동안에 다시 멈추거나 종료할 수없다.  변수를 억세스하고 있는 동안에 인터럽션(interruptions)을 모방하기에 대한 상세한 정보는 21.4.7.3절 [Atomic Usage]293 페이지를 참조하라.

다음은 당신이 체크했던 마지막 시간이후 핸들러가 실행되었는지를 시험 할 수 있는 다른방법이다.  이 기술은 핸들러의 외부에서 결코 변화되지 않을 카운터로 사용한다.  빈도수(count)를 소거하는 대신에, 프로그램은 전의 값을 기억하고 있다가 그 이후에 그 값이 변화되었는지를 보여준다. 이 방법이 유리한점은 프로그램의 다른 부분들을 독림적으로 체크할 수 있다는 것으로, 각각의 부분은 그 부분을 마지막으로 체크한 이후에 시그날이 있었는지를 체크한다.

sig_atomic_t  process_status_change;

sig_atomic_t  last_process_status_change;

. . .

{

sig_atomic_t  prev  =  last_process_status_change;

last_process_status_change  =  process_status_change;

if  (last_process_status_change  !=  prev)  {

struct  process  *p;

for  (p  =  process_list;  p;  p  =  p->next)

if  (p->have_status)  {

. . .  Examine p->status  . . .

}

}

}

21.4.6  시그날 핸들링 과 재진입 불가 함수들

** 역자주 : 재진입(reentrant) : 어떤 모듈을 재진입이 가능하다라고 할 때, 그것은 동시에 두 개 이상의 프로그램에 의하여 공유될 수 있는 모듈을 말한다.  이러한 모듈은 실행 중에 자신의 코드 또는 데이터 영역을 변경시키지 않아야 합니다.

핸들러 함수들은 보통 많은 일을 하지는 않는다.  핸들러 함수에게는 프로그램이 정기적으로 체크하는 외부변수를 설정하는일 이외에는 아무것도 하지 않게하고, 다른 중요한 일들은 프로그램에게 맡기는 것이 좋다.  이것은 핸들러가 예측할 수 없는 시간에_시스템 호출도중, 또는 다중 명령을 요구하는 C연산자의 시작과 끝 사이에_비동기적으로 호출될 수 있기 때문이다.

데이터 구조체가 처리되고 있는 동안 핸들러 함수가 호출되면 데이터 구조체의 상황은 불일치하게 될 것이다.  심지어 한 개의 int 형 변수에서 다른 변수로 값을 복사하는 것 조차도 대부분의 기계에서 두 개의 명령어를 취할 수도 있다. 이것은 당신이 시그날 핸들러에서 무언가를 할 때 많은 주의를 해야만 한다는 것을 의미한다.

□ 만일 당신의 핸들러가 어느 전역변수를 억세스할 필요가 있다면, 그 변수들을 휘발성으로 선언하라.  이것은 변수들의 값이 비동기적으로 변화할것이라고 컴파일러에게 알리고, 그와같은 갱신에 의해 무효로 만들게 될 어떤 최적화를 금한다.

□ 만일 핸들러안의 어떤 함수를 호출한다면, 그것이 시그날들에 대해서는 재진입성이 있음을 확실히 하거나, 시그날이 함수와 연관된 호출에는 인터럽트 할 수 없음을 확실히하라.

스택 (stack)상에 존재하지 않는 메모리를 사용하는 함수는 비-재진입성이 될 수 없다.

□ 만일 어떤 함수가 정적 변수나 전역변수, 또는 동적으로 할당된 오브젝트를 사용한다면, 그것은 비-재진입성이고, 그 함수를 두 번 호출하면 서로 충돌하게 될 수 있다.

예를들어, 시그날 핸들러가 gethostbyname을 사용한다고 가정하자. 이 함수는 정적 오브젝트에 그 값을 리턴하고, 매번 같은 오브젝트를 다시 사용한다.  만일 gethostbyname이 호출된 동안, 또는 심지어 그 후(하지만 여전히 그 값은 프로그램에서 사용하고 있는 중이다.)라도 그 시그날이 도착하는 일이 발생한다면, 그것은 프로그램이 요청한 그 값을 지워버릴 것이다. 그렇지만, 만일 그 프로그램이 gethostbyname이나 같은 오브젝트에 정보를 리턴하는 함수를 사용하지 않거나, 또는 만일 그와 같은 것을 사용한다고 해도 그것을 사용할 때 시그날들을 블록한다면, 당신은 안전하다. 라이브러리 함수들의 대부분은 한 고정 오브젝트에 값들을 리턴하고 항상 같은 오브젝트를 재 사용하기 때문에 그들은 같은 문제를 발생 시킬 가능성이 있다. 이 매뉴얼안에 있는 함수들에 대한 설명에는 이러한것들을 항상 언급할것이다.

□ 만일 어떤 함수가 당신이 공급한 오브젝트를 사용하고 갱신한다면, 그것은 잠재적으로 비-진입성이다.  같은 오브젝트를 사용하고 있는 두 개의 호출은 충돌할 수 있다.

이와 같은 경우는 당신이 스트림을 사용해서 입/출력을 할 때 발생한다.  시그날 핸들러가 fprintf를 사용해서 메시지를 출력한다고 가정하자.  그리고 그 프로그램이 fpintf를 처리하고 있는 도중에 같은 오브젝트를 사용하는 시그날이 배달되었다고 가정하자.  이때 두 개의 호출은 같은 데이터 구조체_스트림 자체_에서 동작하기 때문에 핸들러의 메시지와 프로그램의 데이터는 모두 변조될 것이다. 그렇지만, 만일 핸들러에서 사용하는 스트림이 시그날이 도착하여 동시에 그 스트림이 프로그램에 의해 사용되어질 가능성이 없다는 것을 당신이 알고 있다면, 아무런 문제가 없다.  그리고 만일 프로그램이 다른 스트림을 사용한다면 아무런 문제가 없다.

□ 대부분의 시스템에서, malloc 과 free는 무슨 메모리 블록들이 해제상태에 있는지를 기록하고 있는 정적 데이터 구조체를 사용하기 때문에, 재진입성이 없다.  그렇기 때문에 메모리를 할당하고 해제하는 라이브러리 함수중에 재진입성이 있는 것은 아무것도 없다. 핸들러에서 메모리를 할당할 필요를 피하기 위한 가장 좋은 방법은 시그날 핸들러에서 사용할 공간을 미리 할당받는 것이다.

핸들러에서 메모리를 해제하는 것을 피하는 가장 좋은 방법은 해제할 오브젝트를 플래그로 표시하거나 기록해두고, 어느것이 해제되기를 기다리고 있는지를 나중에 프로그램에서 체크하는 것이다. 그러나 이것은 오브젝트들이 개별적으로 존재하는 것이 아니라 서로 연결되어 있고, 같은 일을 하는 다른 시그날 핸들러에 의해서 그것이 인터럽트되어졌다면, 당신은 오브젝트들중 하나를 "잃어"버릴 수 있기 때문에 주의를 해야만 한다. GNU 시스템에서, malloc 과 free는 시그날들을 블록하기 때문에 시그날 핸들러에서 사용하는 것은 안전하다.  그렇기 때문에, 시그날 핸들러에서 결과를 위해서 공간을 할당하는 것은 또한 안전하다.  obstack 할당 함수들도 당신이 시그날 핸들러의 외부와 내부양쪽에서 같은 obstack를 사용하지 않는다면 안전하다. 재배치(relocating) 할당 함수들( 3.6절 [Relocating Allocator] 38페이지 참조.)을 시그날 핸들러안에서 사용하는 것은 안전하지 않음이 확실하다.

□ errno를 갱신하는 어떤 함수들은 비-진입성이지만, 당신은 이것을 진입성으로 만들 수 있다:  핸들러에서, errno의 원래 값을 저장하고 정상적으로 리턴하기 전에 그것을 반환한다.  이것은 시그날 핸들러 내부에서 발생된 에러들이, 핸들러가 실행되도록 프로그램이 인터럽트 된 순간에 시스템 호출로부터 발생한 에러와 혼동되는 것을 막는다.

이 기술은 일반적으로 응용가능하다;  만일 당신이 핸들러의 내부에서 메모리의 특정한 오브젝트를 갱신하는 함수를 호출하기 원한다면, 당신은 그 오브젝트를 저장하고 다시 반환함을 통해서 안전하게 구현할 수 있다.

□ 메모리 오브젝트로부터 읽기는 시그날이 배달되어질 때라도  오브젝트에 나타날 수 있는 어떤 값들을 취급할수 있도록 안전하게 제공되었다.  어떤 데이터 타입에 배정(assignment)할 때, 그 데이터 타입이 원자단위가 아닌 변수에 배정(assignment)하는 "도중에" 핸들러가 실행될수 있다면 그 배정에는 많은 명령(instruction)이 요구됨을 명심하라.

□  메모리 오브젝트에 기록하기는 핸들러가 실행되고 있는 순간일지라도 안전하고, 어느것도 방해되지 않을 것이다.

21.4.7  원소 데이터 억세스와 시그날 핸들링

당신의 어플리케이션에서 데이터가 원자와 관계가 있던지, 또는 단순한 텍스트이던지, 당신은 원자화가 필요없는 단일한 데이터를 억세스하는 요소에 대해서 주의를 해야만한다.  이것은 단일한 오브젝트를 읽거나 쓰기 위해서는 여러개의 명령이 필요할 수 있다는 것을 의미한다.  그와 같은경우에, 시그날 핸들러는 오브젝트의 읽기나 쓰기 중간에 실행될 수 있다.

이러한 문제를 커버할 수 있는 세가지 방법이 있다.  당신은 항상 원자 단위로 억세스되는 데이터 타입을 사용할 수 있다;  억세스를 인터럽터하여 아무런 부적당한 일이 일어나지 않게 하거나, 또는 인터럽트 보다는 좋지는 않지만 억세스동안에 모든 시그날들을 블록하는등 당신은 주의깊은 조정을 할 수가 있다.

21.4.7.1  비-원자 억세스가 갖는 문제점

다음은 변수를 갱신하는 도중에 시그날 핸들러를 실행하면 무슨일이 발생하는지를 보여주는 예제이다. (변수 읽기를 인터럽트 하는 것도 역설적인 결과에 이르게 할 수 있지만, 여기서 우리는 쓰기를 보여준다.)

#include  <signal.h>

#include  <stdio.h>

struct  two_words  {  int  a,  b;  }  memory;

void

handler(int  signum)

{

printf  ("%d,%d\n",  memory.a,  memory.b);

alarm  (1);

}

int

main  (void)

{

static struct two_words zeros = { 0, 0 }, ones = { 1, 1 };

signal  (SIGALRM,  handler);

memory  =  zeros;

alarm  (1);

while  (1)

{

memory  =  zeros;

memory  =  ones;

}

}

이 프로그램은 계속 번갈아가면서 0, 1, 0, 1 로 메모리를 채운다; 그동안, 일초마다, 알람 시그날 핸들러는 현재의 내용을 프린트한다. ( 핸들러 안에서 printf의 호출은 시그날이 발생했을 때 핸들러외부에서  printf가 확실히 호출되어지지 않을것이므로 이 프로그램은 안전하다. ) 분명히, 이 프로그램은 0 한쌍과 1 한쌍을 프린트 할 수 있다. 하지만 그것이 그 프로그램이 할 수 있는 전부가 아니다!  대부분의 기계에서, 메모리에새로운 값을 저장하기 위해서는 여러개의 명령을 취하고, 그 값은 동시에 한 워드(word)에 저장된다.  만일 시그날이 그 명령들 사이에 배달된다면, 핸들러는 memory.a는 0이고 memory.b는 1인걸 발견할지 모른다(또는 그의 반대).

한 개의 명령으로 메모리안에 한 개의 새로운 값을 저장할 수 있는 어떤 기계에서는 인터럽트 될 수 없다.  그 기계들에서, 핸들러는 항상 두 개의 0과 두 개의 1을 프린트 할 것이다.

21.4.7.2  원자 형

변수를 억세스할 때 인터럽트 하는것에 대한 불확실성을 피하기 위해서, 당신은 항상 원자단위로 억세스를 하는 특별한 데이터 타입을 사용할 수 있다: sig_atomic_t.  이 데이터타입을 읽기와 쓰기는 단일한 명령으로 발생한다는 것이 보증되므로 핸들러가 억세스의 "중간에" 실행될 방법이 없는 것이다.

sig_atomic_t 타입은 항상 정수 데이터 타입이지만, 그 테이타 타입이 몇 개의 비트로 구성되어있는지는 한가지로 정해진 것이 아니라 각각의 기계마다 다양하다.

데이터 타입 :   sig__atomic__t

이것은 정수 데이터 타입이다.  이 타입의 오브젝트는 항상 자동적으로 억세스된다.

실제로, 당신은 int와 int보다는 길지 않은 다른 정수형을 원소단위라고 가정할 수 있다.  당신은 또한 포인터 형을 원소단위로 가정할 수 있다; 그것은 매우 편리하다.GNU C 라이브러리를 지원하는 모든 기계와 모든 POSIX 시스템상에서 그 두 개의 가정은 모두 사실이다.

21.4.7.3  원소단위 사용 형태.

억세스의 어떤 형태는 억세스가 인터럽트 되는것과 같은 문제들을 피한다. 예를들어, 핸들러에 의해 설정되고, 때때로 메인 프로그램에 의해서 소거되고 테스트되는 어떤 플래그를 억세스하는데 실제로 두 개의 명령(instructions)이 필요하다고 할지라도 항상 안전하다.  이것이 그렇게 보이도록 하기 위해서, 우리는 인터럽트 되어질 수 있는 모든 억세스를 고려해야만 하고, 인터럽트 되면 아무런 문제가 없음을 보여야 한다. 플래그를 테스트하는 도중에 발생한 인터럽트는 아무런 문제가 없는 정확한 값인 경우에, 0이 아닌값으로 인식이 되거나 또는 테스트된 다음에 0이 아닌값으로 되어질것이기 때문에 아무런 문제가 없다.

플래그를 소거하는 도중에 인터럽트도 아무런 문제가 없는데, 플래그가 소거되기전에 시그날이 발생한 것은, 그 값이 0으로 끝나거나, 아니면 0이 아닌값으로 끝나고, 플래그가 소거된후에 시그날이 발생한것처럼 연속적인 사건들이 발생한다.  그 두 개의 경우 모두 코드가 처리되기만 하면, 플래그를 소거하는 도중에 발생한 시그날 또한 처리 할 수 있다.  ( 이것은 비-원소단위의 사용이 언제 안전할 수 있는지를 당신에게 설명하기 위한 예제이다. )

때때로 당신은 다른 오브젝트를 사용해서 어떤 오브젝트의 사용을 막음으로써 그 오브젝트에 인터럽트 되지않는 억세스를 보증할 수 있다, 그것의 형태는 원소단위가 확실할 것이다.  21.4.5절 [Merged Signals] 288 페이지에서 예제참조.

21.5  시그날에 의해 인터럽트된 기본동작 ( Primitives )

open 이나 read가 입/출력 디바이스에서 기다리는것과 같은 입/출력 기본동작 동안에 시그날이 발생할 수도 있고 처리될 수도 있다.  만일 시그날 핸들러가 리턴하면, 그 시스템은 의문을 갖는다:  다음에 무슨일이 발생하지?

POSIX는 한가지 접근법을 정한다:  즉시 그 기본동작을 실패로 만든다. 이러한 종류의 실패를 위한 에러코드는 EINTR이다.  이것은 유연하지만, 보통은 불편하다.  전형적으로, POSIX 어플리케이션은 그 호출을 다시 할 목적으로 라이브러리 함수가 리턴했을 때 EINTR인지 체크하는 시그날 핸들러를 사용한다.  종종 프로그래머들은 체크하는 것을 잊는다.

GNU 라이브러리는 매크로 TEMP_FAILURE_RETRY를 사용해서, 임시적인 실패후에 다시 호출을 시도하도록하는 편리한 방법을 제공한다.

매크로 :   TEMP__FAILURE__RETRY  (expression)

이 매크로는 일단 expression을 평가한다. 만일 그것이 실패이면 에러코드 EINTR을 보고하고, TEMP_FAILURE_RETRY는 그것을 다시 평가하고, 그것이 일시적인 실패가 아닐때까지 계속 반복한다. TEMP_FAILURE_RETRY에 의해 리턴된값은 수행된 expression의 값이다.

BSD는 완전히 EINTR을 피하고 좀더 편리한 접근법을 제공한다:  그것을 실패로 만드는 대신에 인터럽트된 기본동작을 다시 시작한다.  만일 당신이 이 접근법을 선택한다면, 당신은 EINTR에 관심을 가질 필요가 없다.

GNU 라이브러리에서는 접근법을 선택할 수 있다.  만일 당신이 시그날 핸들러를 만드는 sigaction을 사용한다면, 당신은 핸들러가 어떻게 동작할지를 정할 수 있다.  만일 당신이 SA_RESTART 플래그를 지정하면, 핸들러부터의 리턴은 어떤 기본동작을 다시 시작할 것이다;  그렇지않으면, 핸들러로부터의 리턴은 EINTR을 발생할 것이다.  21.3.5절 [Flags for Sigaction] 284 페이지 참조. 다른방법은 siginterrupt 함수를 사용하는 것이다.  21.9.1절 [POSIX vs BSD] 306 페이지 참조.

당신이 한 특정한 핸들러에서 sigaction 이나 siginterrupt로 할 일을 정하지 않을 때, 그것은 디폴트 선택을 사용한다.  GNU 라이브러리에서 디폴트 선택은 당신이 정의한 테스트 매크로에 의존한다.  만일 시그날이 발생되기전에 _BSD_SOURCE 또는 _GNU_SOURCE로 정의하면, 디폴트는 기본동작을 다시 시작하는 것이다;  그렇지 않다면, 디폴트는 EINTR로 그들을 실패하게 만드는 것이다. ( 라이브러리는 signal 함수의 다양한 변형을 포함하고 있고, 당신이 사용한 테스트 매크로에 따라서 실제로 호출될 signal 함수가 결정된다. )  1.3.4절 [Feature Test Macros] 6 페이지 참조. 위와 같은 문제에 영향을 받는 기본동작들은 close, fcntl(operation F_SETLK), open, read, recv, recvfrom, select, send, sendto, tcdrain, waitpid, wait, 그리고 write가 있다.

결코 재개(resumption)가 발생되지 않는 한가지 상황이 있다:  read 와 write 와 같은 데이터-참조 함수가 데이터의 일부분만을 참조한 후에 시그날에 의해서 인터럽트 되었을 때.  이 경우, 그 함수는 부분적인 성공을 지적하기 위해서, 이미 참조된 바이트의 개수를 리턴한다.

레코드-지향 디바이스상에서는 두 개의 레코드를 read 하거나 write하려는 것에서 한 개로 read, write를 분리해버리는 것 과 같은 이상한 동작의 원인이 될 수도 있다.(데이타그램 소켓을 포함; 11.9절 [Datagrams] 176 페이지 참조.).  실제로는, 그와 같은 디바이스 상에서는 데이터를 참조 중에 인터럽션이 발생할 수 없기 때문에 아무런 문제가 없다;  그와 같은 디바이스들은 일단 데이터 참조가 시작되면 아무런 기다림이 없이, 한 버스트(burst)에 전체 레코드를 항상 참조한다.

**역자주 : 버스트(burst) : 중간에 어떤 이유들로 인해서 중단이 발생하지 않고, 한 묶음의 데이터를 한꺼번에 전달하는 방법을 의미함.

21.6  시그날 발생시키기

하드웨어 트랩이나 인터럽트의 결과로서 발생되는 시그날을 제외하고, 당신의 프로그램에서 프로세스, 또는 다른 프로세스에게 명시적으로 시그날을 보낼 수 있다.

21.6.1  스스로에게 신호 보내기

프로세스는 raise 함수를 통해서 시그날을 스스로에게 보낼 수 있다. 이 함수는 `signal.h'에 선언되어 있다.

함수 :   int raise  (int signum)

raise 함수는 호출한 프로세스에게 시그날 signum을 보낸다. 성공하면 0을 리턴하고 실패하면 0이 아닌값을 리턴한다. 실패하게 되는 유일한 이유는 signum의 값이 무효한 경우이다.

함수 :   int gsignal  (int signum)

gsignal 함수는 raise와 같은일을 하지만 SVID와의 호환성을 위해서 제공된다.

raise 사용으로 한가지 편리한 점은 당신이 트랩했던 시그날의 디폴트 동작을 재생할 수 있다는것이다.  이를테면, 당신의 프로그램을 사용하는 사용자가 stop 시그날(SIGTSTP)을 보내기 위해서 SUSP 문자를 타이핑할 때, 당신은 멈추기 전에 어떤 내부적 데이터 버퍼들을 소거하기를 원한다고 가정하자.  당신은 다음처럼 이것을 설정할 수 있을 것이다.

#include  <signal.h>

/* stop 시그날이 도착했을 때, 원래의 디폴트로 동작을 설정하고 소거 동작을 한후에 그 시그날을 다시 보낸다. */

void

tstp_handler  (int  sig)

{

signal  (SIGTSTP,  SIG_DFL);

/* 여기서 소거 동작을 하라  */

. . .

raise  (SIGTSTP);

}

/* 프로세스가 다시 계속될 때, 시그날 핸들러를 반환하라. */

void

cont_handler  (int  sig)

{

signal  (SIGCONT,  cont_handler);

signal  (SIGTSTP,  tstp_handler);

}

/* 프로그램을 초기화하는 동안에 양(both) 핸들러를 가능하게 하라  */

int

main  (void)

{

signal  (SIGCONT,  cont_handler);

signal  (SIGTSTP,  tstp_handler);

. . .

}

이식성 노트: raise 는 ANSI C 위원회에 의해 만들어졌다.  오래된 시스템들은 그것을 지원하지 않을것이기 때문에, 이식성을 위해서는 kill을 사용하라.  21.6.2절[Signaling Another Process] 295 페이지 참조.

21.6.2  다른 프로세스에게 시그날 보내기

kill 함수는 다른 프로세스에게 시그날을 보내기 위해서 사용될 수 있다. 함수의 명칭이 악의적이지만, 그것은 다른 프로세스를 종료시키는데 사용하기 보다는 더 많은것들을 위해서 사용될 수 있다.  다음의 경우, 당신이 프로세스들 사이에 시그날들을 보내기 원할 때 사용할수 있다.

□ 부모 프로세스가 작업을 수행하기 위해서 자식 프로세스를 시작한다. 아마도 자식 프로세스는 한정된 루프를 돌것이고_자식 프로세스가 작업에서 더 이상 필요치 않을 때 종료한다.

□ 한 프로세스가 그룹(group)의 일부로써 실행될 때, 에러나 다른 사건이 발생하면 그룹에 있는 다른 프로세스에게 신고하거나 종료할 필요가 있다.

□ 두 개의 프로세스가 서로 작업하는 동안 동기(synchronize)할 필요가 있다.

이 절은 당신이 프로세스가 어떻게 작업하는지에 대해서 조금이나마 알것이라고 가정한다.  이 주제에 대한 자세한 정보는 23장 [Child Process] 325 페이지에 나와있다. kill 함수는 `signal.h'에 선언되어 있다.

함수 :   int kill  (pid_t pid, int signum)

kill 함수는 pid로 정해진 프로세스나 프로세스 그룹에게 시그날 signum을 보낸다.  21.2절 [Standard Signals] 에 나와있는 시그날들뿐만 아니라, signum은 pid의 유효성을 체크하기 위해서 0의 값을 가질 수 있다.  pid는 시그날을 받기위한 프로세스나 프로세스 그룹을 지정한다.

pid > 0

pid는 프로세스이다.

pid == 0

같은 프로세스 그룹에 있는 모든 프로세스에게 시그날을 보내지만, 시그날을 보내는 프로세스 자체는 그 시그날을 받지 않는다.

pid < -1

-pid는 프로세스 그룹이다.

pid == -1

만일 그 프로세스에게 특권이 부여되어 있다면, 어떤 특별한 시스템 프로세스들을 제외한 모든 프로세스들에게 시그날을 보낸다.  그렇지않다면, 같은 사용자 ID를 가진 모든 프로세스에게 시그날을 보낸다.

kill (getpid(), signum) 과 같은 호출로 프로세스는 자신에게 시그날 signum을 보낼 수 있다.  만일 kill이 자신에게 시그날을 보내도록 어떤 프로세스에 의해 사용되고, 그 시그날이 블록되지 않는다면, kill은 리턴하기 전에 프로세스에게 적어도 한 개의 시그날( 시그날 signum 대신에 아직 미해결 상태로 남아있는 블록되지 않은 다른 시그날이 될 수도 있다.)을 배달한다. kill로 부터의 리턴값은, 만일 성공적으로 시그날이 보내질 수 있다면 0을 리턴한다.  그렇지 않고, 아무런 시그날도 보낼 수 없다면 -1을 리턴한다.  만일 pid를 여러개의 프로세스에게 시그날을 보내도록 지정한다면, 그때 만일 kill이 그들에게 적어도 한 개의 시그날을 보낼 수 있다면 성공한 것이다.  당신이 그들 모두에게 시그날이 갔는지 또는 어떤 프로세스가 시그날을 얻었는지를 알 수 있는 방법은 없다. 다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EINVAL	signum 인수가 무효하거나 그 숫자를 지원하지 않는 시그날 번호를 사용했다.

EPERM	당신은 pid에 의해 지정된 프로세스나 프로세스 그룹안의 어떤 프로세스들에게 시그날을 보내기 위한 특권을 갖고 있지 않다.

ESCRH	pid 인수가 현존하고 있는 프로세스나 그룹으로 지정되지 않았다.

함수 :   int killpg  (int pgid, int signum)

이것은 kill과 유사하지만, 프로세스 그룹에게는 시그날을 보낼수 없다.  이 함수는 BSD와의 호환성을 위해서 제공되었다; 이식성을 위해서는 kill을 사용하라.

kill 사용의 간단한 예로서, kill(getpid(), sig)는 raise(sig)와 같은 효과를 갖는다.

21.6.3  kill을 사용하기 위한 허가

어느 임의의 프로세스에게 시그날을 보내기 위해서 kill을 사용하는 것을 방지하기 위한 제한이 있다.  그것은 다른 사용자에게 소속되어 있는 프로세스를 제멋대로 죽이는것과 같은 반사회적인 행동을 방지하기 위한 의도가 있다.  kill을 자식과 부모 프로세스사이에 시그날을 주고 받기위해 사용하는것과 같은 상황에서는, 보통 당신은 시그날을 보내기 위한 허가권을 갖고 있다. 그러나 자식 프로세스에서 setuid 프로그램이 실행될때는 유일하게 제외된다;  만일 프로그램이 실제 UID를 유효 UID로 변경한다면, 당신은 시그날을 보내기 위한 허가권을 가지지 않을 수도 있다. su 프로그램을 이런일을 한다.

프로세스가 다른 프로세스에게 시그날을 보내기 위한 허가권을 가지고 있는지 없는지의 여부는 두 개의 프로세스의 사용자 ID들에 의해 결정된다. 이 원칙은 25.2절 [Process Personal] 353 페이지에 자세하게 논의되고 있다.

일반적으로, 어떤 프로세스가 다른 프로세스에게 시그날을 보낼수 있기 위해서는, 시그날을 보내는 프로세스가 특권이 부여된 사용자(`root'처럼)이거나 시그날을 보내는 프로세스의 실제 또는 유효 사용자 ID가 시그날을 받는 프로세스의 실제 또는 유효 사용자 ID와 매치되어야만 한다. 만일 시그날을 받는 프로세스가 프로세스 이미지 파일에서 set-user-ID 모드를 통해 유효 사용자 ID를 변경했다면, 프로세스 이미지 파일의 소유자가 현재 유효 사용자 ID 대신에 사용된다.  어떤 경우에, 만일 사용자 ID들이 매치되지 않는다 할지라도 부모 프로세스가 자식 프로세스에게 시그날을 보내는 것이 가능하고, 다른 경우에는 다른 제한들이 강요 될 것이다.  SIGCONT 시그날은 특별한 경우이다.  그것은 만일 시그날을 보내는쪽이 시그날을 받는쪽과 같은 세션에 있다면, 사용자 ID들에 상관없이 시그날을 보낼 수 있다.

21.6.4  통신을 위해서 kill을 사용하기

다음은 프로세스간 통신을 위해서 어떻게 시그날들을 사용할 수 있는지 보여주는 조금 긴 예제 프로그램이다.  SIGUSR1 과 SIGUSR2가 프로세스 간 통신을 지원하지 위하여 제공된 것이다.  그 시그날들은 기본적으로 치명적이기 때문에, 그 시그날들을 받을것으로 가정되는 프로세스는 signal 이나 sigaction을 통해서 그들을 트랩해야만 한다.

다음의 예제는, 부모 프로세스가 fork로 자식 프로세스를 생성한 다음 자식 프로세스가 초기화를 수행할때까지 기다린다.  자식 프로세스는 준비가 되었음을 알리기 위해서, kill 함수를 사용해서 SIGUSR1 시그날을 보낸다.

#include  <signal.h>

#include  <stdio.h>

#include  <sys/types.h>

#include  <unistd.h>

/*  SIGUSR1 시그날이 도착할 때, 이 변수를 설정하라 */

volatile  sig_atomic_t  usr_interrupt  =  0;

void

synch_signal  (int  sig)

{

usr_interrupt  =  1;

}

/* 자식 프로세스가 이 함수를 실행한다.  */

void

child_function  (void)

{

/*  초기화를 수행하라  */

printf ("I'm here!!!  My pid is %d.\n", (int) getpid ());

/* 초기화를 수행했음을 부모에게 알리자 */

kill  (getppid  (),  SIGUSR1);

/* 실행을 계속한다   */

puts  ("Bye,  now....");

exit  (0);

}

int

main  (void)

{

struct  sigaction  usr_action;

sigset_t  block_mask;

pid_t  child_id;

/* 시그날 핸들러를 만들어라.   */

sigfillset  (&block_mask);

usr_action.sa_handler  =  synch_signal;

usr_action.sa_mask  =  block_mask;

usr_action.sa_flags  =  0;

sigaction  (SIGUSR1,  &usr_action,  NULL);

/*  자식 프로세스를 만들어라 */

child_id  =  fork  ();

if  (child_id  ==  0)

child_function  ();         /* 리턴하지 말아라. */

/*  자식 프로세스가 시그날을 보낼때까지 기다리자.  */

while  (!usr_interrupt)

;

/*  이제 실행을 계속한다.  */

puts  ("That's  all,  folks!");

return  0;

}

위의 예제는 busy wait(적당한 말이 없어서..)을 사용하는데, 그것은 다른 프로그램에서 사용할 수 있도록 CPU 사이클을 기다려야하기 때문에 좋지 않다.  시그날이 도착할때까지 기다리도록 시스템에게 부탁하는 것이 더 좋다.  21.8절 [Waiting for a signal] 304 페이지에 있는 예제를 참조하라.

21.7  시그날 블록하기

시그날 블록하기는 운영체제에게 그 시그날을 붙잡아서 나중에 배달하도록 알리는 것을 의미한다.  일반적으로, 프로그램에서는 SIG_IGN을 사용해서, 시그날의 동작을 무시하는 것으로 설정할 말정, 불명확하게 시그날들을 블록하지 않는다.  하지만 시그날 블록킹(blocking)은 민감한 오퍼레이션들이 인터럽트 되는 것을 막기 위해서 시그날들을 블록하는데 사용된다.

□ 시그날들 때문에 핸들러에 의해 수정되었던 전역 변수들을 갱신 하는동안 시그날들을 블록하기 위해서 sigprocmask 함수를 사용 할 수 있다.

□특정한 핸들러가 실행되는 동안 어떤 시그날들을 블록하도록 sigaction 함수호출에서 sa_mask를 설정할 수 있다.  이 방법으로, 시그날 핸들러는 시그날들에 의해서 그 자체가 인터럽트 됨이 없이 실행될 수 있다.

21.7.1  왜 시그날 블록킹 ( Blocking ) 이 유용한가

sigprocmask을 사용해서 임시적으로 시그날 블록하기는 당신의 프로그램에서 임계부분(critical parts)이 실행되는 동안에 발생할지도 모를 인터럽트를 막기위한 방법으로 제공된다.   만일 시그날들이 프로그램의 그 부분(critical parts)에 도달한다면, 당신이 그들의 블록을 해제한 후에, 나중에 배달되어진다.  이것의 유용한 사용예는 프로그램의 나머지와 시그날 핸들러 사이에 데이터를 분배하는데 사용하는 것이다.  만일 데이터의 타입이 sig_atomic_t( 21.4.7절 [Atomic Data Access] 292 페이지 참조.)가 아니라면 시그날 핸들러는 프로그램의 나머지가 데이터의 읽기와 쓰기를 완전히 끝냈을 때 실행될 수 있다.  이것은 혼란된 결과를 초래할것이다.

신뢰가능한 프로그램을 만들기 위해서, 프로그램의 나머지가 데이터를 시험하거나 갱신하는 동안에 시그날 핸들러가 실행되는 것을 막을 수 있다_프로그램의 나머지가 실행되는 동안 발생할 여지가 있으며, 그 데이터를 건드릴 위험이 있는 적당한 시그날들을 블록함으로 해서. 만일 어떤 시그날이 도착하지 않았을 때, 당신이 어떤 동작을 수행하기를 바란다면 시그날 블록킹은 필요하다.  그 시그날을 위한 핸들러가 sig_atomic_t 타입의 플래그를 설정한다고 가정하다;  당신은 그 플래그를 시험하고 만일 그 플래그가 설정되지 않았다면 어떤 동작을 수행하도록 하고 싶어한다.  하지만 이것은 신뢰할 수 없다.  만일 그 시그날이 아직 중요한 동작은 수행하기 전이고, 플래그는 테스트 한 직후에 시그날이 배달된다고 가정하면, 그 프로그램은 시그날이 도착할지라도 그 동작을 수행 할 것이다.

어떤 시그날이 도착했는지의 여부를 확인하는 유일한 신뢰가능한 방법은 시그날이 블록되어 있을 동안 테스트하는 것이다.

21.7.2  시그날 설정

시그날을 블록킹하는 함수들 모두는 무슨 시그날들이 영향을 받게되는지를 정하는 데이터 구조체를 사용한다.  그리고, 두 개의 단계, 즉, 시그날을 만들기와 시그날을 라이브러리 함수에 인수로써 사용하기를 포함한다.

그들은 헤더파일 `signal.h'에 선언되어 있다.

데이터 타입 :   sigset__t

sigset_t 데이터 타입은 시그날 셋(set)을 표현하기 위해서 사용된다. 내부적으로, 정수나 구조체 형으로 이행될 것이다.  이식성을 위해서, sigset_t 오브젝트에서 정보를 초기화하고, 변경하고, 추출하는 역할을 하는, 이 절에서 설명된 함수들을 사용하라_그들을 직접적으로 다룰려고 시도하지 마라.

시그날 셋(set)을 초기화하기 위한 두가지 방법이 있다.  하나는 처음에 sigemptyset을 사용하여 비어있게 해놓은다음, 개별적으로 시그날을 하나씩 더한다.  아니면, sigfillset을 사용하여 완전히 채운다음, 개별적으로 정해진 시그날들을 하나씩 지운다.

당신이 어떤식으로든 그것을 사용하기 전에 그 두 개의 함수중 하나로써 시그날 셋(set)을 초기화해야만 한다.  모든 시그날들을 명식적으로 설정하려 시도하지 말아라, 왜냐하면, sigset_t 오브젝트는 초기화될 필요가 있는 어떤 다른 정보(버젼 필드와 같은)를 포함하고 있을것이기 때문이다.  (더하자면, 당신이 알고 있는 것외의 시그날은, 시스템이 발생시키지 않을 것이라는 가정을 당신의 프로그램안에서 하는 것은 현명하지못하다. )

함수 :   int sigemptyset  (sigset_t *set)

이 함수는 정의된 모든 시그날을 포함하도록 시그날 셋(set)을 set으로 초기화 한다.  이 함수는 항상 0을 리턴한다.

함수 :   int sigfillset  (sigset_t *set)

이 함수는 정의된 모든 시그날을 포함하도록 set으로 시그날 셋을 정한다.  리턴값은 0이다.

함수 :   int sigaddset  (sigset_t *set, int signum)

이 함수는 시그날셋에 시그날 signum을 더한다.  sigaddset이 하는 모든 것은 셋(set)을 갱신하는 것이다; 어느 시그날을 블록하지 않거나 또는 블록을 해제하지 않거나 한다. 리턴값은 성공하면 0이고 실패하면 -1이다. 다음의 errno는 이 함수를 위해 정의된 에러 상황이다.

EINVAL        signum 인수로 무효한 시그날을 지정하였다.

함수 :   int sigdelset  (sigset_t *set, int signum)

이 함수는 시그날 셋 set으로부터 시그날 signum을 제거한다.  sigdelset이 하는 모든 것은 셋(set)을 갱신하는 것이다;  그것은 시그날을 블록하지 않거나 블록을 해제 하지 않거나 한다. 리턴값과 에러상황은 sigaddset과 같다.

마지막으로,시그날 셋(set)안에 어떤 시그날들이 있는지를 테스트하기 위한 함수의 설명이다.

함수 :   int sigismember (const sigset_t *set, int signum)

sigismember 함수는 시그날 signum이 시그날 집합 set의 멤버인지의 여부를 테스트하는 함수이다.  만일 그 시그날이 집합안에 있으면 1을 리턴하고, 그렇지 않으면 0을 리턴하고, 에러가 발생하면 -1을 리턴한다. 다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EINVAL        signum 인수에 무효한 시그날이 지정되었다.

21.7.3  프로세스 시그날 마스크

현재 블록되어 있는 시그날들의 모음(collection)을 시그날 마스크라고 부른다.  각 프로세스는 자신 소유의 시그날 마스크를 갖고 있다.  당신이 새로운 프로세스를 만들 때(23.4절 [Creating a Process] 326 페이지 참조) 그것은 부모의 마스크를 상속받는다.  당신은 시그날 마스크를 갱신하여 유연성있게 시그날들을 블록하거나 해제할 수 있다.

sigprocmask 함수의 프로토타입은 `signal.h'에 있다.

함수 : int sigprocmask (int how, const sigset_t *set, sigset_t *oldset)

sigprocmask 함수는 호출된 프로세스의 시그날 마스크를 시험하거나 갱신하는데 사용된다.  how 인수는 시그날 마스크를 어떻게 변경할지 정하는 인수로써, 다음값들중에 하나를 사용해야만 한다.

SIG_BLOCK	set에 있는 시그날들을 블록하라_현존하는 마스크에 그들을 더하라.  즉, 새로운 마스크는 현존하는 마스크와 set의 합집합이다.

SIG_UNBLOCK	set에 있는 시그날들의 블록을 해제하라_현존하는 마스크에서 그들을 제거하라.

SIG_SETMASK	마스크로 set을 사용하라; 마스크의 전의 값은 무시하라.

마지막 인수 oldset은 예전 프로세스 시그날 마스크에 대한 정보를 리턴하는데 사용된다.  만일 당신이 예전 프로세스 시그날 마스크에 대한 정보를 살펴보지 않고 마스크를 변경하기 원한다면, oldset 인수에 널 포인터를 사용하면 된다.  유사하게, 만일 당신이 현존하는 마스크를 변경하지 않고, 단지 마스크안에 무엇이 있는지 알기를 원한다면, set 인수에 널 포인터를 사용하면 된다. ( 이 경우, how 인수는 아무런 의미가 없다. )  oldset 인수는 나중에 예전 프로세스 시그날 마스크를 반환하기 위해 그 시그날 마스크를 기억하는데 종종 사용된다. ( fork 와 exec의 호출로 시그날 마스크가 상속된 후,당신은 당신의 프로그램의 실행을 시작할 때 그 안에 무슨 내용이 있는지 예언할 수 없다.) 호출된 sigprocmask가 어느 미해결인 상태의 시그날의 블록을 해제하게 된다면, 그들 시그날들중 적어도 하나는 sigprocmask가 리턴하기 전에 프로세스에게 배달된다.  배달된 미해결 시그날의 순서는 정해지지 않았지만, 당신이 한꺼번에 여러개의 시그날들의 블록을 해제하도록 다중의 sigprocmask을 사용함으로써 명시적으로 순서를 제어할 수 있다. sigprocmask함수는 성공하면 0을 리턴하고, 실패하면 -1을 리턴 한다.  다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EINVAL        how 인수가 무효하다.

당신은 SIGKILL 과 SIGSTOP 시그날들을 블록할 수 없지만, 만일 시그날 셋이 그들을 포함한다면, sigprocmask는 에러 상황을 보고하는 대신에 그들을 단지 무시한다. 기억하라, SIGFPE와 같은 프로그램 에러 시그날들을 블록하는 것은 실제 프로그램 에러에의해 발생된 시그날로인해 바람직하지 못한 결과를 초래한다. (raise 나 kill에 의해 만들어진 시그날들은 제외하고) 이것은 시그날이 다시 블록이 해제되었을 때, 그 지점에서 실행을 계속하지 못할정도로 프로그램이 파괴되었기 때문이다. 21.2.1절 [Program Erroe Signals] 273페이지 참조.

21.7.4  시그날의 배달 여부를 테스트하기 위한 블럭킹

다음은 간단한 예제이다.  SIGALRM 시그날이 도착할때마다 플래그를 설정하는 핸들러를 만들고, 메인 프로그램에서는 시간마다 이 플래그를 체크하고 그것을 재 설정한다고 가정하자.  당신은 sigprocmask를 호출해서 코드의 임계부분을 보호함으로써 그동안 도착한 부가적인 SIGALRM 시그날을 막을 수 있다.

/* 이 변수는 SIGALRM 시그날 핸들러에 의해 설정된다.  */

int

main  (void)

{

sigset_t  block_alarm;

. . .

/*  시그날 마스크를 초기화한다. */

sigemptyset  (&block_alarm);

sigaddset  (&block_alarm,  SIGALRM);

while  (1)

{

/* 시그날이 도착했는지를 체크하라; 만일 도착했다면, 플래그를 재설정하라.  */

sigprocmask  (SIG_BLOCK,  &block_alarm,  NULL);

if  (flag)

{

actions-if-not-arrived

flag  =  0;

}

sigprocmask  (SIG_UNBLOCK,  &block_alarm,  NULL);

. . .

}

}

21.7.5  핸들러를 위하여 블록된 시그날

시그날 핸들러가 호출되었을 때, 당신은 보통 그 시그날 핸들러가 다른 시그날에 의해 블록됨이 없이 끝나기를 원한다.  그 핸들러가 시작된 순간부터 끝나는 순간까지, 당신은 핸들러의 데이터를 오염시키거나 혼란시킬지도 모르는 시그날을 블록해야만 한다.

한 시그날에 의해 핸들러 함수가 호출되었을 때, 핸들러가 실행되는 동안 그 시그날은 자동적으로 블록된다 ( 다른 시그날과 함께 그 시그날은 이미 프로세스의 시그날 마스크에 존재하게된다.)  만일 예를들어 당신이 SIGTSTP를 위한 핸들러를 준비했을때, 그 시그날이 도착하면 핸들러는 핸들러가 실행되는 동안 기다리도록 하여 나중에 SIGTSTP 시그날을 다시 발생시킨다.

그렇지만, 디폴트로, 다른 종류의 시그날들은 블록되지 않았다;  그들은 핸들러가 실행되는 동안 발생할 수도 있다. 핸들러가 실행되는 동안 다른 종류의 시그날을 블록하기위한 좋은 방법은 sigaction 구조체의 sa_mask 멤버를 사용하는 것이다. 다음은 그에 대한 예제이다.

#include  <signal.h>

#include  <stddef.h>

void  catch_stop  ();

void

install_handler  (void)

{

struct  sigaction  setup_action;

sigset_t  block_mask;

sigemptyset  (&block_mask);

/* 핸들러가 실행되는 동안 다른 터미날-발생 시그날들을 블록하라. */

sigaddset  (&block_mask,  SIGINT);

sigaddset  (&block_mask,  SIGQUIT);

setup_action.sa_handler  =  catch_stop;

setup_action.sa_mask  =  block_mask;

setup_action.sa_flags  =  0;

sigaction  (SIGTSTP,  &setup_action,  NULL);

}

핸들러 코드안에서 명시적으로 다른 시그날들을 블록하는것보다는 더 신뢰가능하다.  만일 당신이 핸들러 안에서 명시적으로 시그날들을 블록한다면, 아직 당신이 그들을 블록하지 않았을, 핸들러 시작 초기의 짧은 간격동안에 발생된 시그날로 인한 문제는 피할 수가 없다.

이 메카니즘을 사용하여 프로세스의 현재 마스크로부터 시그날들을 제거 할 수 없다.  그렇지만, 핸들러 함수에서 sigprocmask를 호출하여, 당신이 원하는 시그날을 블록하거나 해제하도록 만들 수 있다.  어쨌든, 핸들러 함수가 리턴할 때, 시스템은 핸들러 함수가 진입하기 전으로 마스크를 반환한다.

21.7.6  미해결 시그날 체크하기

당신은 sigpending 을호출하여 어느 시점에서 미해결 상태인 시그날들을 발견해낼 수 있다.  이 함수는 `signal.h'에 선언되어 있다.

함수 :  int sigpending (sigset_t *set)

sigpending 함수는 set에 미해결인 시그날에 대한 정보를 저장한다. 만일 미해결 상태의 시그날이 있다면, 그 시그날은 리턴된 set의 멤버이다.  (당신은 sigismember 을 사용해서 특정한 시그날이 이 set의 멤버인지 테스트 할 수 있다; 21.7.2절 [Signal Sets] 299 페이지 참조. ) 성공하면 리턴값은 0이고 실패하면 -1이다.

시그날이 미해결 상태인지를 테스트하는 것은 자주 유용하지는 않다. 시그날이 블록되지 않았을 때 테스트하는 것은 좋지않다.  다음의 예제를 살펴보자.

#include  <signal.h>

#include  <stddef.h>

sigset_t  base_mask,  waiting_mask;

sigemptyset  (&base_mask);

sigaddset  (&base_mask,  SIGINT);

sigaddset  (&base_mask,  SIGTSTP);

/* 다른 프로세싱을 하는 동안에 사용자 인터럽트를 블록하라. */

sigprocmask  (SIG_SETMASK,  &base_mask,  NULL);

. . .

/* 그 후에, 어느 시그날이 미해결인지를 체크하라.  */

sigpending  (&waiting_mask);

if  (sigismember  (&waiting_mask,  SIGINT))  {

/*  사용자가 프로세스를 죽이기기를 시도한다 */

}

else  if  (sigismember  (&waiting_mask,  SIGTSTP))  {

/*  사용자가 프로세스를 멈추기를 시도한다 */

}

당신의 프로세스를 위하여 미해결상태인 특정한 시그날이 있다면 그동안에 도착한 같은 종류의 부가적인 시그날들은 버려질것임을 기억하라. 예를들어, 만일 SIGINT 시그날이 미해결 상태일 때 다른 SIGINT 시그날이 도착하면, 당신의 프로그램은 이 시그날의 블록을 해제할 때 오직 한 개의 SIGINT 시그날로 처리할 것이다.

이식성 노트 : sigpending 함수는 POSIX.1에 새로이 추가되었다.  오래된 시스템들은 이와 동등한 함수가 없다.

21.7.7  나중에 동작하도록 시그날을 기억하기

라이브러리 함수를 사용해서 시그날을 블록하는 대신에, 당신은 당신이 블록을 "해제"할 때, 나중에 테스트 되도록 플래그를 설정하는 핸들러를 만들어서 거의 같은 결과를 얻을 수 있다.

다음의 예제를 살펴보라.

/* 만일 이 플래그가 0이 아니라면, 즉시 그 시그날을 처리하지 말아라.  */

volatile  sig_atomic_t  signal_pending;

/* 이것은 시그날이 도착했고 처리되지 않았으면 0이 아니다. */

volatilesig_atomic_t  defer_signal;

void

handler  (int  signum)

{

if  (defer_signal)

signal_pending  =  signum;

else

. . .  /*  "실제로" 그 시그날을 처리한다. */

}

. . .

void

update_mumble  (int  frob)

{

/*  시그날이 즉각적인 효력을 발휘하는 것을 막아라. */

defer_signal++;

/* 인터럽션에 대한 걱정없이, 이제 mumble을 갱신한다. */

mumble.a  =  1;

mumble.b  =  hack  ();

mumble.c  =  frob;

/* 우리는 mumble을 갱신하였다.  들어와있는 어느 시그날을 처리하라.  */

defer_signal--;

if  (defer_signal  ==  0  &&  signal_pending  !=  0)

raise  (signal_pending);

}

도착한 특정한 시그날이_미해결인_어떻게 signal에 저장되었는지에 주목하라.  그와같은 방법으로, 우리는 아직 해결할 형편이 되지 않은 시그날의 다양한 종류를 처리할 수 있다.

우리는 defer_signal을 증가시키고 감소시켜서 중첩된 임계 구역(critical sections)을 적당히 작업하게 한다; 그래서, 만일 signal_pending 과 함께 호출되었던 update_mumble 의 값이 이미 0이 아니라면, 시그날들은 update_mumble안에서는 연기되지 않고, 오직 caller 내부에서만 연기된다. 이것은 defer_signal 이 여전이 0이 아닐 때, 왜 signal_pending를 체크하지 않는지에 대한 이유가 된다.

defer_signal 의 증가와 감소는 한 개의 명령보다는 많은 명령이 요구된다; 그러므로 중간에 시그날이 발생하는 것이 가능하다.  그러나 이것은 아무런 문제도 야기시키지 않는다.  만일 증가나 감소를 시작하기전에 발생했던 시그날과 동등한 그 시그날이 증가나 감소전에 그 값을 보기위해서 충분히 많이 발생된것이라면, 이 경우 아무런 문제없이 작업한다.

signal_pending 을 테스트하기 전에 defer_signal을 증가시키는 것은 굉장히 중요하다, 왜냐하면 이것은 민감한 버그를 피하게 하기 때문이다. 만일 우리가 그와 같은 일들을 다른 순서로 한다면 이것은 다음과 같다.

if  (defer_signal == 1 && signal_pending != 0)

raise  (signal_pending);

defer_singal--;

위의 경우 if 구문과 감소사이에 도착된 시그날은 불명확한 시간동안은 잃어버리게 된다.   핸들러는 완전하게 defer_signal을 설정하였지만, 프로그램은 이미 이 변수를 테스트해버렸고, 다시는 변수를 테스트하지 않을 것이다.

그와 같은 버그들을 타이밍 에러라고 부른다.  그들은 희귀하게 발생하고 재생시키는데는 굉장히 중요하기 때문에 아주 나쁜 버그이다.  당신은 재생가능한 버그를 발견하는것처럼 디버거로 그들을 발견할거라고 예상하지 마라.  그렇기 때문에 그러한 버그를 피하기 위해서는 특별히 주의할 가치가 있다.

( 당신은 이러한 순서로 코드를 기록하고 싶은 유혹을 받지말아라, defer_signal 이 카운터(counter)로써 사용된다면 signal_pending 과 함께 테스트되어야만 한다.  후에, 0에 대한 테스트는 1에 대한 테스트보다는 깨끗하다.  그러나 만일 당신이 defer_signal을 카운터로써 사용하지 않고, 0과 1의 값만 그것에 주어진다면, 순서는 간단하게 보여질 것이다. 이것은 defer_signal을 카운터로써 사용하는것보다 더한 이득을 갖는다: 그것은 당신이 잘못된 순서로 코드를기록하거고 민감한 버그를 만들어낼 가능성을 감소시킬 것이다. )

21.8  시그날을 위한 기다림

당신의 프로그램이 외부 사건에 의해서 조종되거나, 동기화를 위해서 시그날을 사용한다면, 그때 그 프로그램은 시그날이 도착할때까지 기다릴 수 밖에 없다.

21.8.1  pause 사용하기

시그날이 도착할때까지 기다리기 위한 간단한 방법은 pause 를 호출하는 것이다.  당신이 그것을 사용하기 전에 다음절에 있는, 그것을 사용함으로 써 얻게되는 불리한점을 보아라.

함수 :   int pause ()

pause 함수는 핸들러 함수를 실행하거나, 또는 프로세스를 종료 시키는 시그날이 발생할때까지 프로그램의 실행을 잠시 멈추는 역할을 하는 함수이다.

만일 그 시그날이 핸들러 함수가 실행되도록하는 원인이 된다면, pause는 리턴한다.  이것은 비성공적인 리턴으로 간주한다. (왜냐면, "성공적"인 행동은 영원히 프로그램을 멈추도록 할것이기 때문이다.) 그렇기 때문에 -1을 리턴한다.  심지어 당신이 시스템 핸들러가 리턴할 때 다른 기본동작(primitives)을 재시작하도록 정한다고해도 ( 21.5절 [Interrupted Primitives], 293 페이지 참조.), 이것은 pause에 아무런 영향을 미치지 않는다; 그것은 시그날이 처리될 때 항상 실패한다.  다음의 errno는 이 함수를 위해 정의된 에러 상황이다.

EINTR           그 함수가 시그날의 배달로 인해서 인터럽트 되었다.

만일 그 시그날의 프로그램 종료의 원인이 된다면, pause는 리턴 하지 않는다(명백하게).  pause 함수는 `unistd.h'에 선언되어 있다.

21.8.2  pause 사용의 문제들

pause의 간단함은 프로그램을 이상하게 중단(hang) 시킬수도 있는 심각한 타이밍 에러들을 숨길수도 있다. 만일 당신의 프로그램에서, 실제 작업이 시그날 핸들러에 의해서 수행되고, "메일 프로그램"은 pause는 호출하지만 아무런 일을 하지 않을때는 안전하다. 시그날이 배달될때마다, 핸들러는 해야할 작업을 하고, 다음에 리턴한다, 그래서 프로그램의 메인 루프는 다시 pause를 호출 할 수 있다.

한 개 이상의 시그날이 도착하기를 기다렸다가 실제작업을 재개하기위해서 pause를 사용하는 것은 안전할 수 없다.  당신이 플래그를 설정하는 것으로 시그날 핸들러를 조정한다고 할지라도, 당신은 여전히 pause함수를 믿을 수 없다.  다음에 이문제에 대한 예제가 있다.

/*  usr_interrupt는 시그날 핸들러에 의해 설정된다. */

if  (!usr_interrupt)

pause  ();

/*  시그날이 도착했을 때 작업하라.  */

. . .

이것은 버그를 갖고 있다:  변수 usr_interrupt가 체크된 후,  하지만 pause가 호출되기전에 시그날이 도착할 수 있다.  만일 앞으로 아무런 시그날이 도착하지 않으면, 프로세스는 결코 다시는 재개될 수 없다. puase를 사용하는 대신에 루프안에서 sleep를 사용해서 오랜 기다림에 상위(upper) 제한을 가할수 있다. (sleep에 대한 상세한 정보는 17.4절 [Sleeping], 244 페이지를 참조하라. )  다음의 예제를 보자.

/*  usr_interrupt는 시그날 핸들러에 의해 설정된다.  */

while  (!usr_interrupt)

sleep  (1);

/* 시그날이 도착할 때 작업하라.  */

. . .

어떤 목적으로도, 이것은 사용하기에 충분하다.  조금 더 복잡하기는 하지만, sigsuspend를 사용해서도 특정한 핸들러가 실행되는 동안 확실하게 기다릴 수 있다.

21.8.3  sigsuspend 사용하기

시그날이 도착하기를 기다리는 깨끗하고 신뢰가능한 방법은 그것을 블록하고 sigsuspend를 사용하는 것이다.  루프안에서 사용된 sigsuspend는, 다른 종류의 시그날들이 그들의 핸들러에 의해 처리되는 동안, 어떤 종류의 시그날을 위해서 기다릴 수 있다.

함수 :   int sigsuspend  (const sigset_t *set)

이 함수는 set으로 프로세스의 시그날 마스크를 대체하고, 프로세스를 종료시키거나, 시그날 처리 함수를 호출하는 동작을 하는 시그날이 배달될때까지 프로세스를 중지시킨다.  즉, 프로그램은 set의 멤버가 아닌 시그날중의 하나가 도착할때까지 중지된다.

만일 어떤 프로세스가 핸들러 함수를 호출하는 시그날의 배달로 인해서 재개되면, 그 핸들러 함수는 리턴하고, sigsuspend 또한 리턴한다.  마스크는 sigsuspend가 기다리고 있는 동안만 set으로 유지된다. sigsuspend 함수는 리턴할 때 항상 전의 시그날 마스크를 반환한다.  리턴값과 에러상황은 pause와 같다.

sigsuspend를 사용해서, 앞절에서 나온 pause와 sleep를 완전하게 대체할 수 있다.

sigset_t  mask,  oldmask;

. . .

/*  일시적으로 블록할 시그날들의 마스크를 준비하라.  */

sigemptyset  (&mask);

sigaddset  (&mask,  SIGUSR1);

. . .

/* 시그날이 도착하기를 기다려라.  */

sigprocmask  (SIG_BLOCK,  &mask,  &oldmask);

while  (!usr_interrupt)

sigsuspend  (&oldmask);

sigprocmask  (SIG_UNBLOCK,  &mask,  NULL);

코드의 마지막 부분은 약간 교묘하다.  이것의 핵심은 sigsuspend가 리턴 할 때, 프로세스가 원래 가졌던 시그날 마스크의 값으로 재설정하는 것이다. 이 경우, SIGUSR1 시그날이 다시 블록되어진다.   sigprocmask의 두 번째 호출은 이 시그날의 블록을 명백하게 해제할 필요가 있다.

다른 포인트 : 오직 하나의 SIGUSR1 시그날을 기다리는 그 프로그램에서 왜 while 루프가 필요한지 의아해 할지 모른다.   그 대답은, sigsuspend에 주어지는 마스크가, 예를들어, 작업 제어 시그날처럼 다른 종류의 시그날이 배달됨으로 인해서 구동되어질 프로세스를 허가한다는 것이다.  만일  usr_interrput를 설정하지 않은 시그날에 의해 프로세스가 재개된다면, 그것은 단지 "올바른" 종류의 시그날이 발생할때까지 다시 중지된다.  이 테크닉은 준비작업에 더 낳은 라인이 필요하지만, 당신이 시그날에 대한 정확한 기다림을 위해서는 필요하다.  실제로 기다림을 위한 코드는 단지 4줄뿐이다.

21.9  BSD 시그날 핸들링

이 절은 BSD 유닉스에서 온 시그날 핸들링 함수들에 대해서 설명한다. 이들 도구들은 그들의 시대에서는 진보적이였지만; 오늘날은 굉장히 시대에 뒤떨어진것이고, 오직 BSD와의 호환성을 위해서 제공되고 있다.

21.9.1  POSIX 와 BSD 시그날 기능들

POSIX 시그날 처리 기능들은 BSD 기능들로부터 나온것이기 때문에 BSD와 POSIX 시그날 처리 기능들 사이에는 많은 유사성이 있다.  충돌을 피하기 위해서 모든 함수들이 서로 다른 이름을 갖고 있다는 것을 제외하고, 둘 사이에는 주요한 차이들이 있다.

□ BSD 유닉스는 sigset_t 오브젝트로 시그날 마스크를 나타내는 것이 아니라 int 비트 마스크로써 시그날 마스크를 표현한다.

□ BSD 기능들은 인터럽트된 기본동작(primitive)을 실패하게 할것인지 재개할것인지의 여부에 대해서 다른 디폴트를 사용한다.  POSIX 기능은 당신이 그들을 재개하도록 정할지라도 시스템 호출이 실패하게 만들고, BSD 기능들은, 당신이 그들을 실패하도록 정했을지라도 시스템 호출은 그것을 재개하도록 만드는 것이다.  21.5절 [Interrupted Primitives], 293 페이지 참조.

□ BSD 유닉스는 시그날 스택의 구상을 갖는다.  이것은 보통의 실행 스택대신에, 시그날 핸들러 함수들의 실행동안에 사용되는 대체스택 (alternate stack)이다.

BSD 기능들은 `signal.h'에 선언되어 있다.

21.10  핸들러 함수를 만들기 위한 BSD 함수

데이터타입 :   struct sigvec

이 데이터 타입은 struct sigaction과 동등한 것이다( 21.3.2절 [Advanced Signal Handling] 281 페이지 참조); 이것은 sigvec함수에서 시그날 동작을 지정하기 위해서 사용된다.  그것은 다음과 같은 멤버들을 포함하고 있다.

sighandler_t sv_handler

이것은 핸들러 함수이다.

int sv_mask

이것은 핸들러 함수가 호출되어있을 동안에 블록될 부가적인 시그날들의 마스크이다.

int sv_flags

이것은 시그날의 동작에 영향을 미치는 다양한 플래그를 정하는데 사용되는 비트마스크이다. 당신은 sv_onstack로서 이 필드를 참조할 수 있다.

그들 기호 상수들은 sigvec 구조체의 sv_flags 를 위해 제공되는 값들로 사용될 수 있다.  이 필드는 비트 마스크 값이로써, 당신이 관심을 갖는 플래그들을 비트별-OR를 사용해서 결합할 수 있다.

매크로 : int SV__ONSTACK

만일 이 비트가 구조체 sigvec의 sv_flags에서 설정되면, 그것은 시그날이 배달되었을 때 시그날 스택을 사용하는 것을 의미한다.

매크로 : int SV__INTERRUPT

만일 이 비트가 sigvec 구조체의 sv_flags안에서 설정되면, 이러한 종류의 시그날에 의해 인터럽트된 시스템 호출은 핸들러가 리턴해도 재시작되지 않을것임을 의미한다; 대신에 시스템 호출은 EINTR 에러 상황으로 리턴할 것이다.  21.5절 [Interrupted Primitives] 293 페이지 참조.

매크로 : int SV__RESETHAND

만일 이 비트가 sigvec 구조체의 sv_flags에서 설정되면, 시그날을 받았을 때, SIG_DFL로 원래의 시그날을 위한 동작으로 재설정하는 것을 의미한다.

함수 : int sigvec (int signum, const struct sigvec *action,struct sigvec *old_action)

이 함수는 sigaction (21.3.2절 [Acvanced Signal Handling] 281페이지 참조.)과 같다; 그것은 시그날 signum에 대한 동작을 action으로 하고, old_action에 그 시그날의 예전 동작에 대한 정보를 리턴한다.

함수 : int siginterrupt  (int signum, int failflag)

이 함수는 어떤 기본동작이 시그날 signum에 의해 인터럽트 되었을 때, 사용할 접근법을 지정한다.  만일 failflag가 false이면, 시그날 signum은 기본동작을 다시시작한다.  만일 failflag가 true이면, 처리되는 signum은 에러코드 EINTR로 그들 기본동작을 실패하게 한다.  21.5절 [Interrupted Primitives] 293 페이지 참조.

21.10.1  블록된 시그날을 위한 BSD 함수들

매크로 :   int sigmask  (int signum)

이 매크로는 시그날 signum을 위한 비트를 가진 시그날 마스크를 리턴한다.  당신은 여러개의 시그날을 정해주기 위해서 비트별-OR 연산자를 사용할 수 있다.  다음처럼.

(sigmask  (SIGTSTP)  |  sigmask  (SIGSTOP)

|  sigmask  (SIGTTIN)  |  sigmask  (SIGTTOU))

이것은 모든 작업-제어 시그날중 stop 시그날들을 포함하는 마스크를 지정한다.

함수 :   int sigblock  (int mask)

이 함수는 how 인수를 SIG_BLOCK로 가진, sigprocmask( 21.7.3절 [Process Signal Mask] 300 페이지 참조)과 같다: 그것은 호출된 프로세스의 블록된 시그날의 집합에, mask에 의해 정해진 시그날들을 더한다.  리턴값은 전의 블록된 시그날들의 집합이다.

함수 :   int sigsetmask  (int mask)

이 함수는 how 인수가 SIG_SETMASK 인, sigprocmask와 (21.7.3절 [Process Signal Mask] 300 페이지 참조.) 같다:  그것은 호출된 프로세스의 시그날 마스크를 set으로 정한다. 리턴값은 전의 블록된 시그날들이 집합이다.

함수 :   int sigpause  (int mask)

이 함수는 sigsuspend(21.8 [Waiting for a Signal] 304 페이지 참조.) 과 같다:  호출된 프로세스의 시그날 마스크를 mask로 설정하고, 시그날이 도착하기를 기다린다. 리턴할 때 전의 블록된 시그날의 집합은 다시 반환된다.

21.10.2  분리된 시그날 스택 사용하기

시그날 스택은 시그날 핸들러가 실행되는 동안 실행 스택으로써 사용되는 메모리의 특별한 영역이다.  오버플로우가 일어날 위험을 피하기 위해서는, 꽤 커야한다;  매크로 SIGSTKSZ는 시그날 스택을 위한 정규 크기로 정의 되었다.  당신은 mallocac 을 사용해서 스택을 위한 공간을 할당할 수 있다. 그리고나서 sigaltstack 이나 stgstack를 호출하여 시그날 스택을 사용하도록 시스템에게 알린다.

당신이 시그날 스택을 사용하기 위해서 시그날 핸들러를 달리 만들 필요는 없다.  다른것에서 스택으로의 변경은 자동적으로 발생한다.  그렇지만, 어떤 기계상에 존재하는 어떤 디버거는 시그날 스택을 사용하는 핸들러가 실행되는 동안 스택 트래이스(trace)를 하면 혼란스럽게 될지도 모른다.

분리된 시그날 스택을 사용하도록 시스템에게 알리기 위한 두 개의 인터페이스가 있다.  sigstack은 오래된 인터페이스로써 4.2 BSD 로부터 왔다.  sigaltstack은 새로운 인터페이스로써 4.4 BSD 로부터 왔다. sigaltstack 인터페이스는 스택의 성장 방향을 알리도록 당신의 프로그램에게 요구하지 않고, 정해진 기계와 운영체제에 의존한다는 편리점을 갖는다.

데이터 타입 :   struct sigaltstack

이 구조체는 시그날 스택을 설명한다.  그것은 다음의 멤버들을 포함하고 있다:

void *ss_sp

이것은 시그날 스택의 기준(base)을 가리킨다.

size_t ss_size

`ss_sp'가 가리키는 시그날 스택의 크기(바이트)이다.  당신이 스택을 위한 공간을 얼마나 많이 할것인지를 정한다.  다음 두 개의 매크로는 `signal.h'에 정의되어 있고, 당신은 이것은 계산된 크기로써 사용할 것이다.

SIGSTKSZ

이것은 시그날 스택을 위한 정규 크기이다.  이것은 보통 사용하기 위해서 규격화된 용량을 갖는다.

MINSIGSTKSZ

이것은 단지 운영체제가 시그날 배달을 수행하기 위해서 필요한 시그날 스택 공간의 양이다.  시그날 스택은 적어도 이것보다는 커야만한다.

대부분의 경우,  SIGSTKSZ를 사용한다. 하지만, 당신이 당신의 프로그램 시그날 핸들러가 얼마나 많은 스택 공간을 필요로하는지 안다면, 당신은 다른 크기를 사용하기를 원할 것이다.  이 경우, 우리는 시그날 스택을 MINSIGSTKSZ로 할당하고, ss_size를 증가시킨다.

int ss_flags

이 필드는 다음의 플래그나 그들의 조합을 포함한다.

SA_DISABLE

이것은 시그날 스택을 사용하지 않을것임을 시스템에게 알린다.

SA_ONSTACK

이것은 시스템에 의해 설정되고, 현재 사용중인 시그날 스택을 가리킨다.  만일 이 비트가 설정되지 않으면, 시그날은 보통 사용자 스택상에 배달될 것이다.

함수 : int sigaltstack  (const struct sigaltstack *stack, struct  sigaltstack *oldstack)

sigaltstack 함수는 시그날 핸들링중에 사용할 대체 스택을 정한다. 시그날이 프로세스에 의해 받아들여지고 그것이 시그날 스택을 사용하도록 지정할 때, 시스템은 시그날 핸들러가 실행될동안 사용되도록 현재 인스톨된 시그날 스택으로 변경한다. 만일 oldstack 이 널 포인터가 아니라면, 현재 인스톨된 시그날 스택에 대한 정보가 그곳으로 리턴된다.  만일 stack가 널 포인터가 아니라면, 이것은 시그날 핸들러에의해 사용되도록 새로운 스택으로 인스톨된다. 성공하면 리턴값은 0이고 실패하면 -1이다.  만일 sigaltstack이 실패하면, 다음값들중 하나로 errno를 설정한다.

EINVAL

당신은 실제로 사용중이였던 불가능한 스택으로 시도하였다.

ENOMEM

대체 스택의 크기가 너무 작다.  그것은 적어도 MINSIGSTKSZ 보다는 커야만 한다.

다음은 오래된 sigstack 인터페이스이다.  당신은 sigaltstack 대신에 사용할 수 있다.

데이터 타입 :   struct sigstack

이 구조체는 시그날 스택을 표현한다.  그것은 다음의 멤버들을 포함한다.

void *ss_sp

이것은 스택 포인터이다. 만일 당신의 기계에서 스택이 밑쪽으로 성장한다면, 이것은 당신이 할당한곳의 위를 가리킨다. 만일 그 스택이 위를 향해 성장한다면, 그것은 밑을 가리킨다.

int ss_onstack

이 필드는 만일 프로세스에서 현재 이 스택을 사용하고 있다면 참이다.

함수 : int sigstack  (const struct sigstack *stack, struct sigstack *oldstack)

sigstack 함수는 시그날 핸들링 중에 사용할 대체 스택을 정한다. 시그날이 프로세스에 의해 받아들여지고 그 동작이 시그날 스택을 사용하도록 정해진다면, 시스템은 시그날 핸들러가 실행되는 동안 사용할 현재 인스톨된 시그날 스택으로 변경한다. 만일 oldstack 이 널 포인터가 아니라면, 현재 인스톨된 시그날 스택에 대한 정보가 그것이 가리키는 곳으로 리턴된다.  만일 stack가 널 포인터가 아니라면, 이것은 시그날 핸들러에 의해 사용되도록 새로운 스택으로 인스톨된다. 성공하면 0을 리턴하고 실패하면 -1을 리턴한다.

22  프로세스의 시동과 종료

22장 프로세스의 시동과 종료

프로세스는 시스템 자원들의 할당을 위한 기본적인 동작 단위이다. 각 프로세스는 자신만의 주소 공간(address space)과 제어의 한 쓰레드를 갖는다.  한 프로세스는 한 프로그램을 실행한다;  당신이 같은 프로그램을 실행하는데 여러개의 프로세스를 가질 수 있지만, 각각의 프로세스는 자신의 주소공간에서 자신의 프로그램 복제본을 갖고 다른 프로그램 복사본과 독립적으로 실행된다.

** 역자주 : thread(쓰레드) : 프로세스 보다 더 작은 하나의 작업단위.

일반적으로 우리는 하나의 프로세스를 단위로 하여 독립적인 작업을 수행하는 것으로 알고 있지만, 컴퓨터 기술의 발전으로 프로세스라는 단위는 너무나 큰 단위가 되어버리고 이를 대신하여 쓰레드라는 단위가 사용되게 되었다.  쓰레드를 사용하는 운영체제에서는 작업의 스케쥴링을 수행할 때 쓰레드를 단위로 하여 스케쥴링을 한다.

이 장은 당신의 프로그램에서 프로세스를 시동하고, 그것을 종료시키고, 부모 프로세스로부터 정보(인수들과 환경)를 받기위해서는 무엇을 해야만 하는지를 설명하고 있다.

22.1  프로그램 인수들

시스템은 main 함수를 호출함으로써 C 프로그램을 시작한다.  그것은 당신이 프로그램을 만들 때 main 이라 이름지어진 함수를 꼭 만들어야 함을 의미하고_그것을 하지 않았을 때 에러없이 프로그램을 링크할 수 없을 것이다.

main 함수는 아무런 인수를 취하지 않거나, 커멘드 라인에서 받아들이는 인수를 나타내는 두 개의 인수를 취한다. 다음처럼.

int main (int argc, char *argv[])

커멘드 라인 인수들은 공백으로 분리된 토큰으로써 프로그램을 호출하도록 쉘 커맨드에 주어진다; 그래서, `cat foo bar'라고 했을 때 인수는 `foo' 와 `bar'가 된다.  어떤 프로그램에서 커맨드 라인에 인수를 넣는 방법은 오직 main함수의 인수를 통해서 가능하다.  만일 main 함수가 인수를 취하지 않는다면, 당신은 커맨드 라인에서 인수를 취할 수 없다.

argc 인수는 커맨드 라인 인수들의 개수를 말하고, argv 인수는 C 문자열의 벡터이다; argv의 각 요소들은 공백으로 분리된 개별적인 커맨드 라인 인수 문자열이다.  실행시키려는 프로그램의 파일이름 또한 첫 번째 요소로써 벡터안에 포함된다.  argc의 값은 이 요소들의 개수이다. 널 포인터가 항상 마지막 요소가 된다: 즉, argv[argc]는 널 포인터이다. 커맨드 `cat foo bar' 에서, argc는 3이고, argv는 "cat", "foo", 그리고 "bar" 라는 세 개의 요소들을 갖는다.

만일 당신의 프로그램에서 커맨드 라인 인수들의 구분이 간단하다면, 당신은 직접 argv로부터 간단하게 하나씩 인수를 가져올 수 있다. 당신의 프로그램이 한정된 인수들의 개수를 갖지않거나, 모든 인수들을 한꺼번에 취하지 않는다면 같은 방법으로 인수들을 취할 수 있지만, 그렇지 않다면 그것을 구문분석 하기 위해서 getopt를 사용해서 인수들을 취해야 할 것이다.

22.1.1  프로그램 인수 구문 관례들

POSIX에서 커맨드 라인 인수들을 위해서 다음과 같은 관례들을 권장하고 있다. getopt (22.1.2 [Parsing Options] 312페이지 참조)는 다음의 권장사항을 쉽게 충족시키도록 만든다.

□ 하이픈(hyphen (`_') ) 구획문자(delimiter)로 시작하는 인수들은 옵션이 있다.

□ 한 개의 토큰에서 한 개의 하이픈 구획문자 뒤에 따르는 것은, 만일 그 옵션들이 인수들을 취하지 않는다면 다중 옵션이 된다. 그래서, `-abc'는 `-a -b -c'와 동등하다.

□ 옵션 이름들은 단일한 알파벳 문자들이다( isalnum에 관하여; 4.1절[Classification of Characters] 41 페이지 참조)

□ 어떤 옵션들을 인수를 필요로 한다.  예를들어, ld 명령의 `-o' 옵션은 출력 파일 이름으로 하나의 인수를 필요로 한다.

□ 옵션과 옵션이 필요로 하는 인수들은 토큰으로 분리되어 나타날 수도 있고, 그렇지 않을 수도 있다.(즉, 그들을 분리하는 공백문자는 선택적이다.) 그래서, `-o foo' 와 `-ofoo'는 동등한다.

□ 옵션들은 전형적으로 다른 비-옵션 인수들에 선행된다.

GNU C 라이브러리에 있는 getopt의 구현은 당신의 프로그램을 사용하는 사용자가 심지어 옵션과 비-옵션 인수들을 혼합했을지라도, 구문분석을 위해서 모든 비-옵션 인수들전에 옵션 인수들이 나타나도록 만든다.

□ 인수 `--'는 모든 옵션들을 종결시킨다; 다음에 따르는 인수들은 그들이 하이픈으로 시작할지라도, 비-옵션 인수들로 취급된다.

□ 한 개의 하이픈 문자가 들어가있는 토큰은 보통 비-옵션 인수로써 취급된다.  관례상, 그것은 기본 입력과 출력 스트림으로 입력이나 출력을 정하기 위해서 사용된다.

□ 옵션들은 어떤 순서대로 공급되어지고, 또는 여러번 중복해서 나타나기도 할 것이다.  해석은 특정한 응용 프로그램이 맨 왼쪽에 있다고 해석한다.

GNU 는 그들의 관례에 따라서 긴 옵션을 더한다.  긴 옵션들은 `--'뒤에 따르는 이름으로 구성되는데 그 이름은 영숫자와 대쉬들로 구성된다. 옵션 이름들은 전형적으로 하나에서 세 개 단어의 길이를 갖고, 그들은 하이픈으로 단어들을 분리한다.  사용자들은 옵션을 대표하는 약자가 단일 하다면 얼마든지 옵션이름들을 축약할 수 있다.  한 개의 긴 옵션을 위한 인수를 정하기위해서는, `--name=value'라고 써라. 이 구문은 선택적인 인수를 받아들이는 긴 옵션을 가능하게한다. GNU 시스템은 쉘에서 긴 옵션 이름을 지원할 것이다.

22.1.2  프로그램 옵션들을 구문 분석하기

이번에는 getopt 함수를 어떻게 호출하는지에 대해서 자세히 설명한다. 이 기능을 사용하기 위해서, 당신의 프로그램에 헤더파일 `unistd.h'을 포함시켜야만 한다.

변수 :   int opterr

만일 이 변수의 값이 0이 아니면, getopt는 만일 알지못하는 옵션 문자가 들어오거나 인수를 필요로하는 옵션이 옵션을 갖고있지 않다면 표준 에러 스트림에 에러메세지를 프린트한다. 이것은 디폴트 동작이다.  만일 당신이 이 변수를 0으로 설정하면, getopt는 아무런 메시지도 프린트하지는 않지만, 에러를 지적하기 위해서 여전히 ? 문자를 여전히 리턴한다.

변수 :   int optopt

getopt가 알려지지 않은 옵션 문자와 만나거나 필요한 인수가 빠져있는 옵션일 때, getopt는 그 옵션 문자를 이 변수에 저장한다.  당신은 자신만의 진단 메시지를 제공하기 이해서 이 변수를 사용할 수 있다.

변수 :   int optind

이 변수는 앞으로 분석할 배열 argv의 다음 요소에 대한 인덱스로 getopt에 의해 설정된다.  일단 getopt가 옵션 인수들 모두를 발견했다면, 당신은 나머지 비-옵션 인수들이 어디에서 시작하는지 결정하기 위해서 이 변수를 사용할 수 있다.  이 변수의 초기값은 1이다.

변수 :   chat *optarg

이 변수는 인수들을 받아들이는 옵션들을 위해서, 옵션 인수들의 값을 가리키는 포인터로써 getopt에 의해 설정된다.

함수 :   int getopt(int argc, char **argv, const char *options)

getopt 함수는 argv와 argc 인수들에의해 지정된 인수 목록으로부터 다음 옵션 인수를 얻는다.  보통, 그들 값들은 main에 의해 받아 들여진 인수들로부터 직접적으로 온다.  옵션 인수는 이 프로그램에서 유용하게 쓰일 옵션 문자들을 지정한 문자열이다.  문자열안에 있는 옵션 문자는 인수가 필요함을 알리기 위해서 콜론(`:')의 다음에 나타난다.

만일 옵션 인수 문자열이 하이픈(`-')으로 시작한다면, 이것은 특별하게 취급된다.  그들은 옵션 문자가 `\0'인것처럼 리턴되어, 옵션이 없는 인수들을 허용한다.  getopt 함수는 다음 커맨드 라인옵션의 옵션 문자를 리턴한다.  더 이상 유용한 옵션 인수들이 없을 때, -1을 리턴한다.  그곳은 이제 비-옵션 인수들이 있을 것이다; 당신은 이것을 체크하기 위해서 argc 파라미터에 대응하는외부변수 optind를 비교해야만 한다.

만일 어떤 옵션이 인수를 가진다면, getopt 는 변수들 optarg안에 그것을 저장함으로써 인수를 리턴한다.  당신이 덧씌워질지도 모르는 정적 변수가 아닌, 보통의 argv 배열을 가리키는 포인터를 라면, optarg 문자열을 복사할 필요가 없다.

만일 알려지지 않은 옵션문자를 사용하거나, 옵션 인수가 빠져있다면 getopt는 `?'를 리턴하고 현재 옵션 문자로 외부변수 optopt를 설정한다.  만일 옵션의 첫 번째 문자가 콜론(`:') 이라면, getopt는 옵션 인수가 빠져있음을 알리기 위해서 `?' 대신에 `:'를 리턴한다. 또한, 만일 외부변수 opterr이 0이 아니면(디폴트이다.), getopt는 에러 메시지를 출력한다.

22.1.3  getopt를 사용해서 인수를 구문 분석하는 예제

다음은 getopt가 전형적으로 어떻게 사용되는지를 보여주는 예제이다. 핵심은 다음과 같다:

□ 보통, getopt는 루프안에서 호출된다.  getopt가 더 이상 나타난 옵션이 없음을 알리기 위해서 -1을 리턴하면, 루프는 종료한다.

□ switch 구문은 getopt로부터의 리턴값에 따른 각각의 처리에 사용된다. 각각의 경우는 프로그램에서 나중에 사용될 변수를 설정하게 된다.

□ 두 번째 루프는 남아있는 비-옵션 인수들을 처리하는데 사용된다.

#include  <unistd.h>

#include  <stdio.h>

int

main  (int  argc,  char  **argv)

{

int  aflag  =  0;

int  bflag  =  0;

char  *cvalue  =  NULL;

int  index;

int  c;

opterr  =  0;

while  ((c  =  getopt  (argc,  argv,  "abc:"))  !=  -1)

switch  (c)

{

case  'a':

aflag  =  1;

break;

case  'b':

bflag  =  1;

break;

case  'c':

cvalue  =  optarg;

break;

case  '?':

if  (isprint  (optopt))

fprintf(stderr, "Unknown option `-%c'.\n", optopt);

else

fprintf  (stderr,

"Unknown option character `\\x%x'.\n", optopt);

return  1;

default:

abort  ();

}

printf("aflag = %d, bflag = %d, cvalue = %s\n", aflag, bflag, cvalue);

for  (index  =  optind;  index  <  argc;  index++)

printf  ("Non-option  argument  %s\n",  argv[index]);

return  0;

}

다음은 위의 프로그램을 여러 가지 인수들의 조합을 사용했을 때 어떤 결과를 나타내는지에 대한 예이다.

%  testopt

aflag  =  0,  bflag  =  0,  cvalue  =  (null)

%  testopt  -a  -b

aflag  =  1,  bflag  =  1,  cvalue  =  (null)

%  testopt  -ab

aflag  =  1,  bflag  =  1,  cvalue  =  (null)

%  testopt  -c  foo

aflag  =  0,  bflag  =  0,  cvalue  =  foo

%  testopt  -cfoo

aflag  =  0,  bflag  =  0,  cvalue  =  foo

%  testopt  arg1

aflag  =  0,  bflag  =  0,  cvalue  =  (null)

Non-option  argument  arg1

%  testopt  -a  arg1

aflag  =  1,  bflag  =  0,  cvalue  =  (null)

Non-option  argument  arg1

%  testopt  -c  foo  arg1

aflag  =  0,  bflag  =  0,  cvalue  =  foo

Non-option  argument  arg1

%  testopt  -a  --  -b

aflag  =  1,  bflag  =  0,  cvalue  =  (null)

Non-option  argument  -b

%  testopt  -a  -

aflag  =  1,  bflag  =  0,  cvalue  =  (null)

Non-option  argument  -

22.1.4  긴 옵션들을 구문 분석하기

단일한-문자 옵션들로만 되어있는 GNU-스타일의 긴 옵션들을 받아들이기 위해서는 getopt 대신에 getopt_long를 사용한다. 당신은 이런 가외의 작은 작업을 통해서 모든 프로그램이 긴 옵션들을 받아들이게 만들 수 있고, 초보자들에게 어떻게 그 프로그램을 사용하는지 도울 수있다.

데이터 타입 :   struct option

이 구조체는 getopt_long을 위한 단일한 긴 옵션 이름을 설명한다. 긴 옵션을 구성하는 옵션의 하나인 인수 longopts는 그들 구조체의 배열이 되어야만 한다.  그 배열은 0으로 채워진 요소로써 끝난다. struct option 구조체는 다음과 같은 필드를 갖는다.

const char *name

이 필드는 옵션의 이름으로 문자열이다.

int has_arg

이 필드는 옵션이 인수를 취하는지 또는 취하지 않는지의 여부를 알린다.  그것은 정수이고 그곳에 세 개의 합리적인 값들이 있다: no_argument, required argument, 그리고 optional_argument.

int *flag

int val

이 필드들은 그 옵션이 발생할 때 옵션에 대하여 어떻게 보고를 하거나 동작할것인지를 제어한다. 만일 flag가 널 포인터라면, val은 이 옵션을 대표하는 값이다.  종종 그 값들은 특정한 긴 옵션들의 단일함을 확인하기 위해서 선택되어진다.  만일 플래그가 널 포인터가 아니라면, 그것은 이 옵션을 위한 플래그인 int형 변수의 주소가 되어질 것이다. val안의 값은 나타난 옵션을 지적하도록 플래그 안에 저장하기 위한 값이다.

함수 : int getopt__long (int argc, char **argv, const char *shortopts, struct option *longopts, int	*indexptr)

벡터 argv ( 길이는 argc이다.)로부터 옵션들을 해독한다.  인수 shortopts는 getopt가 해독할 수 있는 만큼의 짧은 옵션을 나타내고, 인수 longopts는 받아들이기 위한 긴 옵션들을 나타낸다. getopt_long이 짧은 옵션을 만났을 때, 그것은 getopt가 하는것과 같은 일을 한다: 그것은 옵션을 위한 문자 코드를 리턴하고, optarg에 옵션 인수들을(만일 그것이한 개를 가진다면) 저장한다.

getopt_long가 긴 옵션을 만났을 때, 그것은 그 옵션을 정의하는 flag 와 val 필드에 기초한 동작들을 취한다.

만일 flag가 널 포인터라면, getopt_long 은 발견한 옵션을 지적하도록 val의 내용을 리턴한다.  당신이 다른 의미를 가진 옵션을 위해서 val 필드안에 별개의 값들을 배열하면, 당신은 getopt_long이 리턴한 후에 그들 값을 해독할 수 있다.  만일 긴 옵션이 짧은 옵션과 동일하다면, 당신은 짭은 옵션의 문자 코드를 사용할 수 있다.

만일 flag가 널 포인터가 아니라면, 단지 프로그램안에서 flag가 설정된 이 옵션을 의미한다.  flag는 당신이 정의한 int형의 변수이다.  flag 필드안에 그 플래그의 주소를 저장하라.  flag에 저장하려는 옵션값을 val 필드안에 저장하라.  이 경우, getopt_long는0을 리턴한다.

어떤 긴 옵션에서, getopt_long는 배열 longopts안의 옵션 정의에 대한 인덱스를, *indexptr에 저장하여 알린다.  당신은 longopts[*indexptr].name 으로 옵션의 이름을 얻을 수 있다. 그래서 당신은 그들의 val 필드안에 있는 값이나, 그들의 인덱스에 의해서 긴 옵션들을 구분할 수 있다.  당신은 플래그를 설정한 긴 옵션들을 이 방법으로 구분할 수 있다.

어떤 긴 옵션이 인수를 가질 때, getopt_long는 리턴하기 전에 변수 optarg안에 인수값을 저장한다.  어떤 옵션이 아무런 인수를 가지지 않을 때, optarg안의 값은 널 포인터이다.  이것은 공급된인수가 옵션을 가졌는지 아닌지에 대한 여부를 당신에게 알리는 방법인 것이다.  getopt_long는 더 이상 처리할 옵션들이 없을 때, -1을 리턴하고, 다음 남아있는 인수의 argv 인덱스를 변수 optind 안에 남긴다.

22.1.5  긴 옵션들의 구분분석에 대한 예제

#include  <stdio.h>

/*  플래그는 `--verbose' 에 의해 설정한다. */

static  int  verbose_flag;

int

main  (argc,  argv)

int  argc;

char  **argv;

{

int  c;

while  (1)

{

static  struct  option  long_options[]  =

{

/*  그들 옵션들은 플래그를 설정한다.*/

{"verbose",  0,  &verbose_flag,  1},

{"brief",  0,  &verbose_flag,  0},

/* 그들 옵션들은 플래그를 설정하지 않는다. 우리는 그들의 인덱스에 의해 그들을 구분한다. */

{"add",  1,  0,  0},

{"append",  0,  0,  0},

{"delete",  1,  0,  0},

{"create",  0,  0,  0},

{"file",  1,  0,  0},

{0,  0,  0,  0}

};

/* getopt_long는 이곳에 옵션 인덱스를 저장한다. */

int  option_index  =  0;

c  =  getopt_long(argc,  argv,  "abc:d:", long_options,  &option_index);

/* 옵션들의 끝을 검출한다.  */

if  (c  ==  -1)

break;

switch  (c)

{

case  0:

/* 만일 이 옵션이 플래그를 설정하면, 지금 아무런 일도 하지 말아라.  */

if  (long_options[option_index].flag  !=  0)

break;

printf("option %s", long_options[option_index].name);

if (optarg)

printf("  with  arg  %s",  optarg);

printf("\n");

break;

case  'a':

puts("option  -a\n");

break;

case  'b':

puts("option  -b\n");

break;

case  'c':

printf("option  -c  with  value  `%s'\n",  optarg);

break;

case  'd':

printf("option  -d  with  value  `%s'\n",  optarg);

break;

case  '?':

/* getopt_long는 이미 에러 메시지를 프린트했다. */

break;

default:

abort  ();

}

}

/* 그들이 맞추쳤던 것으로 `--verbose'와 `--brief'를 보고하는 대신에, 우리는 그들로부터 나온 결과로 마지막 상황을 보고한다.  */

if  (verbose_flag)

puts  ("verbose  flag  is  set");

/* 남아있는 코맨드 라인 인수들을( 옵션이 없는) 출력하라. */

if  (optind  <  argc)

{

printf  ("non-option  ARGV-elements:  ");

while  (optind  <  argc)

printf  ("%s  ",  argv[optind++]);

putchar  ('\n');

}

exit  (0);

}

22.2  환경 변수들

프로그램이 실행될 때, 두가지 방법으로, 실행될 문맥(context)에 대한 정보를 받는다.  첫 번째 방법은 argv와 argc 인수를 메인함수에서 사용하는 것으로써 이것에 대한 것은 22.1절 [Program Arguments] 311 페이지에서 설명하였다.  두 번째 방법은 환경변수를 사용하는것인데 지금부터 설명할것이다.   argv를 사용하는 방법은 실행될 특정한 프로그램에 지정된 커맨드 라인 인수를 주기위해서 전형적으로 사용된다.  다른 방법으로, 환경은, 드물게 변경되고 덜 빈번하게 억세스되는, 많은 프로그램에 의해 분배될 정보를 기억한다.

이 절에서 논의되고 있는 환경변수들은 당신이 쉘에게 커맨드를 보내고 사용될 것을 배정하는 환경변수와 같은 것이다.  쉘로부터 실행된 프로그램들은 쉘로부터 환경변수 모두를 상속받는다. 표준 환경 변수들은 사용자의 홈 디렉토리, 터미날 타입, 현재 지역 등 등에 대한 정보를 위해서 사용된다; 당신은 다른 목적으로 부가적인 환경변수를 정의할 수 있다.  모든 환경변수들의 가진 값들의 집합은 집합적으로 환경으로써 알려진다.

환경변수들의 이름은 대소문자를 구분하고 문자 `='를 포함해서는 안된다. 시스템-정의 환경변수들은 항상 대문자이다. 환경변수들의 값은 문자열로써 표현될 수 있는어떤것도 가능하다. 값에는 널 문자가 포함되서는 안된다, 왜냐하면 이것은 문자열의 끝으로 간주되기 때문이다.

22.2.1  환경 검색

환경변수의 값은 getenv 함수를 통해서 억세스될 수 있다.  이것은 헤더 파일 `stdlib.h'에 선언되어 있다.

함수 :   char *getenv (const char name)

이 함수는 환경변수 name의 값인 문자열을 리턴한다.  당신은 이 문자열을 갱신할 수 없다. 어떤 GNU 라이브러리를 사용하지 않는 비-유닉스 시스템들은 getenv 함수의 연속적인 호출에 의해서 덮어쓰여질지 모른다(다른 라이브러리 함수에 의하지 않는다.) 만일 환경변수 name이 정의되지 않는다면, 그 값은 널 포인터이다.

함수 :   int putenv (const char *string)

putenv 함수는 환경으로부터 정의를 더하거나 제한다.  만일 string가 `name=value'의 형식이라면, 그 정의는 환경에 더해진다.  그렇지 않다면, 그 string는 환경변수의 이름으로써 해석되고, 환경안에서 이 변수에 대한 정의가 제거된다.  GNU 라이브러리는 SVID와의 호환성을 위해서 이 함수를 제공하고 있고; 다른 시스템에서 이 함수유용하지않다.

당신은 환경에 더 많은변수들을 더하기 위해서 환경 오브젝트의 내재된 표현을 직접적으로 다룰 수 있다( 예로, 당신이 실행하려는것에 대해서 다른 프로그램로 통신하기 위해서; 23.5절 [Executing a File] 327 페이지 참조).

변수 :   chat **environ

환경은 문자열의 배열로써 표현된다.  각 문자열은 `name=value'의 형식을 갖는다.  환경안에 나타난 문자열의 순서는 중요하지 않지만, 같은 이름이 한 번이상 나타나서는 결코 안된다.  배열의 마지막 요소는 널 포인터이다.  이 변수는 헤더파일 `unistd.h'에 선언되어 있다. 만일 당신이 환경변수의 값을 얻기 원한다면 getenv를 사용하라.

22.2.2  표준 환경 변수들

그들 환경변수들은 표준 의미를 갖는다.  이것은 그들이 항상 환경안에서만 나타남을 의미하지는 않는다; 그러나 그 변수들이 어디에서든 나타난다고 해도 그들은 그들만의 의미를 갖기 때문에 당신이 다른 목적으로 환경변수 이름을 사용하려고 시도해서는 안된다.

HOME	이것은 사용자의 홈 디렉토리나, 초기 디폴트 작업 디렉토리를 나타내는 문자열이다.  사용자는 어떤 값으로 HOME를 설정할 수 있다.  만일 당신이 어떤 특정한 사용자를 위해서 적당한 홈 디렉토리를 부여하기를 원한다면, HOME을 사용할 수 없고; 대신에, 사용자 데이터 베이스에서 사용자의 이름을 찾아보라 ( 25.12절 [User Database] 360 페이지 참조).  대부분의 목적에서는, HOME을 사용하는 것이 좋다, 왜냐하면 정확하게 사용자가 그 값을 정하도록 허용하기 때문이다.

LOGNAME

이것은 로그 인 하기위해서 사용되는 사용자 이름이다. 환경에서 이 값은 제멋대로 조정될 수 있기 때문에, 이것은 프로세스를 실행시키고 있는 사용자를 구분하기위한 신뢰 가능한 방법이 아니다; getlogin과 같은 함수(25.11 [Who Logged In] 360 페이지 참조)는 그러한 목적을 위해서는 오히려 더 낫다.  대부분의 목적을 위해서는 LOGNAME이 사용자가 그 값을 지정하도록 하기 때문에 사용하기 더 좋다.

PATH	경로는 파일을 찾기 위해서 사용되는 디렉토리 이름들의 열(sequence) 이다.  변수 PATH는 실행시키려는 프로그램을 찾기위해서 사용되는 경로는 저장하고 있다.  execlp와 execvp 함수들은 (23.5절 [Executing a File] 327 페이지 참조) 그들 함수들에 의하여 실행된 쉘과 다른 유틸리티에서, 이 환경변수를 사용한다. 경로의 구분은 콜론으로 분리된 디렉토리 이름들의 순차열(sequence) 이다.  디렉토리 이름 대신에 빈 문자열은 현재의 디렉토리를 나타낸다( 9.1절 [Working Directory] 123페이지 참조).

이 환경변수의 전형적인 값은 다음과 같은 문자열이 될 것이다.

:/bin:/etc:/usr/bin:/usr/new/X11:/usr/new:/usr/local/bin

TERM	이것은 프로그램 출력을 받아들이는 터미날의 종류를 지정한다.  어떤 프로그램들은 터미날의 특정한 종류에 의해 지원되는 특별한 이스케이프 시퀀스 또는 터미널모드들을 이용하기 위해 이 정보를 사용하게 만들 수 있다.  termcap 라이브러리( Termcap 라이브러리 매뉴얼에서 "Finding a Terminal Description" 절을 참고)를 사용하는 많은 프로그램들은 TERM 환경변수를 사용한다.

TZ	이것은 시간대(time zone)를 지정한다.  이 문자열의 형식과 어떻게 그것을 사용하는지에 대한 자세한 정보는 17.2.5절 [TZ Variable] 240 페이지 참조.

LANG	속성 범주를 사용하도록 디폴트 지역을 지정한다.

LC_ALL

범주를 위한 환경변수가 아니면 설정한다.  지역에 대한 자세한 정보를 19장 [Locales] 259 페이지 참조.

LC_COLLATE

이것은 문자열 정렬을 위해서 어떤 지역을 지정한다.

LC_CTYPE

이것은 문자 집합(sets)과 문자 분류를위해 사용하도록 어떤 지역을 지정한다.

LC_MONETARY

이것은 형식화된 통화량 값을 사용하기위한 어떤 지역을 지정한다.

LC_NUMERIC

이것은 형식화된 숫자들을 사용하기 위한 어떤 지역을 지정한다.

LC_TIME

이것은 형식화된 날짜/시간 값들을 위해서 어떤 지역을 지정한다.

_POSIX_OPTION_ORDER

만일 이 환경변수가 정의되었다면, 그것은 getopt에 의한 커맨드 라인 인수의 재정리를 금지한다.  22.1.1절 [Argument Syntax] 311 페이지 참조)

22.3  프로그램 종료

프로그램을 종료시키는 평상적인 방법은 메인 함수가 리턴하도록 하는 것이다.  메인 함수로부터 리턴되는 분기(exit) 상황값은 프로세스의 부모 프로세스나 쉘에게 정보를 보고하기 위해서 사용된다. 프로그램은 exit 함수를 호출함으로써 또한 종료할 수 있다.  또한, 프로그램은 시그날들에 의해서 종료될 수 있다; 이것은 21장 [Signal Handling] 271 페이지에 자세하게 설명되어 있다.  abort함수는 프로그램을 죽이는(kill) 시그날을 발생시킨다.

22.3.1  보통의 종료

프로세스는 프로그램이 exit를 호출할 때 보통 종료된다.  main 함수로부터의 리턴은 exit 를 호출하는것과 같고, main이 리턴한값은 exit에 인수로써 사용된다.

함수 :   void exit (int status)

exit 함수는 상황 status로 프로세스를 종료한다.  이 함수는 리턴하지 않는다.

보통의 종료는 다음과 같은 동작들의 원인이 된다:

1. atexit 나 on_exit 함수들에 등록된 함수들은 등록의 역순으로 호출되어진다.  이 메카니즘은 프로그램이 종료할 때 수행되어지는 자신만의 "cleanup" 동작들을 지정하도록 당신의 응용프로그램에게 허용한다. 전형적으로, 이것은 파일에 프로그램 상황 정보를 저장하거나, 공유 데이터베이스에서 락들을 해제하는것과 같은 일들을 하는데 사용된다.

2. 버퍼된 출력 데이터가 모두 기록되어, 개방된 모든 스트림이 폐쇄되었다.  7.4절 [Closing Streams] 65 페이지 참조.  또한, tmpfile 함수에 의해 개방되었던 임시 파일들은 제거된다; 9.10절 [Temporary Files] 141 페이지 참조.

3. 프로그램을 종료시키도록 _exit가 호출되었다. 22.3.5절 [Termination Internals] 322페이지 참조.

22.3.2  Exit 상황들

프로그램이 종료할 때, 그것은 분기 상황(exit status)을 사용해서, 종료의 원인에 대한 작은 양의 정보를 부모 프로세스에게 리턴할 수 있다.  이것은 현존하는  프로세스가 exit에게 인수로써 주는 0에서 255사이의 값이다. 보통 당신은 성공이나 실패에 대한 매우 광범위한 정보를 보고하기 위해서 exit 상황을 사용할 것이다.  당신은 실패의 이유에 대해서 매우 자세하게는 정보를 줄 수는없고, 대부분의 부모 프로세스도 더 이상세밀한 정보를 원하지 않을 것이다.

어떤 프로그램이 리턴하는 값은 어떤 부류의 상황값에 속한다.  대부분의 관습은 성공하면 0이고 실패하면 1이다.  그러나 비교를 수행하는 프로그램은 다른 관습을 사용한다: 매치되지 않았음을 지적하기 위해서 1을 사용하고 비교 불가능임을 지적하기 위해서 2를 사용한다. 당신의 프로그램이 그것을 위한 종료관습을 이해하려면 종료관습을 따라야한다.

일반적인 괌습은 상황값으로 128개를 예약하고 특별한 목적을 위해서 확장한다.  특별하게, 128개의 값은 서브프로세스에서 다른 프로그램에게 실패를 지적하기 위해서 사용된다.  이 관습은 보편적으로 제공되지는 않지만, 당신의 프로그램이 그것을 따르도록 하는 것이 좋다.

주의 : 분기상황으로써 에러들의 개수를 사용하려고 시도하지 말아라.

이것은 실제로 매우 유용하지 않다; 부모 프로세스는 얼마나 많은 에러들이 발생했는지에 주의를 기울이지 않는다.  그것보다 더 심각한 경우로는, 상황값이 8비트에서 짤리기 때문에 만일 프로그램에서 256개의 에러를 보고 한다면, 부모 프로세스는 0개의 에러가 난 것으로 보고를 받을것이고_결국 성공한 것으로 되어버린다.  같은 이유로, 255개를 초과할 수 있기 때문에 중료의 상황으로써 에러의 개수를 사용하여 작업하지 말아라.

이식성 노트: 어떤 비-POSIX 시스템들은 종료 상황 값들을 위해서 다른 관습을 사용한다.  좀더 좋은 이식성을 위해서는, 성공과 실패를 위한 상황값으로 매크로 EXIT_SUCCESS 와 EXIT_FAILURE 를 사용할 수 있다. 그들은 페더파일 `stdlib.h'에 선언되어 있다.

매크로 :   int EXIT__SUCCESS

이 매크로는 성공적인 프로그램 수행을 알리기위해서 exit 함수와 함께 사용될 수 있다.  POSIX 시스템에서, 이 매크로의 값은 0이고, 다른 시스템에서 이 값은 어떤 다른 정수 표현( 아마도 비-상수 )이 될 것이다.

매크로 :   int EXIT__FAILURE

이 매크로는 비성공적인 프로그램 수행을 알리기위해서 exit 함수와 함께 사용될 수 있다. POSIX 시스템에서, 이 매크로의 값은 1이다.  다른 시스템들에서 그 값은 어떤 다른 정수 표현( 아마도 비-상수 )이 될 것이다.  다른 0이 아닌 상황값들은 앞으로의 일을 알린다.  어떤 프로그램들은 특정한 종류의 "비-성공"을 알리기 위해서 다른 0이 아닌 상황 값들을 사용한다.  예를들어, diff는 파일들이 서로 다름을 의미할 때 상황값 1을 사용하고, 파일을 개방하기가 어렵다면 2 또는 그 이상의 값을 사용한다.

22.3.3  종료시의 상황정리

당신의 프로그램에서 보통의 종료가 발생하면 그 자신만의 정리(cleanup) 함수를 실행하여 정돈할 수 있다.  만일 당신이 여러개의 응용 프로그램에서 한 개의 라이브러리를 사용하고 있을때, 모든 응용프로그램이 종료되기전에 라이브러리의 정리(cleanup) 함수들을 명시적으로 호출한다면 신뢰성이 없다.  atexit 또는 on_exit를 사용해서 라이브러리 그 자체에서 정리 함수를 설정함으로써, 응용 프로그램에게는 보이지 않는 정리(cleanup)함수를 만드는 것이 더 신뢰성있는 프로그램이 된다.

함수 :   int atexit (void (*function) (void))

atexit 함수는 프로그램이 정상적으로 종료하면 호출되도록 함수 function을 등록한다.  그 function은 아무런 인수가 없이 호출된다. atexit로부터의 리턴값은 성공하면 0이고 만일 그 함수가 등록될 수 없으면 0이 아닌값을 리턴한다.

함수 :   int on__exit (void (*function)(int status, void *arg), void *arg)

이 함수는 atexit 보다는 좀더 다양함을 구사할 수 있는 것이다. 이 함수는 두 개의 인수로써 함수 function과 포인터 arg를 받아 들인다.  정상적이 프로그램 종료시, 그 function은 두 개의 인수들, 즉 exit에 주었던 상황값과 arg와 함께 호출된다.  이 함수는 SunOS와의 호환성을 위해 GNU C 라이브러리에 포함되었으며, 다른 시스템에서는 지원되지 않을 것이다. 다음은 exit 와 atexit 의 사용을 설명하는 프로그램 예제이다.

#include  <stdio.h>

#include  <stdlib.h>

void

bye  (void)

{

puts  ("Goodbye,  cruel  world....");

}

int

main (void)

{

atexit  (bye);

exit  (EXIT_SUCCESS);

}

이 프로그램이 실행됐을 때, 단지 메시지를 프린트하고 종료한다.

22.3.4  프로그램 중지시키기

당신은 abort 함수를 사용하여 당신의 프로그램을 중지시킬 수 있다. 이 함수를 위한 프로토타입은 `stdlib.h'에 있다.

함수 :   void abort (void)

abort 함수는 비정상적으로 프로그램을 종료시킨다.  이것은 atexit 또는 on_exit로 등록된 정리(cleanup)함수들을 실행하지 않는다. 이 함수는 SIGABRT 시그날을 발생시킴으로써 프로세스를 종료 시키고, 당신의 프로그램은 이 시그날을 가로채서 처리하는 핸들러를 포함할 수 있다.

앞으로의 변화에 대한 경고 : Proposed Federal consorship regulations 은 호출된 이 함수의  가능성에 대한 정보를 당신에게 주는 것을 우리에게 금하고 있다.  우리는 이것을 프로그램을 종료시키는데 사용하는 방법으로는 용인하지 않는다고 말할 것을 요청받고 있다.

22.3.5  내부적 종료

_exit 함수는 exit에 의해서 프로세스를 종료하기 위해서 사용되는 기본 동작이다.  이것은 `unistd.h'에 선언되어 있다.

함수 :   void __exit (int status)

_exit 함수는 상황 status로 프로세스가 종료되도록 하는 기본 동작(primitives)이다.  이 함수를 호출하는 것은 atexit 또는 on_exit에 등록된 정리(cleanup) 함수들이 실행되지 않는다.

명백히 종료를 호출하거나, 또는 시그날의 결과에 의한 종료이거나_어떤 이유에 의해서 프로세스가 종료될 때, 다음과 같은 일이 발생한다.

□ 프로세스에서 모든 개방된 파일 기술자들은 폐쇠된다.  8장 [Low-Level I/O] 103 페이지 참조.

□ 종료 상황 코드의 하위 8비트는 wait 나 waitpid를거쳐서 부모 프로세스에게 보고되도록 저장된다;  23.6절 [Process Completion] 329 페이지 참조.

□ 종료된 프로세스의 어느 자식 프로세스들은 새로운 부모 프로세스가 할당된다. (이것은 프로세스 ID 1로서, 처음의 프로세스이다.)

□ SIGCHLD 시그날은 부로 프로세스에게 보내어진다.

□ 만일 그 프로세스가 제어중인 터미날을 가진 세션 리더(session leader)라면, SIGHUP 시그날은 전면 작업에 있는 각각의 프로세스에게 보내어 지고, 제어중인 터미날은 세션으로부터 분열된다. 24장 [Job Control] 335 페이지 참조.

□ 어떤 프로세스의 종료가 프로세스 그룹을 고아가 되도록 하고, 프로세스 그룹의 어떤 멤버를 멈추도록 하는 원인이 된다면, SIGHUP 시그날과 SIGCONT 시그날이 그룹에 있는 각각의 프로세스에게 보내어진다. 24장 [Job Control] 335 페이지 참조.

사랑하는 것은

사랑 받느니 보다 행복 하나니라.

오늘도 나는 너에게 편지를 쓰나니

그리운 이여, 그러면 안녕!

설령 이것이 이 세상 마지막 인사가 될지라도

사랑 하였으므로 나는진정 행복 하였네라.

유치환의 □행복□ 에서

23  프로세스

23장 프로세스

프로세스들은 시스템의 자원들의 할당을 위한 기본적인 단위이다. 각 프로세스는 자신만의 주소공간과 (보통) 한 개의 제어 쓰레드를 갖는다. 프로세스는 프로그램을 실행한다;  당신은 같은 프로그램을 실행하는데 여러개의 프로세스를 가질 수 있지만, 각각의 프로세스는 자신의 주소공간에서 자신의 프로그램 복제본을 갖고 다른 프로그램 복사본과 독립적으로 실행된다.

프로세스들은 계층적으로 구성된다.  각 프로세스는 그것을 만들었던 부모 프로세스를 갖는다.  주어진 부모 프로세스에 의해 만들어진 프로세스는 자식 프로세스라고 불린다.  자식 프로세스는 부모 프로세스로부터 그 속성의 대부분을 상속받는다.

이 장은 프로그램에서 어떤 자식 프로세스를 만들고, 종료하고, 제어하는지에 대해서 설명하고 있다.  실제로, 새로운 자식 프로세스를 만들로, 새로운 프로세스가 프로그램을 실행시키고, 원래의 프로그램과 자식 프로세스가 조화롭게 수행되도록 하는데에는 세가지의 독림적인 명령이 있다.

system 함수는 다른 프로그램을 실행시키기 위한 간단하고, 이식성 있는 메카니즘을 제공한다.  그들은 자동적으로 세 개의 단계를 거친다.  만일 당신이 이러한 일들을 하는데 세심하게 제어할 필요가 있다면, 당신은 각 단계를 개별적으로 수행하는 기본 함수들을 사용할 수 있다.

23.1  명령 실행시키기

다른 프로그램을 실행하기 위한 가장 쉬운 방법은 system 함수를 사용하는 것이다.  이 함수는 실행되는 서브프로그램의 모든 작업을 수행하지만, 당신이 세밀하게 제어를 할 수는없다:  당신이 어떤 것을 할 수 있기 전에 그 서브 프로그램이 종료될때까지 기다려야만 한다.

함수 :   int system (const char *command)

이 함수는 쉘 커맨드로써 command를 실행한다.  GNU C 라이브러리에서, 그것은 항상 커맨드를 실행하도록 디폴트 쉘 sh를 사용한다.  특별하게, 실행하기 위한 프로그램을 찾기위해서 PATH에 있는 디렉토리들을 탐색한다.  리턴값은 만일 쉘 프로세스를 만들기가 불가능하면 -1을 리턴하고 그렇지않다면 쉘 프로세스의 상황이 된다.  이 상황 코드가 어떻게 해석되어지는지에 대해서는 23.6절 [Process Completion] 329 페이지 참조. system 함수는 헤더파일 `stdlib.h'에 선언되어 있다.

이식성 노트 : 어떤 C에서는 다른 프로그램을 실행할 수 있는 커맨드 프로세스의 개념을 가지지 않을수도 있다.  당신은 커맨드 프로세스가 존재하는지에 대한 여부를 system(NULL)을 실행해봄으로써 알아낼 수 있다; 만일 그 리턴값이 0이 아니면, 커맨드 프로세서는 유용하다.

popen 과 pclose 함수들은(10.2절 [Pipe to a Subprocess] 146 페이지 참조) system 함수와 밀접하게 연관되어 있다.  그들은 실행되고 있는 커맨드의 표준 입/출력 채널과 통신하도록 부모 프로세스에게 허용한다.

23.2  프로세스 만들기에 대한 원칙

이 절은 프로세스와 프로세스를 만드는데 포함되는 단계와 그것이 다른 프로그램을 실행하도록 만드는데 대한 개요가 있다.  각 프로세스는 프로세스 ID 번호에 의해 이름지어진다.  어떤 단일한 프로세스 ID는 그것이 만들어질 때 각 프로세스에게 할당되어진다.  프로세스의 수명이 다하면 부모 프로세스에게 보고된다;  그때, 그 프로세스 ID에 포함된 프로세스의 모든 자원들은 해제된다.

프로세스들은 fork 시스템 호출로 만들어진다( 그래서 새로운 프로세스를 만드는 동작을 forking a process라고 부르기도 한다.).  fork에 의해 만들어진 자식 프로세스는 오직 자신의 프로세스 ID를 제외하고는, 원래 부모 프로세스의 완전한 복제이다.

자식 프로세스를 만든 후에, 부모와 자식 프로세스 모두는 정상적으로 실행을 계속한다.  만일 당신이 당신의 프로그램에서 실행을 계속하기 전에 자식 프로세스가 실행을 끝내도록 기다리기 원한다면, fork 연산을 수행한 후에 wait 나 waitpid(23.6절 [Process Completion] 329 페이지 참조)를 호출함으로써 명백하게 이일을 하도록 해야만 한다.  그들 함수들은 자식프로세스가 왜 종료되었는지에 대한 제한된 정보_예를 들면, exit 상황코드_를 준다.

새롭게 만들어진 자식 프로세스는 fork가 return을 호출한 지점에서 부모 프로세스와 같은 프로그램을 실행한다.  당신은 그 프로그램이 부모 프로세스 또는 자식 프로세스에서 실행되고 있는지에 대해서 알기 위해서 fork로부터의 리턴값을 사용할 수 있다.

여러개의 프로세스가 같은 프로그램을 실행하기는 때때로 유용하다. 그러나 자식 프로세스는 exec 함수들중의 하나를 사용해서 다른 프로그램을 실행할 수 있다;  23.5절 [Executing a File] 327 페이지 참조.  프로세스가 실행시키고 있는 프로그램을 프로세스 이미지(process image)라고 부른다. 새로운 프로그램의 실행을 시작하는 것은 프로세스가 그 전의 프로세스 이미지에 대한 모든것을 잊게한다;  새로운 프로그램이 종료될 때, 그 전의 프로세스 이미지를 리턴하지 않고, 프로그램처럼 종료한다.

23.3  프로세스 식별

pid_t 데이터 타입은 프로세스 ID들을 나타낸다.  당신은 getpid를 호출함으로써 프로세스의 ID를 얻을 수 있다.  getppid 함수는 현재 프로세스의 (이것은 또한 부모 프로세스의 ID로써 알려져 있다.) 부모 프로세스 ID를 리턴한다.  당신의 프로그램에서 그들 함수를 사용하기 위해서는 `unistd.h'와 `sys/types.h'의 헤더파일을 포함해야한다.

데이터 타입 :   pid__t

pid_t 데이터 타입은 프로세스 ID를 표현하는 부호화 정수 타입이다. GNU 라이브러리에서, 이것은 int 이다.

함수 :   pid_t getpid (void)

getpid 함수는 현재 프로세스의 프로세스 ID를 리턴한다.

함수 :   pid_t getppid (void)

getppid 함수는 현재 프로세스의 부모 프로세스 ID를 리턴한다.

23.4  프로세스 만들기

fork 함수는 프로세스를 만드는 기본동작(primitive)이다.  그것은 헤더파일 `unistd.h'에 선언되어 있다.

함수 :   pid_t fork (void)

fork 함수는 새로운 프로세스를 만든다. 만일 그 명령이 성공하면, 그곳에는 부모와 자식 프로세스가 존재하고 그 둘은 fork의 리턴값을 서로 다른 값으로 보게된다.  자식프로세스안에서는 0의 값을 리턴하고 부모 프로세스 안에서는 자식 프로세스의 ID를 리턴한다.  만일 프로세스 만들기가 실패하면, fork는 부모 프로세스에게 -1의 값을 리턴한다.  다음의 errno는 fork를 위해 정의된 에러 상황이다.

EAGAIN	다른 프로세스를 만들만한 충분한 시스템 자원이 없거나, 사용자가 이미 너무 많은 프로세스들을 실행시키고 있다.

ENOMEM	프로세스는 시스템이 공급할 수 있는것보다 더 많은 공간을 필요로 한다.

다음은 부모 프로세스와는 다른, 자식 프로세스의 정해진 속성이다.

□ 자식 프로세스는 자신만의 단일한 프로세스 ID를 갖는다.

□ 자식 프로세스의 부모 프로세스 ID는 그 자신의 부모 프로세스의 ID이다.

□ 자식 프로세스는 부모 프로세스가 개방한 파일 기술자의 자신 소유의 복사본을 가진다.  부모 프로세스안에서 연속적으로 속성이 변화하는 파일 기술자들은 자식 프로세스의 파일 기술자에게 영향을 미치지 않고, 그리고 자식 프로세스에 속한 파일기술자의 속성이 변한다고 해도 그것또한 부모 프로세스의 기술자에게 영향을 미치지 못한다. 8.7절 [Control Operations] 113 페이지 참조.

□ 자식 프로세스를 위하여 경과된 프로세서 시간은 0으로 설정된다; 17.1절 [Processor Time] 233 페이지 참조.

□ 자식 프로세스는 부모 프로세스에 의해 설정된 파일 락(lock)들을 상속받지 않는다.  8.7절 [Control Operations] 113 페이지 참조.

□ 자식 프로세스는 부모 프로세스에 의해 설정된 알람(alarm)을 상속 받지 않는다.  17.3절 [Setting an Alarm] 243 페이지 참조.

□ 자식 프로세를 위해서 미처리중인 시그날의 설정(21.1.3절 [Delivery of Signal] 272 페이지 참조)은 소거된다.  (자식 프로세스는 부모 프로세스로부터 블록된 시그날의 마스크와 시그날 동작을 상속받는다.)

함수 :  pid_t vfork (void)

vfork 함수는 fork와 유사하지만 더 효율적이다; 하지만, 그것을 안전하게 사용하기 위해서 따랴야만 하는 제한이 있다. fork는 호출된 프로세스의 주소공산의 완전한 복제본을 만들고 부모와 자식 프로세스가 독립적으로 실행되도록 허용하지만, vfork는 이 복제본을 만들지 않는다.  대신에, vfork로 만들어진 자식 프로세스는 exec 함수들중의 하나가 호출될때까지 부모의 주소공간을 분배받는다.  그것은 부모 프로세스가 일시적으로 실행을 멈추게 됨을 의미한다. 당신은 vfork로 만들어진 자식 프로세스가 부모로부터 분배받은 전역 데이터나 심지어 지역 변수들의 갱신을 허용하지 않도록 매우 주의해야만 한다.  게다가, 자식 프로세스는 호출된 vfork 함수로부터 리턴할 수 없다(또는 vfork 함수의 밖으로 long jump하는것).  이것은 부모 프로세스의 제어 정보를 매우 혼란스럽게 만든다.  만일 의심이 된다면, 대신에 fork를 사용하라. 어떤 운영체제는 실제로 vfork를 지원하지 않는다.  GNU C 라이브러리는 모든 시스템에서 vfork를 사용하도록 허용하지만, vfork가 유용하지 않다면 실제로는 fork를 실행한다.  만일 당신이 vfork를 사용하기 위해서 적당하게 예방조치를 취한다면, 당신의 프로그램은 시스템에서 대신에 fork를 사용해서라도 여전히 작업할 것이다.

23.5  파일 실행시키기

이절은 프로세스 이미지로써 파일을 실행시키는 exec부류의 함수들을 설명한다. 당신은 자식 프로세스가 만들어진 후에 자식 프로세스가 새로운 프로그램을 실행하도록 그들 함수들을 사용할 수 있다. 이 부류의 함수들은 같은 일을 하지만, 인수를 정하는 방법은 차이가 있다. 그들은 헤더파일 `unistd.h'에 선언되어 있다.

함수 :   int execv (const char *filename, char *const argv[])

execv 함수는 새로운 프로세스 이미지로써 파일이름 filename을 실행한다.  널-종료 문자열의 배열인 argv 인수는 실행시키려는 프로그램의 메인 함수에 argv 인수로써 제공하기 위한 값으로 사용된다.  이 배열의 마지막 요소는 반드시 널 포인터가 되어야만 한다.  관례대로, 이 배열의 첫 번째 요소는 디렉토리 이름이 없는 프로그램의 파일 이름이다.  어떻게 프로그램이 그들 인수들을 억세스하는지에 대한 자세한 것은 22.1절 [Program Arguments] 311 페이지 참조. 새로운 프로세스 이미지를 위한 환경은 현재 프로세스 이미지의 environ변수로부터 획득된다; 환경에 대한 자세한 정보는 22.2절 [Environment Variables] 317 페이지 참조.

함수 :   int execl (const char *filename, const char *arg(), . . .)

이것은 execv와 유사하지만, argv 문자열은 배열이 아니라 개별적으로 정해져있다.  널 포인터가 마지막 인수로 주어져야만 한다.

함수 :	int execve (const chat *filename, chat *const argv[], chat *const env[])

이것은 execv와 유사하지만, env 인수를 통해 새로운 프로그램을 위한 환경을 명시적으로 지정하도록 허용한다.  이것은 environ변수와 같은 형식으로써, 문자열의 배열이다;  22.2.1 [Environment Access] 318 페이지 참조.

함수 :	int execle (const char *filename, const char *arg(), char *const env[], . . .)

이것은 execl과 유사하지만, 명시적으로 새로운 프로그램을 위한 환경을 정하도록 허용한다.  환경 인수는 마지막 argv 인수로써 표시된 널 포인터의 뒤에 따르고, environ 변수와 같은 형식으로 문자열의 배열이 된다.

함수 :  int execvp (const char *filename, char *const argv[])

execvp 함수는 filename에 슬래쉬가 포함되어 있지 않으면 filename으로부터 완전한 파일 이름을 찾기위해서, PATH 환경변수에 있는 디렉토리를 탐색한다는 점을 제외하고는, execv 와 유사하다 (22.2.2 [Standard Environment] 318 페이지 참조.). 이 함수는 사용자가 선택한 곳에서 그들을 찾기 때문에, 시스템유틸리티 프로그램들을 실행하기에는 유용하다.  쉘들은 사용자가 타이핑한 커맨드를 실행하기 위해서 그것을 사용한다.

함수 :   int execlp (const char *filename, const char *arg(), . . .)

이 함수는 execvp 함수처럼 filename과 같은 이름을 탐색한다는 점을 제외하고는, execl 함수와 같다. 서로 받아들인 인수 리스트와 환경 리스트의 크기는 ARG_MAX 바이트 보다 크지 않아야만한다. 27.1절 [General Limits] 369 페이지 참조.  GNU시스템에서 크기(ARG_MAX와 비교하여)는 각 문자열에서, 문자열안에 있는 문자들의 개수에 char *의 크기를 더하고, char *의 크기의 배수로 반올림된 하나를 더한다.  다른 시스템들은 크기를 셈하기 위한 다른 규칙을 갖는다.

그들 함수들은 일반적으로 리턴하지 않는다.  실패가 발생하면 -1의 값을 리턴한다.  보통의 파일 이름 구문 에러(6.2.3절 [File Name Errors] 60 페이지 참조.)들에 더해서, 다음의 errno는 이 그들 함수들을 위해서 정의된 에러상황이다.

E2BIG	새로운 프로그램의 인수 리스트와 환경 리스트의 결합된 크기가 ARG_MAX 바이트보다 크다.  GNU 시스템은 인수 리스트의 크기에 아무런 제한을 두지 않그 때문에 이 에러가 발생하지는 않지만, 그 인수가 유용한 메모리의 크기보다 크다면 ENOMEN의 에러는 발생할 것이다.

ENOEXEC

정해진 파일이 올바른 형식이 아니기 때문에 실행될 수 없다.

ENOMEM

정해진 파일을 실행시키는데는 현재 유용한것보다 더 많은 공간이 필요하다.

만일 새로운 파일의 실행이 성공하면, 그것은 마치 그 파일을 읽은 것 처럼 그 파일의 억세스 타임 필드(access time field)를 갱신한다.  9.8.9절[File Times] 140 페이지를 참조하여, 파일의 억세스 타임에 대한 자세한 정보를 얻어라. 그 파일이 폐쇠된 지점에서 다시 어떤것도 정해지지 않았다면, 그것은 프로세스가 종료되기 전에나 다른 프로세스 이미지가 실행되기 전인 어떤 지점이다.

새로운 프로세스 이미지를 실행하는 것은 새로운 위치로 인수와 환경 문자열을 복사하고, 메모리의 내용을 완전히 바꾸는 것이다.  그러나 프로세스의 많은 다른 속성들은 변경되지 않는다.

□ 프로세스 ID 와 부모 프로세스 ID. 23.2절 [Process Creation Concepts] 325 페이지 참조.

□ 세션과 프로세스 그룹 멤버쉽.  24.1절 [Concepts of Job Control] 335 페이지 참조.

□ 실 사용자 ID 와 그룹 ID, 그리고 부수적인 그룹 ID들.  25.2절 [Process Presona] 353 페이지 참조.

□ 미해결상태인 알람들.  17.3절 [Setting an Alarm] 243 페이지 참조.

□ 현재 작업중인 디렉토리와 루트 디렉토리.  9.1절 [Working Directory] 123 페이지 참조.

□ 파일 모드 생성 마스크.  9.8.7절 [Setting Permissions] 137 페이지 참조.

□ 프로세스 시그날 마스크; 21.7.3절 [Process Signal Mask] 300 페이지 참조.

□ 미해결상태인 시그날들;  21.7절 [Blocking Signals] 298 페이지 참조.

□ 프로세스와 연관된 경과된 프로세스 시간; 17.1절 [Processor Time] 253 페이지 참조.

만일 프로세스 이미지 파일의 set-user-ID 와 set-group-ID 모드 비트가 설정되면, 이것은 프로세스의 유효 사용자 ID와 유효 그룹 ID에게 영향을 미친다.  그러한 개념들은 25.2절 [Process Persona] 353 페이지에 상세하게 설명되어 있다.

현존하는 프로세스 이미지에서 무시되도록 설정된 시그날들은 새로운 프로세스 이미지에서도 또한 무시되도록 설정된다.  모든 다른 시그날들은 새로운 프로세스 이미지에서 디폴트 동작으로 설정된다.  시그날에 대한 더 많은 정보는, 21장 [Signal Handling] 271 페이지를 참조하라.

실행중인 프로세스 이미지에서 개방한 파일 기술자들은, 그들이 FD_CLOEXEC (close-on-exec) 플래그를 설정하지 않는다면, 새로운 프로세스 이미지에서도 개방된체로 남겨진다.  개방된 상태로 남겨진 파일들은 실행중인 프로세스 이미지로부터 파일 락들을 포함한, 개방 파일 기술의모든 속성을 상속받는다.  파일 기술자는 8장 [Low-Level I/O] 103 페이지에 상세하게 설명되어 있다.

파일과 비교하여 스트림은, exec 함수들을 통해서도 살아남지 못한다, 왜냐하면 그들은 프로세스 자체의 메모리안에 위치하고 있기 때문이다. 새로운 프로세스 이미지는 그들이 새롭게 만든 스트림을 제외하고는 아무런 스트림도 갖지않는다.  pre-exec프로세스 이미지안에 있는 스트림들의 각각은 그 내부에 기술자를 갖고있고, 그들 기술자는 exec을 통해서 살아남는다. (그들은 FD_CLOEXEC의 설정을 가지지 않고 제공된것.  새로운 프로세스 이미지는 fdopen을 사용해서 새로운 스트림에 그들을 재연결 할 수 있다. (8.4절 [Descriptors and Streams] 108 페이지 참조.)

23.6  프로세스 종료

이 절에서 설명하고 있는 함수들은 자식 프로세스가 종료하거나 멈추도록 기다리는데 사용되고, 그러한 상황인지의 여부를 알아보는데 사용된다. 그들 함수들은 헤더파일 `sys/wait.h'에 선언되어 있다.

함수 :   pid_t waitpid (pid_t pid, int *status_ptr, int options)

waitpid 함수는 프로세스 ID를 pid로 가진 자식 프로세스로부터 상황 정보를 요청하는데 사용된다.  일반적으로, 호출된 프로세스는 자식 프로세스가 종료됨으로써 유용한 상황정보를 만들때까지 잠시 중지되어 있다.

pid 인수를 위한 다른값들은 특별한 뜻을 갖는다.  -1의 값이나 WAIT_ANY는 어떤 자식 프로세스를 위한 상황정보를 요청한다; 0의 값이나 WAIT_MYPGRP는 호출된 프로세스와 같은 프로세스 그룹에 있는 어떤 자식 프로세스를 위한 정보를 요청한다;  그리고 다른 음수값 - pgid는 프로세스 그룹 ID로써 pgid를 가진 자식 프로세스를 위한 정보를 요청한다.

만일 자식 프로세스를 위한 상황정보가 즉시 유용한 상태라면, 이 함수는 기다림이 없이 즉시 리턴한다.  만일 한 개의 적합한 자식 프로세스보다 많은 프로세스가 유용한 상황 정보를 갖고있다면, 그둘중의 하나가 임의로 선택되고, 그 상황은 즉시 리턴된다. 다른 적합한 자식 프로세스로부터 상황을 얻기위해서, 당신은 waitpid를 다시 호출할 필요가 있다.

options 인수는 비트 마스크이다.  그 값은 0이나 WNOHANG와 WUNTRACED 플래그들이 비트별 OR로 조합되어질 것이다. 당신은 부모 프로세스가 기다리지 않을것임을 지적하기 위해서 WNOHANG 플래그를 사용할 수 있다;  그리고 WUNTRACED플래그는 종료된 프로세스 뿐만아니라 멈추어진 프로세스들로 부터 상황 정보를 요청하기 위해서 사용되어진다.

자식 프로세스로부터의 상황정보는 status_ptr이 널 포인터가 아니라면, status_ptr이 가리키고 있는  오브젝트 안에 저장된다.  리턴값은 보통 보고된 상황을 가진 자식 프로세스의 프로세스 ID 가 된다.  만일 WNOHANG 옵션이 지정됐고 어떤 자식 프로세스도 기다리고 있지 않다면, 그 값은 0이된다.  에러가 발생한 경우에 -1을 리턴한다.  다음의 errno는 이 함수를 위해서 정의된 에러상황이다.

EINTR	그 함수는 호출된 프로세스에게 배달된 시그날에 의해 인터럽트되어졌다.  21.5절 [Interrupted Primitives] 293 페이지 참조.

ECHILD	그곳에 기다리고 있는 자식 프로세스가 아무것도 없거나, 정해진 pid가 호출된 프로세스의 자식프로세스가 아니다.

EINVAL	올바르지 못한 값이 options 인수로써 공급되었다.

다음 기호 상수들은 waitpid 함수에게 pid 인수를 위한 값으로써 정의되었다.

WAIT_ANY

이 상수 매크로는 waitpid가 어느 자식 프로세스에 대한 상황정보를 리턴하도록  지정되었다.(값은 -1)

WAIT_MYPGRP

이 상수는(값은 0) 호출된 프로세스와 같은 프로세스 그룹 안에 있는 어떤 자식 프로세스에 대한 상황정보를 리턴 하도록 지정되었다.

다음의 기호 상수들은 waitpid 함수의 options 인수를위한 플래그로써 정의되었다.  당신은 그들은 비트별-OR 연산을 통해서 조합할 수 있다.

WNOHANG

이 플래그는 만일그곳에 준비된 자식 프로세스가 없다면, waitpid가 기다리지 않고 즉시 리턴하도록 지정되었다.

WUNTRACED

이 플래그는 종료된 것 뿐만아니라 멈추어진 자식 프로세스에 대한 상황을 보고하도록 지정되었다.

함수 :   pid_t wait (int *status_ptr)

이 함수는 waitpid의 간소화된 변형이고, 어떤 한 개의 자식 프로 세스가 종료될때까지 기다리는데 사용된다.  호출은:

wait  (&status)

는 정확히 다음과 동등하다:

waitpid  (-1,  &status,  0)

다음은 기다림이 없이, 종료된 모든 자식 프로세스에서 보내온 상황을 얻기 위해서는 waitpid를 어떻게 사용하는지에 대한 예제가 있다. 이 함수는 SIGCHLD 시그날을 위한 핸들러로써 만들어졌는데, 그 시그날은 적어도 한 개의 자식 프로세스가 종료되었음을 알리기 위해서 발생된다.

void

sigchld_handler  (int  signum)

{

int  pid;

int  status;

while  (1)

{

pid  =  waitpid  (WAIT_ANY,  &status,  WNOHANG);

if  (pid  <  0)

{

perror  ("waitpid");

break;

}

if  (pid  ==  0)

break;

notice_termination  (pid,  status);

}

}

23.7  프로세스 종료 상황들

만일 자식 프로세스의 종료 상황 값(22.3절 [Program Termination] 320페이지 참조.)이 0이면, waitpid 또는 wait에 의해 보고된 상황 값 또한 0이다.  당신은 다음의 매크로를 사용해서 리턴된 상황값안에 있는 암호화된 정보를 테스트할 수 있다.

매크로 :   int WIFEXITED (int status)

이 매크로는 만일 자식 프로세스가 exit 또는 _exit로써 정상적으로 종료되면 0이 아닌값을 지닌다.

매크로 :   int WEXITSTATUS (int status)

만일 status가 참인 상태의 WIFEXITED이면, 이 매크로는 자식프로세스로부터의 종료 상황 값의 하위 8비트를 리턴한다.  22.3.2절 [Exit Status] 320 페이지 참조.

매크로 :   int WIFSIGNALED (int status)

이 매크로는 만일 자식 프로세스가 처리되지않은 시그날을 받았기 때문에 종료되었다면 0이 아닌값을 리턴한다.  21장 [Signal Handling] 271 페이지 참조.

매크로 :   int WTERMSIG (int status)

만일 status가 참인 WIFSEGNALED이면, 이 매크로는 자식 프로세스를 종료시켰던 시그날의 시그날 번호를 리턴한다.

매크로 :   int WCOREDUMP (int status)

이 매크로는 자식 프로세스가 종료되고 코아덤프를 생성했다면, 0이 아닌값을 리턴한다.

매크로 :   int WIFSTOPPED (int status)

이 매크로는 만일 자식 프로세스가 멈추어져있다면 0이 아닌값을 리턴한다.

매크로 :   int WSTOPSIG (int status)

만일 status가 참인 WIFSTOPPED 이면, 이 매크로는 자식 프로세스를 멈추게한 원인이 된 시그날의 시그날 번호를 리턴한다.

23.8  BSD 프로세스 Wait 함수들

GNU 라이브러리는 BSD 유닉스와의 호환성을 위해서 그들과 연관된 기능들을 제공한다.  BSD는 int 와 다르게 상황값을 표현하는 union wait 데이터 타입을 사용한다.  두 개의 표현은 실제로 상호간에 변경가능하다; 그들은 동일한 비트 패턴을 표현하기 때문이다.  WEXITSTATUS 와 같은 매크로를 정의하고 있는 GNU C 라이브러리는 오브젝트의 둘중 한 종류를 선택해서 작업할것이고, wait 함수는 status_ptr 인수로써 포인터의 한종류를 받아들이도록 정의된다.  그들 함수들은 `sys/wait.h'에 선언되어 있다.

데이터 타입 : union wait

이 데이터 타입은 프로그램 종료 상황 값들을 표현하고, 다음과 같은 멤버들을 갖는다.

int w_termsig

이 멤버의 값은 매크로 WTERMSIG의 결과와 같다.

int w_coredump

이 맴버의 값은 매크로 WCOREDUMP의 결과와 같다.

int w_retcode

이 맴버의 값은 매크로 WEXITSTATUS의 결과와 같다.

int w_stopsig

이 맴버의 값은 매크로 WSTOPSIG의 결과와 같다.

직접적으로 이 맴버들을 억세스하는 대신에, 당신은 동등한 매크로 를 사용하도록 하라.

함수: pid_t wait3 (union wait *status_ptr, int options, struct rusage *usage)

usage가 널 포인터라면, wait3는 waitpid(-1, status`ptr, options)와 같다.  만일 usage가 널 포인터가 아니라면, wait3는 *rusage에 있는 자식 프로세스를 위한 사용 형태를 저장한다(그러나 오직 멈추어 있는 것이 아니라, 종료된 자식 프로세스라면.)17.5절 [Resource Usage] 245 페이지 참조.

함수 :  pid_t wait4 (pid_t pid, union wait *status_ptr, int options, struct  rusage *usage)

만일 usage가 널 포인터라면, wait4는 waitpid (pid, status`ptr, options)와 같다.  만일 usage가 널 포인터가 아니라면, wait4는 *rusage에 있는 자식 프로세스를 위하여 사용 형태들을 저장한다. (그러나 오직 자식 프로세스는 멈추어 있는 것이 아니라, 종료된 것이다.)  17.5절 [Resource Usage] 254 페이지 참조.

23.9  프로세스 만들기 예제

다음은 내장 시스템과 유사한 함수를 어떻게 만들것인지를 보여주는 예제 프로그램이다.  그것은 `sh -c command'와 동등한 것을 사용하여 command 인수를 실행한다.

#include  <stddef.h>

#include  <stdlib.h>

#include  <unistd.h>

#include  <sys/types.h>

#include  <sys/wait.h>

/* 쉘 프로그램을 사용해서 command 를 실행한다.  */

#define  SHELL  "/bin/sh"

int

my_system  (const  char  *command)

{

int  status;

pid_t  pid;

pid  =  fork  ();

if  (pid  ==  0)

{

/* 이것은 자식 프로세스로써 쉘 커맨드를 실행한다. */

execl  (SHELL,  SHELL,  "-c",  command,  NULL);

_exit  (EXIT_FAILURE);

}

else  if  (pid  <  0)

/* fork가 실패하였으므로 실패를 보고하라. */

status  =  -1;

else

/* 이것은 부모 프로세스로써, 자식이 수행을 완료할 때까지 기다린다.  */

if  (waitpid  (pid,  &status,  0)  !=  pid)

status  =  -1;

return  status;

}

당신이 이 예제에서 주목해야 할것이 두가지 있다.  프로그램에 공급된 첫 번째 argv 인수는 실행시키려는 프로그램의 이름을 표현한다는 것을 기억하라.  그것은 execl의 호출에서, SHELL에게 일단 실행하려는 프로그램의 이름이 주어지고 두 번째로 argv[0]를 위한 값을 공급하는 이유이다.

자식 프로세스에서 execl 호출은 만일 그것이 성공하면 리턴하지 않는다. 만일 그것이 실패하면, 당신은 자식 프로세스가 종료되도록 무엇인가를 해야만 한다.  단지 return을 통하여 나쁜 상황 코드만을 리턴하는 것은 원래의 프로그램을 실행시키던 두개의 프로세스를 그냥 남기게 된다. 대신에, 올바른 처리는 부모 프로세스에게 자식 프로세스가 실패를 보고하는 것이다.

그것을 수행하도록 -exit를 호출하라.  exit 대신에 _exit를 사용하는 이유는 stdout와 같은 완전히 버퍼화된 스트림들을 플러쉬 하는 것을 피하기 위함이다.  아마도 데이터를 담고 있는 그들 스트림의 버퍼들은 fork에 의해 부모 프로세스로부터 복사된 것이고, 데이터는 부모 프로세스에 의해 결국 출력될 것이다.  자식 프로세스에서 호출된 exit는 데이터를 두 번 출력할 것이다.  22.3.5절 [Termination Internals] 322 페이지 참조.

사랑한 이야기야

허구헌날 사무쳐도 내 못 말하고

사랑한 이야기야

글썽대며 목이 메도 내 못 말하고

죽을 때나 가만가만 뇌어볼 이름임을

소녀는 아직 어려 세상도 몰라

사랑한 이야기를 하랍니다.

꽃이지는 봄날에랴.

희어서 설은 꽃잎 잎새마다 보챈다고

김 남조의 □사랑한 이야기□ 에서

24  작업 제어

24장 작업 제어

작업제어는 단일한 로그인 세션에서 사용자가 여러개의 프로세스 그룹들(또는 작업들) 사이에서 제어를 옮기는 것을 허용하는 프로토콜을 적용한다. 작업제어 기능은 대부분의 프로그램에서 자동적으로 적당하게 수행되고, 작업제어에 대한 특별한 어떤 것을 할 필요가 없다.  그래서 당신이 쉘이나 로그인 프로그램을 만들지 않는다면 이 장에 있는 것들을 무시할 수 있다.

당신이 이 장에 나와있는 것들을 이해하기 위해서는 시그날 핸들링(21장 [Signal Handling] 271 페이지 참조)과 프로세스 생성(23.2절 [Process Creation Concepts] 325 페이지 참조) 과 연관된 개념에 친숙해질 필요가 있다.

24.1  작업제어의 개념

대화형(interactive) 쉘의 기본적인 목적은 사용자 터미날로부터 명령문을 읽고 그 명령문에의해 지정된 프로그램을 실행하도록 프로세스들을 만드는 것이다. 그것은 fork( 23.4절 [Creadting a Process] 326 페이지 참조.)와 exec ( 23.5절 [Executing a File] 327 페이지 참조.)함수를 사용해서 할 수 있다.

단일한 명령문은 단지 한 개의 프로세스를 실행시키지만_종종 한 개의 명령문도 여러개의 프로세스들을 사용한다.  만일 당신이 쉘 커맨드에서 `|'을 사용한다면 당신은 명시적으로 그들 프로세스안에 여러개의 프로그램들을 요청한것이다.  그러나 만일 당신이 한 개의 프로그램을 실행시킬지라도, 그것이 내부적으로는 여러개의 프로세스들을 사용할 수 있다. 예를들어, `cc -c foo.c'와 같은 단일한 컴파일 명령문은, 전형적으로 네 개의 프로세스들을 사용한다.  만일 당신이 make를 실행한다면, 그 작업은 분리된 프로세스 안에서 다른 프로그램을 실행하는 것이다.

단일한 명령문에 소속되어 있는 프로세스들은 프로세스 그룹이나 작업(job)이라고 불린다.  당신은 동시에 그들 모든 프로세스들을 작동시킬 수 있다.  예를들어, C-c를 타이핑하면 전면 프로세스 그룹에 있는 모든 프로세스가 종료되도록 시그날 SIGINT를 보낸다.  세션은 프로세스들의 좀 큰 그룹이라고 할 수 있다.  일반적으로 단일한 로그인에 있는 모든 프로세스들은 같은 세션에 속한다.

모든 프로세스는 프로세스 그룹에 속한다.  어떤 프로세스가 만들어질 때, 그것은 부모 프로세스와 같은 프로세스 그룹과 세션의 멤버가 된다.  당신은 setpid 함수를 사용해서 다른 프로세스 그룹으로 소속을 바꿀 수 있는데, 그때 그 프로세스 그룹은 같은 세션에 소속된 프로세스 그룹이다.

다른 세션에 프로세스를 소속시키는 유일한 방법은 setsid 함수를 사용해서, 새로운 세션이나, 또는 세션 리더의 처음 프로세스를 만드는 것이다. 새로운 프로세스 그룹에 세션리더를 소속시키면, 당신은 다시 프로세스 그룹의 외부로 그것을 다시 옮길 수 없다.

보통, 새로운 세션들은 시스템 로그인 프로그램에 의해 만들어지고 세션 리더는 사용자의 로그인 쉘을 실행시키는 프로세스이다.  작업제어를 지원하는 쉘은 어떤 시간에 어떤 작업이 터미날을 사용할 수 있도록 제어를 조정해야만 한다.  그렇지않다면 여러개의 작업들이 동시에 터미널로부터 입력을 받아들이려 시도할것이고, 어떤 프로세스가 사용자가 타입한 입력을 받을 것인지가 혼란하게 될 수도 있다.  이것을 방지하기 위해서, 쉘은 이 장에 설명된 프로토콜을 사용해서 터미날 드라이버와 협력해야만 한다.

쉘은 한 번에 오직 한 프로세스 그룹에게만 터미날을 제어하는 제한없는 권한을 줄 수 있다.  이처럼 터미날을 제어하고 있는 프로세스 그룹을 전면 작업(foreground job)이라고 부른다.  터미날을 억세스 하지 않고 실행중인, 쉘에 의해 처리되고 있는 다른 프로세스 그룹들은 배경작업(background job)이라고 불린다.

만일 배경 작업이 터미날을 제어하여 터미날로부터 읽거나 쓰거나 할 필요가 있다면, 그것은 터미날 드라이버에 의해 멈추어진다.  사용자는 SUSP문자( 12.4.9절 [Special Characters] 195 페이지 참조)를 입력함으로써 전면 작업을 멈출 수 있고 프로그램은 SIGSTOP시그날을 보냄으로써 어떤 작업을 멈출 수 있다.  작업들이 멈추었을 때 쉘은 그들에 대하여 사용자에게 알릴 책임이 있고, 멈추어진 작업에 대해서 계속 진행할 것인지에 대한 여부를 사용자에게 선택하도록 허용하기 위한 메카니즘의 제공과 전면 작업과 배경 작업사이의 작업전환도 쉘에게 책임이 있다.  제어중인 터미날에 입/출력 하는것에 대한 자세한 정보는 24.4절 [Access to the Terminal] 336 페이지참조.

24.2  작업제어는 선택적이다

모든 운영체제가 작업제어를 지원하는 것은 아니다. GNU 시스템은 작업제어를 지원하지만, 만일 다른 시스템에서 GNU 라이브러리를 사용한다고해도 그 시스템에서 작업제어를 지원하지 않을 수 도 있다.  당신은 시스템이 작업제어를 지원하는지에 대한 여부를 시험하기 위해서 컴파일할 때 _POSIX_JOB_CONTROL 매크로를 사용할 수 있다.  27.2절[System Options] 370 페이지 참조.

만일 작업제어가 지원되지 않으면, 한 세션만다 오직 한 개의 프로세스 그룹만 존재하고 그 프로세스 그룹은 항상 전면 작업으로써 행동한다. 부가적인 프로세스 그룹을 만드는 함수에는 에러코드 ENOSYS를 사용해서 간단히 실패했음을 알린다.

다양한 작업제어 시그날(21.2.5절 [Job Control Signals] 277 페이지 참조.)들을 대표하고 있는 매크로들은 작업제어가 지원되지 않는다고 할지라도 정의되어 있다.  그렇지만, 작업제어를 지원하지 않는 시스템에서 그 시그날들을 결코 발생되지 않고, 에러를 보고하거나 아무것도 하지않는 등의 시그날에 대한 행동을 정하거나 시험하거나 보내려고 시도하지 않는다.

24.3  프로세스의 터미날 제어하기

프로세스의 속성들중 하나는 그 프로세스가 제어중인 터미날이다.  fork를 사용해서 만들어진 자식 프로세스는 그들의 부모 프로세스로부터 제어중인 터미날을 상속받는다.  이와같은 방법으로, 한 세션안에 있는 모든 프로세스들은 세션 리더(session leader)로부터 제어중인 터미날을 상속받는다. 터미날의 제어권을 가진 세션리더를 터미날의제어중인 프로세스(controlling process)라고 불린다.

당신이 로그인할 때 시스템이 당신을 위해서 그 일을 하기 때문에, 당신은 세션에 터미날 제어권을 할당하는 정확한 메카니즘에 대해서는 걱정할 필요가 없다. 개별적인 프로세스가 새로운 세션의 리더가 되기위해서 setsid를 호출할 때, 그 프로세스는 제어중인 터미날로부터 단절된다.

24.4  제어중인 터미날 억세스

터미날을 제어하고 있는 전면작업안의 프로세스들은 터미날에 대한 제한 없는 억세스 권한을 가진다; 배경프로세스들은 권한을 가지지 않는다. 이 절에서는 배경작업에 있는 프로세스가 터미날을 제어하려 시도할 때 어떤 일이 발생하는지에 대해서 자세히 설명하고 있다.

배경작업에 있는 프로세스가 전면작업이 제어하고 있는 터미널로부터 읽으려 시도할 때, 프로세스 그룹은 SIGTTIN 시그날을 받는다.  이것은 그룹안에 있는 모든 프로세스들의 동작을 멈추게 하는 원인이 된다(그 시그날이 처리되지 않거나 그들 스스로 멈추게 하지 않는다면).  그렇지만 읽기를 시도한 프로세스가 이 시그날을 무시하거나 블록한다면, 대신에 EIO 에러를 얻게 될 것이다.

유사하게, 배경작업에 있는 프로세스가 전면작업에서 제어하고 있는 터미날에 출력을 시도한다면, 그것에 대응된 디폴트 동작은 프로세스 그룹에게 SIGTTOU 시그날을 보내는 것이다.  그렇지만, 그 동작은 지역 모드 플래그(local modes flags)(12.4.7절 [Local Modes] 192 페이지 참조.)의 TOSTOP 비트에 의해서 갱신된다. 만일 이 비트가 설정되지 않는다면(이것이 디폴트이다.),  전면 작업에 의해 제어중인 터미날에 배경작업이 출력을 시도하는 것에는 시그날을 보내지 않고 항상 허가된다.  또한 출력을 시도하는 프로세스에 의해 SIGTTOU 시그날이 무시되거나 블록된다면 이때도 출력은 허가된다.

프로그램에서 할 수 있는 터미날 명령들의 대부분은 입력이나 출력을 위한 것이다. 기본적인 입력과 출력 함수에 대한 정보는 8.2절 [I/O Primitives] 105 페이지 참조.

24.5  고아가된 프로세스 그룹들

터미날 제어중이던 프로세스가 종료되면, 그 터미날은 해제되고 새로운 세션에 의해서 다시 제어될 수 있다.(실제로, 다른 사용자가 터미날 상에서 로그인 할 수 있다.)  이것은 만일 예전의 세션에 있는 어떤 프로세스가 그 터미날을 계속 사용하려고 시도한다면 문제가 발생할 수 있다.

그러한 문제들을 막기위해서, 세션리더가 종료된 후에도 계속 실행되고 있는 프로세스 그룹들은 고아가된 프로세스 그룹으로써 표시된다.  고아 프로세스 그룹(orphand process group) 안에 소속된 프로세스들은 터미널에 읽거나 쓰기를 할 수 없다.  만일 그렇게 하려 시도한다면 EIO 에러를 얻게 될 것이다.

어떤 프로세스 그룹이 고아가 되었을 때, 그 프로세스들은 SIGHUP 시그날을 받는다.  보통, 이것은 프로세스를 종료시키는 원인이 된다. 그렇지만, 만일 프로그램이 이 시그날을 무시하거나 시그날을 위한 핸들러를 만들었다면(21장 [Signal Handling] 271 페이지 참조.), 터미날을 제어중이던 프로세스가 종료되었다고 하더라도 실행중이던 고아 프로세스들은 계속 그 작업을 수행할 수 있다; 그렇지만 더 이상은 터미날의 억세스를 시도할 수 없다.

24.6  작업제어 쉘 실행시키기

이 절은 쉘이 작업 제어를 수행하기 위해서는 무엇을 해야만 하는지, 그 개념이 포함된 간단한 예제 프로그램을 통해서 설명하고 있다.

□ 24.6.1절 [Data Structures] 337 페이지, 이곳에서는 그 예제를 소개하고, 주요한 데이터 구조체를 소개하고 있다.

□ 24.6.2절 [Initializing the Shell] 339 페이지, 쉘이 작업제어를 준비하기 위해서 수행해야만 하는 동작들에 대해서 논의하고 있다.

□ 24.6.3절 [Launching Jobs] 340 페이지, 명령문을 실행하기 위해서 어떻게 작업들을 만들것이지에 대한 정보가 포함되어 있다.

□ 24.6.4절 [Foreground and Background] 343 페이지, 배경작업에 대립하는것으로써 전면으로 작업을 진출시킬 때 쉘이 무엇을 다르게 해야 만하는지를 설명하고 있다.

□ 24.6.5절 [Stoppend and Terminated Jobs] 344 페이지, 쉘에게 작업상황을 보고하기에 대해서 설명한다.

□ 24.6.6 [Continuing Stopped Jobs] 347 페이지, 작업이 멈추어졌을 때 다시 계속하기 위해서 어떻게 해야하는지를 설명한다.

□ 24.6.7 [Missing Pieces] 348 페이지, 쉘의 다른 부분들에 대해서 설명하고 있다.

24.6.1  쉘을 위한 데이터 구조체들

이 장에 포함된 모든 예제 프로그램들은 간단한 쉘 프로그램의 일부분이다. 이 절은 이 예제에서 사용되고 있는 데이터 구조체와 유틸리티 함수들을 설명하고 있다.

간단한 쉘은 주로 두 개의 데이터 구조체를 다룬다.  job 타입(type)은 파이프(pipes)로 서로 연결된 서브프로세스들의 집합인 작업에 대한 정보를 포함한다.  process 타입(type)은 단일한 서브프로세스에 대한 정보를 저장하고 있다.  다음은 적절한 데이터 구조체 선언들이다.

/*  process는 한 개의 단일한 프로세스이다.  */

typedef  struct  process

{

struct  process  *next;	/* 파이프라인에 있는 다음 프로세스 */

char  **argv;                /* exec을 위한 */

pid_tpid;                     /* 프로세스 ID */

char  completed;             /* 프로세스가 수행되었으면 참이다. */

char  stopped;               /* 만일 프로세스가 멈추었으면 참이다.  */

int  status;                  /* 보고된 상황들의 값  */

}  process;

/*  job 은 프로세스들의 파이프라인이다.  */

typedef  struct  job

{

struct  job  *next;           /* 다음 활동할 작업 */

char  *command;            /* 메시지를 위해서 사용되는, 커맨드 라인 */

process  *first_process;      /* 작업안에 있는 프로세스의 리스트 */

pid_t  pgid;                 /*  프로세스 그룹 ID */

char  notified;               /* 사용자가 멈춘 작업에 대해서 말했으면 참이다.  */

struct  termios  tmodes;     /* 저장된 터미날 모드들 */

int  stdin,  stdout,  stderr;   /* 표준 입/출력 채널들 */

}  job;

/*  활성화된 작업들은 리스트안에 연결되어 있고 이것이 그 헤드(head)이다.*/

job  *first_job  =  NULL;

다음은 작업 오브젝트들은 운영하기 위해서 사용되는 유틸리티 함수들에 대한 것이다.

/* pgid를 통해서 활성 작업을 찾아라. */

job  *

find_job  (pid_t  pgid)

{

job  *j;

for  (j  =  first_job;  j;  j  =  j->next)

if  (j->pgid  ==  pgid)

return  j;

return  NULL;

}

/* 만일 job 안에 있는 모든 프로세스들이 완료되었거나 멈추어있다면 참을 리턴한다 */

int

job_is_stopped  (job  *j)

{

process  *p;

for  (p  =  j->first_process;  p;  p  =  p->next)

if  (!p->completed  &&  !p->stopped)

return  0;

return  1;

}

/* 만일 job 안에 있는 모든 프로세스들이 완료되었다면 참을 리턴 */

int

job_is_completed  (job  *j)

{

process  *p;

for  (p  =  j->first_process;  p;  p  =  p->next)

if  (!p->completed)

return  0;

return  1;

}

24.6.2  쉘 초기화하기

일반적으로 작업제어를 수행하는 쉘 프로그램이 시작될 때, 그 쉘이 이미 자신의 작업제어를 수행하고 있는 다른 쉘로부터 호출되었을 경우에는 주의를 해야만 한다. 서브쉘(subshell)에서 작업제어가 가능하게 되기전에 우선 부모 쉘에 의해서 전면에 놓여져야만 한다.  그것은 getpgrp로 초기 프로세스 그룹 ID를 얻고, 현재 터미날을 제어하고 있는 전면작업의 프로세스 그룹 ID와 그것을 비교해서 한다( tcgetpgrp함수를 사용해서 추출될 수 있다. )

만일 그 서브쉘이 전면 작업으로써 실행되고 있지 않다면, 그것은 그 자신의 프로세스 그룹에 SIGTTIN 시그날을 보냄으로써 스스로 멈춰야만 한다.  그것은 전면에 그 자신을 제멋대로 놓을수 없다; 사용자가 부모 쉘에게 이것을 시키도록 기다려야만 한다.  만일 그 서브쉘이 계속된다면, 검사를 반복하고 만일 그것이 여전히 전면에 있지 않다면 다시 스스로 멈춘다.

일단 서브쉘이 부모 쉘에 의해서 전면에 놓이게되면, 서브쉘은 자신의 작업 제어가 가능하게 된다.  자신소유의 프로세스 그룹안에 그 자신이 놓이도록 setpgid를 호출하고, 그다음 전면에 이 프로세스 그룹이 놓이도록 tcsetpgrp를 호출함으로써 이러한 일을 한다.

쉘이 작업제어가 가능하게 되었을 때, 작업제어를 멈추도록 하는 모든 시그날에 대해서 무시하도록 그 자체에서 설정하기 때문에 그 자체는 우연히 멈추게 되지 않는다.  당신은 모든 멈춤 시그날들을 SIG_IGN 으로 설정하여 이러한 일을 할 수 있다.

비-대화식으로 실행되는 서브쉘은 작업제어가 지원되지도 않고 지원될 수도 없다.  그것은 그자체가 쉘인 프로세스 그룹안에 만든 모든 프로세스가 존재해야만 한다; 이것은 비-대화실 쉘과 부모 쉘에 의해 단일한 작업으로 취급되는 자식 프로세스를 허용한다. 이것은 작업제어를 사용하지 않지만 그것을 하는 쉘을 만들기 위해서는 기억해야만 한다.

다음은 이러한 모든 것을 어떻게 하는지 보여주는 예제 쉘을 위한 초기 코드이다.

/* 쉘의 속성을 기억하라. */

#include  <sys/types.h>

#include  <termios.h>

#include  <unistd.h>

pid_t  shell_pgid;

struct  termios  shell_tmodes;

int  shell_terminal;

int  shell_is_interactive;

/* 진행하기 전에 쉘이 전면작업으로써 대화식으로 실행되고 있는지를 확인하라. */

void

init_shell  ()

{

/* 만일 대화식으로 실행된다면 보아라.  */

shell_terminal  =  STDIN_FILENO;

shell_is_interactive  =  isatty  (shell_terminal);

if  (shell_is_interactive)

{

/* 전면에 놓일때까지 루프를 돈다. */

while (tcgetpgrp (shell_terminal) != (shell_pgid = getpgrp()))

kill  (-  shell_pgid,  SIGTTIN);

/* 대화와 작업-제어 시그날들을 무시하라. */

signal  (SIGINT,  SIG_IGN);

signal  (SIGQUIT,  SIG_IGN);

signal  (SIGTSTP,  SIG_IGN);

signal  (SIGTTIN,  SIG_IGN);

signal  (SIGTTOU,  SIG_IGN);

signal  (SIGCHLD,  SIG_IGN);

/*  우리소유의 프로세스 그룹에 우리자신을 넣어라. */

shell_pgid  =  getpid  ();

if  (setpgid  (shell_pgid,  shell_pgid)  <  0)

{

perror("Couldn't put the shell in its own process group");

exit  (1);

}

/*  터미날의 제어를 잡아라 */

tcsetpgrp  (shell_terminal,  shell_pgid);

/*  쉘을 위한 디폴트 터미날 속성을 저장하라. */

tcgetattr  (shell_terminal,  &shell_tmodes);

}

}

24.6.3  작업들을 개시하기

일단 쉘이 터미날을 제어하여서 작업제어를 수행하는 책임을 갖게 되면, 쉘은 사용자에의해 입력된 명령문에 응답하여 작업을 개시할 수 있다. 프로세스 그룹에서 프로세스들을 만들기 위해서는, 23.2절 [Process Creation Concepts] 325 페이지에 설명된 fork 와 exec를 사용한다. 그곳에는 여러개의 자식 프로세스들이 이미 존재하고 있기 때문에, 그 일을 하기는 좀 많이 복잡하고 당신은 올바른 순서로 프로세스를 만들도록 주의를 해야만 한다.  그렇지 않다면, 고약한 경쟁상황(race condition)이 발생될 것이다.

당신은 프로세스들 사이의 부모-자식 관계를 어떻게 구성할것인지에 대해서 두가지를 선택할 수 있다.  프로세스 그룹안에 있는 모든 프로세스들을 쉘 프로세스의 자식 프로세스로 만들거나, 또는 그룹에 있는 한 개의 프로세스를 그 그룹에 있는 다른 모든 프로세스들의 조상이 되도록 만들수 있다.  이 장에 설명된 예제 쉘 프로그램은 첫 번째가 좀 간단하게 때문에 첫 번째를 사용한다.

만들어진 각각의 프로세스는, setpid를 호출하여 새로운 프로세스 그룹에 그 자신을 넣어야만 한다; 24.7.2절 [Process Group Functions] 349 페이지 참조.  새로운 그룹에 있는 첫 번째 프로세스는 그 프로세스 그룹의 리더가 되고 그 프로세스 ID가 그 그룹을 위한 프로세스 그룹 ID가 된다.

쉘은 새로운 프로세스 그룹으로 각각의 자식 프로세스들을 넣기 위해서 setpgid를 호출한다.  이것은 타이밍 문제가 내포되어 있다;  각각의 자식 프로세스는 새로운 프로그램이 실행되기 전에 프로세스 그룹에 들어가야만 하고, 쉘은 실행을 계속하기 전에 그룹이 가지고 있는 모든 자식 프로세스에 의존한다.  만일 자식 프로세스와 쉘이 모두 setpgid를 호출한다면, 이것은 프로세스가 먼저 그 일을 처리하도록 해서 아무런 문제가 발생되지 않을 것이다.

만일 전면 작업으로 어떤 작업이 진행되고 있다면, 새로운 프로세스 그룹을 tcsetpgrp를 사용해서 터미날을 제어하고 있는 전면으로 넣을 필요가 있다. 다시, 이것은 경쟁 상황을 피하기 위해서 각각의 자식 프로세스 뿐만아니라, 쉘에 의해서도 수행된다.  각 자식 프로세스의 다음 일은 시그날동작을 재설정하는 일이다.

초기화 하는동안, 쉘 프로세스는 작업제어 시그날들을 무시하도록 그 자신을 설정한다; 24.6.2절 [Initializing the Shell] 339 페이지 참조. 그 결과로, 그것이 만든 어떤 자식 프로세스도 또한 상속으로 인해서 그러한 시그날들을 무시하게 된다.  이것에 만족하지 못하면, 각각의 자식 프로세스는 그것이 만들어진 후에 SIG-DFL을 사용해서 그 시그날의 원래 동작으로 되돌려 설정할 수 있다.

쉘이 이러한 관습을 따르기 때문에, 응용 프로그램은 그들이 부모 프로세스로부터 시그날의 처리에 대한 것을 정확히 상속받는다고 가정 할 수 있다.  그러나 모든 응용프로그램은 멈춤 시그날들의 처리에 간섭할 수 없다.  SUSP 문자의 일반적인 해석이 불가능하게 만든 응용프로그램은 사용자가 작업을 멈추게 하기 위해서 다른 메카니즘을 제공한다.  사용자가 이 메카니즘을 호출할 때, 그 프로그램은 단지 프로세스 그 자체가 아니라, 그 프로세스의 프로세스 그룹에게 SIGTSTP 시그날을 보낸다.  21.6.2절 [Signaling Another Process] 295 페이지 참조.

마침내, 각각의 자식 프로세스는 보통의 방법으로 exec를 호출하여야 한다. 이것은 표준 입/출력 채널들의 리다이렉션이 처리되는 지점이다. 8.8절 [Duplicating Descriptors] 114 페이지를 참조하여 이것을 어떻게 하는지 보아라. 다음은 프로그램을 개시하기 위한, 대화식 쉘 프로그램에 있는 함수이다. 그 함수는 쉘에 의해서 자식 프로세스가 만들어진 후에 즉시 자식 프로세스에 의해서 실행되고 결코 리턴하지 않는다.

void

launch_process  (process  *p,  pid_t  pgid, int  infile,  int  outfile,  int  errfile,  int  foreground)

{

pid_t  pid;

if  (shell_is_interactive)

{

/* 프로세스 그룹에 그 프로세스를 넣고 터미날을 프로세스 그룹에게 주어라, 적당하면,  이것은 쉘과 개별적인 자식 프로세스에 의해서 모두 실행되기 때문에 경쟁상황을 포함하고 있다.  */

pid  =  getpid  ();

if  (pgid  ==  0)  pgid  =  pid;

setpgid  (pid,  pgid);

if  (foreground)

tcsetpgrp  (shell_terminal,  pgid);

/* 작업제어 시그날위한 처리를 디폴트로되돌려 설정하라. */

signal  (SIGINT,  SIG_DFL);

signal  (SIGQUIT,  SIG_DFL);

signal  (SIGTSTP,  SIG_DFL);

signal  (SIGTTIN,  SIG_DFL);

signal  (SIGTTOU,  SIG_DFL);

signal  (SIGCHLD,  SIG_DFL);

}

/* 새로운 프로세스를 위한 표준 입/출력 채널들을 설정하라. */

if  (infile  !=  STDIN_FILENO)

{

dup2  (infile,  STDIN_FILENO);

close  (infile);

}

if  (outfile  !=  STDOUT_FILENO)

{

dup2  (outfile,  STDOUT_FILENO);

close  (outfile);

}

if  (errfile  !=  STDERR_FILENO)

{

dup2  (errfile,  STDERR_FILENO);

close  (errfile);

}

/* 새로운 프로세스를 실행하라.  exit를 확인하라.  */

execvp  (p->argv[0],  p->argv);

perror  ("execvp");

exit  (1);

}

만일 쉘이 대화식으로 실행되지 않으면, 이 함수는 프로세스 그룹이나 시그날로 아무일도 하지 않는다.  작업제어를 수행하지 않는 쉘은 그 자체가 쉘인 프로세스 그룹안에 모든 서브프로세스들이 존재해야만 한다.

다음은 실제로 완전한 작업을 시작하는 함수이다.  자식 프로세스가 만들어진 후에, 이 함수는 전면이나 배경으로 새로이 만들어진 작업을 넣기위해서 어떤 다른 함수를 호출한다; 그것은 24.6.4절 [Foreground and Background] 343 페이지에 설명되어 있다.

void

launch_job  (job  *j,  int  foreground)

{

process  *p;

pid_t  pid;

int  mypipe[2],  infile,  outfile;

infile  =  j->stdin;

for  (p  =  j->first_process;  p;  p  =  p->next)

{

/*  만일 필요하면 pipe들을 준비하라. */

if  (p->next)

{

if  (pipe  (mypipe)  <  0)

{

perror  ("pipe");

exit  (1);

}

outfile  =  mypipe[1];

}

else

outfile =  j->stdout;

/* 자식 프로세스들을 만들어라 */

pid  =  fork  ();

if  (pid  ==  0)

/* 이것은 자식 프로세스이다. */

launch_process  (p,  j->pgid,  infile, outfile,  j->stderr,  foreground);

else  if  (pid  <  0)

{

/*  fork 가 실패했다. */

perror  ("fork");

exit  (1);

}

else

{

/* 이것은 부모 프로세스이다.  */

p->pid  =  pid;

if  (shell_is_interactive)

{

if  (!j->pgid)

j->pgid  =  pid;

setpgid  (pid,  j->pgid);

}

}

/*  pipe들을 정리하라.  */

if  (infile  !=  j->stdin)

close  (infile);

if  (outfile  !=  j->stdout)

close  (outfile);

infile  =  mypipe[0];

}

format_job_info  (j,  "launched");

if  (!shell_is_interactive)

wait_for_job  (j);

else  if  (foreground)

put_job_in_foreground  (j,  0);

else

put_job_in_background  (j,  0);

}

24.6.4  전면 과 배경

이제 전면(foreground)에 있는 작업을 시작할 때 쉘에 의해서 행해져야만 하는 동작은 무엇이며, 배경작업이 시작될 때 해야만 되는 것과 무엇이 어떻게 다른지 알아보자. 전면작업이 시작될 때, 쉘은 첫 번째로 tcsetpgrp를 호출해서 그 전면작업에 터미날 제어권을 줘야만 한다.  그리고나서, 쉘은 프로세스 그룹안에 있는 프로세스들이 종료되거나 멈출때까지 기다려야한다.  이것에 대한 상세한 내용은 24.6.5절 [Stopped and Terminated Jobs] 344 페이지를 참조하라.

그룹안에 있는 모든 프로세스들이 수행됐거나 멈추었을 때, 쉘은 다시 tcsetpgrp를 호출해서 자신의 프로세스 그룹을 위해서 터미날 제어권을 되찾는다.  배경작업으로부터의 입/출력이나 사용자에의해 입력된 SUSP 문자가 원인이 된 멈춤 시그날이 프로세스 그룹에게 보내어지면, 보통, 작업안에 있는 모든 프로세스들이 함께 멈춘다.

전면작업이 터미날을 이상한 상황으로 만들었을지도 모르므로, 쉘은 계속하기전에 자신이 저장해놓았던 터미날 모드들을 반환하여야한다.  작업이 완전히 멈춘경우에, 쉘은 일단 현재 터미날 모드들을 저장하고, 그래서 만일 그 작업이 나중에 다시 계속된다면 그들을 반환할 수 있다.  터미날 모드들을 다루는 함수들은 tcgetattr 과 tcsetattr이 있다; 그들은 12.4절 [Terminal Modes] 186 페이지를 참조하라. 다음은 위에 설명된 일들을 하는 예제 쉘을 함수이다.

/*  작업 j를 전면에 놓는다.  만일 cont가 0이 아니면, 저장된 터미날 모드들을 반환하고 우리가 블록하기 전에 그것이 계속되도록 SIGCONT 시그날을 프로세스 그룹에게 보낸다.  */

void

put_job_in_foreground  (job  *j,  int  cont)

{

/*  전면으로 작업을 놓는다.  */

tcsetpgrp  (shell_terminal,  j->pgid);

/* 만일 필요하다면 작업을 계속하도록 시그날을 보낸다. */

if  (cont)

{

tcsetattr  (shell_terminal,  TCSADRAIN,  &j->tmodes);

if  (kill  (-  j->pgid,  SIGCONT)  <  0)

perror  ("kill  (SIGCONT)");

}

/* 보고하도록 기다린다.  */

wait_for_job  (j);

/* 전면에 쉘을 다시 놓아라.  */

tcsetpgrp  (shell_terminal,  shell_pgid);

/* 쉘의 터미날 모드들을 반환한다.  */

tcgetattr  (shell_terminal,  &j->tmodes);

tcsetattr  (shell_terminal,  TCSADRAIN,  &shell_tmodes);

}

만일 프로세스 그룹이 배경작업으로써 시작된다면, 쉘은 전면에 자신을 놓고 터미날로부터 명령문 읽기를 계속한다. 예제 쉘에서는, 배경에 작업을 놓아야 될 필요가 그다지 없다.  다음은 그것을 사용하는 함수이다.

/*  배경에 작업을 놓는다.  만일 cont 인수가 참이면, 그것이 계속되도록 SIGCONT 시그날을 프로세스 그룹에게 보낸다.  */

void

put_job_in_background  (job  *j,  int  cont)

{

/*  만일 필요하면, 작업을 계속하도록 시그날을 보낸다.  */

if  (cont)

if  (kill  (-j->pgid,  SIGCONT)  <  0)

perror  ("kill  (SIGCONT)");

}

24.6.5  멈추고 종료된 작업들

전면 프로세스가 시작될 때, 쉘은 작업에 있는 모든 프로세스들이 멈추거나 종료될때까지 블록해야만 한다.  그것은 waitpid 함수를 호출함으로써 이루어진다;  23.6절 [Process Completion] 329 페이지 참조. 프로세스가 종료된것뿐만 아니라 멈춤 또한 보고하도록 WUNTRACED 옵션을 사용하라. 쉘은 배경작업이 멈추었거나 종료된 작업들을 사용자에게 보고하도록 배경작업들의 상황을 체크해야만 한다; 이것은 WNOHANG 옵션을 사용해서 waitpid 함수를 호출함으로써할 수 있다.  작업이 멈추었거나 종료되었음을 체크하는것과 같은 코드문을 넣는 좋은 위치는 새로운 명령문을 읽으려(prompting) 하기 전이다.

쉘은 SIGCHLD 시그날을 처리하는 핸들러를 만듦으로써 자식 프로세스를 위한 유용한 상황정보를 비동기적으로 통지받을 수 있다.  21장 [Signal Handling] 271 페이지 참조.

예제 쉘 프로그램의 경우에는, SIGCHLD 시그날은 보통 무시된다. 쉘이 조작하고 있는 전역 데이터 구조체를 재진입하는 문제를 피하기 위함이다.  그러나 쉘이 그들 데이터 구조체를 사용하지 않는 어떤 시간동안에는 _예를들어, 터미날에서 입력을 기다리는 때와 같은_SIGCHLD 시그날을 처리하는 핸들러는 가능하다.  동기적으로 상황들을 체크하는데 사용되는 같은 함수는 (이 경우, do_job_notification) 이 핸들러로부터 호출될 수 있다.

다음은 작업의 상황들을 체크하고 사용자에게 정보를 보고하는 것을 취급하는 예제 쉘 프로그램의 일부분이다.

/*  waitpid에 의해 리턴된 프로세스 pid의 상황을 저장하라. 모두 잘 됐으면 0을 리턴하고, 아니면 0이 아닌값을 리턴하라. */

int

mark_process_status  (pid_t  pid,  int  status)

{

job  *j;

process  *p;

if  (pid  >  0)

{

/* 프로세스를 위해서 기록을 갱신하라.  */

for  (j  =  first_job;  j;  j  =  j->next)

for  (p  =  j->first_process;  p;  p  =  p->next)

if  (p->pid  ==  pid)

{

p->status  =  status;

if  (WIFSTOPPED  (status))

p->stopped  =  1;

else

{

p->completed  =  1;

if  (WIFSIGNALED  (status))

fprintf(stderr, "%d: Terminated by signal

%d.\n", (int)pid, WTERMSIG(p->status));

}

return  0;

}

fprintf  (stderr,  "No  child  process  %d.\n",  pid);

return  -1;

}

else  if  (pid  ==  0  ||  errno  ==  ECHILD)

/* 보고하기위해 준비된 프로세스가 없다. */

return  -1;

else  {

/*  Other weird errors.   */

perror  ("waitpid");

return  -1;

}

}

/* 블록하지지 않고, 유용한 상황정보를 가진 프로세스들을 체크하라. */

void

update_status  (void)

{

int  status;

pid_t  pid;

do

pid = waitpid(WAIT_ANY, &status, WUNTRACED|WNOHANG);

while  (!mark_process_status  (pid,  status));

}

/*  주어진 작업안에 있는 모든 프로세스들이 보고되었을때까지 블록하여, 유용한 상황정보를 가진 프로세스들을 체크하라.  */

void

wait_for_job  (job  *j)

{

int  status;

pid_t  pid;

do

pid  =  waitpid  (WAIT_ANY,  &status,  WUNTRACED);

while  (!mark_process_status  (pid,  status)

&&  !job_is_stopped  (j)

&&  !job_is_completed  (j));

}

/* 사용자에게 보이기위한 작업상황에 대한 정보를 형식화하라.  */

void

format_job_info  (job  *j,  const  char  *status)

{

fprintf(stderr, "%ld (%s): %s\n", (long)j->pgid, status, j->command);}

/* 멈추었거나 종료된 작업들에 대해서 사용자에게 보고하라. 활성화된 작업 리스트로부터 종료된 작업들을 지워라.  */

void

do_job_notification  (void)

{

job  *j,  *jlast,  *jnext;

process  *p;

/* 자식 프로세스를 위한 상황정보들을 갱신하라.  */

update_status  ();

jlast  =  NULL;

for  (j  =  first_job;  j;  j  =  jnext)

{

jnext  =  j->next;

/* 만일 모든 프로세스들이 완수되었다면, 작업들이 완수되었음을 사용자에게 알리고 활성 작업 리스트에서 그것을 지워라.  */

if  (job_is_completed  (j))  {

format_job_info  (j,  "completed");

if  (jlast)

jlast->next  =  jnext;

else

first_job  =  jnext;

free_job  (j);

}

/* 더 이상 진행할 수 없음을 표시하여, 멈추어진 작업에 대하여 사용자에게 통지하라.  */

else if  (job_is_stopped  (j)  &&  !j->notified)  {

format_job_info  (j,  "stopped");

j->notified  =  1;

jlast  =  j;

}

/* 여전히 실행되고 있는 작업들에 대해서는 아무것도 말하지 말아라.  */

else

jlast  =  j;

}

}

24.6.6  멈추어있는 작업들을계속 실행시키기

쉘은 프로세스 그룹에게 SIGCONT 시그날을 보냄으로써 멈추어진 작업을 계속하게 할 수 있다.  만일 작업이 전면에서 계속 실행되어야하면, 쉘은 일단 tcsetpgrp 를 호출해서 터미날 제어권을 그 작업에게 부여하고, 저장된 터미날 설정을 반환한다.  전면에서 작업이 재개된 후에, 쉘은 그 작업이 마치 전면에서 시작된것처럼, 그 작업이 멈추거나 종료되기를 기다린다.

예제 쉘 프로그램에서 새로이 만들어진 작업과 재개된 작업들에 대한 처리는 put_job_in_foreground 와 put_job_in_background 한쌍의 함수로 이루어진다.  그들 함수에 대한 정의는 24.6.4절 [Foreground and Background] 343 페이지에 나와있다.  멈추었던 작업을 재개할 때, cont 인수에 주어진 0이 아닌값은 SIGCONT 시그날을 보내고 적당한 터미널 모드를 반환함을 확실하게 한다.

다음은 재개되어 실행되고 있는 작업에 대해서 쉘이 내부적으로 보유하고 있던 정보를 갱신하는 함수이다.

/* 멈추어있던 작업 J를 다시 실행되고 있는 작업으로써 표시하라.  */

void

mark_job_as_running  (job  *j)

{

Process  *p;

for  (p  =  j->first_process;  p;  p  =  p->next)

p->stopped  =  0;

j->notified  =  0;

}

/* 작업 J를 계속하라.  */

void

continue_job  (job  *j,  int  foreground)

{

mark_job_as_running  (j);

if  (foreground)

put_job_in_foreground  (j,  1);

else

put_job_in_background  (j,  1);

}

24.6.7  이 장에서 설명되지 않은 부분들

이장에 포함된 예제 쉘을 위한 여분의 코드는 전체 쉘 프로그램을 위한 일부분이다.  하지만, 어떻게 작업과 프로그램 데이터 구조체들이 할당되고 초기화되는지에 대해서는 전혀 얘기하지 않았다. 대부분의 실제 쉘은 명령문; 변수들; 약어, 대입, 그리고 파일이름의 패턴 매칭등을 위한 것을 제공하는, 복잡한 사용자 인터페이스로 되어있다. 이곳에 설명된 이 모든 것은 복잡함과는 거리가 멀다!  대신에, 우리는 어떻게 코아 프로세스를 생성을 하는지를 보여주고 그와 같은 쉘에서 어떤 작업제어 함수들이 호출될 수 있는지에 대해서 관심을 두었다. 다음은 우리가 표현했던 주요 엔트리 포인트를 요약한 테이블이다.

void init_shell (void)

쉘의 내부 상황을 초기화한다.  24.6.2절 [Initializing the Shell] 339 페이지 참조.

void init_sh

void launch_job (job *j, int foreground)

전면 또는 배경에서 작업 j를 시작한다.  24.6.3절 [Launching Jobs] 340 페이지 참조.

void do_job_notification (void)

종료되거나 멈추어진 어떤 작업들이 있는지 체크하고 보고한다. SIGCHLD 시그날을 위한 핸들러 안에서 또는 동기적으로 호출될 수 있다.  24.6.5절 [Stopped and Terminated Jobs] 344 페이지 참조.

void continue_job (job *j, int foreground)

작업 j를 재개한다.  24.6.6절 [Continuing Stopped Jobs] 347페이지 참조.

물론, 실제 쉘은 작업을 처리하기 위해서 다른 함수들을 제공받기 원할지 모른다.  예를들어, 활성화된 모든 작업들의 리스트를 보여주거나, 작업에게 시그날(SIGKILL 과 같은)을 보내는 명령문은 유용할 것이다.

24.7  작업제어를 위한 함수들

이 절은 작업제어와 연관된 함수들을 자세하게 설명하고 있다.

24.7.1  제어중인 터미날 확인하기

당신은 제어중인 터미날을 개방하기 위해서 사용할 수 있는 파일이름을 얻기 위해서 ctermid 함수를 사용할 수 있다.  GNU 라이브러리에서는, 항상 같은 문자열이 리턴된다: "/dev/tty".  그것은 현재 프로세스의 제어 중인 터미날(만일 그것이 한 개라면)을 참조하기위한 "이상한" 특별파일 이름이다.  ctermid 함수는 헤더파일 `stdio.h'에 선언되어 있다.

함수 :   char * ctermid (char *string)

ctermid 함수는 현재 프로세스를 위해서 제어중인 터미날의 파일이름이 포함된 문자열을 리턴한다.  만일 문자열이 널 포인터가 아니라면, 그것은 적어도 L_ctermid 문자들을 저장하고 있는 배열이 될 것이다. 문자열을 이 배열안에 리턴된다.  그렇지 않다면, 정적 지역안에 리턴된 문자열에 대한 포인터는 이 함수가 연속적으로 호출되어 덧씌워질 것이다.

만일 어떠한 이유로 파일이름이 결정될 수 없다면 빈 문자열(empty string)을 리턴한다.  심지어 파일이름이 리턴됐을지라도, 그 파일에 대한 억세스는 보증되지 않는다.

매크로 :   int L_ctermid

이 매크로는 ctermid에 의해 리턴된 파일이름을 저장하기에 충분히 큰 문자열의 크기를 나타내는 정수 상수 표현식이다.

또한 12.1절 [Is It a Terminal] 185 페이지에 있는 isatty 와 ttyname 함수를 참조하라.

24.7.2  프로세스 그룹 함수들

다음은 프로세스 그룹을 다루기 위한 함수를 설명하고 있다. 당신의 프로그램에서 그들 함수들을 사용하기 위해서는 헤더파일 `sys/types.h' 와 `unistd.h'를 포함해야만 한다.

함수 :   pid_t setsid (void)

setsid 함수는 새로운 세션을 만든다.  호출한 프로세스는 세션리더가 되고, 그 프로세스의 프로세스 ID와 같은 ID를 가진 새로운 프로세스 그룹이 만들어지고 프로세스가 그 안에 소속된다.  새로운 프로세스 그룹에 다른 프로세스들이 없고 새로운 세션안에 다른 프로세스 그룹이 없이 초기화된다. 이 함수는 또한 호출한 프로세스가 제어중인 터미날을 갖지 않도록 만든다.  setsid 함수는 만일 성공하면 호출한 프로세스의 새로운 프로세스 그룹 ID를 리턴하고 실패하면 -1의 값을 리턴한다. 다음은 이 함수를 위해 정의된 에러상황이다.

EPERM	호출한 프로세스가 이미 프로세스 그룹의 리더이거나, 같은 프로세스 그룹 ID를 가진 다른 프로세스 그룹이 있다.

getpgrp 함수는 두 개의 정의를 가진다;  한 개는 BSD 유닉스에서 왔고, 한 개는 POSIX.1 표준으로부터 온 것이다.  당신이 선택한 테스트 매크로(1.3.4절 [Feature Test Macros] 6 페이지 참조.)에 따라서 당신이 어떤 정의를 선택했는지가 결정된다.  즉, 당신이 _BSD_SOURCE를 정의하면 BSD 것이 선택되고; 그렇지않고, _POSIX_SOURCE 또는 _GNU_SOURCE를 정의한다면 POSIX의 것이 선택된 것이다.  오래된 BSD 시스템에서 만들어진 프로그램들은 _BSD_SOURCE 하에서 특별히 정의된 getgrp를 사용하도록 `unistd.h'를 인클루드 할 수없다.  당신은 BSD정의를 획득 하기 위해서 -lbsd-compact 로 그와 같은 프로그램들을 링크해야만 한다.

POSIX.1 함수 :   pid_t getpgrp (void)

getpgrp의 POSIX.1 정의는 호출한 프로세스의 프로세스 그룹 ID를 리턴한다.

BSD 함수 :   pid_t getpgrp (pid_t pid)

getpgrp 의 BSD 정의는 프로세스 pid의 프로세스 그룹 ID를 리턴한다.  당신이 호출한 프로세스에 대한 정보를 얻기위해서는 pid인수를 위해서 0의 값을 사용할 수 있다.

함수 :   int setpgid (pid_t pid, pid_t pgid)

setpgid 함수는 프로세스 그룹 pgid에 프로세스 pid를 소속시킨다. 특별한 경우로, pid 또는 pgid는 호출한 프로세스의 프로세스 그룹을 지적하기 위해서 0으로 될 수 있다.  이 함수는 작업제어를 지원하지 않는 시스템에서는 실패한다.  24.2절 [Job Control is Optional] 336 페이지 참조하여 상세한 정보를 얻어라. 만일 명령이 성공하면, setpgid는 0을 리턴한다. 그렇지 않다면 -1을 리턴한다.  다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EACCES	pid라는 이름을 가진 자식 프로세스가 만들어진 후에 exec 함수를 통해서 실행되었다.

EINVAL	pgid의 값이 유용하지 않다.

ENOSYS	시스템이 작업제어를 지원하지 않는다.

EPERM	pid 인수에 의해 지적된 프로세스가 세션리더이거나, 호출한 프로세스와 같은 세션에 없거나, pgid 인수의 값이 호출한 프로세스와 같은 세션안에 있는 프로세스 그룹 ID와 매치되는 것이 없다.

ESRCH	pid 인수에 의해 지적된 프로세스가 호출한 프로세스가 아니거나 호출한 프로세스의 자식 프로세스가 아니다.

함수 :   int setpgrp(pid_t pid, pid_t pgid)

이것은 setpgid를 위한 BSD용이다. 두 함수는 같은 일을 한다.

24.7.3  제어권을 가진 터미날을 억세스하기 위한 함수들

다음은 터미날의 전면프로세스 그룹을 알아내거나 설정하기 위한 함수들이다. 당신은 그들 함수들을 당신의 어플리케이션에서 사용하기 위해서는 헤더파일 `sys/types.h'와 `unistd.h'를 인클루드해야만 한다. 그들 함수들이 터미날 디바이스를 정하도록 파일 기술자 인수를 정한다고 하더라도, 전면작업은 터미날 파일 그자체와 연관되어 있고 특별히 개방된 파일 기술자가 아니다.

함수 :   pid_t tcgetpgrp(int filedes)

이 함수는 기술자 filedes로 개방한 터미날과 연관된 전면 프로세스 그룹의 프로세스 그룹 ID를 리턴한다.  만일 아무런 전면 프로세스 그룹이 없다면, 현존하는 프로세스 그룹의 프로세스 그룹 ID와 매치 되지 않는, 1보다 큰 수를 리턴한다.  그러한 경우는 전면작업으로써 수행되고 있던 작업안에 있는 모든 프로세스들이 종료되고 전면으로 옮겨질 작업들이 없을 때 발생할 수 있다  에러의 경우에 -1의 값이 리턴된다.  다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EBADF	filedes 인수가 유용한 파일 기술자가 아니다.

ENOSYS	시스템이 작업제어를 지원하지 않는다.

ENOTTY	filedes 인수와 연관된 터미날 파일이 호출한 프로세스가 제어중인 터미날이 아니다.

함수 :   tcsetpgrp(int filedes, pid_t pgid)

이 함수는 터미날의 전면 프로세스 그룹 ID를 설정하는데 사용된다. 인수 filedes는 터미날을 지정하는 기술자이다; pgid 인수는 프로세스 그룹을 지정한다.  호출한 프로세스는 pgid와 같은 세션의 멤버가 되어야만 하고 같은 터미날을 제어하고 있어야만 한다.

터미날 억세스를 목적으로 하여, 이 함수는 출력으로 취급된다. 만일 배경 프로세스로부터 이 함수가 호출된다면, 보통 그 프로세스 그룹안에 있는 모든 프로세스들에게 SIGTTOU 시그날이 보내어진다.  만일 호출한 프로세스 자체가 SIGTTOU 시그날을 무시하는 예외의 경우라면, 그 명령은 수행되고 아무런 시그날도 보내어지지 않는다.

만일 성공하면, tcsetpgrp 는 0을 리턴한다.  에러가 발생한 경우에는 -1을 리턴한다. 다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EBADF	filedes 인수가 유용한 파일 기술자가 아니다.

EINVAL	pgid 인수가 유용하지 않다.

ENOSYS	시스템이 작업제어를 지원하지 않는다.

ENOTTY	filedes는 호출한 프로세스가 제어하고 있는 터미날이 아니다.

EPERM	pgid 는 호출한 프로세스와 같은 세션에 있는 프로세스 그룹이 아니다.

마음 가운데

무시로 외로웠던 내 마음 가운데

말할 수 있는.

새로운 가슴 하나 더 있었으면

피어난 꽃 한포기

너무나 아름다웠다.

말 하리다만.

25  사용자와 그룹

25장 사용자와 그룹

시스템에서 로그인하고 있는 모든 사용자는 사용자 ID(user ID)라고 불리는 한 개의 단일한 숫자에 의해서 구분된다.  각 프로세스는 그 프로세스를 가진 사용자의 억세스 권한을 알리는 유효 사용자 ID를 갖는다. 사용자들은 억세스 제어를 목적으로 하여 그룹으로 분류된다. 각 프로세스는 그 프로세스가 파일들을 억세스하기 위해서 사용할 수 있는 어떤 그룹들이 있는지를 알리는 한 개 이상의 그룹 ID(group ID)가 있다. 프로세스의 유효 사용자 ID 와 유효 그룹 ID들은 persona의 집합적인 형식이다.  이것은 그 프로세스가 어떤 파일을 억세스 할 수 있는지를 결정한다.  일반적으로, 프로세스는 부모 프로세스로부터 persona를 상속 받지만, 특별한 상황하에서는 그 persona를 변경해서 억세스 권한을 변경할 수 있다.

시스템에 있는 각 파일은 또한 사용자 ID 와 그룹 ID를 갖는다.  억세스제어(access control)는, 실행되고 있는 프로세스의 사용자 ID 와 그룹 ID를 파일의 사용자 ID 와 그룹 ID를 비교함으로써 수행된다. 그 시스템은 등록된 사용자들을 모두 데이터베이스에 저장하고, 다른 데이터베이스에 정의된 모든 그룹들을 저장한다.  그 데이터베이스를 시험하기 위해 사용할 수 있는 라이브러리 함수들이 있다.

25.1  사용자와 그룹 ID들

컴퓨터 시스템에 등록된 각 사용자들은 사용자 이름(user name) 과 사용자ID(user ID) 에 의해 구분된다.  일반적으로, 각 사용자 이름은 한 개의 단일한 사용자 ID를 갖지만, 같은 사용자 ID에 여러개의 로그인 이름들을 가질 수 있다.  사용자 이름과 그에 해당하는 사용자 ID는 25.12절 [User Database] 360 페이지에 설명된, 당신이 억세스 할 수 있는 데이터베이스에 저장되어 있다.

사용자는 그룹안에 분류된다.  각사용자 이름은 디폴트로는 한 개의 그룹이지만, 한 개 이상의 그룹에 소속된다.  같은 그룹의 멤버인 사용자들은 그 그룹의 멤버가 아닌 사용자가 억세스할 수 없는 자원들(파일과 같은)을 공유한다.  각 그룹은 한 개의 그룹 이름과 그룹 ID를 갖는다.  25.13절[Group Database] 362 페이지에, 그룹 ID 또는 그룹 이름에 대한 정보를 어떻게 찾는지 나와있다.

25.2  프로세스의 persona

어느 시간에, 각 프로세스는 프로세스의 권한을 결정하는 한 개가 단일한 사용자 ID와 한 개의 그룹 ID를 갖는다.  그들은 프로세스의 집합적인 persona라고 불리는데, 왜냐하면 그들은 억세스 제어의 목적을 위하여 "그것은 누구이다"를 결정하기 때문이다.  그 ID들은 프로세스의 유효 사용자 ID(effective user ID) 와 유효 그룹 ID(effective group ID) 라고 불린다. 당신의 로그인 쉘은 당신의 사용자 ID 와 당신의 디폴트 그룹 ID로 구성되는 persona로 시작한다. 일반적인 환경에서, 모든 당신의 다른 프로세스들은 그들의 값들을 상속받는다.

프로세스는 그 프로세스를 만들었던 사용자를 확인할 수 있는 실제사용자 ID(real user ID)와 사용자의 디폴트 그룹을 확인하는 실제 그룹 ID(real group ID)를 갖는다.  그들의 값들은 억세스 제어의 역할을 하지 않기 때문에, 우리는 그들을 persona의 부분으로 간주하지 않는다. 하지만 그들은 중요하다. 실제 와 유효 사용자 ID 둘은 모두 프로세스의 생존기간 동안에 변경될 수 있다.  25.3절 [Why Change Persona] 354 페이지 참조. 부가적으로, 사용자는 여러개의 그룹에 포함될 수 있기 때문에, persona에는 억세스 권한에 기여하는 부가적인 그룹 ID들이 포함된다. 어떻게 프로세스의 유효 사용자 ID와 그룹 ID들이 파일을 접근하는 권한에 영향을 미치는지에 대한 상세한 정보들은, 9.8.6절 [Access Permission]137 페이지 참조.

프로세스의 사용자 ID는 kill 함수를 사용해서 시그날들을 보낼 수 있는 권한 또한 제어한다.  21.6.2절 [Signaling Another Process] 295 페이지 참조.

25.3  왜 프로세스의 persona를 변경하는가

프로세스를 위하여 사용자 그리고/또는 그룹 ID들을 변경할 필요가 있는 가장 명백한 상황이 있는 곳은 로그인 프로그램이다. 로그인이 실행을 시작할 때, 그 사용자 ID는 루트이다. 작업은 로그인하고 있는 사용자의 사용자 ID와 그룹 ID들을 가진 쉘을 시작하는 것이다. (이것을 완전히 수행하기 위해서, 로그인은 persona 뿐만 아니라 실제 사용자와 그룹 ID들을 설정해야만 한다.  하지만 이것은 특별한 경우이다.) persona를 변경하는 더 일반적인 경우는 보통의 사용자 프로그램에서 실제로 그 프로그램을 실행시키고 있는사용자에게는 억세스가 가능하지 않는 자원을 사용자가 억세스할 필요가 있을 때 이다.

예를들어, 당신이 당신의 프로그램에 의해 제어되지만 다른 사용자에 의해서 직접적으로 읽거나 갱신될 수 없는 어떤 파일이 있다고 했을 때, 다른 사용자에게는 개방되지 않은 어떤 종류의 프로토콜의 기능을 원하거나, 또는 그것이 가지고 있는 정보의 비밀이나 완전을 보장하기 원할 때, persona를 변경하는 것이 필요하다.  이 제한된 억세스의 종류는 원하는 자원과매치되도록 유효 사용자와 그룹 ID를 프로그램이 변경함으로써 가능하다.

그러면, 파일안에 점수를 저장하는 게임 프로그램을 상상해보자.  게임프로그램 자체는 누가 그것을 실행시키는지에 상관없이 이 파일을 갱신할 수 있도록 할 필요가 있지만, 만일 사용자가 그 게임을 실행시키지 않고도 그 점수파일에 기록이 가능하다면, 그들은 그들이 좋아하는 어느 점수로 그 점수파일에 그들 점수를 기록할 수 있다. 어떤 사람은 이것을 상상할 수도 없거나, 아니면 비난할 것이다.  이러한것은 점수 파일을 소유하는 새로운 사용자 ID와 로그인 이름을 만들고 이 사용자에 의해서만 오직 기록 가능하도록 그 파일을 만들면 방지할 수 있다.  그러면, 게임 프로그램이 이 파일을 갱신하기 원할 때, 그것은 그 게임의 유효 사용자 ID를 변경할 수 있다.  실제로, 프로그램은 점수파일에 기록할 수 있는 게임의 persona를 받아 들여야만 한다.

25.4  어떻게 응용프로그램이 persona를 변경할수 있는가

프로세스의 persoan를 변경하기 위한 능력은 무심한 비밀의 침해나, 또는 고의적인 남용의 소스(source)가 될 수 있다.  persona를 변경하는, 프로그램에 포함된 문제는 특별한 상황들에 제한이 있기 때문이다.

당신은 제멋대로 당신이 원한다고 당신의 사용자 ID 나 그룹 ID를 변경 할 수는 없다; 오직 특권이 부여된 프로세스들만이 그것을 할 수 있다. 대신에, 프로그램을 위하여 persona를 변경하기 위한 일반적인 방법은 미리 특별한 사용자나 그룹으로 변경하여 프로그램이 시작되는 것이다. 이것은 파일의 억세스 모드의 setuid 와 setgid 비트들의 함수이다.  9.8.5절 [Permission Bits] 136 페이지 참조.

억세스가능한 파일의 setuid 비트가 설정될 때, 그 파일은 자동적으로 그 파일을 소유한 사용자로 유효 사용자 ID를 변경하여 실행된다.  그와같이, setgid 비트가 설정된 파일은 파일의 그룹으로 유효사용자 ID를 변경하여 실행된다.  23.5절 [Executing a File] 327 페이지 참조. 특별한 사용자나 그룹 ID로 변경하여 만들어진 파일은 사용자나 그룹 ID에게 완전한 억세스를 보장한다. 파일 모드와 억세스가능성에 대한 일반적인 내용은 9.8절 [File Attributes]131 페이지를 참조하라.

프로세스는 유효 사용자(또는 그룹) ID는 실제 ID로 언제든지 다시 변경할 수 있다.  프로그램을 좀더 견고한 프로그램으로 만들기 위해서, 그들이 필요치 않을 때는 그들에 대한 특별한 권한을 없애야(turn off) 한다.

25.5  프로세스의 persona 읽기

다음은 실제와 유효 사용자와 그룹 ID들을 읽기위한 함수들의 상세한 설명이다.  그 기능들을 사용하기 위해서는 당신은 헤더파일 `sys/types.h'와 `unistd.h'를 포함시켜야만 한다.

데이터타입 :   uid__t

이것은 사용자 ID들을 나타내는데 사용되는 정수 데이터 타입이다. GNU 라이브러리에서, 이것은 unsigned int 와 같다.

데이터타입 :   gid__t

이것은 그룹 ID들을 나타내기 위해서 사용되는 정수 데이터타입이다. GNU 라이브러리에서, 이것은 unsigned int 와 같다.

함수 :   uid_t getuid (void)

getuid 함수는 프로세스의 실제 사용자 ID를 리턴한다.

함수 :   gid_t getgid (void)

getgid 함수는 프로세스의 실제 그룹 ID들을리턴한다.

함수 :   uid_t geteuid (void)

geteuid 함수는 프로세스의 유효 사용자 ID를 리턴한다.

함수 :   gid_t getegid (void)

getegid 함수는 프로세스의 유효 사용자 ID 를 리턴한다.

함수 :   int getgroups (int count, gid_t *groups)

getgroups 함수는 프로세스의 부가적인 그룹 ID들에 대해서 묻는데 사용된다.  그들 그룹 ID들의 개수까지도 배열 groups에 저장된다; 함수로부터의 리턴값은 실제로 저장된 그룹 ID들의 개수이다. 만일 count가 부가적인 그룹 ID들의 전체 개수보다 작다면, getgroups는 -1의 값을 리턴하고 errno를 EINVAL로 설정한다. 만일 count 가 0이면, getgroups는 단지 부가적인(supplementary) 그룹 ID들의 전체 개수를 리턴한다.  부가적인 그룹들을 지원하지 않는 시스템에서는, 이것은 항상 0이 될 것이다.  다음은 getgroups가 어떻게 모든 부가적인 그룹 ID들을 읽어서 사용하는지에 대한 것이다.

gid_t  *read_all_groups  (void)

{

int  ngroups  =  getgroups  (NULL,  0);

gid_t  *groups

=  (gid_t  *)  xmalloc  (ngroups  *  sizeof  (gid_t));

int  val  =  getgroups  (ngroups,  groups);

if  (val  <  0)

{

free  (groups);

return  NULL;

}

return  groups;

}

25.6  사용자 ID 설정하기

이절은 프로세스의 사용자 ID(실제 그리고/또는 유효)를 변경하기 위한 함수들을 설명하고 있다.  그 기능들을 사용하기 위해서, 당신은 헤더파일 `sys/types.h'와 `unistd.h'를 포함해야만 한다.

함수 :   int setuid (uid_t newuid)

이 함수는 적당한 특권을 가지고 제공된 프로세스에서,그 프로세스의 실제 사용자 ID 와 유효사용자 ID를 newuid로 설정한다. 만일 프로세스가 특권이 없다면, newuid는 실제 사용자 ID또는 저장된 사용자 ID(만일 시스템이 _POSIX_SAVED_IDS 를 지원한다면)중의 하나가 되어야만 한다.  이 경우, setuid는 오직 유효 사용자 ID만을 설정하고 실제 사용자 ID는 설정하지 않는다. setuid 함수는 성공하면 0의 값을 리턴하고 에러가 발생하면 -1의 값을 리턴한다.  다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EINVAL	newuid 인수의 값이 유용하지 않다.

EPERM	그 프로세스가 적당한 특권을 가지지 않는다; 당신은 정해진 ID로 변경하기 위한 권한을 가지지 않았다.

함수 :   int setreuid (uid_t ruid, uid_t euid)

이 함수는 ruid로 프로세스의 실제 사용자 ID를 설정하고 euid로 유효 사용자 ID를 설정한다.  만일 ruid가 -1이면, 그것은 실제 사용자 ID를 변경하지 않음을 의미한다;  그처럼 만일 euid가 -1이면 유효사용자 ID를 변경하지 않음을 의미한다.

setreuid 함수는 저장된 ID(saved IDs)들을 지원하지 않는 4.3 BSD 유닉스와의 호환성을 위해서 존재한다.  당신은 프로세스의 유효와 실제 사용자 ID들을 교환하기 위해서 이 함수를 사용할 수 있다. (특권을 가진 프로세스는 이 특별한 사용에 대해서 제한이 없다.) 만일 저장된 ID들이 지원된다면, 당신은 이 함수대신에 25.8절 [Enable/Disable Setuid] 357 페이지에 있는 함수들을 사용하는 것이 좋다. 리턴값은 성공하면 0이고 실패하면 -1이다.  다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EPERM	그 프로세스가 적당한 특권을 가지지 않았다;당신은 정해진 ID로 변경하기 위한 권한이 없다.

25.7  그룹 ID들을 설정하기

이 절은 프로세스의  그룹 ID들(실제 와 유효)을 변경하기 위한 함수들을 설명하고 있다.  이 기능들을 사용하기 위해서, 헤더파일 `sys/types.h'와 `unistd.h'를 포함해야만 한다.

함수 :   int setgid (gid_t newgid)

이 함수는 적당한 특권을 가지고 제공된 프로세스의 실제와 유효 그룹 ID를 newgid로 설정한다.  만일 그 프로세스가 특권이 없다면, newgid는 반드시 실제 그룹 ID 또는 저장된 그룹 ID중의 하나와 동일해야만 한다.  이 경우, setgid는 오직 유효 그룹 ID만 설정하고 실제 그룹 ID는 설정하지 않는다. setgid를 위한 리턴값과 에러 상황들은 setuid와 같다.

함수 :   int setregid (gid_t rgid, fid_t egid)

이 함수는 rgid로 프로세스의 실제 그룹 ID 를 설정하고 egid로 유효 그룹 ID를 설정한다.  만일 rgid가 -1이면, 그것은 실제 그룹 ID를 변경하지 않음을 의미한다; 그와같이 만일 egid가 -1이면, 유효 그룹 ID를 변경하지 않음을 의미한다.

setregid 함수는 저장된 ID들을 지원하지 않는, 4.3 BSD 유닉스와의 호환성을 위해서 제공되었다. 당신은 프로세스의 유효와 실제 그룹 ID들을 교환하기 위해서 이 함수를 사용할 수 있다( 특권을 가진 프로세스들은 이 사용에 대해서 제한이 없다.).  만일 저장된 ID들이 지원된다면, 이 함수를 사용하는 대신에 25.8절 [Enable/Disable Setuid] 357 페이지에 있는 함수들을 사용하라. setregid 를 위한 리턴값과 에러상황들은 setreuid와 같다. GNU 시스템은 특권을 가진 프로세스가 그들의 부가적인 그룹 ID들을 변경하는 것을 허용한다. setgroups 나 initgroups를 사용하기 위해서, 당신은 헤더파일 `grp.h'를 포함해야만 한다.

함수 :   int setgroups (size_t count, gid_t *groups)

이 함수는 프로세스의 부가적인 그룹 ID들을 설정한다. 그것은 오직 특권을 가진 프로세스로부터 호출될 수 있다. count 인수는 배열 groups안에 있는 그룹 ID들의 개수를 지정한다. 이 함수는 성공하면 0을 리턴하고 에러가 발생하면 -1을 리턴한다. 다음의 errno 는 이 함수를 위해 정의된 에러상황이다.

EPERM	호출한 프로세스가 특권이 없다.

함수 :   int initgroups (const char *user, gid_t gid)

initgroups 함수는 사용자 이름 user를 위해서, 디폴트가 되도록 프로세스의 부가적인 그룹 ID들을 설정하기 위해서 setgroups를 호출한다.  그룹 ID gid 또한 포함된다.

25.8  Setuid 억세스를 가능하게 하거나 불가능하게 하기

전형적인 setuid를 사용하는 프로그램에서 항상 특별한 억세스가 필요한 것은 아니다.  그래서 필요하지 않을 때 이 억세스를 꺼버리면, 무의식적인 억세스에 대한 빌미를 제공하지 않게 된다. 만일 시스템이 저장된 사용자 ID 기능을 지원하지 않는다면, 당신은 setuid로 이것을 수행할 수 있다. 게임 프로그램이 시작할 때, 그것의 실제 사용자 ID는 jdoe이고, 유효 사용자 ID는 games이고, 그것의 저장된 사용자 ID 또한 games이다.  그 프로그램은 다음과 같이, 시작할 때 일단 사용자 ID값들을 기록하게 된다.

user_user_id  =  getuid  ();

game_user_id  =  geteuid  ();

그러면   setuid (user_user_id); 로 게임 파일 억세스를 끌 수 있고 setuid (game_user_id); 로 다시 켤 수 있다. 이 프로세스를 통해서, 실제 사용자 ID를 jdoe로 남고 저장된 사용자 ID는 games로 저장되기 때문에, 프로그램은 항상 다른 것으로 유효 사용자 ID를 설정할 수 있다.

저장된 사용자 ID 기능을 지원하지 않는 다른 시스템에서, 당신은 프로세스의 실제 사용자 ID 와 유효 사용자 ID를 교환하기 위해서는 setreuid를 사용해서 setuid 억세스를 켜고 끌 수 있다.  다음처럼;

setreuid (geteuid (), getuid ());

이것은 실패하지 않고_항상 허용되는 특별한 경우이다.

왜 이것은 setuid 억세스를 토글링하는 효과를 갖는가? 게임 프로그램이 단지 시작되어 있다고 가정하고, 유효 사용자 ID가 games 일동안 실제 사용자 ID는 jdoe라고 가정하자.  이 경우, 게임은 점수 파일을 기록할 수 있다.  만일 두 개의 유효 사용자 ID 와 실제 사용자 ID를 교환한다면, 실제 사용자 ID는 games가 되고 유효 사용자 ID는 jdoe가 된다; 이제 프로그램은 오직 jdoe만 억세스를 갖는다.  다른 교환은 유효 사용자 ID를 다시 games로 되돌리고 점수 파일에 억세스를 저장한다.

시스템의 두 종류를 다루기 위해서는, 다음처럼 프리프로세스를 사용해서 저장된 사용자 ID 기능을 지원하는지 테스트해보아야한다.

#ifdef  _POSIX_SAVED_IDS

setuid  (user_user_id);

#else

setreuid  (geteuid  (),  getuid  ());

#endif

25.9  setuid 프로그램 예제

다음은 유효 사용자 ID를 변경하는 프로그램을 어떻게 준비하는지 보여주는 예제이다. 이것은 caber-toss라고 불리는 게임 프로그램의 일부분으로써 그 게임 프로그램은 오직 게임 프로그램 그 자체에 의해서만 점수 파일을 다룰수 있도록 되어있다. 그 게임 프로그램은 실행파일이 set-user-ID 비트로 인스톨될것이고 실행파일이 점수파일과 같은 사용자에 의해 소유된다고 가정한다.  전형적으로, 시스템 관리자는 이 목적을 위해서 게임과 같은 계정을 준비할 것이다.

실행가능파일은 모드 4755가 주어지고, 그래서 `ls -l'을 하면 다음과 같은 출력을 얻게된다.

-rwsr-xr-x    1  games     184422  Jul  30  15:17  caber-toss

set-user-ID 비트는 파일 모드에서 `s'로 나타난다. 점수 파일을 모드 644로 주어지고, `ls -l'하면 다음과 같은 결과를 얻는다.

다음은 어떻게 변경된 사용자 ID를 준비할것인지를 보여주는 프로그램의 일부분이다.  이 프로그램은 만일 시스템이 저장된 ID 기능을 지원한다면 그것을 사용하고, 그렇지않다면 setreuid를 사용해서 유효 사용자 ID와 실제 사용자 ID를 교환한다.

#include  <stdio.h>

#include  <sys/types.h>

#include  <unistd.h>

#include  <stdlib.h>

/*  유효와 실제 사용자 ID들을 저장하라.  */

static  uid_t  euid,  ruid;

/*  원래의 값으로 유효 사용자 ID를 반환하라.  */

void

do_setuid  (void)

{

int  status;

#ifdef  _POSIX_SAVED_IDS

status  =  setuid  (euid);

#else

status  =  setreuid  (ruid,  euid);

#endif

if  (status  <  0)  {

fprintf  (stderr,  "Couldn't  set  uid.\n");

exit  (status);

}

}

/*  실제 사용자 ID로 유효 사용자 ID를 설정하라.  */

void

undo_setuid  (void)

{

int  status;

#ifdef  _POSIX_SAVED_IDS

status  =  setuid  (ruid);

#else

status  =  setreuid  (euid,  ruid);

#endif

if  (status  <  0)  {

fprintf  (stderr,  "Couldn't  set  uid.\n");

exit  (status);

}

}

/*  메인 프로그램 */

int

main  (void)

{

/*  실제와 유효 사용자 ID들을 저장하라.  */

ruid  =  getuid  ();

euid  =  geteuid  ();

undo_setuid  ();

/*  게임을 하고 점수를 기록하라.  */

. . .

}

메인 함수에서 첫째로 어떻게 실제 사용자 ID로 유효 사용자 ID를 되돌려 설정하는지에 대해서 주목하라.  이것은 사용자가 게임을 실행하고 있을 동안 수행되는 파일 억세스들로써 권한을 결정하기 위해서 실제 사용자 ID를 사용한다.  오직 프로그램이 점수 파일을 개방할 필요만 있을 때 다음처럼, 원래의 유효 사용자 ID로 되바꾼다.

/*  점수를 기록하라. */

int

record_score  (int  score)

{

FILE  *stream;

char  *myname;

/*  점수파일을 개방하라.  */

do_setuid  ();

stream  =  fopen  (SCORES_FILE,  "a");

undo_setuid  ();

/*  파일에 그 점수를 기록하라.  */

if  (stream)

{

myname  =  cuserid  (NULL);

if  (score  <  0)

fprintf(stream, "%10s: Couldn't lift the caber.\n", myname);

else

fprintf  (stream,  "%10s:  %d  feet.\n",  myname, score);

fclose  (stream);

return  0;

}

else

return  -1;

}

25.10  setuid 프로그램을 만들기 위한 팁

setuid 프로그램은 원래 의도하지 않았던 사용자 억세스를 부여하는 오류를 범하기 쉽다_실제로, 만일 당신이 이러한 문제를 피하기를 원한다면, 각별한 주의를 해야만 한다.  다음은 의도되지 않은 억세스를 막고, 만일 그것일 발생했을 때 그 영향력을 최소화하기위한 안내지침이다.

□ 절대적인 필요가 없는한 root 와 같은 특권을 가진 사용자 ID들은 setuid 프로그램을 가져서는 안된다.  만일 자원(resource)이 당신의 특정한 프로그램에게 특정한 것이라면, 특권을 가지지 않은 사용자 ID 또는 그룹 ID가 단지 그 자원을 다루기만 하도록, 새롭게 정의하는 것이 좋다.

□ 유효 사용자 ID를 변경하는것과 함께 system 함수와 exec 함수를 사용하는것에 대해서는 주의를 해야만 한다.  당신 프로그램의 사용자가 변경된 사용자 ID를 사용하여 프로그램을 실행시키는 것을 허가하지 말아라.  execlp 와 execvp 함수들도 위험을 내포하고 있다 (그들은 사용자의 PATH 환경변수에 의존하여 실행되기 때문이다.) 만일 변경된 ID로 다른 프로그램을 실행해야만 한다면, 실행가능 파일을 절대 파일 이름( 6.2.2절 [File Name Resolution] 59 페이지 참조.) 으로 지정하고, 실행가능 파일과 원래의 users 와 같은 모든 포함된 디렉토리들이 다른 프로그램과 함께 그것의 위치를 변경하지 못하도록 보호를 해야만 한다.

□ 실제로 자원을 사용하는 프로그램의 일부분에서 자원을 제어하고 있는 사용자 ID만을 사용하라.  당신이 그 작업을 마쳤을 때, 실제 사용자의 사용자 ID로 유효 사용자 ID를 반환하라.  25.8절 [Enable/Disable Setuid] 357 페이지 참조.

□ 만일 당신의 프로그램에서 setuid 부분이 제어할 수 있는 자원이외에 다른 파일을 억세스할 필요가 있다면, 그와 같은 파일을 억세스할 수 있는 권한을 가진 실제 사용자를 조회해야만 한다. 당신은 그와 같은 일을 위해서 access 함수 ( 9.8.6절 [Access Permission] 137 페이지 참조.)를 사용할 수 있다;  그것은 유효 ID들보다는 실제 사용자와 그룹 ID들을 사용한다.

25.11  누가 로그인 했는지 확인하기

이절에 설명된 함수들을 사용해서 프로세스를 실행시키고 있는 사용자의 이름과 현재 세션에 로그인된 사용자의 이름을 알아낼 수 있다.  getuid와 friends 함수( 25.5절 [Reading Persona] 354 페이지 참조.)를 또한 참조하라. getlogin 함수는 `unistd.h'에 선언되어 있고 cuserid 와 L_cuserid는 `stdio.h'에 선언되어 있다.

함수 :   char *getlogin (void)

getlogin 함수는 프로세스가 제어하고 있는 터미날에 로그인되어 있는 사용자의 이름이 포함된 문자열을 가리키는 포인터를 리턴 하거나, 만일 그 정보를 결정할 수 없다면 널 포인터를 리턴한다. 문자열은 정적으로 할당되고 이 함수나 cuserid 함수의 연속적인 호출에 의해서 덧씌워진다.

함수 :   char * cuserid (char *string)

cuserid 함수는 프로세스의 유효 ID와 연관된 사용자 이름이 포함된 string을 가리키는 포인터를 리턴한다.  만일 string이 널 포인터가 아니라면, 그 string은 적어도 L_cuserid개의 문자를 저장할 수 있는 배열이 된다;  사용자의 이름이 포함된 문자열을 이 string에 리턴된다.  그렇지않다면, 정적인 지역에 있는 문자열을 가리키는 포인터가 리턴된다.  이 string는 정적으로 할당되고 이 함수나 getlogin 함수의 연속적인 호출에 의해서 덧씌워질 것이다.

매크로 :   int L__cuserid

사용자의 이름을 저장하기에 필요한 배열이 얼마나 길어야 하는지를 지적하는 정수 상수이다. 그 함수들은 이 세션에 로그인된 사용자나 프로세스를 실행시키고 있는 사용자를 명확하게 확인하도록 허용한다. (그들은 setuid 프로그램이 포함 되었을때는 다를 수 있다; 25.2절 [Process Persona] 353 페이지 참조.) 사용자는 그 함수들을 속일수 있는 방법이 아무것도 없다.

대부분의 경우에, 사용자를 찾아내는 환경변수 LOGNAME를 사용하는 것이 더욱 유용하다.  이것은 사용자가 제멋대로 LOGNAME 을 설정할 수 있기 때문에 정밀함에 있어서는 좀더 유연성이 있다.  22.2.2절 [Standard Environment] 318 페이지 참조.

25.12  사용자 데이타베이스

이 절은 등록된 사용자의 데이터베이스를 검색하고 찾기위한 모든 것에 대해서 설명한다.  데이터베이스 자체는 거의 대부분의 시스템에서 `/etc/passwd'에 보존되어 있지만, 어떤 시스템에서는 특별한 네트웍 서버에게 그것을 억세스할 수 있도록 한다.

25.12.1  사용자의 정보를 담고 있는 데이터 구조체

시스템 사용자 데이터베이스를 억세스하기위한 함수와 데이터 구조체들은 헤더파일 `pwd.h'에 선언되어 있다.

데이터타입 :   struct passwd

passwd 데이터구조체는 시스템 사용자 데이터베이스 안에 엔트리들에 대한 정보를 저장하는데 사용된다. 그것은 적어도 다음과 같은 멤버들을 갖고 있다.

char *pw_name	사용자 로그인 이름.

char *pw_passwd.	암호화된 비밀번호(password) 문자열.

uid_t pw_uid	사용자 ID 번호.

gid_t pw_gid	사용자의 디폴트 그룹 ID 번호.

char *pw_gecos	이 문자열은 전형적으로 사용자의 실제 이름을 포함하고, 가능하면 전화번호와 같은 다른 정보도 포함된다.

char *pw_dir	사용자의 홈 디렉토리(home directory), 또는 초기작업디렉토리. 이것에 대한 해석이 시스템-의존적인 경우에는 널포인터가 되어야한다.

char *pw_shell	사용자의 디폴트 쉘, 이나 사용자가 로그인할 때 실행되는 초기 프로그램.  시스템에서 디폴트로 지정된 것이 사용되는 경우에는, 널 포인터가 되어야한다.

25.12.2  한명의 사용자에대해 자세히 알아보기

특정한 사용자에 대한 정보를 알아내기 위해서는, getpwuid 또는 getpwnam을 사용해서 시스템 사용자 데이터베이스를 검색할 수 있다. 그 함수들은 헤더파일 `pwd.h'에 선언되어 있다.

함수 :   struct passwd * getpwuid (uid_t uid)

이 함수는 사용자 ID가 uid인 사용자에 대한 정보를 담고 있는 정적으로 할당된 구조체를 가리키는 포인터를 리턴한다.  이 구조체는 getpwuid의 연속적인 호출에 의해서 덧씌워진다.  널 포인터값은 데이터베이스에 사용자 ID가 uid인 사용자가 없다는 말이다.

함수 :   struct passwd *getpwnam (const char *name)

이 함수는 사용자 이름이 name인 사용자에 대한 정보를 담고 있는 정적으로 할당된 구조체를 가리키는 포인터를리턴한다.  이 구조체는 getpwnam의 연속적인 호출로 덧씌워질 것이다.  널 포인터값은 사용자의 이름이 name인 사용자가 데이터베이스에 없다는 말이다.

25.12.3  모든 사용자 리스트를 검색하기

이 절은 한 번에 한 사용자씩, 시스템에 있는 모든 사용자들의 리스트를 읽을 수 있기 위해서는 프로그램에서 어떻게 해야하는지를 설명한다. 이곳에 설명된 함수는 헤더파일 `pwd.h'에 선언되어 있다. 당신은 특정한 파일로부터 사용자 엔트리들을 읽기위해서 fgetpwent 함수를 사용할 수 있다.

함수 :   struct passwd * fgetpwent (FILE *stream)

이 함수는 stream 으로부터 다음 사용자 엔트리를 읽고 그 엔트리를 가리키는 포인터를 리턴한다. 그 구조체는 정적으로 할당되었고, fgetpwent의 연속적인 호출로 덧씌워진다.  그러므로, 만일 당신이 그 정보를 저장하길 원한다면 구조체의 내용을 다른곳에 복사해 놓아야만 한다.  stream은 표준 password 데이터베이스 파일과 같은 형식의 파일이어야만 한다.이 함수는 시스템 V 로부터 왔다.

사용자 데이터베이스의 모든 엔트리들을 검색하는 방법에는 setpwent, getpwent, 그리고 endpwent 가 있다.

함수 :   void setpwent (void)

이 함수는 getpwent 가 사용자 데이터베이스를 읽기 위해서 사용하는 스트림을 초기화한다.

함수 :   struct passwd * getpwent (void)

getpwent 함수는 setpwent 에 의해 초기화된 stream으로부터, 다음 엔트리를 읽고 엔트리를 가리키는 포인터를 리턴한다. 그 구조체는 정적으로 할당되었고 getpwent의 연속적은 호출에 의해서 덧씌워진다.  그래서 만일 당신이 그 정보를 저장하기를 원한다면 구조체의 내용을 다른곳으로 복사해놓아야만 한다.

함수 :   void endpwent (void)

이 함수는 getpwent에 의해 사용된 내부 스트림을 닫는다.

25.12.4  사용자 엔트리를 기록하기

함수 :   int putpwent (const struct passwd *p, FILE *stream)

이 함수는 표준 사용자 데이터베이스 파일에서 사용된 형식으로, 스트림 stream에 사용자 엔트리 *p를 기록한다.  성공하면 리턴값은 0이고 실패하면 0이 아닌값이다. SVID 와의 호환성을 위해서 이 함수는 존재한다.  우리는 구조체 struct passwd 가 위에 설명된 표준적인 것을 제외하고는 부가적 으로 아무런 멤버들을 가지지 않았다고 예상이 되는 경우에만 이 함수를 사용하고, 그렇지 않은 경우들에는 이 함수를 사용하지 말 것은 권장한다; 전형적인 유닉스 데이터베이스와 더불어 사용자에 대한 다른 확장된 정보를 합병하는 시스템에서, 이 함수를 사용해서 엔트리를 더하는 것은 중요한 정보를 생략하게 되는 것이다.

함수 putpwent 는 `pwd.h'에 선언되어 있다.

25.13  그룹 데이터 베이스

이 절은 등록된 그룹들의 데이터베이스를 어떻게 검색하고 찾을것인지에 대한 모든 것이 설명되어 있다.  대부분의 시스템에서 데이터베이스 그 자체는 `/etc/group'에 보존되고 있는데, 어떤 시스템에서 특정한 네트웍 서비스는 그것에 대한 억세스를 제공한다.

25.13.1  그룹을 위한 데이터 구조체

시스템 그룹 데이터베이스를 억세스하기 위한 함수와 데이터구조체는 헤더파일 `grp.h'에 선언되어 있다.

데이터 타입 :   struct group

group 구조체는 시스템 그룹 데이터베이스 안에 엔트리에 대한 정보를 저장하기 위해서 사용된다.  그 구조체는 적어도 다음과 같은 멤버들을 갖고 있다.

char *gr_name	그룹의 이름.

gid_t gr_gid	그룹의 그룹 ID

char **gr_mem	그룹에 있는 사용자들의 이름을 가리키는 포인터의 벡터.  각 사용자 이름은 널문자로 끝나는 문자열이고, 벡터 자체는 끝이 널 포인터로 끝난다.

25.13.2  한 개의 그룹에 대한 상세한 정보를 알아내기

getgrgid 나 getgrnam 을 사용해서 정해진 그룹에 대한 정보를 그룹 데이터베이스에서 찾아낼 수 있다.  그 함수들은 `grp.h'에 선언되어 있다.

함수 :   struct group * getgrgid (gid_t gid)

이 함수는 그룹 ID 로 gid를 갖고 있는 그룹에 대한 정보를 갖고 있는 정적으로 할당된 구조체를 가리키는 포인터를 리턴한다. 이 구조체는 getgrgid 의 연속적인 호출에 의해서 덧씌워질 것이다. 널 포인터는 그룹 ID로 gid 를 가진 그룹이 없다는 말이다.

함수 :   struct group * getgrnam (const char *name)

이 함수는 그룹 이름으로 name을 가진 그룹에 대한 정보를 담고 있는 정적으로 할당된 구조체에 대한 포인터를 리턴한다.  이 구조체는 getgrnam의 연속적인 호출에 의해서 덧씌워진다. 널 포인터는 그룹이름으로 name을 가진 그룹이 없다는 말이다.

25.13.3  모든 그룹에 대한 리스트를 검색하기

이 절은 프로그램에서 한 번에 한 그룹씩, 시스템에있는 모든 그룹에 대한 리스트를 어떻게 읽을 수 있는지를 설명한다.  이곳에 설명된 함수들은 헤더파일 `grp.h'에 설명되어 있다. 특정한 파일로부터 그룹 엔트리들을 읽기 위해서 fgetgrent 함수를 사용할 수 있다.

함수 :   struct group * fgetgrent (FILE *stream)

fgetgrent 함수는 stream으로부터 다음 엔트리를 읽고, 그 엔트리에 대한 포인터를 리턴한다.  그 구조체는 정적으로 할당되었고 fgetgrent 의 연속적인 호출에 의해 덧씌워지기 때문에 만일 당신이 그 정보를 저장하기를 원한다면 그 구조체의 내용을 다른곳으로 복사해놓아야만 한다.  stream은 표준 그룹 데이터파일과 같은 형식을 가진 파일이어야만 한다.

그룹 데이터베이스에 있는 모든 엔터리들을 검색하는 방법은 setgrent, getgrent, 그리고 endgrent 가 있다.

함수 :   void setgrent (void)

이 함수는 그룹 데이터베이스로부터 읽기 위한 스트림을 초기화한다.  getgrent 함수의 호출에서 이 스트림이 사용된다.

함수 :   struct group * getgrent (void)

getgrent 함수는 setgrent 에 의해 초기화된 스트림으로부터 다음 엔트리를 읽고 그 엔터리에 대한 포인터를 리턴한다.  그 구조체는 정적으로 할당되었고 getgrent의 연속적인 호출에 의해서 덧씌워진다.  그래서 만일 당신이 그 정보를 저장하기를 원한다면 그 구조체의 내용을 다른곳으로 복사해놓아야만 한다.

함수 :   void entgrent (void)

이 함수는 getgrent 에 의해 사용된 내부스트림을 닫는다.

25.14  사용자와 그룹 데이터베이스에 대한 예제

다음은 시스템 데이터베이스 질의 함수들을 사용하는 예를 보여주는 예제 프로그램이다.  다음 프로그램은 프로그램을 실행시키고 있는 사용자에 대한 어떤정보를 출력한다.

#include  <grp.h>

#include  <pwd.h>

#include  <sys/types.h>

#include  <unistd.h>

#include  <stdlib.h>

int

main  (void)

{

uid_t  me;

struct  passwd  *my_passwd;

struct  group  *my_group;

char  **members;

/*  사용자 ID에 대한 정보를 얻어라.  */

me  =  getuid  ();

my_passwd  =  getpwuid  (me);

if  (!my_passwd)

{

printf  ("Couldn't  find  out  about  user  %d.\n",  (int)  me);

exit  (EXIT_FAILURE);

}

/*  정보를 출력하라.  */

printf  ("I  am  %s.\n",  my_passwd->pw_gecos);

printf  ("My  login  name  is  %s.\n",  my_passwd->pw_name);

printf  ("My  uid  is  %d.\n",  (int)  (my_passwd->pw_uid));

printf  ("My  home  directory  is  %s.\n",  my_passwd->pw_dir);

printf  ("My  default  shell  is  %s.\n",  my_passwd->pw_shell);

/*  디폴트 그룹 ID에 대한 정보를 얻어라.  */

my_group  =  getgrgid  (my_passwd->pw_gid);

if  (!my_group)

{

printf  ("Couldn't  find  out  about  group  %d.\n",

(int)  my_passwd->pw_gid);

exit  (EXIT_FAILURE);

}

/*  정보를 출력하라.  */

printf  ("My  default  group  is  %s  (%d).\n",

my_group->gr_name,  (int)  (my_passwd->pw_gid));

printf  ("The  members  of  this  group  are:\n");

members  =  my_group->gr_mem;

while  (*members)

{

printf  ("   %s\n",  *(members));

members++;

}

return  EXIT_SUCCESS;

}

다음은 이 프로그램을 통한 출력이다.

I  am  Throckmorton  Snurd.

My  login  name  is  snurd.

My  uid  is  31093.

My  home  directory  is  /home/fsg/snurd.

My  default  shell  is  /bin/sh.

My  default  group  is  guest  (12).

The  members  of  this  group  are:

friedman

tami

26  시스템 정보

26장 시스템 정보

이 장은 사용하고 있는 특정한 기계_즉 하드웨어의 타입, 소프트웨어의 타입, 그리고 개별적인 기계들의 이름_에 대한 정보를 리턴하는 함수를 설명한다.

26.1  호스트 확인

이 절은 당신의 프로그램이 실행되고 있는 특정한 기계(machine)를 어떻게 확인할것인지를 설명하고 있다.  기계의 구분은 인터넷 호스트 이름(Internet host name) 과 인터넷 주소(Internet address)로 한다;  11.5절 [Internet Namespace] 155 페이지 참조. 그 함수들에 대한 프로토타입은 `unistd.h'에 나와있다.  쉘 명령어 hostname과 hostid는 그들을 호출하여 작업을 수행한다.

함수 :   int gethostname (char *name, size_t size)

이 함수는 배열 name에 호스트 기계의 이름을 리턴한다.  size인수는 이 배열의 크기를 바이트 단위로 지정한다.  이 함수는 성공하면 0을 리턴하고, 실패하면 -1을 리턴한다.  GNU C 라이브러리에서, gethostname은 만일 size 인수가 충분히 크지 않다면 실패하기 때문에 그 경우에는 그 배열을 크게해서 다시 그 함수를 호출하라.  다음의 errno는 이 함수를 위해 정의된 에러상황이다.

ENAMETOOLONG

size 인수가 호스트 이름에다 1을 더한 크기보다 작다. 어떤 시스템에서는, 최대 가능한 호스트 이름의 길이가 심볼 MAXHOSTNAMELEN 으로 정해져있다. 그것은 `sys/param.h'에 정의되어 있다.  그것에 맞지않는다면 실패로 처리하고 다시 시도하라.

gethostname은 만일 호트스 이름의 길이가 길어서 짤린다고 하더라도 호스트이름을 name에 저장하고 그것을 가리키는 포인터를 리턴한다.  어떤때는 잘려진 호스트이름으로도 충분할 때가있다. 만일 그렇다면, 그때 발생한 에러코드는 무시할 수 있다.

함수 :   int sethostname (const char *name, size_t length)

sethostname 함수는 length의 길이를 가진 문자열 name으로 호스트 기계의 이름을 설정한다.  오직 특권을 가진 프로세스만이 이 일을 하도록 허락된다.  보통 시스템이 부팅할때 딱 한 번 발생한다. 성공하면 리턴값은 0이고 실패하면 -1이다.  다음의 errno는 이함수를 위해 정의된 에러상황이다.

EPERM	이 프로세스는 특권이 없기 때문에 호스트이름을 설정할 수 없다.

함수 :   long int gethostid (void)

이 함수는 프로그램이 실행되고 있는 기계의 "호스트 ID"를 리턴한다.  관례적으로, 리턴되는 호스트 ID는 보통 기계의 주요한(primary) 인터넷 주소로써, long int로 변환된 것이다.  그러나 어떤기계에서는 각각의 기계에 불변인(hard-coded) 단일한 번호로 의미가 없다.

함수 :   int sethostid (long int id)

sethostid 함수는 id로 호스트 기계의 "호스트 ID"를 설정한다. 오직 특권을 가진 프로세스만이 이러한 일을 하도록 허용된다. 보통 그것은 시스템이 부팅될 때, 딱 한 번 발생한다. 성공하면 리턴값은 0이고 실패하면 -1이다.다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EPERM

이 프로세스는 특권이 없기 때문에 호스트 이름을 설정할 수 없다.

ENOSYS

운영체제가 호스트 ID를 설정하는 것을 지원하지 않는다.  어떤 시스템에서, 호스트 ID는 불변의 단일한 숫자이기 때문에 의미가 없다.

26.2  하드웨어/소프트웨어 타입 확인하기

당신은 당신의 프로그램이 실행되고 있는 컴퓨터의 타입에 대한 정보를 얻기위해서 uname 함수를 사용할 수 있다.  이 함수와 그것과 연관된 데이터타입은 헤더파일 `sys/utsname.h'에 선언되어 있다.

데이터타입 :   struct utsname

utsname 구조체는 uname 함수에 의해 리턴된 정보를 저장하는데 사용된다.  다음과 같은 멤버들을 갖는다.

char sysname[]

사용중인 운영체제의 이름이다.

char nodename[]

이것은 특정한 컴퓨터의 네트웍 이름이다.  GNU라이브러리에서, 이 값은 gethostname에 의해 리턴된 것과 같은 값이다; 26.1절 [Host Identification] 367 페이지 참조.

char release[]

이것은 운영체제의 현재 개정판 번호이다.

char version[]

이것은 운영체제의 현재 버전(version) 이다.

char machine[]

이것은 사용중인 하드웨어 타입에 대한 명세이다.  어떤 시스템은 이 정보를 직접적으로 커널로부터 얻을 수 있는 메카니즘을 제공한다.  이러한 메카니즘이 없는 시스템에서, GNU C 라이브러리는 그 라이브러리가 만들어지고 인스톨 될 때 정해진 형태에 기초하여 이 필드를 채운다. GNU 는 시스템 구성을 설명하기 위해서 세-부분을 사용한다; 세부분은 중앙처리장치, 제조회사, 그리고 시스템-타입이고, 그들은 대쉬로 구분된다.  어떤경우에 그 세부분의 조합된 정보는 유용하지만, 대부분은 실제적으로 유용하지 않고 필요가 없다. 기계에서 그 값이 단지 하드웨어에 대한 명세만을 지원한다면, 그것은 두 개의 부분으로 형태의 이름이 구성된다.

예를들면 다음과 같다:

"sparc-sun",   "i386-anything  ",   "m68k-hp",  "m68k-sony",  "m68k-sun", "mips-dec"

함수 :   int uname (struct utsname *info)

uname 함수는 info가 가리키고 있는 구조체를 운영체제와 호스트 기계에 대한 정보로 채운다.  음이 아닌값은 성공적으로 정보가 저장되었음을 얘기하고 -1은 실패할 경우 리턴된다.  오직 가능한 에러는 EFAULT 밖에 없는데, 그 에러는 항상 발생가능성이 있는 것으로써 간단히 얘기할 수는 없다.

27  시스템 구성 파라미터

27장 시스템 구성 파라미터

이 장에 설명된 함수들과 매크로들은 운영체제의 구성 파라미터에 대한 정보를 제공한다_시스템 구성 파라미터는, 예를들면, 용량제한, 선택적인 POSIX 특징의 존재, 실행 파일들을 위한 디폴트 경로(path) ( 27.12절[String Parameters] 379 페이지 참조.)

27.1  일반적인 용량 제한

POSIX.1 과 POSIX.2 표준은 시스템의 용량 제한들을 나타내는 파라미터의 개수를 정한다.  그 제한들은 주어진 운영체제에서 고정된 상수이거나, 아니면 기계마다 다양할 수 있다.  예를들면, 어떤 제한된 값은 실행할때나, 커널을 다시 재건할 때, 시스템 관리자에 의해서 구성될 수있고, 이것은 응용 프로그램들의 재 컴파일이 요구되지 않는다.

다음의 제한 파라미터들은 파라미터에 단일한 제한값을 갖는, 고정된 시스템일 경우에만 'limits.h'에 정의되어 있다.  만일 시스템이 다른 파일시스템이나 혹은 다른 제한을 갖는 파일들을 허용한다면, 그 매크로는 정의되지 않는다;  특정한 시스템에서 특정한 시간에 적용되는 제한을 찾아내려면 sysconf를 사용하라.  27.4절 [Sysconf] 371 페이지 참조.

그 파라미터 각각은 다른 이름의 매크로를 갖는데, 그것은 어떤 POSIX시스템에서 갖도록 허용되는, 최소한의 제한값이 주어지는, `_POSIX'로 시작하는 문자열이다.  27.5절 [Minimums] 374 페이지 참조.

매크로 :   int ARG__MAX

만일 정의되면,  exec부류 함수들에게 인수로 줄 수 있는 argv와 환경변수들의 결합된 최대길이를 나타냄.  그 최대길이는 불변이다.

매크로 :   int CHILD__MAX

만일 정의되면, 어떤 시간에 같은 실제 사용자 ID로 존재할  수 있는 프로세스의 최대개수를 말함.  그 최대 개수는 불변이다.

매크로 :   int OPEN__MAX

만일 정의되면, 단일한 프로세스가 동시에 개방할 수 있는 파일들의 최대개수를 말한다.  그 최대개수는 불변이다.

매크로 :   int STREAM__MAX

만일 정의되면, 단일한 프로세스가 동시에 개방할 수 있는 스트림의 최대개수를 말한다.  그 최대개수는 불변이다.  7.3절 [Opening Streams] 64 페이지 참조.

매크로 :   int TZNAME__MAX

만일 정의되면, 시간대(time zone)의 불변하는 최대길이를 말한다. 17.2.6 [Time Zone Functions] 241 페이지 참조.

다음 제한 매크로들은 `limits.h'에 항상 정의되어 있다.

매크로 :   int NGROUPS__MAX

한 프로세스가 가질 수 있는 부가적인 그룹 ID들의 최대개수. 이 매크로의 값은 실제로는 최대값의 하한 경계이다.  그것은 당신이 얼마나 많은 부가적인 그룹 ID들을 가질 수 있는지를 알 수 있게 하지만, 어떤 특정한 기계는 그것보다 더 많은 것을 허용하기도 한다. 당신은 어떤 특정한 기계가 당신에게 더 많은 ID들을 허용할 수 있는지에 대해서 알아보기 위해서 sysconf 를 사용할 수 있다.( 27.4절 [Sysconf] 371 페이지 참조.)

매크로 :   int SSIZE__MAX

타입 ssize_t 의 오브젝트안에 넣을 수 있는 가장 큰 값. 효율적이게, 이것은 단일한 명령에서 읽거나 쓸 수 있는 바이트의 개수에 대한 제한이 된다.  이 매크로는 제한이 불변이기 때문에 모든 POSIX 시스템들에서 정의되어 있다.

매크로 :   int RE__DUP__MAX

정규표현식의 `\{min,max\}'의 구성안에 허용되는 반복의 최대갯수. 이 매크로의 값은 실제로는 최대값의 하향 경계선이다. 그것은 당신이 얼마나 많은 반복을 할 수 있는지를 알리지만, 어떤 특정한 기계는 그것보다 더 많은 반복을 허용한다. 어떤 특정한 기계가 더 많은 반복을 허용하는지를 알기 위해서는 sysconf를 사용할 수 있다. (27.4절 [Sysconf] 371 페이지 참조.)  sysconf의 값은 단지 하향 경계의 값을 말해주므로_더 큰값으로 작업할 수 있다. POSIX.2 시스템들에서는 그들에게 어떤 정해진 제한값이 없다고 하더라도 항상 정의되어야 하기 때문에 모든 POSIX.2에서 이 매크로는 항상 정의되어 있다.

27.2  포괄적인 시스템 옵션들

POSIX 는 모든 POSIX 시스템들이 지원하지 않는 시스템-명세 옵션을 정의한다.  그 옵션들은 라이브러리가 아니라 커널에 제공되고 있기 때문에, 간단히 GNU C 라이브러리를 사용해서는 그 기능들이 지원됨을 보증할 수 없다;  당신이 사용중인 시스템에 달려있다.

이 절에 있는 매크로들과 sysconf 함수를 함께 사용해서 주어진 옵션의 유용성을 테스트할 수 있다.  매크로들은 `unistd.h'에 정의되어 있다. 다음 매크로들은, 만일 그 매크로들이 `unistd.h'에 정의되어있다면, 옵션들이 지원되는것이고, 그렇지않다면 옵션이 될 수도 있고 되지않을 수도 있다. 알아내려면 sysconf를 사용하라.  27.4절 [Sysconf] 371 페이지 참조.

매크로 :   int __POSIX__JOB__CONTROL

만일 이 심볼이 정의되면, 시스템이 작업제어를 지원함을 알린다. 그렇지않다면, 한 세션에 있는 모든 프로세스들은 단일한 프로세스 그룹에 소속되어있다.  24장 [Job Control] 335 페이지 참조.

매크로 :   int __POSIX__SAVED__IDS

만일 이 심볼이 정의되면,  실행파일이 set-user-ID 또는 set-group-ID 비트들을 설정하여 실행되기 전에 프로세스의 유효 사용자와 그룹 ID들을 시스템이 기억했다가 나중에 그 값들을 다시 반환함을 의미한다.  이 옵션이 정의되지 않으면, 만일 특권을 가지지 않은 프로세스가 그 유효 사용자와 그룹 ID 를 프로세스의 실제 사용자 또는 그룹 ID로 변경했을 때 그것을 다시 되돌릴수 없다.  25.8절 [Enable/Disable Setuid] 357 페이지 참조.

다음 매크로에서, 만일 그 매크로들이 `unistd.h'에 정의되면, 그 값은 그 옵션이 지원되는지의 여부를 알린다.  -1의 값의 의미는 없음을, 제외한 다른값은 있음을 의미한다.  만일 그 매크로가 정의되지 않았다면, 그 옵션은 지원될 수도 있고 안될 수도 있다;  알아내려면 sysconf를 사용하라. 27.4절 [Sysconf] 371 페이지 참조.

매크로 :   int __POSIX2__C__DEV

만일 이 심볼이 정의되면, 그것은 그 시스템이POSIX.2 C 컴파일러 명령, c89를 가지고 있음을 알린다.  GNU C 라이브러리는 만일 당신이 C 컴파일러를 가지지 않았다면 당신이 그것을 인스톨하지 않았다는 가정으로, 항상 그 값을 1로써 정의한다.

매크로 :   int __POSIX2__FORT__DEV

만일 이 심볼이 정의되면, 시스템이 POSIX.2 포트란 컴파일러 명령 fort77을 가지고 있음을 알린다.  GNU C 라이브러리에서 이 값은 결코 정의되지 않는다.

매크로 :   int __POSIX2__FORT__RUN

만일 이 심볼이 정의되면, 시스템이 포트란 캐리지 제어를 해석하기 위한 명령으로 POSIX.2 asa 명령을 갖고 있음을 지적한다. GNU C 라이브러리는 그 시스템이 무엇을 가졌는지 알지못하기 때문에 이 매크로는 결코 정의하지 않는다.

매크로 :   int __POSIX2__LOCALEDEF

이 심볼이 정의되면, 시스템이 POSIX.2 localedef 명령을 갖고 있음을 알린다. GNU C라이브러리는 그 시스템이 무엇을 가졌는지 알지 못하기때문에 이 매크로는 결코 정의하지 않는다.

매크로 :   int __POSIX2__SW__DEV

이 심볼이 정의되면, 시스템이 POSIX.2 명령들 ar, make 그리고 strip을 가지고 있음을 알린다. GNU C라이브러리에서, ar과 make는 라이브러리를 인스톨 시키기 위해서는 당신이 꼭 갖고 있어야만하고 strip이 없음은 가망없는 일이므로 이 매크로 값을 항상 1로써 정의한다.

27.3  지원되는 POSIX의 버젼은?

매크로 :   long int __POSIX__VERSION

이 상수는 POSIX.1 표준의 버전을 나타낸다. 1990 POSIX.1은 199009L의 값이다. _POSIX_VERSION은 항상 어느 POSIX 시스템에서든지 `unistd.h'에 정의되어 있다.

사용법 노트: `unistd.h' 를 인클루드 함으로써 시스템이 POSIX를 지원하는지의 여부를 테스트하지 말고 _POSIX_VERSION이 정의되었는지를 통해서 그 여부를 테스트하라.  비-POSIX 시스템은, `unistd.h'가 없기 때문에 아마도 실패할 것이다.  컴파일 시간에 POSIX가 지원되는지, 또는 `unistd.h'가 존재하는지의 여부를 당신이 알 수 있는 방법은 없다.

GNU C 컴파일러는 만일 목표 시스템이 POSIX 시스템이라면 심볼 __POSIX__를 미리정의한다.  정의된 __POSIX__를 테스트 해서, 당신이 POSIX 시스템상에서 사용하지 않는 것을 제공했는지, 그러한것을 신뢰성있게 검출할 것이다.

매크로 :   long int __POSIX2__C__VERSION

이 상수는 라이브러리와 시스템 커널이 제공하는 POSIX.2 표준의 버전을 나타낸다. 우리는 POSIX.2 표준의 첫 번째 버전이 어떤 값인지 알지못하기 때문에 그 값은 그 표준이 공식적으로 채택된 연도와 달을 기준으로 한다.

이 심볼의 값은 시스템에 인스톨된 유틸리티에 대한 것은 아무것도 말하지 않는다.

사용법 노트: 당신은 POSIX.1 시스템 라이브러리가 POSIX.2를 지원하는지의 여부를 알기 위해서 이 매크로를 사용할 수 있다. 어떤 POSIX.1 시스템이 `unistd.h'를 갖고있다면,  그 파일을 인클루드하고 정의된 (_POSIX2_C_VERSION) 을 테스트하라.

27.4  sysconf 사용하기

당신의 시스템이 시스템 제한을 구성할 수 있을 때, 당신은 어느 특정한 기계에 적용되는 그 값을 알아내기 위해서 sysconf 함수를 사용할 수 있다.  그 함수와 연관된 파라미터 상수들은 헤더파일 `unistd.h' 에 선언되어 있다.

27.4.1  sysconf의 정의

함수 :   long int sysconf (int parameter)

이 함수는 실행시간 시스템 파라미터에 대해서 질의하기 위해서 사용된다.  그 parameter 인수는 밑에 설명된 심볼들 `_SC_'중 하나가 되어야 한다.  sysconf의 보통의 리턴값은 당신이 요청한 값이고 만일 그 제한이 허용되지 않거나 에러가 발생한 경우에는 -1의 값을 리턴한다. 다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EINVAL	parameter의 값이 유용하지 않다.

27.4.2  sysconf의 parameter를 위한 상수들

다음은 sysconf에 parameter 인수로써 사용하기 위한 기호상수들이다. 그 값들은 모두 정수 상수들이다 (좀더 특별하게 말하면, 열거형 값이다.)

_SC_ARG_MAX

ARG_MAX 에 해당하는 파라미터에 대해 질의한다.

_SC_CHILD_MAX

CHILD_MAX 에 해당하는 파라미터에 대해 질의한다.

_SC_OPEN_MAX

OPEN_MAX에 해당하는 파라미터에 대해 질의한다.

_SC_STREAM_MAX

STREAM_MAX에 해당하는 파라미터에 대해 질의한다.

_SC_TZNAME_MAX

TZNAME_MAX에 해당하는 파라미터에 대해 질의한다.

_SC_NGROUPS_MAX

NGROUPS_MAX 에 해당하는 파라미터에 대해 질의한다.

_SC_JOB_CONTROL

_POSIX_JOB_CONTROL에 해당하는 파라미터에 대해 질의한다.

_SC_SAVED_IDS

_POSIX_SAVED_IDS 에 해당하는 파라미터에 대해 질의한다.

_SC_VERSION

_POSIX_VERSION에 해당하는 파라미터에 대해 질의한다.

_SC_CLK_TCK

CLOCKS_PER_SEC에 해당하는 파라미터에 대해 질의한다; 17.1.1 [Basic CPU Time] 233 페이지 참조.

_SC_2_C_DEV

POSIX.2 C 컴파일러 명령, c89를 시스템이 가졌는지에 대해서 질의한다.

_SC_2_FORT_DEV

시스템이 POSIX.2 포트란 컴파일러 명령, fort77을 가졌는지 여부를 질의한다.

_SC_2_FORT_RUN

시스템이 포트란 캐리지 제어를 해석하기 위한 POSIX.2 asa 명령을 가졌는지 여부를 질의한다.

_SC_2_LOCALEDEF

시스템이 POSIX.2 localedef 명령을 가졌는지 질의한다.

_SC_2_SW_DEV

시스템이 POSIX.2 명령들 ar, make 그리고 strip를 가졌는지 질의한다.

_SC_BC_BASE_MAX

bc 유틸리티에 있는 obase의 최대값에 대해서 질의한다.

_SC_BC_DIM_MAX

bc 유틸리티에 있는 배열의 최대 크기에 대해서질의한다.

_SC_BC_SCALE_MAX

bc 유틸리티에 있는 스케일의 최대 값에 대해서 질의한다.

_SC_BC_STRING_MAX

bc 유틸리티에 있는 문자열 상수의 최대크기에 대해서 질의한다.

_SC_COLL_WEIGHTS_MAX

지역(locale)에서 대조문자열을 정의하는데 사용될수 있는 가중치의 최대수에 대해서 질의한다.

_SC_EXPR_NEST_MAX

expr 유틸리티를 사용할때 괄호안에 충첩된 표현식의 최대갯수에 대해서 질의한다.

_SC_LINE_MAX

POSIX.2 텍스트 유틸리티들이 처리할 수 있는 텍스트 라인의 최대크기에 대해서 질의한다.

_SC_EQUIV_CLASS_MAX

지역(locale) 정의에 있는 LC_COLLATE 범주 `order'에 배정될 수 있는 가중치의 최대수에 대해서 질의한다. GNU C 라이브러리는 곧 지역(locale) 정의를 지원하지 않는다.

_SC_VERSION

라이브러리와 커널이 지원하는 POSIX.1의 버전 번호에 대해서 질의한다.

_SC_2_VERSION

시스템 유틸리티들이 지원하는 POSIX.2 의 버전번호에 대해서 질의한다.

_SC_PAGESIZE

기계의 가상적인 메모리 페이지 크기에 대해서 질의한다. getpagesize도 같은값을 리턴한다.

27.4.3  sysconf의 예제

우리는 일단 당신이 관심을 갖고있는 파라미터에 대한 매크로 정의가 있는지의 여부를 테스트 해보고 없다면 그다음 sysconf를 사용하기를 권장한다.  예를들어, 작업제어가 지원되는지의 여부를 어떻게 테스트하는지에 대한 예제가 다음에 있다:

int

have_job_control  (void)

{

#ifdef  _POSIX_JOB_CONTROL

return  1;

#else

int  value  = sysconf  (_SC_JOB_CONTROL);

if  (value  <  0)

/* 만일 시스템이 고정되어 있다면, 시도를 계속할 수 없다.  */

fatal  (strerror  (errno));

return  value;

#endif

}

다음은 수치적 제한의 값을 얻기 위한 방법이다.

int

get_child_max  ()

{

#ifdef  CHILD_MAX

return  CHILD_MAX;

#else

int  value  =  sysconf  (_SC_CHILD_MAX);

if  (value  <  0)

fatal  (strerror  (errno));

return  value;

#endif

}

27.5  총괄적인 용량 제한들을 위한 최소값

다음은 시스템 제한 파라미터들을 위한 POSIX 최소 하향 한계들을 위한 이름들이다. 그 값들의 유의값(significance)은 당신이 사용하고 있는 시스템이 무엇이든지 상관없이 그 제한을 안전하게 사용할 수 있는 값들이다.

_POSIX_ARG_MAX

이 매크로의 값은 exec 함수에 사용할 수 있는 argv와 환경변수들의 결합된 최대길이를 위한 POSIX에 의해 허용되고 있는 대부분의 제한값이다.  그 값은 4096이다.

_POSIX_CHILD_MAX

이 매크로의 값은 실제 사용자 ID마다 동시에 가질 수 있는 프로세스들의 최개 개수로써 POSIX에 의해 허용되고 있는 대부분의 제한값이다. 그 값은 6이다.

_POSIX_NGROUPS_MAX

이 매크로의 값은 프로세스당 가질 수 있는 부가적인 그룹 ID들의 최대 개수로써 POSIX에 의해 대부분 허용되고 있는 제한값.  그 값은 0이다.

_POSIX_OPEN_MAX

이 매크로의 값은 단일한 프로세스에서 동시에 개방할 수 있는 파일들의 최대갯수로써 POSIX에 의해 대부분 허용되고 있는 제한값.  그 값은 16이다.

_POSIX_SSIZE_MAX

ssize_t 의 오브젝트에 저장될 수 있는 최대값으로써 POSIX에 의해 대부분 허용되고 있는 제한값.  그 값은 32767이다.

_POSIX_STREAM_MAX

이 매크로의 값은 단일한 프로세스가 동시에 개방할 수 있는 스트림의 최대갯수로써 POSIX에 의해 대부분 허용되고 있는 제한값.  그 값은 8이다.

_POSIX_TZNAME_MAX

이 매크로의 값은 시간대의 최대 길이로써 POSIX에 의해 허용되고 있는 대부분의 제한값.  그 값은 3이다.

_POSIX2_RE_DUP_MAX

이 매크로의 값은 정규표현식의 `\{min,max\}'에서 사용되고 있는 숫자로써 POSIX에 의해 허용되고 있는 대부분의 제한값.  그 값은 255이다.

27.6  파일 시스템 용량의 제한

POSIX.1 표준은 파일 시스템의 제한들을 나타내는 파라미터들의 개수를 한정하고 있다. 이것은 보통의 경우는 아니지만, 한 파라미터에 단일한 값을 갖는 고정된 시스템에서 가능한 것이다.  대부분의 시스템에서, 다른 최대 제한을 갖는 다른 파일 시스템 ( 그리고, 다른 파라미터들이나, 심지어 다른 파일들 )이 가능하다.  예를들어, 다른 기계들로부터 파일 시스템들의 어떤 것을 마운트(mount) 하기 위해서 NFS를 사용한다면 매우 가능성있는일이다.

다음 매크로들 각각은 만일 그 파라미터들이 고정된 값을 갖는 시스템이라면 `limits.h'에 정의되어 있다.  만일 그 시스템이 다른 제한들을 갖는 파일시스템이나 파일들을 허용한다면, 그 매크로는 정의되지 않는다; 특정한 파일에 적용되는 제한을 알아니려면 pathconf 나 fpathconf를 사용하라.  27.9절 [Patcconf] 377 페이지 참조. 각 파라미터들은 다른 매크로들을 갖는데, 그 매크로는 POSIX 시스템에서 갖도록 허용되는 제한의 최소값을 갖는 `_POSIX'로 시작되는 이름이다. 27.8절 [File Minimums] 376 페이지 참조.

매크로 :   int LINK__MAX

불변의 시스템은 주어진 파일을 위한 이름들의 개수를 제한한다. 9.3절 [Hard Links] 127 페이지 참조.

매크로 :   int MAX__CANON

불변의 시스템은 입력 편집이 가능할 때 한 라인의 텍스트의 양을 제한한다.  12.3절 [Canonical or Not] 186 페이지 참조.

매크로 :   int MAX__INPUT

불변의 시스템은 입력(input)으써 미리 입력(typed)된 문자들의 총 개수를 제한한다. 12.2 [I/O Queses] 185 페이지 참조.

매크로 :   int NAME__MAX

불변의 시스템은 파일 이름 구성요소의 길이에 대해서 제한한다.

매크로 :   int PATH__MAX

불변의 시스템은 전체 파일이름의 길이에 대해서 제한한다 (그것은 open과 같은 시스템 호출에 주어진 인수이다).

매크로 :   int PIPE__BUF

불변의 시스템은 파이프에 자동적으로 쓰여질 수 있는 바이트의 개수에 대해서 제한한다.  만일 다중 프로세스가 동시에 같은 같은 파이프에 기록하고 있다면, 다른 프로세스들로부터의 출력은 이 크기의 청크(chunks) 에 삽입될 것이다. 10장 [Pipes and FIFOs] 145 페이지 조.

다음은 같은 역할을 하는 매크로의 다른 이름이다.

매크로 :   int MAXNAMLEN

이것은 NAME_MAX 를 위한 BSD 이름이다. `dirent.h'에 정의되어 있다.

매크로 :   int FILENAME__MAX

이 매크로의 값은 파일 이름 문자열의 최대 길이를 나타내는 정수 상수 표현이다. 이것은 `stdio.h'에 정의되어 있다.  PATH_MAX와 달리, 이 매크로는 실제로 제한된 값이 없더라도 정의되어 있다. 그 경우,  그 값은 전형적으로 매우 큰값으로 존재한다. GNU 시스템에서는 항상 그 경우이다.

사용법 노트: 파일 이름을 저장하기 위해서 배열의 크기를 FILENAME_MAX를 사용하지 말아라!  당신이 그렇게 배열을 크게 만드는 것은 가능하지 않다!  대신에 동적 할당을 사용하라( 3장 [Memory Allocation] 21 페이지 참조.)

27.7  파일에 지원되는 선택적 기능들

POSIX는 시스템에서 파일들을 운영하기 위해서 호출하는 시스템-설정 옵션들을 정의한다.  어떤 시스템들은 그 옵션들을 지원하고 다른 것은 지원하지 않는다.  그 옵션들이 라이브러리가 아니라 커널에서 제공되기 때문에, GNU C 라이브러리를 사용해서 사용해서 그 기능둘 중 어느것이 지원되는지 간단히 확인할 수 없다.  그들은 파일 시스템과 기계 사이에서 굉장히 다양할 수 있다.

이 절은 당신의 기계가 특별한 옵션을 지원하는지에 대한 여부를 테스트 할 수 있는 매크로를 설명한다.  만일 주어진 매크로가 `unistd.h'에 정의되어 있다면, 그 값을 통해서 해당하는 기능들을 제공하는지에 대해서 알 수 있다. (-1의 값은 없음을 지적하고; 그외 다른값은 있음을 지적한다.)  만일 그 매크로가 정의되어 있지않으면, 그것은 특별한 파일들이 그 기능을 지원할 수도 있고 지원하지 않을 수도 있음을 의미한다.

GNU C 라이브러리를 지원하는 모든 기계에서는 또한 NFS를 지원하기 때문에, 모든 파일 시스템들이 _POSIX_CHOWN_RESTRICTED 와 _POSIX_NO_TRUNC 기능들을 지원하는지에 대해서 일반적인 구문을 전혀 만들 수 없다.  그래서 GNU C 라이브러리에서 그 매크로들은 결코 정의되지 않는다.

매크로 :   int __POSIX__CHOWN__RESTRICTED

이 옵션이 가능하다면, chown 함수는 특권이 없는 프로세스들이 그 프로세스의 유효 사용자 ID나 부가적인 그룹 ID중의 하나로 파일의 그룹 소유자를 변경하는 것만을 허용하도록 제한한다. 9.8.4절 [File Owner] 135 페이지 참조.

매크로 :   int __POSIX__NO__TRUNC

만일 이 옵션이 효과가 있다면, NAME_MAX 보다 긴 파일 이름 구성요소들은 ENAMETOOLONG 에러를 발생한다.  그렇지 않다면, 너무 긴 파일이름 구성요소는 잘리고 만다.

매크로 :   unsigned char __POSIX__VDISABLE

이 옵션은 터미날 디바이스인 파일에만 오직 유효하다.  만일 이 옵션이 효력을 갖는다면, 특정한 제어문자를 처리할 때 개별적으로 효력이 없게 할 수 있다.  12.4.9절 [Special Characters] 195 페이지 참조.

만일 그 매크로들중 하나가 정의되어 있지 않다면, 그것은 그 옵션이 어떤 파일들에는 효과가 있고 어떤 파일들에는 효과가 없음을 의미한다. 특정한 파일에 대해서 문의하려면, pathconf 또는 fpathconf를 호출하라. 27.9절 [Pathconf] 377 페이지 참조.

27.8  파일 시스템 제한을 위한 최소값들

다음은 위에 설명된 파라미터들에 대한 POSIX 최소 하향 경계들을 나타내는 이름이다.  그 값들은 당신이 사용하고 있는 특정한 시스템에 상관없이 제한들을 안전하게 사용할 수 있다.

_POSIX_LINK_MAX

POSIX에 의해 대부분 허가되고 있는 제한값으로 파일의 링크 개수의 최대값이다.  이 상수값은 8이다.그래서, 당신은 시스템 제한을 시험하지 않고도 한 개의 파일을 위해서 항상 8개의 이름을 만들 수 있다.

_POSIX_MAX_CANON

POSIX에 의해 대부분 허가되고 있는 제한값으로, 터미널 디바이스로부터 한 개의 정규 입력 라인에서 최대한 받아들일 수 있는 바이트의 개수이다.  이 상수의 값은 255.

_POSIX_MAX_INPUT

POSIX에 의해 대부분 허가되고 있는 제한값으로, 터미널 디바이스 입력 큐(또는 선행입력 버퍼)에 들어갈 수 있는 최대 바이트의 개수이다  12.4.4절 [Input Modes] 189 참조. 이 상수의 값은 255이다.

_POSIX_NAME_MAX

POSIX에 의해 대부분 허가되고 있는 제한값으로, 파일 이름 구성에 있는 바이트의 최대 개수이다.  이 상수의 값은 14이다.

_POSIX_PATH_MAX

POSIX에 의해 대부분 허가되고 있는 제한값으로 파일 이름의 최대 바이트 개수이다.  이 상수의 값은 255이다.

_POSIX_PIPE_BUF

POSIX에 의해 대부분 제한되고 있는 제한값으로, 한 파이프에 자동적으로 씌여질 수 있는 바이트의 최대 개수이다.  이 상수의 값은 512이다.

27.9  pathconf 사용하기

당신의 기계가 한 개의 파일 시스템 파라미터에 대해서 여러개의 값을 갖는 다른 파일들을 허용할 때, 당신이 어느 특정한 파일에 적용되는 값을 찾아내기 위해서는 이 절에 설명된 파일들을 사용해야만 한다. 그 함수들과 그와 연관된 파라미터 인수를 위한 상수들은 헤더파일 `unistd.h'에 선언되어 있다.

함수 :   long int pathconf (const char *filename, int parameter)

이 함수는 filename이라는 이름을 가진 파일에 적용되는 제한에 대해서 질의하는데 사용된다.  parameter 인수는 밑에 설명된 `_PC_' 상수중 한 개가 된다.

pathconf로부터의 보통 리턴되는 값은 당신이 요청한 값이다. 어떤 제한으로 강요할 수 없거나 에러가 발생한 경우에는 -1의 값을 리턴한다.  전자의 경우에는 errno가 설정되지 않았고, 후자의 경우에는 문제가 발생된 원인을 지적하기 위해서 errno가 설정되었다.  그래서 이 함수를 안정적으로 사용하기 위한 유일한 방법은 그것을 호출하기 전에 errno를 0으로 저장해놓는 것이다.

파일 이름 구문 에러들 외에도( 6.2.3절 [File Name Errors] 60 페이지 참조.), 다음의 errno는 이 함수를 위해서 정의되었다.

EINVAL	parameter값이 유효하지 않거나, 그 실행이 지정된 파일을 위해서 그 parameter를 지원하지 않는다.

함수 :   long int fpathconf (int filedes, int parameter)

이것은 정보를 요청하기 위한 파일을 정함에 있어서, 파일이름을 사용하는 것이 아니라 개방된 파일 기술자를 사용한다는점을 제외하고는 pathconf와 같다.  다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EBADF	filedes 인수가 유효한 파일 기술자가 아니다.

EINVAL	parameter 의 값이 유효하지 않거나, 그 실행은 지정된 파일을 위해서 그 parameter를 지원하지않는다.

다음은 pathconf 와 fpathconf에 parameter 인수로써 사용할 수 있는 기호 상수들이다.  그 값들은 모두 정수 상수들이다.

_PC_LINK_MAX	LINK_MAX 의 값에 대해서 질의한다.

_PC_MAX_CANON	MAX_CANON 의 값에 대해서 질의한다.

_PC_MAX_INPUT	MAX_INPUT의 값에 대해서 질의한다.

_PC_NAME_MAX	NAME_MAX의 값에 대해서 질의한다.

_PC_PATH_MAX	PATH_MAX의 값에 대해서 질의한다.

_PC_PIPE_BUF	PIPE_BUF의 값에 대해서 질의한다.

_PC_CHOWN_RESTRICTED

_POSIX_CHOWN_RESTRICTED의 값에 대해서 질의한다.

_PC_NO_TRUNC	_POSIX_NO_TRUNC의 값에 대해서 질의한다.

_PC_VDISABLE	_POSIX_VDISABLE의 값에 대해서 질의한다.

27.10  유틸리티 프로그램 용량 제한들

POSIX.2 표준은 유틸리티 동작에 적용되는 시스템 제한을 정해놓았고, 당신은 sysconf를 통해서 그 제한을 억세스할 수 있다. GNU C 라이브러리는 그 제한들을 위해서 매크로를 정의하고, 만일 당신이 그 값을 질의하면 sysconf는 그들의 값을 리턴한다; 하지만 그 값들은 아무런 의미없는 정보이다.  그들은 POSIX.2가 허가하는 가장 작은 값들이다.

매크로 :   int BC__BASE__MAX

bc 유틸리티를 지원하도록 보증되는 obase의 최대값.

매크로 :   int BC__SCALE__MAX

bc 유틸리티를 지원하도록 보증되는 scale의 최대값

매크로 :   int BC__DIM__MAX

bc 유틸리티를 지원하도록 보증되는 한 개의 배열안에 들어갈수 있는 요소들의 최대갯수.

매크로 :   int BC__STRING__MAX

bc 유틸리티를 지원하도록 보증되는 한 개의 문자열안에 들어갈 수 있는 문자들의 최대개수.

매크로 :   int BC__DIM__MAX

bc 유틸리티를 지원하도록 보증되는 한 개의 배열안에 들어갈 수 있는 요소들의 최대 개수.

매크로 :   int COLL__WEIGHTS__MAX

로케일(locale)을 위해서 대조문자열을 정의하는데 사용될 수 있는 가중치의 최대값.

매크로 :   int EXPR__NEST__MAX

expr 유틸리티에 의해서 괄호안에 중첩될 수 있는 표현식의 최대 개수.

매크로 :   int LINE__MAX

텍스트-지향 POSIX.2 유틸리티들이 지원할 수 있는 최대 텍스트 라인. (만일 당신이 그 유틸리티의 GNU 버전을 사용하고 있다면, 가상 메모리를 사용함으로써 강요된 것을 제외하고는 실제로 아무런 제한이 없지만, 그 라이브러리가 당신에게 이것에 대해서 알릴 수 있는 아무런 방법이 없다. )

매크로 :   int EQUIV__CLASS__MAX

로케일(locale) 정의에서 LC_COLLATE 범주 `order' 키워드의 엔트리에 배정될 수 있는 가중치의 최대값.  GNU C 라이브러리는 곧 로케일 정의들을 지원하지 않는다.

27.11  유틸리티 제한들을 위한 최대값

_POSIX2_BC_BASE_MAX

POSIX.2에 의해 허용되고 있는 대부분의 제한으로써, bc 유틸리티안에 있는 obase의 최대값이다.  그값은 99.

_POSIX2_BC_DIM_MAX

bc 유틸리티안에 있는 배열의 최대크기를 위해서 POSIX.2에 의해서 대부분 허용되고 있는 제한값. 그값은 2048.

_POSIX2_BC_SCALE_MAX

bc 유틸리티안에 있는 scale의 최대크기를 위해서 POSIX.2에 의해서 대부분허용되고 있는 제한값. 그값은 99.

_POSIX2_BC_STRING_MAX

bc 유틸리티안에 있는 문자열 상수의 최대크기를 위해서 POSIX.2에 의해서 대부분 허용되고 있는 제한값. 그 값은 1000이다.

_POSIX2_COLL_WEIGHTS_MAX

로케일을 위해서 대조 문자열을 정의하는데 사용될 수 있는 가중치의 최대값으로 POSIX.2에 의해서 대부분 허용 되고 있는 제한값.  그 값은 2이다.

_POSIX2_EXPR_NEST_MAX

expr 유틸리티를 사용할 때 괄호안에 중첩된 표현식의 최대개수로써 POSIX.2에 의해서 대부분 허용되고 있는 제한값이다.  그 값은 32이다.

_POSIX2_LINE_MAX

텍스트 유틸리티들이 처리할 수 있는 텍스트 라인의 최대 크기로써 POSIX.2에 의해서 대부분 허용되고 있는 제한값. 그 값은 2048이다.

_POSIX2_EQUIV_CLASS_MAX

로케일 정의에서 LC_COLLATE 범주 `order' 키워드 엔트리에 배정할 수 있는 가중치의 최개값으로써, POSIX.2에 의해서 대부분 허용되고 있는 제한값이다. 그 값은 2.  GNU C 라이브러리는 곧 로케일 정의를 지원하지 않는다.

27.12  문자열-평가 파라미터들

POSIX.2는 confstr 함수를 사용해서 운영체제로부터 문자열-평가 파라미터들을 얻는 방법을 정의한다.

함수 :   size_t confstr (int parameter, char *buf, size_t len)

이 함수는 buf에서 시작되는 메모리 공간에 len길이의 바이트를 가진 문자열을 저장하여, 문자열-평가 시스템 파라미터를 읽는다. parameter 인수는 밑에 설명된 `_CS_'중의 하나가 될 것이다.

confstr로부터 보통 리턴되는 값은 당신이 요청했던 문자열값의 길이다.  만일 당신이 buf를 널 포인터로 하여 그 함수를 호출 한다면, confstr은 그 문자열을 저장하려 시도하지 않는다; 그것은 단지 그 길이를 리턴한다.  에러를 지적하는 값은 0이다.

만일 당신이 요청했던 문자열이 버퍼보다 길다면(그것은, len-1 보다 길다), confstr은 단지 그 만큼( 널 종료 문자를 위한 공간은 남기고.)만 저장한다.  당신은 confsr이 len보다 크거나 같은 값을 리턴했을 때 그와 같은 일이 발생했다는 것을 알 수 있다. 다음의 errno는 이 함수를 위해 정의된 에러상황이다.

EINVAL	parameter 값이 유효하지 않다.

다음은 confstr로 읽을 수 있는 단지 한 개의 파라미터이다.

_CS_PATH

이 파라미터의 값은 실행파일을 찾을 때 디폴트 경로에서 찾도록한다.  이것은 로그인한후에 사용자가 디폴트로 갖는 경로가 된다.

문자열 크기에 대한 제한없이 confstr을 사용하는방법은 그것을 두 번 호출하는 것이다.  첫 번째 호출은 길이를 얻고, 그것에 근거하여 버퍼를 할당하고 다시 confstr을 호출해서 버퍼를 체워라.  다음과 같이.

char  *

get_default_path  (void)

{

size_t  len  =  confstr  (_CS_PATH,  NULL,  0);

char  *buffer  =  (char  *)  xmalloc  (len);

if  (confstr  (_CS_PATH,  buf,  len  +  1)  ==  0)

{

free  (buffer);

return  NULL;

}

return  buffer;

}

부록 A  라이브러리에 있는 C 언어 기능들

부록 A 라이브러리에 있는 C 언어 기능들

C 라이브러리에 의해서 실행되는 기능들중 어떤 것은 C 언어 그 자체의 일부처럼 생각될 수 있다.  그 기능들은 라이브러리 매뉴얼이 아니라, C 언어 매뉴얼에서 문서화되어야 하지만, 우리는 아직 그 C 언어 매뉴얼을 만들지 않았기 때문에, 그 기능들을 이곳에서 설명할 것이다.

A.1  내부적 일관성을 명백하게 테스트하기

당신이 프로그램을 만들 때, "불가능한" 에러나 기본 가설의 위반에 대해서 전략적인 위치에서 그들을 체크하는 것은 좋은 생각이다.  그 체크는 프로그램의 다른 부분들 사이에서 불화합으로 일어나는 문제들을 디버깅하는데 유익하다.

헤더파일 `assert.h'에 정의된, assert 매크로는 프로그램의 에러가 검출된 곳에서 에러메세지를 출력하는 동안 프로그램을 중지시키기에 편리한 방법을 제공한다. 당신이 당신의 프로그램을 디버깅하려할 때, 당신은 정의된 매크로 NDEBUG를 사용해서 재컴파일하면 assert 매크로에 의해서 수행됐던 에러 체크는 디버그하지 않을 수 있다.  이것은 기능무효(disable)로 체크된 프로그램 소스 코드는 변경하지 않는다는 것을 의미한다.

그러나 그 체크를 무효화(disabling)하는 것은 그 프로그램을 확연하게 느리게 만들지 않는한 바람직하지 않다.  또한 누가 그 프로그램을 실행시키던지 발생할 수 있는 가능한 에러에 대해서 대비하도록  좀더 많이 에러를 체크해보는 것이 좋다.  똑똑한 사용자는 어떤 것 이 잘못되었을 때 그것을 지적하지 않고 무의미한 리턴값을 갖는 것 보다는 차라리 프로그램이 파손되는 것을 원할 것이다.

매크로 :   void assert (int expression)

프로그램 안에서 그 expression이 그 순간 0이 아닌값이 되는지를 검증한다.  만일 NDEBUG 가 정의되지 않으면, assert는 expression의 값을 테스트하는데, 그 값이 0이면, assert는 다음 형식처럼 메시지를 프린트한후에 프로그램을 중지시킨다 ( 22.3.4절 [Aborting a Program] 322 페이지 참조)

`file':linenum:  Assertion  `expression'  failed.

표준 에러 스트림 stderr에 메시지를 프린트한다 ( 7.2절 [Standard Streams] 63 페이지 참조.). 파일 이름과 라인 번호는 C 전처리기 매크로 __FILE__ 과 __LINE__ 으로부터 가져오고 그 파일이름과 라인번호는 assert가 씌여져서 호출된곳으로 정한다.  만일  전처리기 매크로 NDEBUG가 `assert.h'가 인클루드된 지점에서 정의되었다면, assert 매크로는 완전히 아무것도 하지않도록 정의된 것이된다.

주의 :  만일 NDEBUG가 영향력을 발휘하는곳에서는 인수 expression 표현식도 평가되지 않는다.  그러므로 부작용이 있을 법한 인수를 넣고 assert를 사용하지 말아라. 예를들어, assert(++i>0); 은 만일 NDEBUG가 정의된곳에서는 i변수가 증가되지 않기 때문에 좋지않다.

사용법 노트:  assert 기능은 내부적 모순을 검출하기 위해서 설계되었다; 그러므로 사용자에 의한 부적당한 사용이나 무효한 입력을 보고하는데는 적당하지 않다.

매크로 assert에 의해 프린트된 진단 메시지에 있는 정보는, 당신의 프로그램을 사용하는 사용자에게 왜 입력이 무효한지 또는 왜 명령이 실행되지 않았는지를 알리는데는 유용하지 않지만, 프로그래머나 당신이, 프로그램에서 나타난 버그를 추적하는데 도움이 된다.  그러므로 당신은 예측할 수 없는 사건에 대한 에러메세지를 출력하는데 assert를 사용할 수 없다.

더 말하자면, assert에 무효한 입력이 주어진다면 당신의 프로그램은 중지된다_그것은 에러메세지를 출력한 후에 0이 아닌 상황(22.3.2절 [Exit Status] 320 페이지 참조.)으로 종료되거나, 또는 다른 명령을 읽거나 다음 입력 파일로 옮긴다. 프로그램에 나타나지 않은 버그 문제에 에러메세지를 출력하는것에 대한 정보는 2.3절 [Error Messages] 17 페이지 참조.

A.2  가변인자 함수들

ANSI C 는 인수의 타입이나 개수를 다양하게 취할 수 있는 함수를 선언하기 위한 구문을 정의한다. ( 그와 같은 함수들은 varargs 함수 또는 variadic 함수라고 부른다. )  그렇지만, 언어 그 자체는 그와 같은 함수들을 위한 메카니즘을 제공하지 않는다;  대신에, `stdarg.h'에 정의된 가변 인수 매크로들을 사용하는 것이다.  이 절은 어떻게 가변인자 함수들을 선언하고, 어떻게 사용하며, 호출할것인지에 대해서 설명한다.

이식성 노트 :  많은 오래된 C 방언들은 `varargs.h'를 사용해서 다양한 개수의 변수를 정의하는 함수 메카니즘으로 유사한 것을 제공하지만, 호환성이 없다.

A.2.1  왜 가변인자 함수들이 사용되는가?

원래 C 함수들은 고정된 개수의 인수들을 취한다.  당신이 함수를 정의할때, 당신은 각 인수의 데이터 타입을 정한다.  함수가 호출될때마다 그전에 정해진 예상된 개수의 인수들이 공급되는데, 그 인수들의 타입은 정해진 것으로 변경될 수 있는 것이다.  그래서, 만일 함수 `foo'가 foo(int, char *); 로 선언된다면, 하나는 숫자 다른 하나는 문자열 포인터인 두 개의 인수를 가지고 foo 함수를 호출해야만 한다.

그러나 어떤 함수들은 정해지지 않은 개수의 인수를 받아들일 수 있는 동작을 수행한다.  어떤 경우, 함수는 한 블록에 그들의 모두를 처리함으로써 여러 개수의 값들을 처리할 수 있다.  예를들어, 정해진 값들의 집합을 저장하기 위해서 malloc으로 일차원 배열을 할당하는 함수를 고려해보자. 이 연산은 숫자에 해당하는 배열의 길이로써 어떤 개수의 값이 있다고 이해한다.   가변 인수 기능이 없다면, 당신은 가능한 배열 크기를 얻어내는 또 다른 한 개의 함수를 정의해야만 한다.

라이브러리 함수 printf ( 7.9절 [Formatted Output] 70 페이지 참조.)는 가변인수가 유용하게 쓰이는 다른 부류의 함수에 대한 예제가 된다. 이 함수는 규정된 템플리트 문자열의 제어하에 인수들( 개수뿐만 아니라 다양한 형을 가질 수 있다)을 프린트한다.   가변인자 함수는 많은 인수들을 처리할 수 있다는 점에서 가변인수 함수를 정의하는 이유가 된다.

open과 같은 함수들은 고정된 개수의 인수들을 취하지만, 때때로 마지막 몇개는 무시된다.   ANSI C 는 그 함수를 가변으로 정의하도록 요구하지만; GNU C 컴파일러와 대부분 다른 C 컴파일러들을 고정된 인수를 취하는 함수처럼 정의하도록 허용하고 선언할때만 가변으로써 그 함수를 선언한다 (또는 그 인수들을 전혀 선언하지 않는다. ).

A.2.2  어떻게 가변인자 함수를 정의하고 사용하는가?

가변인자 함수를 정의하고 사용하는 세가지 단계이다.

□ 인수리스트안에 생략표시 (`. . .')을 사용하고, 가변 인수들을 억세스하도록 특별한 매크로들을 사용하여서, 가변인수 함수를 정의하라. A.2.2.2절 [Receving Arguments] 383 페이지 참조.

□ 그것을 호출하는 모든 파일에서, 생략표시 (`. . .')와 함께 프로토타입을 사용해서 가변으로써 함수를 선언하라.  A.2.2.1절 [Varidic Prototypes] 382 페이지 참조.

□ 고정된 인수들뒤에 가변인수들이 뒤에 나오도록 해서 함수를 호출하라. A.2.2.4 [Calling Variadics] 384 페이지 참조.

A.2.2.1  가변 인수들을 위한 구문

가변 인수를 받아들이는 함수는 올바른 프로토타입으로 선언되어야만 한다. 당신은 보통 고정된 인수들을 사용하고 가변인수들의 가능성을 지적하기 위해서 `. . .'을 취한다.  ANSI C 구문은 `. . .'가 나오기 전에 적어도 한 개의 고정 인수를 필요로 한다.  예를들어,

int

func  (const  char  *a,  int  b,  . . .)

{

. . .

}

고정된 두 개의 인수로써, const char * 와 int 인수를 취하고 int형의 값을 리턴하는 func 함수의 정의이다.  그 두 개의 고정인수 다음에 알려지지 않은 인수들이 몇 개가 따르게 된다.

이식성 노트 :  어떤 C 컴파일러에서, 함수 정의에서 가변인수는 형을 선언하여 등록될 수 없다. 좀더 자세히 말하면, 이 인수들의 타입은 자체-진행(self-promoting)이 되어야만 한다: 즉, 디폴트 진행은 그 타입들을 변경하지 않아야한다.  이것은, float , char(부호가 있던지 없던지), 그리고 short int (부호가 있거나 없거나) 뿐만아니라 배열과 함수들의 타입을 무시한다.

A.2.2.2  인수 값들을 받기

보통 고정된 인수들은 개별적인 이름을 갖고, 당신은 그들의 값을 억세스하기 위해서 그들의 이름을 사용할 수 있다.  그러나 가변 인수들은 아무런 이름을 갖지않는다.  어떻게 당신이 그들을 억세스 할것인가? 그들을 억세스하기 위한 유일한 방법은 그들이 기록된 순서대로, 순차적으로 역세스하고 다음 세가지 단계에서 있는 헤더파일 `stdarg.h'에 선언된 특별한 매크로들을 사용해야만 한다.

1. va_start를 사용해서 va_list 형의 포인터 변수를 인수로써 초기화 한다.  초기화된 인수 포인터는 첫 번째 가변인수를 가리킨다.

2. va_arg를 호출함으로써 가변인수들을 억세스한다.  va_arg를 첫 번째 호출하면, 첫 번째 인수를 리턴하게 되고, 다음 호출은 두 번째 인수를 리턴하고... 그렇게 진행이 된다.

당신이 만일 남겨진 가변인수를 무시하기를 원한다면 언제든지 멈출 수 있다.  호출로 공급된 인수들보다는 소수의 인수들을 억세스하는 함수를 위해서 아주 좋지만, 만일 당신이 너무 많은 인수들을 억세스하려 시도 한다면 당신은 쓰레기 값을 얻게 될 것이다.

3. va_end를 호출해서 포인터 변수인 인수를 끝냈음을 알려라.

(실제로, 대부분 C 컴파일러에서, va_end의 호출은 아무일도 하지않고 당신은 그것을 실제로 호출할 필요가 없다.  이것은 GNU C 컴파일러에서는 항상 참이다.  그러나 누군가 당신의 프로그램을 독특한 컴파일러에서 컴파일하는 경우라면 va_end를 호출해야만 할 것이다.

va_start, va_arg 그리고 va_end에 대한 완전한 정의는 A.2.2.5절 [Argument Macros] 384 페이지를 참조하라. 단계 1과 3은 가변 인수를 받아들이는 함수에서 반드시 수행되어야만 한다. 그렇지만, 당신은 다른 함수에 인수로써 va_list 변수를 줄 수 있고 전부 또는 단계 2를 수행할 수 있다.

당신은 단일한 함수 호출에서 여러번 세 단계의 전부를 반복해서 수행 할 수 있다.  만일 당신이 가변 인수를 무시하기를 원한다면, 세단계를 하지 않을 수 있다. 만일 당신이 원한다면 포인터 변수인 한 개의 인수보다 더 많은 것을 가질 수 있다.  당신은 당신이 원할 때 va_start로 각 변수를 초기화 할 수 있고, 그러고 나면 당신이 원하는 각각의 포인터 인수를 추출할 수 있다.  각 포인터 변수인 인수는 인수 값들의 같은 집합을 통해서 진행되지만, 그것은 자신만의 페이스(pace)를 갖는다.

이식성 노트: 어떤 컴파일러로, 당신이 서브루틴(subroutine)에 인수로써 포인터 변수를 사용한다면, 당신은 서브루틴이 리턴한 후에 같은 포인터 변수인 인수를 사용해서 기록하지 않아야만 한다.  완벽한 이식성을 위해서, 당신은 va_end에 그것을 주어야한다.  이것은 실제로 ANSI C의 권장사항이지만, 대부분 ANSI C 컴파일러는 다행이 상관없이 작업한다.

A.2.2.3  어떻게 많은 인수들이 공급되는가?

가변 인수들의 타입과 개수를 알 수 있는 일반적인 방법은 없다.  그래서 누구든 그 가변인수가 얼마나 많은 인수들을 가졌고, 그것이 무슨 종류인지를 알아낼 수 있는 특별한 방법의 함수를 고안해야한다.  그것은 가변인수 함수의 호출 관습에 적당하게 정의되어야 하고, 그것에 근거하여 프로그램에서 가변 인수 함수를 호출해야 한다.

호출관습의 한가지는 한 개의 고정된 인수를 사용해서 가변인수의 개수를 공급하는 것이다.  이 방법은 공급된 가변 인수들이 모두 같은 형일 경우에 가능한 방법이다. 그와 유사한 방법으로는 가변인수가 공급될 가능성에 대한 정보를 한 비트에 담은, 비트 마스크가될 고정인수를 인수중에 하나로 갖는 것이다. 당신은 미리 선언된 시퀀스안에 있는 비트들을 테스트할 수 있다;  만일 그 비트가 설정되면,다음 인수의 값을 추출하는것이고, 그렇지않다면, 디폴트값을 사용하는 것이다. 고정된 인수는 가변 인수들의 개수와 타입, 이둘을 지정하는 패턴으로써 사용될 수 있다.  printf에서 형식화된 문자열 인수는 이것의 한 예가된다. (7.9.7절 [Formatted Output Functions] 76 페이지 참조.

다른 가능성은 마지막 가변 인수로써 "끝 표시"값을 사용하는 것이다. 예를들어, 예측할 수 없는 포인터 인수들의 개수를 처리하는 함수가 있다면, 널 포인터는 인수 리스트의 끝을 지적할 것이다. (이것은 널 포인터가 함수에게 의미있는 값이 아니라고 가정한다.)  execl 함수는 이 방법으로 작업한다;  23.5절 [Executing a File] 327 페이지 참조.

A.2.2.4  가변인수 함수들을 호출하기

당신이 가변인수 함수를 호출할 때 특정한 어떤 것을 써서는 안된다. 단지 괄호안에 보통, 콤마에 의해 분리된 인수들(가변으로써, 요청된 인수)만 사용하라.  그러나 당신은 프로토타입으로 그 함수를 선언함으로써 준비하고, 그 인수의 값들이 어떻게 변환되는지를 알아야만 한다.

원칙적으로, 가변으로써 정의된 함수들은 당신이 그들을 호출할때마다 함수 프로토타입을 사용해서 가변이 되도록 선언되어야한다.  (A.2.2.1 [Variadic Prototypes] 382 페이지 참조.) 이것은 함수가 가변 인수 또는 고정된 인수를 취하는지의 여부에 의존하여 함수에 인수값들을 부여하는 다른 호출 관습을 가진 C 컴파일러 때문이다.

실제로, GNU C 컴파일러는 항상 당신이 가변인수 또는 요청된 인수를 사용하는지에 상관없이 같은 방법으로 인수형의 주어진 집합을 부여한다. 그래서, 인수들의 타입이 자체-진행인 동안, 당신은 그들의 선언을 안전하게 생략할 수 있다.  보통 가변함수를 위해서 인수의 형을 선언하는 것은 좋은 방법이고, 모든 함수들을 위해서는 물론 당연한 것이다.  그런데 몇 개의 함수는 그렇지 않은 경우가 있다_예를들어, open과 printf

함수의 프로토타입이 가변인수들의 타입을 정하지 않았을 때, 가변인수 함수를 호출하면, 함수의 가변인수값들은 디폴트 인수 승급이 수행된다. 디폴트 인수 승급이란 char 또는 short int (부호가 있던지 없던지)의 형을 가진 오브젝트들은 int 나 unisgned int로 승급되고; float의 형을 가진 오브젝트들은 double로 승급되는 것을 말한다.  그래서, 가변인수에 char형의 값을 넣으면, 그것은 int로 승급되고, 그 함수는 va_arg(ap, int)과 함께 그것을 얻을 것이다.

고정 인수들은 보통 함수의 원형을 통해서 제어된다: 인수 표현식은 마치 그형의 변수로 할당되었던 것 처럼 선언된 인수의 형으로 변환된다.

A.2.2.5  인수 억세스 매크로들

다음은 가변 인수들을 가져오기 위해서 사용되는 매크로에 대한 기술이다. 그 매크로들은 헤더파일 `stdarg.h'에 정의되어 있다.

데이터 타입 :   va__list

va_list는 포인터 변수들인 인수를 위해서 사용된다.

매크로 :   void va__start (va_list ap, last_required)

이 매크로는 현재 함수의 가변 인수들의 첫 번째를 가리키는 포인터 변수 ap를 초기화한다; lastrequired는 함수에 있는 마지막 고정인수가 되어야 한다.  `varargs.h'에 있는 va_start의 정의를 변경하려면 A.2.3.1 [Old Varargs] 386 페이지를 참조하라.

매크로 :   type va__arg (va_list ap, type)

va_arg 매크로는 다음 가변 인수의 값을 리턴하고, 다음 인수를 가리키도록 ap의 값을 갱신한다. 그래서, va_arg의 성공적인 사용은 가변 인수들을 성공적으로 리턴한다. va_arg에 의해 리턴된 값의 타입은 호출에서 정했던 타입이다. type은 반드시 실제 인수의 타입과 매치되는 자체-승급 타입 (char나 short int 나 float가 아닌)이 되어야 한다.

매크로 :   void va__end (va_list ap)

이것은 ap의 사용을 끝낸다.  va_end 호출후에, 다음에 같은 ap를 사용해서 va_arg를 호출하면 작업하지 않을 것이다.  당신은 같은 ap 인수를 사용하는 va_start를 호출했던 함수를 리턴하기전에 va_end를 호출해야만 한다. GNU C 라이브러리에서, va_end는 아무일도 하지않기 때문에 이식성의 이유가 아니라면 va_end를 호출할 필요가 없다.

A.2.3  가변인수 함수의 예제

다음은 인수들을 가변적인 개수로 받아들이는 함수에 대한 예이다. 함수의 첫 번째 인수는 리턴된 결과와 합산된, 남겨진 인수들의 개수이다. 이 함수는 가변 인수 기능을 어떻게 사용하는지 설명하는데 충분하다.

#include  <stdarg.h>

#include  <stdio.h>

int

add_em_up  (int  count,...)

{

va_list  ap;

int  i,  sum;

va_start  (ap,  count);

/* 인수 목록을 초기화하라.  */

sum  =  0;

for  (i  =  0;  i  <  count;  i++)

sum  +=  va_arg  (ap,  int);

/* 다음 인수값을 얻어라. */

va_end  (ap);

/*  정리하라. */

return  sum;

}

int

main  (void)

{

/*  이 호출은 16을 출력한다. */

printf  ("%d\n",  add_em_up  (3,  5,  5,  6));

/*  이 호출은 55를 출력한다. */

printf  ("%d\n",  add_em_up  (10,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10));

return  0;

}

A.2.3.1  오래된-형태의 가변인수 함수들

ANSI C 이전에, 프로그래머들은 가변함수들을 쓰기위해서 완전히 다른 기능을 사용했었다.  GNU C 컴파일러는 여전히 그것을 지원한다; 현재, 그것은 ANSI C가 여전히 일반적이지 않기 때문에, ANSI C 기능보다는 더 이식성이 있다.  오래된-형태의 가변인수 함수를 정의하고 있는 헤더파일은 `varargs.h'라고 불린다.

`varargs.h'를 사용하는 것은 `stdarg.h'를 사용하는것과 거의 같다.  가변인수 함수를 어떻게 호출하는지에 대한 것은 거의 다름이 없다;  A.2.2.4절 [Calling Variadics] 384 페이지 참조.  오직 유일한 차이는 그들을 정의하는 방법이다.  무엇보다도, 당신은 오래된 형태의 비- 프로토타입 구문을 사용해야만 한다.  다음처럼:

tree

build (va_alist)

va_dcl

{

두 번째로, va_start에 오직 한 개의 인수만을 넣어야한다. 다음처럼:

va_list p;

va_start (p);

오래된 형태의 가변인수 함수들을 정의하기 위해서는 특정한 매크로가 사용된다:

매크로 :   va__alist

이 매크로는 가변인수 함수에 있는 고정 인수이름 목록을 나타낸다.

매크로 :   va__decl

이 매크로는 가변인수 함수를 위한 인수들이나 또는 암묵적인 인수를 선언한다.

매크로 :   void va__start (va_list ap)

`varargs.h'에 선언된 이 매크로는 현재 함수의 첫 번째 인수를 가리키는 포인터로 포인터 변수인 인수를 초기화한다.

다른 인수 매크로, va_arg 와 va_end는 `varargs.h' 와 `stdarg.h'의 것이 서로 같다;  A.2.2.5절 [Argument Macros] 384 페이지 참조. 동일한 컴파일 단위에서 `varargs.h'와 `stdarg.h'가 둘다 인클루드되어서는 안된다; va_start가 서로 충돌하게 된다.

A.3  널 포인터 상수

널 포인터 상수는 어느 실제 오브젝트를 가리키고 있는 것이 아니라는 것을 말한다.  당신은 void * 형을 가진 포인터 변수로 그것을 할당할 수 있다.  널 포인터 상수를 사용하기 위한 좋은 방법은 NULL을 사용하는 것이다.

매크로 :   void * NULL

이것은 널 포인터 상수이다.

널 포인터 상수로써 0 또는 (void *)0을 사용할 수 있지만, NULL을 사용하는 것이 상수를 사용하는 목적으로 좀더 분명하게 만들어졌기 때문에 더 깨끗하다.  만일 당신이 함수 인수로써 널 포인터 상수를 사용한다면, 그 함수가 가진 프로토타입 선언과 완전히 맞도록 널 포인터 상수를 사용해야한다.  그렇지 않다면, 만일 목표 머쉰(machin)이 두 개의 다른 포인터 표현을 가진다면, 컴파일러는 그 인수를 위해 사용할 표현이 무엇인지 알지 못한다.

당신은 적당한 포인터 타입으로 상수를 캐스트(cast)함으로써 그 문제를 피할 수 있지만, 우리는 그보다는 당신이 호출할 함수를 위해서 프로토타입에 더할 것을 권장한다.

A.4  중요한 데이터 타입들

C에서 두 개의 포인터를 뺀 결과는 항상 정수이지만, 정밀한 테이타 타입은 C 컴파일러에 따라 다르다..  그처럼 데이터 타입에 따라서, sizeof의 결과 또한 컴파일러에 따라서 다르다.  ANSI 는 그 두 개의 데이터 타입을 위해서 표준 이름을 정의하기 때문에, 당신은 이식성을 위해서 그 데이터 타입을 사용할 수 있다.  그들은 헤더파일 `stddef.h'에 정의되어 있다.

데이터 타입 :   ptrdiff__t

이것은 두 개 포인터를 빼서 나온 결과의 부호화된 정수 타입이다. 예를들어, char *p1, *p2; 이렇게 선언되었다면; 표현식 p2 - p1은 ptrdiff_t 형의 결과값을 갖는다.  이것은 아마도 표준 부호형 정수 타입들(short int, int 또는 long int)중의 하나가 될 것이지만, 표준 부호형 정수 타입들이 이러한 목적으로 존재한다면 비표준 타입이 되어버릴 것이다.

데이터 타입 :   size__t

이것은 오브젝트(objects)의 크기를 나타내는데 사용하는 비부호형 정수 타입이다.  sizeof 연산의 결과가 이 타입을 갖고, malloc( 3.3절 [Unconstrained Allocation] 22 페이지 참조.) 그리고 memcpy(5.4절 [Copying and Concatenation] 46 페이지 참조.)과 같은 함수들은 오브젝트의 크기를 정하는데 이 타입의 인수를 받아들인다.

사용법 노트: size_t는 오브젝트의 크기를 저장하는 인수나 변수를 선언하는데 좋은 방법을 제공한다.

GNU 시스템에서 size_t는 unsigned int 또는 unsigned long int 와 동일하다.  그 타입들은 GNU 시스템 상에서 동일한 특성을 갖고 있고, 그들을 사용할 때 대부분은 그들 사이를 서로 변경시키지않고도 사용할 수 있다. 그렇지만, 그들은 어떤 구문들에서는 차이를 갖기 때문에 다른 데이터타입으로 구분된 것이다.

예를들어, 함수의 원형으로 함수 인수의 타입을 정할 때, 당신이 사용하는 것은 차이가 있다.  만일 시스템 헤더파일이 size_t 타입의 인수를 갖는 malloc 함수를 선언하고, 당신이 unisgned int의 타입을 갖는 malloc를 선언했을 때, 만일 size_t가 당신의 시스템에서 unisgned long int형으로 발생한다면, 당신은 컴파일 에러를 얻을 것이다.  이러한 문제의 가능성을 피하기 위해서, 함수의 인수나 값은 다른 방법으로 그 형을 선언하기 보다는 size_t의 타입을 갖도록 선언하라.

호환성 노트 :  ANSI C가 나타나기 전에 C는 포인터 뺄셈의 결과를 나타내기 위해서 int를 사용하고 오브젝트의 크기를 표현하기 위해서 unsigned int를 사용했었다.  그들은 size_t나 ptrdiff_t를 정의할 필요가 없었다.  유닉스 시스템들은 `sys/types.h'에 size_t를 정의해놓았지만, 그 정의는 보통 signed 형을 말한다.

A.5  데이터 타입 측정

당신이 당신의 프로그램에서 사용되는 오브젝트의 적당한 C 데이터 타입을 선정할 때 대부분 그것이 얼마나 많은 비트들을 사용하고 그 오브젝트가 어떻게 표현되는지에 관심을 가질 필요가 없다.  당신이 그와 같은 정보를 필요로 할 때, C 언어 자체는 그것을 얻을 수 있는 방법을 제공하지 않는다. 헤더파일 `limits.h'와 `float.h'에 포함된 매크로들은 당신에게 그것에 관한 세심한 정보를 줄 것이다.

A.5.1  정수 테이타 타입의 너비 계산하기

정수 타입이 얼마나 많은 비트로 구성되었는지 알 필요가 있는 프로그램은 비트 벡터(bit vector)로써 lont int의 배열을 사용하는 경우가 일반적이다.

당신은 vector[n / LONGBITS] & (1 << (n % LONGBITS)) 로 인덱스 N을 구성하는 비트를 억세스할 수 있다. LONGBITS는 long int를 구성하는 비트의 개수로 당신이 정의해서 공급하라.

C 언어에서 정수 데이터 타입에 있는 비트의 수에 대한 정보를 당신에게 줄 수 있는 연산자는 없다.  그렇지만 헤더파일 `limits.h'에 정의된 매크로 CHAR_BIT를 사용해서 그것을 계산할 수 있다.

CHAR_BIT

이것은 한 개의 char안에 있는 비트의 개수를 말하는데, 대부분의 시스템에서 그값은 8이다.  그 값은 int형을 갖는다.  당신은 다음처럼 어느 데이터 타입 type안의 비트의 개수를 계산할 수 있다

A.5.2  정수 타입의 범위

당신이 0에서 일백만 사이에 있는 정수의 값을 저장할 필요가 있다고 가정해보자.  당신이 사용할 수 있는 가장작은 타입은 무엇인가?  그것을 정하는데 일반적인 규칙은 없다;  그것은 C 컴파일러와 목표 머쉰(machine)에 의존한다.  당신은 타입을 결정하기 위해서 `limits.h'에 있는 매크로 `MIN'과 `MAX'를 사용할 수 있다.

각각의 부호가 있는 정수 타입은 그것이 저장할 수 있는 가장 작은 값과 가장 큰 값을 나타내는 한쌍의 매크로를 갖는다.  부호가 없는 정수 타입은 최대의 값을 나타내는 한 개의 매크로를 갖는다;  최소값은 물론 0이다. 그 매크로들의 값은 모두 정수 상수 표현이다.  다른 타입들을 위한 `MAX' 와 `MIN' 매크로들은 매크로에 의해 설명된 같은 타입의 값을 갖는다_그래서 ULONG_MAX는 unisgned long int 의 타입을 갖는다.

SCHAR_MIN

signed char에 의해 표현될 수 있는 최소값.

SCHAR_MAX

UCHAR_MAX

signed char 와 unsigned char에 의해 표현될 수 있는 최대값.

CHAR_MIN

char 에 의해 표현될 수 있는 최소값.  여기서 만일 char에 부호가 있는것이라면 SCHAR_MIN과 같고, 그렇지 않다면 0이다.

CHAR_MAX

char 에 의해 표현될 수 있는 최대값.  char가 부호가 있는 것이라면 SCHAR_MAX와 같고, 그렇지않다면 UCHAR_MAX와 같다.

SHRT_MIN

signed short int에 의해 표현될 수 있는 최소값.  GNU C 라이브러리가 실행되는 대부분의 컴퓨터에서, short int 는 16-비트로 동일하다.

SHRT_MAX

USHRT_MAX

signed short int 와 unsigned short int에 의해 표현될 수 있는 최대값.

INT_MIN

signed int 에 의해 표현될 수 있는 최소값.  GNU C 시스템이 실행되는 대부분의 컴퓨터에서, int 32-비트로 동일하다.

INT_MAX

UINT_MAX

signed int 와 unsigned int 에 의해 각각 표현될 수 있는 최대값들.

LONG_MIN

signed long int 에 의해 표현될 수 있는 최대값. GNU C 시스템이 실행중인 대부분의 컴퓨터에서, long 정수들은 int와 동일한 크기를 갖고 32-비트로 동일하다.

LONG_MAX

ULONG_MAX

signed long int 와 unsigned long int에 의해 표현될 수 있는 최대값.

LONG_LONG_MIN

signed long long int에 의해서 표현될 수 있는 최소값. GNU C 시스템이 실행되는 대부분의 컴퓨터에서,  long long 정수들은 64-비트로 동일하다.

LONG_LONG_MAX

ULONG_LONG_MAX

signed long long int 와 unsigned long long int에 의해 표현될 수 있는 최대값.

WCHAR_MAX

wchar_t에 의해 표현될 수 있는 최대값.  18.4절 [Wide Char Intro] 252 페이지 참조.

헤더파일 `limits.h'는 또한 다양한 운영체제와 파일 시스템 제한들을 파라미터화한 부가적인 상수들을 정의하고 있다. 그 상수들은 27장[System Configuration] 369 페이지에 설명되어 있다.

A.5.3  부동형 매크로들

플로팅 포인트 수의 구체적인 표현 방법은 기계마다 다르다.  플로팅 포인트 수들은 내부적으로 근사치로써 표현되기 때문에, 플로팅 포인트 데이터를 다루기 위한 알고리즘은, 때때로 기계의 플로팅 포인트 표현에 대한 자세한 정밀도를 참작하는데 사용된다.

C 라이브러리에서 어떤 함수들은 이러한 정보를 필요로 한다; 예를들어, 플로팅 포인트 숫자들을 읽거나 출력하는 알고리즘 (7장 [I/O on Stream] 63 페이지 참조) 과 삼각함수를 계산하기 위한 알고리즘 그리고 무리수 함수들을 위한 알고리즘들은 정확성의 상실이나 반올림-에러를 피하기 위해서 그 정보를 사용한다.  수학적인 분석 기술을 다루는 프로그램들은 에러 경계를 계산하거나 최소화하기위해서 이 정보를 필요로 한다. 헤더파일 `float.h'는 당신의 기계에 의해서 사용되는 형식을 설명한다.

A.5.3.1  플로팅 포인트 표기 개념

이 절은 플로팅 포인트 표현법을 설명하기 위한 용어들을 설명한다. 당신은 플로팅 포인트 숫자들을 표현하는 과학적인 표기 또는 지수적인 표기의 개념에 대해서 이미 친숙할 것이다.  예를들어, 숫자 123456.0은 가수가 1.23456이고 베이스가 10으로 5승임을 가리키는, 1.234546e+05의 지수적 표기로써 표현될 수 있다. 더 형식적으로, 플로팅 포인트 수의 내부적 표현은 다음의 파라미터로써 특징을 나타낼 수 있다.

□ 부호는 -1 또는 1 이다.

□ 지수를 위한 베이스(base) 또는 기수(radix)는 1보다 큰 정수이다. 이것은 특정 표기에 따라서 다른 상수이다.

□ 베이스에 몇승인지를 나타내는 수가 지수이다.  지수의 상한과 하한은 특정한 표기에 따라서 다른 상수이다.

때때로, 플로팅 포인트 수를 표현하고 있는 실제 비트들에서, 지수를 항상 unsigned로 표현되도록 만들기 위해서 그것에 상수를 더하여 부호의 의미를 부여한다.  이것은 만일 당신이 직접 플로팅 포인트 수를 구성하고 있는 비트 영역들의 어떤 부분을 사용할 필요가 있는 경우에만 중요하다. GNU 라이브러리는 이러한 것을 지원하지 않는다.  그러므로 다음부터는 이것에 대한 논의는 무시될 것이다.

□ 가수부(mantissa) 또는 유효수(significand)는 플로팅 포인트 숫자를 이루는 한부분으로써 부호가 없는 정수이다.

□ 가수부의 정밀도.  만일 어떤 플로팅 표현에서 베이스(base)가 b라고 했을 때, 정밀도는 베이스-b를 기반으로 가수부안에 들어가있는 숫자들의 개수이다. (즉.. 가수부가 몇 개의 비트로써 표현되느냐를 정밀도라고 한다.) 이것은 특정한 표현에 따라 다른 상수이다.

많은 플로팅 포인트 표현들은 가수부안에 암묵적으로 숨겨진 비트를 가지고 있다.  이것은 가수부안에서 실질적으로는 표현되지만 그 값이 항상 1로 되어있기 때문에 메모리에는 저장되지 않는다. 정밀도 형태는(위를 보라) 숨겨진 비트들도 표함한다. 다시, GNU 라이브러리는 플로팅 포인트 표현을 위해서 그와 같은  저수준의 관점을 다루는 기능을 제공하지 않는다.

플로팅 포인트 수의 가수부는 지수의 몇승을 가진 함축적인 소수부로써 표현된다. 그래서 가장 크게 표현할 수 있는 가수부가 이 정밀도보다 적은 것이면, 소수의 값은 항상 1보다 적다.  플로팅 포인트 수의 수학적인 값은 소수, 부호 그리고 베이스의 몇승임을 나타내는 지수로써 만들어진다.

b가 베이스라고 했을 때, 소수가 적어도 1/b라면, 플로팅 포인트 수가 일반화되었다고 말한다. 바꾸어 말하면, 가수부에 지수승이 곱해지면 맞추기에 너무 크게 될 것이다.  비-일반화된 수들은 디노멀(denomal)이라고 부른다;  그들은 플로팅 포인트 수가 일반적으로 저장될 수 있는 정밀도 보다도 작은 정밀도를 갖고 있다.

만일 그 수가 일반화되지 않았다면, 가수부를 base로 나눈다음 나온 지수로부터 1을 뺄 수 있고, 그러면 같은 값을 가진 다른 표기형식의 플로팅 포인트를 얻게 된다.  그 수가 일반화될때까지 반복적으로 위와 같은 일을 하면 일반화된 플로팅 포인트 수가 나오게 된다. 두 개의 다른 일반화된 플로팅 포인트 수들은 값이 같을 수 없다.

( 이 규칙에는 예외가 있다:  만일 가수부가 0이라면, 그것은 일반화된 것으로 간주된다.  특정한 기계에서 발생할 수 있는 예외상황이란, 지수부가 그 표기법으로 저장할 수 있기에는 너무 작은 경우이다.  그러면 지수부로 부터 1을 빼는 것이 불가능하기 때문에 , 소수가 1/b보다 적은 소수부라면 일반화될 수 있을 것이다.)

A.5.3.2  플로팅 포인트 파라미터들

다음 매크로 정의들은 헤더파일 `float.h'에 있다.  `FLT_'로 시작하는 매크로들은 float 타입에 관한것이고, `DBL_'로 시작되는 매크로들은 double 타입에 관한것이고 `LDBL_'로 시작되는 매크로들은 long double 타입에 관한 것이다.  (현재 GCC 는 분리된 데이터 형으로써 long double를 지원하지 않기 때문에, `LDBL_'상수들을 위한 값들은 double형을 위한 상수에 해당되는 값과 같다.)

그 매크로들 중에서, 오직 FLT_RADIX는 상수 표현식이 되도록 보증된다. 이곳에 설명된 다른 매크로들은 상수 표현식, `#if'와 같은 전처리 지시자 또는 정적 배열안의 차원을 요구하는 곳에서 사용될 수 없다.

ANSI C 표준이 대부분의 파라미터들을 위한 최소값과 최대값을 정했다고 하더라도, GNU C는 목표 기계의 플로팅 포인트 표현에 따른 값을 사용한다.  그래서 GNU C는 목표 기계가 안정적이라면 ANSI C 요구를 만족시키게 되는 것이다.  실제로, 현재 지원되는 모든 기계들은 안정적이다.

FLT_ROUNDS

이 값은 반올림 형식을 지정하는 값이다.  다음값은 표준 반올림 모드를 나타낸다.

-1              반올림하지 않는다.

0               소수점 뒤를 0으로 만든다.

1               가장 가까운 수로 반올림한다.

2               무한대로 양의 값을 향한다.

3               무한대로 음의 값을 향한다.

이 이외의 값은 기계_의존적인 비표준 반올림 모드를 나타낸다.  대부분의 기계에서, 그 값은 IEEE 표준에 따라서 1로 되어있다.

다음은 FLT_ROUNDS의 값에 따라서 값들이 어떻게 변하는지를 보여주는 테이블이다,

0       1            2            3

1.00000003     1.0     1.0           1.00000012    1.0

1.00000007     1.0     1.00000012    1.00000012    1.0

-1.00000003    -1.0    -1.0         -1.0           -1.00000012

-1.00000007    -1.0    -1.00000012  -1.0           -1.00000012

FLT_RADIX

이것은 지수부의 베이스(base) 또는 기수(radix)의 값이다. 이것은 이 절에 설명된 다른 매크로와는 달리 상수 표현식임이 보장된다.  IBM 360과 그곳에서 파생된 제품을 제외하고는 모든 기계에서 2로 되어있다.

FLT_MANT_DIG

float형에서 가수부를 표현하는데 사용되는 비트수.  다음 표현식은 가수부 숫자들의 제한된 수 때문에 1.0이 나온다(수학적으로는 그것이 될 수 없을 지라도):

float radix = FLT_RADIX;

1.0f + 1.0f / radix / radix /  . . .  / radix

여기서 radix는 FLT_MANT_DIG 번 나타난다.

DBL_MANT_DIG

LDBL_MANT_DIG

이것은 각각 double 과 long double형 각각이 가수부를 표현하는데 사용되는 비트수이다.

FLT_DIG

This is the number of decimal digits of precision for the float data type.  Technically, if p and b are the precision and base (respectively) for the representation,  then the decimal precision q is the maximum number of decimal digits such that any floating point number with q base 10 digits can be rounded to a floating point number with p base b digits and back again, without change to the q decimal digits.

float형에서 유효숫자의 최소개수.

이 매크로의 값은 ANSI C에서, 적어도 6으로 지원되고 있다.

DBL_DIG

LDBL_DIG

FLT_DIG와 유사하지만, double와 long double형을 위한 것이다.  그 매크로의 값은 적어도 10이 되도록 지원된다.

FLT_MIN_EXP

이것은 float형을 위해서 가능한 지수값으로 가장작은 값이다.  더 자세하게는, FLT_RADIX에서 1을 뺀값이 float형으로써 일반화된 플로팅 포인트 수로써 표현될 수 있는 최소 음의 정수이다.

DBL_MIN_EXP

LDBL_MIN_EXP

FLT_MIN_EXP와 유사하지만, double 와 long double를 위한 것이다.

FLT_MIN_10_EXP

This is the minimum negative integer such that 10 raised to this power minus 1 can be representedas a normalized floating point number of type float.  This is supposed to be -37 or even less.

지수부의 최소범위.  -37이거나 그보다 적다.

DBL_MIN_10_EXP

LDBL_MIN_10_EXP

double와 long double형 각각을 위한 것으로, 지수가 나타낼 수 있는 최소범위.

FLT_MAX_EXP

This is the largest possible exponent value for type float.  More precisely, this is the maximum positive integer such that value FLT_RADIX raised to this power minus 1 can be represented as a floating point number of type float.

float이 표현할 수 있는 지수의 최대값.

DBL_MAX_EXP

LDBL_MAX_EXP

double 와 long double형이 각각 표현할 수 있는 지수의 최대값.

FLT_MAX_10_EXP

This is the maximum positive integer such that 10 raised to this power minus 1 can be represented as a normalized floating point number of type float.  This is supposed to be at least 37.

float형에서 베이스가 10일때 표현할 수 있는 지수의 최대값. 이것은 적어도 37이다.

DBL_MAX_10_EXP

LDBL_MAX_10_EXP

double 와 long double형엣 베이스가 10일 때 각각이 표현할 수 있는 지수의 최대값.

FLT_MAX

이 매크로의 값은 float형이 표현할 수 있는 최대수를 의미한다. 이것은 적어도 1E+37이 된다. 값 자체도 float형을 갖는다.  표현가능한 가장 작은수는 -FLT_MAX가 된다.

DBL_MAX

LDBL_MAX

doble 와 long double 형 각각이 표현할 수 있는 최대수를 의미한다.  이 매크로값이 가지는 형은 그것을 설명하는 형과 동일하다.

FLT_MIN

이 매크로의 값은 float형이 표현할 수 있는 표준화된 양의 플로팅 포인트 수의 최소값.  그 값은 1E-37보다 크지 않다.

DBL_MIN

LDBL_MIN

double 과 long double형 각각을 위한 것으로 표준화된 양의 플로팅 포인트수의 최소값.  매크로의 값 자체가 가지는 형은 그것이 설명하는 형과 동일하다.

FLT_EPSILON

1.0 + FLT_EPSILON != 1.0이 참인 float형의 플로팅 포인트 수의 최소 양의 수이다.  1E-5보다 크지 않다.

DBL_EPSILON

LDBL_EPSILON

double 와 long double를 위한 것으로 의미는 FLT_EPSILON과 같다.  이 매크로 값 자체가 가지는 형은 그것이 설명하는 형과 동일하다.  그 값은 1E-9보다 크지 않다.

A.5.3.3  IEEE 플로팅 포인트

다음은 이진 플로팅 포인트 연산을 위해서 IEEE 표준에서(ANSI/IEEE Std 754-1985) 정한, 대부분의 일반 플로팅 포인트 표기에서 산출된 float형의 대부분의 매크로 값을 보여주고 있다.  1980년대 이후에 디자인된 대부분의 컴퓨터는 이 형식을 사용한다.

IEEE 단정도(single-precision) float 표기법은 베이스로 2를 사용한다. 그것은 23비트에 한 개의 숨겨진 비트를 더해서(그래서 총 정밀도는 베이스를 2로 했을때 24가 된다.) 부호 비트와 가수부를 나타내고, 8-비트 지수부는 -125에서 128까지의 범위에 있는 값을 표현할 수 있다. 다음은, float형 데이터를 이 표기법을 사용할 경우, 그것에 연관된 파라미터의 적당한 값을 나타내고 있다.

FLT_RADIX                                     2

FLT_MANT_DIG                                24

FLT_DIG                                        6

FLT_MIN_EXP                               -125

FLT_MIN_10_EXP                            -37

FLT_MAX_EXP                                128

FLT_MAX_10_EXP                            +38

FLT_MIN                      1.17549435E-38F

FLT_MAX     3.40282347E+38F

FLT_EPSILON                 1.19209290E-07F

다음은 double 데이터 타입을 위한 값들이다.

DBL_MANT_DIG                                53

DBL_DIG                                       15

DBL_MIN_EXP	-1021

DBL_MIN_10_EXP	-307

DBL_MAX_EXP                               1024

DBL_MAX_10_EXP                            308

DBL_MAX	1.7976931348623157E+308

DBL_MIN	2.2250738585072014E-308

DBL_EPSILON	2.2204460492503131E-016

A.5.4  구조체 필드 옵셋 ( offset ) 측정

구조체 형안에서 특정한 구조체멤버의 위치를 계산하기 위해서는 offsetof를 사용할 수 있다.

매크로 :   size_t offsetof (type, member)

이것은 구조체 형을 가진 type안에 있는 member라는 이름을 가진 구조체 멤버의 옵셋(offset)을 구하는 정수 상수 표현식이다. 예를들어, offsetof(struct s, elem)은 struct s라는 구조체 안에 있는 멤버 elem의 옵셋(offset)이 된다.  이 매크로는 만일 멤버가 비트 필드가 아니면 작업하지 않는다; 당신은 그 경우에 컴파일러로부터 에러를 얻게 될 것이다.

엄숙

나 혼자 뫼 위에 올랐어라.

솟아 퍼지는 아침 햇볕에

풀잎도 번쩍이며

바람은 속삭여라.

그러나

아아 내 몸의 상처받은 맘이여

맘은 오히려 저리고 아픔에 고요히 떨려라

또 다시금 나는 이 한때에

사람에게 있는 엄숙을 모두 느끼면서.

김 소월

부록 B  라이브러리 기능들의 요약

부록 B 라이브러리 기능들의 요약

이 부록은 GNU C 라이브러리 안에서 공급되는 헤더파일안에 선언된 기능들의 완전한 리스트이다.  각 엔트리는 그 기능이 표준인지 아니면 다른것으로부터 왔는지를 표시하고, 그것을 어떻게 사용하는지에 대한 정보를 이 매뉴얼의 어느 부분에서 찾을것인지를 알려준다.

### 잠깐만!!!

이 부록 B는 함수들의 리스트가 나와있습니다. 번역이 필요치 않아서 위의 한 문단만 번역하고 넘어갑니다.

† GLIBC 영문판 1.09 버전일 경우에 529 페이지에서 609 페이지까지의 내용입니다.

밤은 고요하고

한밤중 싸늘하고 고요하였다.

한탄하면서 나는 숲을 헤메었다.

나는 나무들의 잠을 흔들어 깨웠지

나무는 가엾다는 듯 머리를 저었다.

십자로 묻히는 건

스스로 목 매어 죽는 자

거기 파란 꽃이 탄생한다.

그 이름도 슬픈 사형수의 꽃

십자로에 나는 서서 한숨을 쉬었다.

밤은 싸늘하고 고요하였다.

달 그림자에 한들 흔들린 사형수의 꽃.

부록 C  라이브러리 유지보수

부록 C 라이브러리 유지보수

C.1  어떻게 GNU C 라이브러리를 인스톨할것인가?

GNU C 라이브러리의 인스톨은 비교적 간단하다. 인스톨하려면 최근 버전의 GNU make가 필요하다. 다른 make 프로그램을 사용해서 GNU C 라이브러리를 갱신하는 것은 실로 어렵고 우리는 대신에 GNU make를 사용할 것을 권장한다.

당신의 시스템에 맞는 GNU C 라이브러리를 형성하기 위해서는, 쉘 스크립트 `configure' 와 함께 sh를 실행하라.  당신의 시스템 구성을 위한 관습적인 GNU 이름을 인수로 사용하라_예를들어, `sparc-sun-sunos4.1'은 Sunos 4.1이 실행되는 Sun 4를 위한 것이다.  표준 GNU 구성 이름들의 완전한 명세를 보려면, GNU CC 사용과 포팅에 있는 "GNU CC 인스톨하기" 절을 참조하라.  만일 당신이 구성이름을 빠트리면, `configure'는 실행중인 시스템을 점검함으로써 짐작하려고 시도할 것이다.  짐작을 할 수 있거나, 없거나, 아니면 짐작이 틀리거나 할 것이다.  `configure'는 진행하기전에 선택된 구성의 정규이름을 알릴 것이다.

GNU C 라이브러리는 다음 패턴들과 맞는 구성을 지원하고 있다.

alpha-dec-osf1

i386-anything-bsd4.3

i386-anything-gnu

i386-anything-sco3.2

i386-anything-sco3.2v4

i386-anything-sysv

i386-anything-sysv4

i386-sequent-bsd

m68k-hp-bsd4.3

m68k-sony-newsos

m68k-sun-sunos4.n

mips-dec-ultrix4.n

sparc-sun-solaris2.n

sparc-sun-sunos4.n

지원되지 않은 구성들에서, 그들 몇 개를 위한 다른 이름을 편리하게 지원한다. (그들은 물론 다른 GNU 소프트웨어에서 동작한다.)

decstation

hp320-bsd4.3  hp300bsd

i386-sco

i386-sco3.2v4

i386-sequent-dynix

i386-svr4

news

sun3-sunos4.n  sun3

sun4-solaris2.n  sun4-sunos5.n

sun4-sunos4.n  sun4

다음은 configure를 실행시킬 때 당신이 지정할 수 있는 몇 개의 옵션이다.

`--with-gnu-ld'

만일 당신이 GNU C 라이브러리와 프로그램을 링크하기 위해서 GNU ld를 사용할 계획이라면 이 옵션을 사용하라. (우리는 강력하게 당신이 이것을 하도록 권장한다.)

`--with-gnu-as'

만일 당신이 GNU C 라이브러리를 만들때, GNU 어셈블러 gas를 사용하려면 이 옵션을 사용하라.  어떤 시스템에서, 만일 당신이 gas를 사용하지 않는다면 그 라이브러리가 적당하게 만들어지지 않을것이다.

`--nfp'

만일 당신의 컴퓨터가 하드웨어에서 플로팅 포인트를 지원하는 것이 부족하다면 이 옵션을 사용하라.

`--prefix=directory '

`directory'의 서브디렉토리들안에 기계-독립적인 데이터 파일들을 인스톨하라. ( 당신은 또한 `configparms'로 이것을 설정할 수 있다; 밑을 보라.)

`--exec-prefix=directory '

`directory'의 서브디렉토리들안에 라이브러리와 기계-독립적인 파일들을 인스톨하라.  (당신은 또한 `configparms' 를 사용해서 이것을 설정할 수 있다;  밑을 보라.)

configure를 실행하기 위한 가장 간단한 방법은 라이브러리 소스가 포함된 디렉토리에서 그것을 실행하는 것이다.  이것은 그 디렉토리안에 라이브러리를 만들기 위해 준비한다.

당신은 configure를 실행하여 다른 디렉토리로 가서 그 다른 디렉토리에 라이브러리를 만들기위한 준비를 할 수 있다.

mkdir  ../hp320

cd  ../hp320

../src/configure  hp320-bsd4.3

configure는 당신이 정한 디렉토리가 무엇이던지 configure 그 자체를 찾기 위해서 소스들을 찾는다.  파일 시스템안의 어디에 소스와 디렉토리들이 있는지에 상관없이 당신이 configure를 실행할 때 소스디렉토리를 정해주기만 하면, 당신은 적당한 결과를 얻을 수 있을것이다.

이 기능은 다른 디렉토리에 소스와 바이너리 파일들을 달리 관리할 수 있고, 같은 소스를 가지고 여러개의 다른 기계를 위한 라이브러리를 만들기 쉽게 한다.  각 목표 기계를 위한 라이브러리가 들어갈 디렉토리를 만들고, 그 디렉토리에서 목표 기계의 구성 이름을 정해서 configure를 실행하라.

라이브러리는 특정한-목적의 구성 파라미터들을 위한 수를 가지고 있다. 그들은 `Makeconfig' 파일에 정의되어 있다; 자세한 것은 파일안에 있는 설명을 참조하라. 그러나 당신이 `Makeconfig'파일을 편집해서는 안되고_대신에 당신이 라이브러리를 만들어놓은 디렉토리안에 `configparms'라는 파일을 만들고, 당신이 지정하기를 원하는 파라미터들을 그 파일에 정의해놓으면 된다. `configparms'는 `Makeconfig'의 편집된 복사본이 되어서는 안된다; 오직 당신이 무시하기를 원하는 파라미터들만 지정하라.

특정 기계-의존적인 코드를 위해서는 GNU C 컴파일러에 있는 확장들을 사용하라, 그러므로 당신은 GCC로 라이브러리를 컴파일할 필요가 있을 것이다. (실제로, 현존하는 완전한 포트들은 모두 GCC를 필요로한다.) C 라이브러리의 현재 배포본은 컴파일러가 일반적으로 제공하는 헤더파일들을 포함하고 있다: `stddef.h', `stdarg.h' 그리고 `va-machine.h' 형식의 이름들을 가진 여러개의 파일들.  GCC의 오래된 배포본으로부터 온 그 파일의 다른 버전은 GNU C 라이브러리에서 올바르게 작동하지 않는다. 배포본 2.2.에 있는 `stddef.h' 파일과 GCC의 최근것은 올바르게 작동한다. 만일 당신이 배포본 2.2. 또는 최근의 GCC를 가지고 있다면, C 라이브러리에 있는 것 대신에 그 버전의 `stddef.h'의 파일을 사용하라.  그렇게하려면, `configparms' 안에 `override stddef.h = '줄을 넣어라.  다른 파일들은 배포본 2.3과 최근의 GCC에서 수정되었다.  `configure'는 C 라이브러리와 호환성이 있는 인스톨되어 있는 `stdarg.h'와 `va-machine.h' 파일들을 검색하고 만일 그것들이 없다면 자신의 것을 사용한다.

배포본 2.4의 이전에 있었던 GCC와 size_t 형은 문제를 포함하고 있다. ANSI C 는 size_t가 항상 unisgned 형이 될 것을 요구한다.  현존하는 시스템 헤더파일들과의 호환성을 위해서, GCC는 `sys/types.h'가 그것을 무엇으로 정의했는지에 따라서 `stddef.h'에 size_t를 정의한다.  대부분의 유닉스 시스템들은 `sys/types.h'에 size_t를 정의하고, signed 형이 되도록 한다.  size_t가 unisgned 형으로 되어있는것에 의존하는 라이브러리 안에 있는 어떤 코드들은, 만일 그것이 signed가 된다면 올바르게 동작하지 않을 것이다.

size_t가 unsigned로 예상되는 GNU C 라이브러코드는 올바르다. signed 형인 size_t 정의는 올바르지 않다.  버전 2.4 와 최근의 GCC는 size_t를 항상 unsiged형으로 정의하고, GCC의 `fixincludes' 스크립트 메시지는 시스템의 `sys/types.h'의 비위를 맞추기 때문에 그것과 충돌하지 않는다. 그동안, 우리는 GNU C 라이브러리를 컴파일할 때 size_t의 타입으로 unsigned 형을 사용할것이라고 명백하게 GCC에게 알림으로써 작업한다. `configure'는  만일 필요하다면 size_t를 위해서 GCC가 무슨 형을 사용할 것인지 자동적으로 검출할 것이다.

라이브러리를 만들기 위해서, make lib를 입력하라.  이것은 make로부터 에러처럼 보이는 많은 양의 출력을 만들것이다(에러는 아니다).  make로부터 나온 에러메세지에서 `***'가 포함된 것을 찾아봐라.  그들중 어떤 것은 실제로 에러메세지일 수 있다. 라이브러리의 기능들을 시험하기 위해서 어떤 테스트 프로그램을 만들고 실행하려면, make tests를 입력하라.  이것은 `program.out'과 같은 이름으로 여러개의 파일들을 만들 것이다.

GNU C 라이브러리 레퍼런스 매뉴얼을 프린팅하기 위한 형식으로 만들려면, make dvi를 입력하라.  make info를 입력하면, Emacs 나 info 프로그램에서 C-h i로 라인을 읽을수 있는 매뉴얼 형식으로 만들어준다.

라이브러리와 그 헤더파일들을 인스톨하고, info 프로그램을 위한 매뉴얼 형식을 만들러면, `configparms'에서 인스톨 디렉토리들을 설정한 후에, make install을 입력하라.  이것은 그들을 인스톨하기 전에 필요한것들을 만들 것이다.

C.2  버그들을 보고하기

GNU C 라이브러리 안에는 아마도 버그들이 있고, 이 매뉴얼 안에도 잘못된점과 생략된 것이 있을 것이다.만일 당신이 그들을 보고한다면, 그들은 고쳐질 것이다.  당신이 그렇게 하지않는다면, 아무도 그들에 대해서 알지못할것이고 그들은 영원토록 틀린 상태로 있게 될 것이다.

버그를 보고하기 위해서, 첫째로 당신은 그것을 발견해야만 한다. 다행스럽게도 버그를 발견하는 것은 쉬운일이 아닐 것이다. 일단 당신이 버그를 발견했다면, 그것이 실제로 버그인지를 확실히 밝혀라. 이것을 알아내기 위한 좋은 방법은 다른 C 라이브러리에 있는 같은 기능을 동작시켜보고 GNU C 라이브러리의 것과 비교해보는 것이다. 그들이 같은 동작을 하면, 아마도 당신이 틀리고 라이브러리가 맞을 것이다.  만일 그렇지 않다면, 라이브러리중에 하나가 틀린 것이다.

그런과정을 통해서 일단 당신이 발견한 것이 버그임이 확실해지면, 그 문제를 일으키는 경우의 범위를 좁히도록 시도하라.  C 라이브러리의 경우에, 만일 가능하다면, 당신은 실제로 한 개의 라이브러리 함수 호출로 그 범위를 좁힐 필요가 있다.  이것이 그리 어렵지는 않을 것이다.

당신이 할 수 있는 가장 간단한 테스트 케이스(case)를 가질 때 마지막 단계로써 버그를 보고하다.  버그를 보고할 때, 당신의 시스템 타입과, 당신이 사용하는 GNU C 라이브러리 버전, 그리고 당신이 생각하는 문제는 무엇인지, 테스트 프로그램을 통해서 당신이 예상했던 결과와, 그리고 당신이 얻었던 결과를 테스트 프로그램과 함께 보내라.   또한 `configure'를 실행함으로써 만들어지는 `config.status'와 `config.make' 파일들도 같이 보내라;  그들은 당신이 현재 무슨 디렉토리에서 `configure'를 실행시켰는지에 대한 것이다.

만일 당신이 ANSI 와 POSIX 표준들 (1.2절 [Standards and Portability] 1 페이지 참조.)을 따르지 않는 GNU C 라이브러리에 있는 어떤 것을 발견했다면, 그것은 명확히 버그이다.  그것을 보고하라! 버그 보고서를 보낼 인터넷 주소는 `bug-glibc@prep.ai.mit.edu' 또는 UUCP 경로 `mit-eddie!prep.ai.mit.edu!bug-glibc'이다.  만일 당신이 인스톨과정이나 사용하는데 다른 문제들을 발견하게 된다면, 그것 또한 보고하라.

만일 당신이 어떤 함수가 어떻게 동작하는지 확실히 알 수 없고, 이 매뉴얼이 그것에 대해서 말하지 않는다면 이 매뉴얼에 문제가 있는 것이다. 그것 또한 보고하라! 만일 함수의 동작이 매뉴얼과 다르다면, 라이브러리나 매뉴얼중에 하나가 문제가 있는것이므로 그것을 보고하라. 만일 당신이 매뉴얼에서 잘못된점이나 생략된 것을 발견했다면 다음 인터넷주소로 그들을 보고해달라.

□bug-glibc-manual@prep.ai.mit.edu□ or UUCP 경로 □mit-eddie!prep.ai.mit.edu!bug-glibc-manual□

C.3  새로운 함수들을 더하기

라이브러리를 설치하는(build) 프로세스는 GNU make의 특별한 기능들을 어렵게 사용해서 만드는 메이크파일들(makefiles)에 의해 조종된다. 메이크파일들은 메우 복잡하고, 당신은 아마도 그것을 이해하려 시도하기를 원하지 않을것이다.  그러나 그들이 하는일은 굉장히 간단한 것으로, 당신이 올바른 위치에 정의한 몇 개의 변수들만을 요구한다.

라이브러리소스는 주제(topic)에 따라서, 서브디렉토리로 나누어서 묶여져 있다. `string'서브디렉토리는 모든 문자열-처리 함수들을 가지고 있고, `stdio'는 모든 표준 I/O 함수들을 가진다.

각 서브디렉토리는 `Makefile'라고 불리는 간단한 메이크파일을 가지고 있는데, 그 파일은 몇 개의 메이크 변수들을 정의하고 전역 메이크파일 `Rules'를 다음 라인과 함께 포함하고 있다.

include ../Rules

서브디렉토리 메이크파일을 정의하는 기본 변수들은 다음과 같다:

subdir

`stdio'와 같은 서브디렉토리의 이름.  이 변수는 반드시 정의되어야만 한다.

headers

`stdio.h'처럼, 라이브러리의 한부분을 이루는 헤더파일의 이름.

routines

aux

라이브러리의 한 부분을 이루는 모듈들의 이름(소스 파일들) 그들은 `strlen'과 같은 간단한 이름이다 ( `strlen.c'와 같은 파일 이름보다는 간단하다는 말).  라이브러리안에 함수를 정의하는 모듈들을 위해서 routines를 사용하고, 데이터 정의와 같은 것들을 포함하고 있는 보조 모듈들을 위해서는 aux를 사용하라.  그러나 routines 와 aux의 값들은 단지 연결된것이고, 실제로 아무런 실제적인 차이는 없다.

tests

라이브러리를 구성하고 있는 테스트 프로그램의 이름. 그들은 `tester'과 같은 간단한 이름이다 ( `tester.c'와 같은 완전한 파일 이름보다는 짧다는 말.) `make tests'는 테스트 프로그램을 설치하고 실행할 것이다. 만일 입력을 필요로 하는 테스트 프로그램이라면, `test-program.input' 이라 불리는 파일안에 테스트 테이타를 입력하라; 그것은 표준 입력상에서 테스트 프로그램으로 입력될 것이다.  만일 테스트 프로그램에 인수를 넣어서 실행하길 원한다면, `test-program.args'라고 불리는 파일안에 인수들(단일한 라인으로)을 넣어라.

others

라이브러리를 구성부분과 연관된 "others" 프로그램들의 이름들이다.  그들은 se에 대하여 테스트가 없는 프로그램들이지만, 라이브러리와 함께 포함된 작은 프로그램을 말한다.  그들은 `make others'에 의해서 만들어진다.

install-lib

install-data

install

`make install'에 의해 인스톨되는 파일들.  `install-lib' 안에 목록이 있는 파일들은 `configparms' 또는 `Makeconfig' (C.1절 [Installation] 469 페이지 참조.) 안에 `libdir'에 의해 정해진 디렉토리안에 인스톨된다.  install-data 안에 목록이 있는 파일들은 `configparms' 또는 `Makeconfig' 에 있는 `datadir'에 의해 정해진 디렉토리안에 인스톨된다. install안에 목록이 있는 파일들은 `configparms' 또는 `Makeconfig' 안에 있는 `bindir' 에 의해서 정해진 디렉토리안에 인스톨된다.

distribute

결점이 있는 파일안에 넣게될 서브디렉토리에 있는 파일들. 당신은 메이크파일 자체나 소스 그리고 다른 표준 변수들로 등록된 헤더파일들은 이곳에 넣을 수 없다.  오직 비정상적인 방법으로 사용되는 파일들만 이곳에 정의된다.

generated

이 서브디렉토리안에서 `Makefile'에 의해 발생되는 파일들. 그 파일들은 `make clean'에 의해서 제거되고, 그들은 결코 비정상적인 파일안에 들어갈 수 없다.

extra-objs

이 서브디렉토리 안에서 `Makefile'에 의해서 만들어지는 여분의 오브젝트 파일들.  이것은 `foo.o'와 같은 파일이름의 리스트가 될 것이다; 그 파일들은 오브젝트 파일들이 만들어진 디렉토리 어느곳에서나 발견될 것이다.  그 파일들은 `make clean' 에 의해서 제거될 것이다.  이 변수는 others 나 tests 를 만들기 위해서 필요한 부차적인 오브젝트 파일들을 위해서 사용된다.

C.4  GNU C Library 포팅하기

GNU C 라이브러리는 다양한 기계들과 운영체제들에 쉽게 이식될 수 있도록 만들어졌다.  기계와 운영체제에 의존적인 함수들은 새로운 기계나 운영체제를 위해서 기능을 더하기 쉽게 만들도록 잘 분리되었다.  이 절은 라이브러리 소스 트리(tree)의 배치를 설명하고 기계 의존적인 코드를 선택하기 위해서 사용되는 메카니즘에 대해서 설명하고 있다.

라이브러리안에 있는 모든 기계-와 운영체제-의존적인 파일들은 최고-단계의 라이브러리 소스 디렉토리밑에 `sysdeps'라는 서브디렉토리에 있다. 이 디렉토리는 계층적인 서브디렉토리들로 구성되어 있다. (C.4.1절 [Hierarchy Conventions] 454 페이지 참조.)

`sysdeps'의 각 서브디렉토리는 특정한 기계나 운영체제, 또는 기계나 운영체제의 부류(예를들어, 특정한 공급자에 의한 시스템이나, IEEE 754 플로팅-포인트 형식을 사용하는 모든 기계들)를 위한 소스 파일들을 포함하고 있다.  구성도는 그 서브디렉톨들의 순서 리스트를 정한다.  각 서브디렉토리들은 리스트에서 부모 디렉토리 밑에 붙여진다.  예를들어, `unix/bsd/vax'라고 정해진 리스트는 `unix/bsd/vax unix/bsd unix' 리스트와 동일하다.

서브디렉토리는 디렉토리 계층도에서 직접적으로 위에 없는 다른 서브디렉토리들을 내포하도록 정할 수 있다.  만일 `Implies'파일이 서브디렉토리 안에 존재한다면, `sysdeps'의 다른 서브디렉토리들의 리스트들은 `Implies' 파일이 포함된 서브디렉토리의 리스트를 만든후에, 그 리스트에 덧붙인다. `#' 문자로 시작하는 `Implies'파일안에 문자는 주석문으로 무시된다. 예를들어, `unix/bsd/Implies' 는 다음을 포함한다:

# BSD has Internet-related things.

unix/inet

그리고 `unix/Implies'는 다음을 포함한다:

posix

그래서 최종적인 리스트는 `unix/bsd/vax unix/bsd unix/inet unix posix'가 된다.

`sysdeps'는 두 개의 "특별한" 서브디렉토리를 가지는데, 그것은 `generic' 과 `stub'이다.  그 두 개는 서브디렉토리들의 리스트에 항상 명확히 덧붙여지므로, 당신이 `Implies' 파일안에 그들을 넣을 필요가 없고, 당신이 그 디렉토리 밑에 다른 서브디렉토리를 만들어서는 안된다.  `generic'은 다른 C 라이브러리에 있는 기계-독립 함수들을 사용하여, 기계-독립 C에서 구현될 수 있는 것들을 위한것이다.  `stub'는 특정한 기계나 운영체제에서 이행될 수 없는 함수들의 토막 버전들이다.  그 토막 함수들은 항상 에러를 리턴하고, errno를 ENOSYS로 설정한다(함수가 이행되지 않는다.).  2장 [Error Reporting] 11 페이지 참조.

소스 파일은 `generic'나 `stub'에 소스 파일의 개정판(version)을 가짐으로써 시스템-독립적이 되도록 알려진다;  모든 시스템-독립 함수는 generic이나 stub의 기능중 하나를 가져야 한다 ( 그 둘을 모두 가져도 아무런 문제가 없다.)

만일 당신이 메인 소스 디렉토리(main source directories)들중의 하나에 있는 어떤 파일에 대해서, 그것을 기계- 또는 운영체제-독립적으로 만들기를 원한다면, 그 파일을 `sysdeps/generic'으로 옮기고 적당한 시스템-지정 서브디렉토리에 있는 새로운 함수를 사용해서 파일을 수정하라.  만일 어떤 파일이 시스텝-독립적이 되었다면 그것을 기록해서, 그것이 메인 소스 디렉토리들중의 어떤것에도 나타나서는 안된다.

다음은 `sysdeps'의 각 서브디렉토리에 존재할 수 있는 몇 개의 특별한 파일들이다.

`Makefile'

기계나 운영체제, 또는 기계나 운영체제의 부류를 위한 메이크파일.  이 파일은 최고단계에 있는 메이크파일과 서브디렉토리에 있는 메이크파일들에 의해서 사용되는 라이브러리 메이크파일 `Makerules'에 의해서 인클루드된다. 포함된 메이크 파일의 변수 설정을 변경하거나 새로운 규칙 을 더할 수 있다.  라이브러리의 다른 부분을 위해서 변수들과 규칙들의 다른 설정을 선택하려면 (위를 보라) 변수 `subdir'에 기초한 조건적인 지시로 GNU make를 사용할 수 있다.  그것은 또한 라이브러리에 포함될 여분의 모듈들을 정하기 위해서 make 변수 `sysdep-routines'를 설정할 수 있다. 당신은 `routines'이 메인 소스 트리(main source tree) 각 서브디렉토리에 무엇을 공급할 것인지를 결정하는데 사용되기 때문에 `routines'에 모듈들을 더하기 보다는 `sysdep-routines'를 사용할 것이다.

찾기 쉽도록 서브디렉토리들을 순서대로 리스트를 만든 서브디렉토리안의 각 메이크파일은 순서대로 인클루드된다. 여러 시스템-의존 메이크파일들이 인클루드되면, 각각은 그것을 간단히 설정하기보다는 `sysdep-routines'에 덧붙여 질 것이다:

sysdep-routines := $(sysdep-routines) foobar

`Subdirs'

이 파일은 이 시스템을 위해 인클루드되어질 최고-단계의 라이브러리 소스 트리밑에 있는 전체의 서브디렉토리들에 에 대한 이름들을 갖고 있다.  그 서브디렉토리들은 `stdio' 와 `math'처럼, 라이브러리 소스 트리안에 있는 시스템-독립 서브디렉토리들처럼 취급된다.

`sysdeps' 도구들의 이 서브디렉토리 시스템을 위한 라이브러리안에 넣을 함수들과 헤더파일들을 완전히 새롭게 설정하고자 할 때 이것을 사용하라. 예를들어, `sysdeps/unix/inet/Subdirs'는 `inet'를 포함한다; `inet' 디렉토리는 인터넷을 지원하는 시스템상에서 라이브러리에 넣을만한 다양한 네트웍-지향 명령들을 포함한다.

`Dist'

이 파일은 distribution 안에 포함될 파일들( 그것이 있는 `sysdeps'의 서브디렉토리에 관한)의 이름을 포함한다. 같은 디렉토리의 `Makefile'에 있는 규칙들에 의해 쓰여지는 새로운 파일들이나, 또는 그 디렉토리의 소스 파일들에 의해서 사용되는 헤더파일들에 대한 목록이다.  당신은 메인 소스 트리에 있는 기계-독립 메이크파일들에서 이름이 부여된 routines의 구현인 파일들을 목록화할 필요가 없다.

`configure'

이 파일은 구성시(configuration time)에 실행되는 쉘 스크립트 부속이다.  `configure' 스크립트는 순서대로, 선택된 각 시스템-의존 디렉토리에서 `configure' 파일을 읽기 위해서 shell . command를 사용한다.  `configure' 파일들은 Autoconf를 사용하는 파일들 `configure.in'로부터 발생된다.

시스템-의존적 `configure' 스크립트는 쉘 변수 `DEFS'와 `config_vars'에 더할 것이다; 자세한 것은 최고-수준 `configure' 스크립트를 참조하라.  그 스크립트는 최고-수준 `configure'에 넣을 `--with-package' 옵션들을 위해서 체크 할 수 있다. `--with-package=value' 옵션에서, `configure' 는 값으로 쉘 변수 `with_package'(package안의 대쉬는 언더스코어로 변환된다)를 설정한다; 만일 옵션이 단지 `--with-package'로 되어있다면(인수가 없이), 그것은 `yes'로 `with_package'를 설정한다.

`configure.in'

이 파일은 서브디렉토리의 `configure'파일안에서 처리되기 위한 Autoconf 입력 부분이다.  Autoconf안에 있는 "Introduction"을 참조하라: Autoconf의 명세는 Generating Automatic Configuration Scripts.  당신은 `configure' 또는 `configure.in'을 사용할 수 있지만, 그 둘을 함께 사용해서는 안된다.  `configure.in'의 첫 번째 줄은 m4 macro인 `GLIBC_PROVIDES'를 호출하여야한다.  이 매크로는 최고-수준 `configure' 스크립트에서 사용되는 Autoconf 매크로드를 위해서 여러 `AC_PROVIDE'를 호출한다; 이것이 없이는, 그 매크로들은 Autoconf에 의해서 쓸모없이 다시 호출될 것이다.

그것은 어떻게 시스템-의존적인 것들을 고립시킬것인지에 대한 일반적인 시스템이다.  다음절은 `sysdeps'에 무슨 디렉토리를 사용할것인지를 어떻게 결정하는지 설명한다.  다양한 유닉스 시스템에 따라서 라이브러리를 포팅(porting)하는 팁에 대한 정보는 C.4.2절 [Porting to Unix] 456 페이지를 참조하라.

C.4.1  `sysdeps' 디렉토리 계층의 배치

GNU 구성 이름은 세부분으로 되어있다: CPU 타입, 제작자의 이름, 그리고 운영체제.  `configure'는 시스템-의존 디렉토리들의 리스트를 고르기 위해서 그들을 살펴보는데 사용한다.  만일 `--nfp' 옵션이 `configure'에 주어지지 않았다면, `machine/fpu' 디렉토리가 또한 사용된다.

운영체제는 기본 운영 체제를 갖는다; 예를들면 만일 운영체제가 `sunos4.1' 이라면, 기본 운영체제는 `unix/bsd'가 된다.  디렉토리의 리스트를 고르는데 사용되는 알고리즘은 간단하다: `configure'는 기본 운영체제, 제작자, CPU 그리고 운영체제의 리스트를 순서대로 만든다.  그것은 디렉토리 이름을 만들어서, 서로 슬래쉬로 구분되어 연결되어 있다: 예를들면, `sparc-sun-sunos4.1'은 `unix/bsd/sun/sparc/sunos4.1'에 있다.  `configure' 는 리스트의 각 요소를 제거하려 시도하는데, `unix/bsd/sparc' 와 `sun/sparc' 또한 다른 것 사이에서, 시도된다.  운영체제의 정밀한 버전 번호는 그다지 중요하지 않고, 그것은 매우 불편하다. 예를들어, `sunos4.1.1'와 `sunos4.1.2' 디렉토리를 구분하는 것은 불편하기 때문에 `configure'는 점(period)으로 시작되는 접미사를 제거함으로써 덜 결정적인 운영 시스템 이름들을 시도한다.

예를들어, `sparc-sun-sunos4.1' 구성을 위해서 시도됐던 디렉토리들의 완전한 리스트가 있다.

sparc/fpu

unix/bsd/sun/sunos4.1/sparc

unix/bsd/sun/sunos4.1

unix/bsd/sun/sunos4/sparc

unix/bsd/sun/sunos4

unix/bsd/sun/sunos/sparc

unix/bsd/sun/sunos

unix/bsd/sun/sparc

unix/bsd/sun

unix/bsd/sunos4.1/sparc

unix/bsd/sunos4.1

unix/bsd/sunos4/sparc

unix/bsd/sunos4

unix/bsd/sunos/sparc

unix/bsd/sunos

unix/bsd/sparc

unix/bsd

unix/sun/sunos4.1/sparc

unix/sun/sunos4.1

unix/sun/sunos4/sparc

unix/sun/sunos4

unix/sun/sunos/sparc

unix/sun/sunos

unix/sun/sparc

unix/sun

unix/sunos4.1/sparc

unix/sunos4.1

unix/sunos4/sparc

unix/sunos4

unix/sunos/sparc

unix/sunos

unix/sparc

unix

sun/sunos4.1/sparc

sun/sunos4.1

sun/sunos4/sparc

sun/sunos4

sun/sunos/sparc

sun/sunos

sun/sparc

sun

sunos4.1/sparc

sunos4.1

sunos4/sparc

sunos4

sunos/sparc

sunos

sparc

다른 기계 구조들은 관습적으로 `sysdeps' 디렉토리 트리의 최고수준에 존재한다.  예를들어, `sysdeps/sparc'와 `sysdeps/m68k'.  그들은 기계 구조에 대한 파일 명세를 포함하지만, 어느 특정 운영체제에 대한 명세는 아니다. `sysdeps/m68k/s8020' 처럼, 그 구조들의 한정을 위한 서브디렉토리들이 될 것이다.  특별한 기계에서 사용되는 플로팅-포인트 코프로세서에 대한 명세 코드는 `sysdeps/machin/fpu'에 들어가야한다.

다음은 특정한 기계 구성들을 위한 것이 아닌 `sysdeps' 계층의 최고단계에 있는 몇 개의 디렉토리에 관한 것이다.

`generic'

`stub'

위에 설명된것처럼 (C.4절 [Proting] 452 페이지 참조.), 모든 구성들이 다른것들 후에 사용하는 두 개의 서브디렉토리들이 있다.

`ieee754'

이것은 C 타입 float가 IEEE 754 단정도 형식과, double가 IEEE 754 배정도 형식인곳에서, 사용되는 IEEE 754 플로팅-포인트 형식 코드를 위한 디렉토리이다. 보통 이 디렉토리는 `m68k/Implies'와 같은, 기계 구조-명세 디렉토리에있는 `Implies'파일에서 참조된다.

`posix'

이 디렉토리는 POSIX.1 함수들을 위해서 라이브러리를 구현해놓은것들을 포함한다. 이것은 POSIX.1 함수들 중 어떤 것을 포함한다.  물론, POSIX.1은 그 자체만으로 완전히 구현될 수 없기 때문에, 단지 `poisx'를 사용하는 구성은 완전할 수 없다.

`unix'

이것은 Unix와 같은것들을 위한 디렉토리이다. C.4.2절 [Porting to Unix] 456 페이지 참조.  `unix'는 `posix'를 내포한다.  다음은 `unix'의 특별한-목적을 위한 서브디렉토리이다.

`unix/common'

이 디렉토리는 BSD와 시스템 V 릴리즈 4 이 둘의 common을 위한 것이다. `unix/bsd'와 `unix/sysv/sysv4' 둘은 `unix/common'을 내포한다.

`unix/inet'

이 디렉토리는 소켓과 Unix 시스템상의 해당 함수들을 위한 것이다.  `inet' 최고 단계 서브디렉토리는 `unix/inet/Subdirs'에 의해 가능해진다. `unix/common'은 `unix/inet'를 내포한다.

`mach'

이것은 CMU로부터(GNU 운영체제가 포함된) 마하(Mach) 마이크로커널에 기초한것들을 위한 디렉토리이다. 다른 기본운영시스템들(예를들어, VMS)은 `sysdeps' 계층의 최고 단계에 있는, `unix'와 `mach'와 유사한, 그들 자신만의 디렉토리들을 가지고 있다.

C.4.2  GNU C 라이브러리를 Unix 시스템들로 포팅하기

대부분의 Unix 시스템들은 기본적으로 매우 유사하다. 커널에 제공되는 기능들과 여러 기계들 사이에 차이점이 있다.  하지만 운영체제와의 인터 페이스는 대부분에서, 단일하고 간단하다.

Unix 시스템들을 위한 코드는 `sysdeps'계층의 최고단계에 있는 `unix'디렉토리에 있다. 이 디렉토리는 다양한 Unix 변형을 위한 서브디렉토리 (그리고 서브디렉토리 트리)들을 포함한다.

대부분 Unix 시스템에서 시스템 호출 함수들은 `sysdeps/unix'안에 있는 파일들에 어셈블리 코드로 구현되어 있다. 그 파일들은 `.S'의 접미사로 끝나는 이름을 가지고 있다; 예를들면, `__open.S'.  `.S'로 이름이 끝나는 파일들은 어셈블러에게 주어지기 전에 C 프리프로세서를 통해서 실행된다.

그 파일들은 모두 `sysdep.h'에 정의된 매크로들의 설정을 사용한다. `sysdeps/unix'에 있는 `sysdep.h' 파일은 특별하게 그들을 정의한다; 다른 디렉토리안에 있는 `sysdep.h'파일은 특정한 기계와 운영체제의 변형을 위해서 그들을 정의하는 것을 끝내야만 한다.  무슨 매크로들이 있고 그들이 무엇을 하는지 알기 위해서는 `sysdeps/unix/sysdep.h'와 기계-명세 `sysdep.h' 를 참조하라.

`unix' 디렉토리를 위한 시스템-명세 메이크파일은 (그것은, 파일 `sysdeps/unix/Makefile') 당신이 라이브러리를 만들었던 (또는, 당신이 라이브러리를 만들 목표 시스템으로 가정하라.) Unix 시스템으로부터 여러 파일들을 발생시키기 위한규칙들을 부여한다.

모든 발생된 파일들은 오브젝트 파일들이 관리되고 있는 디렉토리안에 들어 간다; 그들자체는 소스 트리에 영향을 미치지 않아야한다.  발생된 파일들은 `icctl.h', `errnos.h', `sys/param.h' 그리고 `errlist.c'이다 ( 라이브러리에 있는 `stdio' 부분을 위한).

C.5  GNU C 라이브러리의 공헌자

GNU C 라이브러리는 지금 그것을 유지보수하고 있는 Roland McGrath에 의해 거의 만들어졌다. 라이브러리의 어떤 부분은 다른 사람들에 의해서 작업되었거나 공헌한바 있다.

□ getopt 함수와 그와 연관된 코드는 Richard Stallman, David J. MacKenzie, 그리고 Roland McGrath에 의해 만들어졌다.

□ 대부분의 수학함수들은 4.4 BSD로부터 따온 것이다; 그들은 GNU C 라이브러리로 작업하기 위해서 완전히 갱신되었다. 인터넷-관련 코드 ( 대부분 `inet' 서브디렉토리에 있는)와 여러 다른 잡다한 함수들과 헤더파일은 조금 또는 갱신없이 포함되었다.

4.4 BSD 로부터 합병된 모든 코드들은 다음과 같은 저작권하에 있다.

Copyright Oc 1991 Regents of the University of California  All rights reserved.

갱신을 하거나, 또는 하지 않고 소스와 바이너리 파일들을 사용하고 재배포하는 것은, 다음과 같은 상황하에서 공급됨이 허가된다.

1. 소스코드의 재배포는 위의 저작권, 조건의 목록과 다음의 거부 경우를 숙지해야만 한다.

2. 바이너리 형식 파일의 재배포는 원래 공급된 것들과 and/or 시킨 문서를 통해서 위의 저작권, 조건의 목록과 거부의 경우를 다시 만들어야만 한다.

3. 언급되는 기능들이나 이 소프트웨어를 사용하여 광고되는 것에는 다음을 표시해야만 한다. 이 산물은 캘리포니아 대학의, Berkeley와 그 공헌자들에 의해서 개발된소프트웨어를 포함하고 있다.

4. 대학의 이름이나 공헌자의 이름은 사용 권한에 우선하여 이 소프트웨어로부터 만들어진 산물을 후원하고 뒷받침하기 위해서 사용될 것이다.

이 소프트웨어는 "as is"인 협의회나 공헌자들에 공급되고 아무런 제한은 없지만, 상업적으로 사용되는것에 대한 경고와 특정한 목적에 맞추어 줄 수 없음 등에 대한 경고들을 포함하거나 표시되어 공급된다.  협의회나 공헌자들이 어떠한 손해도 보상할 책임은 없다.  그리고그와 같은 손해의 가능성에 대해서 조언을 했을지라도, 이 소프트웨어를 사용할때는 알지못하는 손해를 입을 가능성을 포함하고 있다.

□ 랜덤 수를 발생시키는 random, srandom, ststate, 그리고 initstate 함수는 rand 와 srand를 기초로하여, 캘리포이나에 있는 버클리 대학의 Earl T. Cohen에 의해 만들어졌고, 캘리포니아 대학의 협의회(regents)가 저작권을 갖고 있다.  그들은 GNU C 라이브러리와 ANSI C 표준과 맞추기 위해서 작은 변화가 있었지만, 그 함수적인 코드는 버클리에 있다.

□ 합병 정렬 함수 qsort는 Michael J. Haertel에 의해 만들어졌다.

□ qosrt 함수에 대체되는 퀵 정렬 함수는 Douglas C. Schmidt에 의해 만들어졌다.

□ 메모리 할당 함수 malloc, realloc 그리고 free와 그에 해당되는 코드는 Michael J. Haertel에 의해 만들어졌다.

□ 문자열 함수들의 대부분을 빠르게 구현한 함수들은(memcpy, strlen, 등) Torbj"orn Granlund에 의해 만들어졌다.

□ 마하를 위해 지원하는 코드중에 어떤 것은 CMU에 의한 마하 3.0으로부터 온것이고, 다음과 같은 저작권하에 있다.

Mach Operatin System Copyright Oc 1991,19901989 Carnegie Mellon University All Right Reserved.

이 소프트웨어와 문서들을 사용하고, 복사하고, 갱신하고 공급하기 위한 권한은 그들의 일부라도 버전을 갱신하거나, 작업을 파생시키는 경우라 할지라도 모든 복사물에 저작권과 권한에 대한 고지가 나타나야하고, 또한 저작권과 권한 고지는 지원되는 문서에도 나타나야 한다.

carnegie mellon은 "as is" 상황하에서 이 소프트웨어의 자유로운 사용을 허용하고 있다.  carnegie mellon은 이 소프트웨어의 사용으로 초래되는 어떤한 손해도 책임을 부인한다.

Carneige Mellon은 개선된것이나 확장된 것이 있다면 `Software.Distribution@CS.DMU.EDU' 또는 Software Distribution Coordiantor

School of Computer Science

Carnegie Mellon University

Pittsburgh PA 1521303890

으로 보내주고, 이 변경된것에 대한 재분배 권한을 Carneigie Mellon에게 허가할 것을 요청한다.

□ `tar.h' 헤더파일은 David J. Mackenzie에 의해서 만들어졌다.

□ Ultrix 4에서 실행되는 MIPS DECStation으로 포팅은 Brendan Kehoe 와 Ian Lance Taylor에 의해서 공헌되었다.

□ DES 암호화 함수 crypt와 그와 연관된 함수들은 Michael Glad에 의해서 공헌되었다.

□ 몇 개의 함수는 Ian Lance Taylor에 의해서 공헌되었다.

□ SunOs 공유 라이브러리들을 지원하는 코드는 Tom Quinn에 의해서 공헌되었다.

□ mktime 함수는 Noel Cragg에 의해서 공헌되었다.

□ Dynin version 3에서 실행되는 Sequent Summetry로의 포팅은 Jason Merrill에 의해서 공헌되었다.

□ timezone 지원 코드는 Arthur David Olson에 의한 공공-도메인 타임죤(timezone) 으로부터 온 것이다.

□ 인터넷 해결자 코드(Internet resolver code)는 위와 같은 버클리 저작권하에 있는 BIND 4.9.1에서 직접 획득한 것이다.

Portions Copyright Oc 1993 by Digital Equipment Corporation. 이 소프트웨어를 사용하고 복사하고 갱신하고 공급하기 위한 권한은 Digital Equipment 회사의 이름이 광고나 사용의 목적으로 사용되지않는곳에서, 위의 저작권과 권한에 대한 공지가 모든 복제물에 나타난다면 제한없이 사용할 것을 허가한다. digital equipment사는 이 소프트웨어에 대한 모든 권한을 포기 하였다.  그리므로 digital equipmint 사가 이 소프트웨어를 사용함으로써 발생되는 손해에 책임지지 않는다.

□ OSF/1 (alpha-dec-osf1)에서 실행되는 DEC Alpha로의 포팅은 Roland McGrath에 의해서 만들어졌던 어떤 코드를 사용해서, Brendan Kehoe에 의해서 공헌되었다.

□ printf 와 friends 함수에 의해서 사용되는 플리팅-포인트 출력 함수는 Roland McGrath 와 Torbj"orn Granlund에 의해서 만들어졌다. 그 함수에서 사용되는 다-정도(multi-precion) 정수 함수들은 Torbj"orn Granlund 에 의해서 공헌된, GNU MP로부터 획득된 것이다.

그대의 눈빛에서

내 마음의 자작나무 숲으로 오십시오

그대를 편히 쉬게 할

그늘이 마련되어 있습니다.

맑은 하늘에

바람도 간간이 불어

사랑을 나누기에 적합한 때입니다.

오직

그대만을 생각하고

그대만을 위하여 살아가렵니다.

사랑을 시작할 때

그대도 홀로

나도 홀로였으니

우리 사랑은 방해 받을 것들이

하나도 없습니다.

그 누가 무어라

우리들의 사랑을

비난하거나 조롱하여도

그대의 마음이 동요되어서는 안됩니다.

우리들의 사랑은

오랜 기다림 속에 피어난

난초의 꽃처럼

순결하기 때문입니다.

현명한 그대가 우리들의 사랑의 모양새를

더 잘알고 있기에

걱정이 없습니다.

수 많은 말들로 표현해도

다 못할 고백이지만

오늘은 아무 말없이 있겠습니다.

나를 바라보는

그대의 눈빛에서

사랑을 읽었습니다.

부록 D  GNU LIBRARY GENERAL PUBLIC

부록 D GNU LIBRARY GENERAL PUBLIC LICENSE

LICENSE

Version 2, June 1991

1991년, 6월, 버전 2

Copyright Oc 1991 Free Software Foundation, Inc.

675 Mass Ave, Cambridge, MA 02139, USA

누구나 이 라이센스 문서를 복사하고 요약해서 공급하는 것이 허용되지만 허락없이는 변경할 수 없다.

[이것은 라이브러리 GPL 의 첫 번째 개정판이기 때문에 원래 GPL 두 번째 버전으로써 번호 2를 부쳤다.]

서론

대부분의 소프트웨어를 위한 라이센스는 당신이 자유롭게 배포하고, 그것을 변경할 수가 없지만, 그에 반하여, GNU General Public Licenses는 당신이 자유롭게 배포하고 변경할 수 있도록, 즉, 그 소프트웨어를 사용하는 모든사람에게 확실한 자유를 부여하려는 의도가 있다.

이 라이센스, 라이브러리 General Public License는, FSF(Free Software Foundation) 소프트웨어로 특별히 디자인된 것들과 라이브러리를 만든 사람이 이 라이센스를 사용하고자 결정했을 때 적용된다.  당신도 당신의 라이브러리에 이 라이센스를 사용할 수 있다.

우리가 프리 소프트웨어(free software)를 말할 때, 우리는 우리가 돈(price)에 대해서 얘기하는 것이 아니라 자유에 대해서 얘기하는 것이다. 우리의 General Public Licenses는, 프리 소프트웨어의 복제판을 자유롭게 배포하는 자유를 갖을수 있고 ( 만일 당신이 원한다면 이 서비스를 요구하라), 당신이 원한다면 소스 코드를 받아서 그 소프트웨어를 변경하거나, 새로운 프리 프로그램에 이것을 사용할 수도 있도록 만들어졌다; 당신은, 당신이 그와 같은일을 할 수 있다는 것을 안다.

당신의 권리를 보호하기 위해서, 우리는 누군가 당신의 권리를 방해하거나, 그 권리를 넘기도록 요청하는 것을 금하기 위해서 제한을 만들 필요가 있었다.  그 제한은 당신이 그 라이브러리의 복제본을 배포하거나, 그것을 갱신하려 할 때 당신이 지켜야하는 책임으로써 해석하면된다.

예를들어, 만일 당신이 라이브러리의 복제본을 배포한다면, 그것이 무료이고 프리(free)라고 할지라도, 당신이 우리에게서 받았던 권리를 상대에게 주어야만 한다.  당신은 그들이 소스 코드를 받거나 얻을 수 있음을 또한 확실히 인식시켜라.  만일 라이브러리에 프로그램을 링크한다면 라이브러리를 수정하고 그것을 재 컴파일한 이후에, 그 라이브러리에 그들을 다시 링크할 수 있도록 완전한 오브젝트 파일들을 함께 공급해야만 한다. 그리고 그들의 자신의 권리를 누릴 수 있도록 그 조건들을 그들에게 보여주어야만 한다.

당신의 권리를 보호하는 방법으로 두 단계가 있다: (1) 라이브러리 저작권, 그리고 (2) 복제하거나, 그 라이브러리를 배포하거나 갱신하는 합법적인 권리를 당신에게부여한 이 라이센스를 당신이 제공한다. 또한, 배포자를 보호하기 위해서, 이 프리 소프트웨어에는 아무런 품질보증이 없음을 모든사람에게 확실히 이해시키길 원한다.  만일 그 라이브러리가 누군가에 의해서 수정되거나 나아졌다면, 우리는 그들이 원래의 버전이 아니라 수정된 것을 받았음을 알게하기를 원한다, 그것은 수정된 것으로 인해 발생된 문제로 원래의 프로그램을 만든사람의 명성에 누가되지 않기를 바라기 때문이다.

마침내, 프리 프로그램은 소프트웨어 특허권으로 인해서 지속적으로 위협 받고있다.  우리는 프리 소프트웨어를 공급하는 회사들이 개별적으로 특허권 라이센스를 얻어서, 독점 소프트웨어로 프로그램을 변형시키는 효과를 갖는, 그 위험을 피하기를 원한다. 이것을 막기 위해서, 우리는 모든 사람들이 자유롭게 사용하도록 하거나 아니면 전혀 허가되지 않음을 명백히 하고 있다.

어떤 라이브러리들이 포함되는 대부분의 GNU 소프트웨어는, 원래의 GNU General Public License와는 다른 유틸리티 프로그램들을 위해 만들어진 또다른 GNU Library General Public 라이센스를 갖게된다.  GNU Library General Public 라이센스는 어떤 지정된 라이브러리들에 적용된다.  나중의 라이센스는 원래의 것과는 많이 다르다; 완전히 그것을 읽고, 그안에 있는 어떤것도 보통의 라이센스의 적용을 받는다고 가정하지 말아라.

우리가 어떤 라이브러리를 위하여 분리된 공공 라이센스를 갖으려 하는 이유는 그것을 사용하여 프로그램에 덧붙이고 갱신하는 사이에 우리가 만들었던 구분을 그들이 모호하게 할지도 모르기 때문이다.  라이브러리를 변경하지 않고, 라이브러리를 프로그램에 링크하는 것은 라이브러리를 사용하여 간단히 어떤 것을 만드는것이고, 유틸리티 프로그램이나 어플리케이션 프로그램을 실행시키는것과 유사하다.  그렇지만, 정상적인 원래의 의미에서, 라이브러리가 링크된 실행화일은 보통의 라이브러리에서 유도된, 결합된 작업이고, 보통의 General Public License는 그것처럼 그 실행화일을 취급한다.

구분이 모호해지면, 라이브러리를 위한 보통의 General Public License를 사용해서 소프트웨어의 배포를 효과적으로 조정할 수 없고, 그렇게 되면 대부분의 개발자들이 그 라이브러리를 사용하지 않게될 것이다.  우리는 그래서 우리는 약화된 조건은 좀더 잘 배포를 조정할것이라고 결론을 내렸다.

그렇지만, 비-프리 프로그램의 제한없는 링킹(lingking)은 그 프로그램이 라이브러리의 자유로운 사용으로 인해서 그 이득을 얻었지만, 그 프로그램의 사용자에게는 그 자유를 뺐을 것이다.  라이브러리 General Public License는 프리 라이브러리들을 사용해서 만들어진 프로그램의 사용자에게 자유로운 사용을 보장하지만, 프리 라이브러리들을 사용해서 비-프리 프로그램을 개발하는 것을 금하려는 의도이다.  (우리는 헤더파일들의 변화에 관하여 이것을 어떻게 이루어내는지 보여줄 수 없지만, 우리는 라이브리리의 실제 함수들의 변화에 관하여서는 그것을 이루어냈다.) 이것이 프리 라이브러리들의 빠른 발전을 선도할것이라는 희망을 갖게한다.

복제, 배포 그리고 갱신을 위해서는 자세한 제한과 조건이 따른다. "라이브러리에 기초한 작업" 과 "라이브러리를 사용하는 작업" 과의 작은 차이에 주목하라.  전자는 라이브러리로부터 유도된 코드를 포함하는 것이고 후자는 오로지 라이브러리와 함께 작업하는 것이다.

라이브러리를 위해서는 특별한것보다는 보통의 General Public License 가 효력을 발휘하게 됨을 기억하라.

복제, 배포 와 수정에 대한 제한과 조건

1. 이 라이센스 협정은 라이브러리 General Public License의 제한하에 공급되었다는, 작자와 저작권자에 의해서 만들어진 통지를 포함하고 있는 어떤 소프트웨어 라이브러리에 적용된다. 허가를 받은 사람은 바로 당신이다.

"라이브러리"는 실행가능한 응용 프로그램을 만들기 위해서 편리하게 링크되도록 미리 준비된 데이터 그리고/또는 소프트웨어 함수들의 집합을 의미한다.

"라이브러리"는 그 제한하에 공급된 소프트웨어 라이브러리나 작업물이 속하는 것으로 한다.    "라이브러리에 기초한 작업물"은 저작권하에 유도된 작업물이나 라이브러리를 의미한다: 그것은 요약되거나, 갱신되거나 그리고/또는 다른 랭귀지로 변경된 라이브러리나 라이브러리의 일부가 포함된 작업물을 말한다.  ( 다른 랭귀지로의 변경은 "갱신"의 항목에 제한을 받지 않는다.)

작업물의 "소스 코드"는 그 작업물을 갱신할 때 사용되는 것을 의미한다.  라이브러리에서 완전한 소스코드는 그 라이브러리가 포함하고 있는 모든 모듈들의 소스 코드에, 연관된 인터페이스 정의 파일과, 라이브러리의 인스톨과 컴파일을 제어하는데 사용되는 스크립트들을 더한 것이다.

복제, 배포 그리고 갱신과는 다른 활동들은 이 라이센스에 의해 제약을 받지 않는다;  그들은 그 범주를 벗어났다.  라이브러리를 사용해서 프로그램을 실행시키는 행동은 제한이 없고, 그와 같은 프로그램으로 부터 나온 출력은 만일 그 내용이 라이브러리에 기초한 작업물을 구성 할 때만 라이센스의 제약을 받는다 ( 그것을 출력하기 위해서 툴에서 라이브러리를 사용하는것과는 독립적이다.)  라이브러리의 제약 여부는 라이브러리가 무엇을 하고 그 라이브러리를 사용한 프로그램이 무엇을 하는지의 여부에 달렸다.

2. 당신은 당신이 받았던 라이브러리의 완전한 소소코드를 복제하거나 축약해서 배포하는것도 좋지만, 품질보증에 대한 거부와 저작권에 대한 공지는 확실하고 적당하게 함께 공급해야만 한다; 이 라이센스에 속한 모든 공지를 변형없게 하고 품질보증에 대한 거부를 빼먹지말고 보존하라; 라이브러리를 배포할 때 이 라이센스도 함께 배포하라. 당신이 프로그램을 다른 언어를 사용해서 물리적으로 변경하여 사례금을 받는다면 당신은 사례금과 품질보증에 대한 의무와 바꾼 것이 된다.

3. 당신이 라이브러리나 그것의 일부를 갱신하고, 그 라이브러리에 기초하여 작업물을 복제하고 배포하거나, 위의 1항에 해당하는 상황으로 작업하는 경우에는 그와 같이 했음을 받는 사람들이 알 수 있도록 하라.

a. 갱신된 작업물이 소프트웨어 라이브러리 그 자체가 되어야만 한다.

b. 당신이 파일을 갱신했을때는 변경된 파일들의 상황과 변경날짜를 확실하게 공지해야만 한다.

c. 모든 써드파티(third party)들에서 대가없이 만들어진 전체작업물은 이 라이센스의 제한하에 있게된다.

d. 만일 응용 프로그램에 의해 공급된 데이터의 테이블이나 함수가 속해있는 갱신된 라이브러리안의 기능이 그 기능을 호출할 때 인수를 주는것과는 다른 기능을 사용한다면, 당신은 그것을 확장하도록 노력하여 그 기능이 여전히 동작하고 수행되도록 신뢰성 있게 만들어야만 한다.

(예를들어, 루트함수를 계산하는 라이브러리안에 있는 함수는 어플리케이션에 독립하여 전체적으로 잘 정의된 목적을 가지고 있다.  그것으로부터, 어떤 어플리케이션에서 부항목 2d처럼 이 함수에서 사용되는 임의의 함수나 테이블을 공급할 것을 요청한다: 만일 그 어플리케이션이 그것을 공급하지 않는다면, 루트 함수는 여전히 루트함수를 계산해야한다.)

그 요구는 전체적으로 갱신된 작업물에 적용된다.  만일 그 작업물의 부분들이 라이브러리로부터 온 것이 아니고 독립적으로 간주될 수 있고 작업물을 분리할 수 있다면, 라이센스와 항목은 그 분리된 작업들을 배포할 때 적용되지 않는다.  그러나 라이브러리에 기초한 작업물의 일부를 배포할 때나, 전체를 배포할 때 이 라이센스의 제한을 받아야만 한다.

당신에 의해 전체적으로 만들어진 작업물에 대한 당신의 권리를 논쟁하거나 거부할 의사는 없다; 오히려, 라이브러리에 기초한 집합적인 작업물이나 파생물의 배포를 제어하기 위한 권리를 적용하기 위함이다.

4. 당신은 당신에게 주어진 라이브러리의 복제본의 라이센스 대신에 보통의 GNU General Public License의 항목을 적용하도록 선택해도 좋다.  이렇게 하기 위해서, 당신은 이 라이센스에 속한 모든 공지들을 선택해야만 하고, 그러면 그들은 이 라이센스 대신에, 두 번째 버전, 보통의 GNU General Public License에 속하게 된다. ( 만일 두 번째 버전보다 새로운 버전이 나왔을 때, 당신이 원한다면 대신에 그 버전을 정할 수 있다.)  그들 공지중 어떤것도 변경하지 말아라.

일단 주어진 복제본에 GNU General Public License를 선택하면, 그 복제본을 위해서 그것을 변경할 수 없고, 보통의 GNU General Public License가 그 복제본으로부터 만들어진 파생 작업물과 연속적인 복제에 모두 적용받게 된다.  이 옵션은 라이브러리가 아닌 프로그램에 라이브러리 코드의 일부분을 넣고자할 때 유용하다.

5. 당신이오브젝트 코드나 실행화일과 더불어 소스 코드를 복제하거나, 배포해도 좋지만 그것은 위의 1항과 2항의 조건하에서 공급되어야만 한다.

만일 오브젝트 코드의 배포가 지정된 위치로부터 복제하도록 만들어 졌다면, 소스 코드의 배포 또한 같은 위치로부터 소스코드를 복제하도록 허용된것과 같다, 심지어 써드 파티(third party)들이 오브젝트 코드와 함께 소스코드를 복제하도록 강요할수 없을지라도.

6. 프로그램이 라이브러리의 일부를 전혀 포함하지 않는다고 할지라도, 컴파일될 때 라이브러리와 함께 작업되도록 만들어졌거나 라이브러리와 링크된다면, "라이브러리를 사용한 작업물"이라고 불리워진다. 그와 같은 작업물은, 라이브러리에서 파생된 작업물이 아니고, 그러므로 이 라이센스의 범주에서 벗어나있다.

그렇지만, 링크과정을 통해서 라이브러리와 링크된 "라이브러리를 사용한 작업물"인 실행화일은 라이브러리의 파생물이다. (왜냐하면 그들은 라이브러리의 일부를 포함하기 때문이다.   6번 항목과 같은 경우로 만들어진 실행화일은 이 라이센스의 범주에 들어간다.

"라이브러리를 사용한 작업물"이 라이브러리의 일부분인 헤더파일에 있는 것을 사용할 때, 그 작업문의 소스코드는 라이브러리의 파생물이 아닐지라도 오브젝트 코드는 라이브러리의 파생물이 되어야 한다. 만일 그 작업물이 라이브러리없이 링크될 수 있거나, 작업물 자체가 라이브러리인지의 여부에 따라서 파생물이 되고 안되고 한다. 이것을 가리기위한 것은 자세히 정의되지 않았다.

만일 오브젝트 파일이 오로지 수적인 파라미터들, 데이터 구조체와 그에 대한 억세스, 그리고 작은 매크로와 작은 인라인(inline) 함수들을 사용한다면(길이가 10줄이하), 그 오브젝트 파일의 사용은 파생된 작업물의 여부에 상관없이 제한이 없다. ( 이 오브젝트 코드가 포함된 실행화일에 라이브러리의 일부가 더해진다면 여전히 6항의 제한을 받는다.) 그렇지않고, 만일 작업물이 라이브러리의 파생물이라면, 당신은 6항에 있는 제한에 따라서 작업물의 오브젝트 코드를 배포해야한다.

그 오브젝트 코드를 포함하고 있는 실행화일은 그것이 직접적으로 라이브러리를 링크하느냐의 여부에 따라서 6항의 제한을 받는다.

7. 위의 항목들을 예외로써, 라이브러리의 일부를 포함시켜 작업물을 만들기 위해서 라이브러리와 함께 "라이브러리를 사용한 작업물을 링크하거나 컴파일해도 좋고, 당신이 선택한 제한들 하에서 그 작업물을 배포해도 좋다.  하지만 고객 자신의 사용을 위해서 작업물을 갱신하고 디버깅으로 갱신된 것을 다시 역으로 처리하는 것은 금한다.

당신은 라이브러리가 사용된 작업물과 라이브러리를 사용하는것에는 이 라이센스에 의해서 제약을 받음을 확실히 주지시켜야 한다. 당신은 이 라이센스를 복제물과 같이 공급해야만 한다.  만일 작업물이 실행되는 동안에 저작권 공지를 표시한다면, 당신은 이 라이센스를 복제하여 사용자에게 직접적으로 언급하는것뿐만 아니라 그들 사이에 그 라이센스를 포함해야만 한다.  또한, 당신은 그들중의 하나를 해야만 한다.

a. 작업물에서 사용된 라이브러리가 어떤 변경을 했던지 작업물과 함께 그 라이브러리를 위한 완전한 소스 코드를 같이 배포하라 (1항와 2항의 제한을 따라야만 한다.); 그리고, 만일 그 작업물이 라이브러리와 "라이브러리를 사용한 작업물"과 링크된 실행화일이라면 완전한 오브젝트나 소스코드를 같이 배포해서, 사용자들이 라이브러리를 수정할 수 있고 갱신된 라이브러리를 포함하여 갱신된 실행물을 만들어서 재링크할 수 있다. (라이브러리 안에 정의된 파일들의 변화된 내용을 알고 있는 사용자는 갱신된 정의를 사용하기 위해서 응용 프로그램을 재 컴파일할 필요가 없을 수도 있다.)

b. 작업물과 함께 항목 6a에 근거한 내용을 같은 사용자에게 적어도 3년간은 공급할 수 있어야 한다.  그것은 배포를 이행하는 대가라기 보다는 의무이다.

c. 만일 작업물의 배포가 지정된 장소에서 복제가 허락되도록 만들어 졌다면, 같은 장소에서 위에 정해진 변경된 내용의 복제가 허용 되는것과 동등하다.

d. 사용자가 받았던 것들이 이미 변화된 내용이 포함된것인지 아니면 당신이 사용자에게 이것을 이미 보냈는지 검증하라.

실행프로그램에서, "라이브러리를 사용한 작업물의 요청된 형식은 그것으로부터 실행프로그램을 재생산하기 위해 필요한 데이터와 유틸리티 프로그램들을 포함해야만 한다.  그렇지만, 특별한 예외로써, 배포본의 구성요소 자체가 실행화일과 함께 들어있지 않다면, 운영 체제상에서 실행시킬수 있는 주요한 구성요소들( 컴파일러, 커널, 등..) 도 함께 배포해야만 한다.

그것은 다른 독점적인 라이브러리들의 라이센스 제한을 거부하는 결과를 낳을 수도 있다.  그와 같은경우에는 당신이 배포한 살행화일 안의 라이브러리와 실행화일을 모두 사용할 수 없게된다.

8. 당신은 이 라이센스에 의해 제한을 받지않는 다른 라이브러리 기능들과 함께 라이브러리 기능을 넣어서 단일한 라이브러리로, 라이브러리에 기초한 작업물을 만들고, 그것을 배포하려 할 때, 오직 라이브러리에 기초한 작업물을 분리하여 배포하고 다른 라이브러리 기능들은 다음 두가지를 했을 때 공급할 수 있다.

a. 라이브러리에 기초한 같은 작업문의 복제본과 결합된 라이브러리를 합병하고, 다른 라이브러리 가능들과는 합병하지 말아라. 이것은 반드시 위의 항목들의 제한하에 공급되어야만 한다.

b. 결합된 라이브러리로 만들어진 작업물에서  결합된 라이브러리에는 라이브러리의 일부가 들어가 있음을 확실히 명시하고, 같은 작업물에 결합되지 않은 것을 어디서 찾을것인지 설명하라.

9. 이 라이센스에 제한에 따라서 라이브러리를 복제하고, 갱신하고, 링크하거나 배포하는 것을 제외하고는 그렇게 할 수 없다.  만일 당신이 그렇게 하려 시도한다면, 당신은 자동적으로 이 라이센스하에 있는 당신의 권리가 종료될 것이다.  당신이 라이센스의 제한에 따라서 복제본을 공급했을지라도 당신으로부터 받은 사람이 완전히 이 제한에 동의하지 않는다면, 그들의 라이센스는 종료될 것이다.

10. 당신이 이것을 인정할 수 없다면 이 라이센스를 받아들이도록 요청할 수는 없다.  그렇지만, 당신이 라이브러리나 그것으로부터 유도된 작업물들을 마음대로 갱신하고 배포하도록 허락할 수 없다.  만일 당신이 이 라이센스를 받아들이지 않는다면 법으로 금지되어 있다.  라이브러리를 배포하고 갱신하는 행위로써 당신은 이 라이센스에 동의했다고 인정할 수 있고, 당신은 라이브러리나 그것에 기초한 작업물을 배포하거나 갱신할 때 라이센스의 제한과 조건을 지켜야한다.

11. 당신이 라이브러리(또는 라이브러리에 기초한 어떤 작업물)를 재배포할 때, 그 라이브러리를 받은 사람들은 자동적으로 원래의 라이센스를 가진 사람으로부터 자동적으로 라이센스를 받은 것이다.  당신은 그들에게 이곳에 승인된 제한외에는 어떠한것도 라이브러리를 받은 사람에게 강요 할 수 없다.  당신은 써드파티(third party)에게 이 라이센스를 지키도록 강요할 책임이 없다.

12. 만일, 법정 판결의 결과나, 특허권의 위반이나, 어떠한 이유로 당신이 라이센스의 조건을 부인하도록 강요된 상황이 있다해도, 그들은 당신을 용서하지 않는다.  만일 당신이 이 라이센스하에서 지켜야할 의무와 다른 라이센스하에서 지켜야할 의무를 동시에 만족시킬수 없다면, 당신은 라이브러리를 전혀 공급하지 않는 것이 좋다.  예를들어, 만일 특허 라이센스가 당신을 통해서 직접적이든, 간접적이든 복제본을 받은 사람에게 로얄티없이는 자유로운 재배포를 금한다면, 그때, 당신이 이 라이센스와 그것을 둘다 만족시키는 유일한 방법은 배포를 그만두는 것이다.

만일 이 항목의 어떤 부분을 특별한 상황하에서 이행할수 없거나 부정하게 된다면, 이 항목과 조화롭게 다른 상황에 적용하도록 하는 의도가 있다.

이 항목이 가지는 목적은 당신에게 특허권을 어기도록 유도하거나, 특허권을 부정하거나 시험하기 위함이 아니다; 이 항목은 자유로운 소프트웨어 배포 시스템의 고결성을 지키기 위함이라는 하나의 목표를 갖고 있다.  많은 사람들은 이 시스템을 통해서 광범한 범위로 소프트웨어를 공급하기를 원하지만; 작가/기증자가 만일 다른 시스템을 통해서 소프트웨어를 공급하려는 결정을 할 때 그 선택을 우리가 강요 할 수는 없다.

이 항목은 이 라이센스의 나머지의 결과가 되도록 완전히 깨끗한 신뢰를 갖도록 하려는 의도가 있다.

13.  만일 배포자 그리고/또는 라이브러리의 사용자가 어떤 나라의 특허권이나 저작권에 의한 제한을 받게된다면, 이 라이센스의 조건하에서 라이브러리를 받은 저작권자는 제한조건에 그 나라를 포함시켜야 한다. 그래서 배포가 제외되지 않은 나라들 사이에서만 허가되도록 한다. 그와 같은 경우, 이 라이센스는 라이센스안에 제한을 써넣어서 그 제한과 합병하게 된다.

14.  Free Software Foundation은 시간이 지나면 라이브러리 General Public License의 새로운 버전이나 교정된 것을 공급할 것이다. 그처럼 새로운 버전들은 현재 버전과 유사한 정신을 담고있지만, 새로운 문제나 관심사항에 대한 좀더 세부적인 것이 다를 뿐이다.

각 버전은 구분되는 버전 번호가 주어진다.  만일 그 라이브러리의가 그것에 적용된 라이센스의 버전과"어느 최근의 버전"을 적용하려 한다면, 당신은 Free Software Foundation에 의해 공급된 최근의 버전이나 지금 있는 버전중에 제한과 조건의 목록을 선택할 자유가 있다. 만일 그 라이브러리가 라이센스 버전 번호를 지정하지 않았다면, 당신은 Free Software Foundation에 의해 공금된 이전의 어떤 버전을 선택해도 좋다.

15.  만일 당신이 라이브러리 라이센스와 모순이 있는 배포 조건을 가진 프리 프로그램안으로 라이브러리의 일부를 합병시키기 원한다면, 당신은 작자에게 허가를 요청해야만 한다.  Free Software Foundation 이 저작권을 가진 소프트웨어라면 Free Software Foundation에게 요청하라;  우리는 이것에 대한 예외상황을 만들었다.  우리는 우리는 프리 소프트웨어로부터 유도되는 모든 자유로운 상황을 보장하고 소프트웨어를 배포하고 재사용하는데 대한 조정을 하려는 두가지의 목표에 따라서 결정한다.

품질보증 없음

16.  법적인 허용을 확장하기 위해서 라이브러리가 대가로부터 자유롭게 라이센스되었기 때문에 아무런 품질보증이 없다.  저작권을 가진 사람들과 다른 파티(party)들이 라이브러리를 어떤 종류의 아무런 품질보증없이 "as is"로써 공급하는 것, 제한없이 인클루드 시키거나 축약하는것에 대한 것을 제외하고는, 상업적을 갖고 특정한 목적에 맞추어주는 품질보증이 강요된다.  라이브러리의 실행과 품질에 대한 전체적인 위험은 당신과 함께 있다.

라이브러리의 결점을 증명하게 된다면, 복구하거나 정정하는 것은 모든 필요한 서비스에 대한 대가라고 가정하라.

17.  저작권자나 다른 부류들이 라이브러리를 사용함으로써 얻게되는 손해의 가능성에 대해서 경고했을지라도, 우연히 알수없게 일어날 수 있는 손해들이 있다( 데이터를 잃거나 하는 경우..) 하지만 그런 경우 법적으로 보장되거나 이 라이센스 하에서 자유롭게 갱신하거나 배포하도록 허락한 저작권자나 그런 부류들이 그것을 승인하지 않는한 보상받을 수 있는 경우는 없다.

제한과 조건의 끝.

당신의 새로운 라이브러리에 그 항목을 어떻게 적용할것인가.?

만일 당신이 새로운 라이브러리를 개발하고, 가능한 모든 사람들이 사용하기를 원한다면, 우리는 누구나재배포하고 갱신할 수 있는 프리 소프트웨어를 만들기를 권장한다.  당신은 그 항목하에서 재배포를 허락함으로써 그렇게 할 수 있다 (또는, 보통의 General Public License의 항목하에서 선택적으로,) 그 제한들을 적용하기 위해서, 라이브러리에 다음 공지들을 덧붙여라. 품질보증에서 제외되는 소스 파일의 시작에 그들을 덧붙이는 것이 좋다; 그리고 각 파일은 적어도 "저작권"라인은 가져야하고 완전한 제한과 조건에 대한 공지를 어디에서 발견할 수 있는지에 대해서 안내해야 한다.

한라인은 라이브러리의 이름과 무엇에 대한것인지를 적는다.

Copyright (C) year  name of author

이 라이브러리는 프리 소프트웨어이다;  당신은 Free Software Foundation에 의해 공급된 GNU Library General Public License 의 제한하에서 그것을 재배포하거나 갱신할 수 있다;  라이센스 버전 2, 또는 최근 버전.

이 라이브러리는 유용하게 사용할것이라는 희망으로 공급되었지만, 아무런 품질보증이 없다; 즉 어떤 상업적이거나 특정한 목적에 맞추는 품질보증을 내포하지 않는다.  GNU Library Gereral Public License 에 대한 자세한 사항을 참조하라.

당신은 이 라이브러리와 함께 GNU Library General Public의 복제본을 받아야한다; 만일 없다면, Free Software Foundation으로 문의하라.

주소는 Free Software Foundation, Inc, 675 Mass Ave, Cambridge, MA 02139, USA.

그리고 전자메일이나 편지를 통해 당신과 접촉할 수 있는 방법에 대한 정보도 함께 보내라.

당신은 필요하다면 직장을 구하거나 배움의 터를 구하거나 라이브러리의 "저작권 위반자"알리거나 할 수 있다.  다음의 이름들중 골라라.

Yoyodyne, Inc., hereby disclaims all copyright interest in the library

`Frob' (a library for tweaking knobs) written by James

Random Hacker.

signature of Ty Coon, 1 April 1990

Ty Coon, President of Vice

That's all there is to it!

홀로서기

- 둘이 만나 서는 게 아니라

홀로선 둘이가 만나는 것이다.-

기다림은

만남을 목적으로 하지 않아도

좋다.

가슴이 아프면

아픈 채로,

바람이 불면

고개를 높이 쳐들면서, 날리는

아득한 미소.

어디엔가 있을 나의 한쪽을 위해

헤매이던 숱한 방황의 날들.

태어나면서 이미

누군가가 정해졌었다면,

이제는 그를

만나고 싶다.

부록 E  뒷 얘기

부록 E 뒷 얘기

E.1  역자 후기

하이텔 소프트웨어 동호회 소모임, 직장인을 위한 C 모임에서 번역한 사람들이 그간 게시판에 올렸던 내용중 몇가지입니다.

E.1.1  김소영(flpigy)

##번역을 그만 둘까 합니다! ## 08/04 23:00  (1996년)

이유를 말씀드리자면 제가 번역을 해오면서, 그동안 많이 재미있었고, 그다지 힘들지도 않았지만 제 실력이 번역을 할만한 실력이 되지 않는다는 깨달음 때문입니다.

적어도 GCC 라이브러리 레퍼런스를 번역할려면, 리눅스나 유닉스에 대한 기본 지식이 있어야 하는데... 전 그런 지식이 없다보니(학교 다닐 때 놀지 말걸... ) 그동안 틀린 번역이 꽤 있었을 거라 생각합니다. 제 번역을 보셨던 분들은 다 느끼셨을 거예요. 번역을 보시는 분들이 다들 실력이 출중하셔서, 제가 틀린 부분을 감안하고 보셨으리라 생각하지만...

번역을 할 때...

제가 아는 런타임 라이브러리가 나오면 대충 맞게 해석한다는 생각이 들기도 했지만, 그외 다른 함수들이 나오면 해석을 해놓고도 이것이 맞는 해석인지 자신이 없었지요. 그런데.. 다들 보시는 분들이 말씀이 없으셔서 전 그동안 제가 번역을 꽤 잘하는 줄 알았더랬습니다. 히히...

하지만 그건 역시 착각...

어떤분의 뼈아픈(?) 충고가 있었습니다. 잘못된 번역은 오히려 보는 사람을 혼란스럽게 한다는 그런 말씀을 하시면서 차라리 저의 실력을 좀더 키운후 좀더 완벽한 번역을 하는게 어떻겠냐고도 하셨습니다.

많이 망설였습니다.

그만 둬야되나, 말아야되나.. 를 가지고... 제가 번역을 제 날짜에 올리는 것은 여러분과의 약속이라고 생각했고 (아무리 보는 분들이 적을 망정...) 그 약속을 지켜나가는 것이 저의 임무이며, 직씨를 위한 일이라고 생각했더랬습니다. 스스로 자신과 직씨회원 여러분과 했던 약속을 깬다는 것 때문에.. 그리고 지금처럼 직씨의 활동이 침체된 상황에서 저마저 이렇게 나간다면 안될거라는 생각에... 망설여진 것 입니다.

하지만 좀 더 멀리 보기로 했습니다. 진정으로 다른 사람을 생각한다면, 제가 그만두는 것이 더 나을것이란 쪽으로 생각을 굳혀가고 있습니다.

그래서 여러분께 제안합니다.

저를 대신할 수 있는 분이 저를 이어서 번역을 계속 이어주셨으면 합니다.  리눅스 동호회에서 GCC 라이브러리 레퍼런스외에 여러 가지 문서를 제본한걸로 알고 있습니다.  그래서 앞으로는 좀더 많은 분들이 저희가 해나가는 번역에 관심을 갖을거란 생각이 드는데 그리고 해나가면서 스스로 찾는 보람도 있을거구요. 저를 대신할분 누구 없으십니까?

여러분과의 소중한 약속을 깨서 죄송하단 말씀 드리고 새로운 누군가가 나타나기를 간절한 마음으로 기다리고 있겠습니다.  그리고 저에겐 제 실력이 맞는 다른일을 시켜주세요.

-- 고개 숙인 희동이..--

번역과 내동생 08/05 22:48   (1996년)

번역에 대한 고민을 다시 시작했습니다. 전 귀가 얇은 사람인가 봅니다.  이말도 맞는 듯 하고, 저말도 맞는 듯 하고 성훈님이 그렇게 칭찬을 해주시니 정말 몸둘바를 모르겠고, 원봉님의 말씀도 일리가 있고 소영이의 힘내라는 쪽지도 너무 고맙고 성호님의 말씀도 고맙구요. 오늘 동생이 집에 일찍 들어오라고 해서 들어갔더니 내일모레 처음으로 소개팅을 한데요. 크크크...

애가 아직 순진한건지, 군대도 갔다오고, 3학년이나 되었는데...글세.. 소개팅은 처음이라는 군요. 무슨 얘기를 해야하나 걱정하면서...결국 하는말 "누나 지원금 좀 줘..." 그래서 '얼마나 필요한데?' 라고 물었더니.. 동생왈 "잘 못되면 1~2만원이면 되고, 잘되면 적어도 4만원은 필요하겠지?" "하지만 잘될걸 예상해야하니까.. 4만원만 주라.." (동생인지 웬수인지...)

제 동생 참 귀엽죠?

25살이나 먹은 애한테 귀엽다고 하면 안되나?

-- 번역에 대해 고민하며..희동이. --

## 번역 다시 할께요.. 히~ ##  08/08 21:25  (1996년)

안녕하세요?  직씨 회원 여러분...

그동안 번역에 대한 고민을 좀 했습니다. 여러분들께서 그렇게까지 말씀을 하셨는데도 거부한다는 것은 도리가 아닌 것 같았습니다.

사실은 번역을 그만두겠다고 해놓고, 많이 서운했었습니다. 별로 한것도 없지만, 그래도 돌아보면 몇 달동안 저의 노력이 조금이나마 들어간 곳이기에..  그동안 정이 많이 들었었던가 봅니다.  그래서....'내가 시작한 것은 내가 마무리 해볼까나~' 란 결론을 내리게 됐고, 시작하더래도 조금 쉬었다 할까도 생각했지만, 이왕 할 일 잠시 쉰다고 달라질 사실은(저의 실력에 대한) 없는 것 같아서 그냥 내친김에 계속하기로 했지요. 변덕이 죽 끊듯한 것 같아서... 그리고 괜히 튀어보인 것 같아서...괜시리 쑥스럽고 그렇네요.

며칠전부터 출퇴근 버스에서 '알기쉬운 유닉스'란 책을 잡지책을 뒤적이듯 보고 있습니다.  쉬운책이라 책장이 참 잘 넘어가더군요. (집에 들어가면 펑펑~ 노는 사람이 사람 많은곳에서 튀어보이고  싶어서.. 쯔쯔..) 이거라도 보면 좀더 번역에 도움이 될려나~

-- 번역분과여.. 영원하라~ 희동이..--

번역을 마치며... 흑흑흑...  01/30 18:51 (1997년)

여러분께 감사의 마음을 전하려합니다. 직씨의 모든 회원들께 감사하지만.. 그중에서도 특히 꾸준하게 번역을 보아주시고.. 중간에 한번 번역을 그만두고자 했을 때.. 용기를 주신분들께 무한한 감사를 드립니다. 인생의 소중한 경험이 될것도 같네요. 하면서 재미도 있었고, 가끔은 지겹기도 했었고.. 이제 씨원~하기도 하지만.. 섭섭한 마음이 앞서네요.

감사합니다.. 여러분... 흑흑흑... 으앙~

E.1.2  조원봉 (thirnine)

##번역을 그만둘 때 두드라도##         08/05 16:31   73 line  (1996년)

음..오늘은 몸살이나서 출근도 못하고

어제부터 약먹고 주사맞고 생고생중이빈다만,...

/입니\

김소영님이 번역을 중단하시는 건 자유이겠지만,

이유가 "실력"문제라면 ..그건 말이 안되는 것 같아서...

이 노병의 개똥사상을 한번 밝히고

가고자 합니다.

===

생각이 정리되지 않은 관계로

당초에 번역이 시작되던 때부터

짚어보면서 문제를(아니,내나름대로의 생각을) 들추어보기로 하지요.

===

애시당초에 이 번역사업은

정확한 번역을 전제로 하지 않은 것이었습니다.

왜냐하면,

번역은 어디까지나 "일방적인 제공"으로서

그 자체가 "소모임에 대한 애정의 표시","소모임에 기여하기",

"자발적인 참여"일 뿐이지

<정확성>에 대한 의무란 것은 존재하지 않는 것이었습니다.

나의 생각으로는

자본주의 사회에서

노동의 댓가로 돈을 지급한다는 계약이 존재하지 않는 한,

번역의 부정확성에 대한 미안감 같은 것을

과도하게 가져야할 하등의 이유가 없다고 생각합니다.

===

나자신도 처음엔

열심히 번역사업에 참여하였지만,

만약 누가 번역의 부정확성을 지적해 주었더라면,

그것은 오히려 번역에 관심을 표시해주고

나의 발전을 채직질해주는

좋은 약이 되었을 것이라고

반갑게 받아들였을 것입니다만,

불행히도 나에겐

그런 최소한의 고나심조차 기울여지지

/관\

않았었는데,

그래도 김소영님에게는 관심의 따뜻함이 전해지고 있었군요...

====

말나온김에,

사실상 이몸이

번역을 중도하차한 가장 큰 이유는

번역 자체에 걸리는 시간보다는

전송에 걸리는 시간이었습니다.

하드를 자주 주물럭거리다 보니,

포맷도 수차례 되고

통신프로그램 잊어먹고..등등..

전송에는 거의 2시간씩 걸렸지요.

그러던 와중에

전문 프로그래머로서 늦은 나이에

공부,영업,기타 욕심이 뒤덤벅되다보니

번역대열에서 이탈되어 갔던거죠...

====

지금은

김소영님이 번역을 안 하겠다고 하면

과연 누가 할 사람이 있겠는가?라는

생각이 듭니다.

그리고..내가 번역상의 실수를 가볍게 생각한 이유는

그 번역은 참고용일뿐이지

혹시 그것을 이용하려는 사람이 있드라도

그 내용을 100% 그대로 믿고 쓰지는 않을 거란

판단때문이었습니다.

====

...

언제나 그렇듯이

노병의 개똥사상은

누구에게 강요되는 성질의 것이 아닙니다.

...

요즘 내 생각 같아선

C++이나 Visual C++관련

번역을 하고 싶어지네여...흐흐

왜냐,

난 컴공학과 1학년 독학반 반장이거든..흐흐

기럼~~!

E.1.3  고석갑 (sukgab)

저도 번역 끝났습니다.                 02/01 21:56   5 line  (1997년)

그동안 엉터리 번역하는데, 힘이 되 주셔서 감사합니다.

많은 도움을 주신 소영님께는 더더욱 감사합니다.

어째거나 제가 맡은 부분을 미진하나마 마쳤군요.

나중에 HWP로도 자료실에 올리겠습니다.

E.2  편집 후기

E.2.1  1차 편집 후기

본 GLIBC 번역판을 먼저 정리하신 하이텔 한호중(0demon0)님이 추가하신 내용입니다.

※C프로그래머가 되기 위해서 공부해야 할 내용※

아래에 언급한 내용은 C 프로그래머가 되기 위해서 공부해야만 하는 내용을 전부 적은 것입니다.여러분들 중에는 "C 공부"를 어느정도 했음에도 불구하고 직접 프로그램을 작성하는데 어려움이 있는 분들도 있을 것입니다. 아래에 언급한 내용중 어느 부분에 문제가 있을지 스스로 점검해 보시기 바랍니다.

--------------------------------------------------------------------------------

C 문법 (교재 내용 전부)

--------------------------------------------------------------------------------

<> 자료형, 예약어, 연산자, 제어문, 배열, 포인터, 구조체, 유니온,

<> 프리프로세서, 칼라 그래픽, 변수의 기억 부류, 파일 입출력,

---------------------------------------------------------------------------------

라이브러리 함수(약 300개 - 400개) 사용법

---------------------------------------------------------------------------------

<> 함수의 형태, 선언법

<> 파라메타를 주고 받는 방법

<> 반환값을 되돌리는 방법

<> 기능별 내용 학습

-> 문자 분류 함수	-> 변환함수

-> 디렉토리 제어 함수	-> Text 커서 관련 함수

-> Text 컬러 관련 함수	-> Text 모드 관련 함수

-> 그래픽 관련 다양한 함수	-> 스트링 조작 관련 함수

-> 프로세스 제어 관련 함수	-> 인터페이스 관련 함수

-> 메모리 조작 함수	-> 수학 관련 다양한 함수

-> 랜덤 수 관련 함수	-> 시간과 날짜 관련 함수

-> 소리(sound) 관련 함수	-> 자료검사 및 분류 검사 함수

================================================================================

컴파일러 및 기타 tool 사용법

================================================================================

<> 컴파일러 사용법(tc.exe, tcc.exe)

-> 여러가지 option의 의미파악. 터보 C 는 비교적 사용하기 쉬워서 문제가 적을 것임.

<> 디버거 사용법 (debug.exe)

-> "파일" 및  "메모리의 구조" 등을 살펴봄으로서 컴퓨터의 내부사항을  "소프트웨어적으로" 근본을 이해	하기 위한 기본 tool 입니다.

<> 링커 사용법 (tlink.exe)

-> 중급 이상 사용자에게 필요한 여러가지 option의 의미

<> 라이브러리 tool 사용법 (tlib.exe)

-> 자신이 만든 라이브러리 함수를 등록하여 사용할 때 사용.(중급 이상 사용자에게 필요)

=================================================================================

프로그램 작성법 (초급, 중급)

=================================================================================

<> 각 기능별 루틴을 작성하면서 점차 큰 프로그램으로 개발

프로그램에도 질(quality)이 있는 법입니다. 비록 똑같은 "주소 관리 프로그램" 을 짠다고 하더라도 그 내부는 천차 만별입니다. 즉 "프로그램을 짠다" 는게  중요한게 아니고 (그건 기본일 따름이고) "프로그램을잘 짜야" 합니다. 이런 문제에 비록 정답은 없지만, 고품질의 프로그램을 짜기 위해서는 처음부터 "특별한 의식"을 가지고 습관을 들여 나가야 합니다. 아래의 기본적인 루틴을 그러한 관점에서 짜나가는 방법을 공부합니다.

-> 메뉴 구성 및 운용에 관한 루틴	-> 키 입출력에 관한 루틴

-> 마우스 입출력에 관한 루틴	-> 순환문 사용에 관한 루틴

-> 랜덤 수 이용관련 루틴	-> 포인터의 실질적 사용에 관한 루틴

-> 파일 입출력을 위한 구조체, 유니온의 사용방법

-> 파일 레코드의 입력, 출력, 수정, 삭제 관련 루틴

-> 시간(시계) 기능을 이용하는 루틴

-> 유저 인터페이스(user interface) 문제를 고려한 "소리"기능

-> 그래픽 관련 루틴	-> 한글 처리 관련 루틴.

-> 등등, 실제 프로그램 개발에 필요한 다양한 루틴 학습

<> 컴파일 에러 잡기(디버깅) 방법 실습

-> 컴파일  시 발생하는 에러의 종류는  다양합니다. 크게 나누어 다음과 같습니다.

1. C 문법 자체가 틀린 경우(컴파일러에서 걸리는 경우)

2. 링크 할 때 문제가 되는 경우

3. 특별한 파일이 존재하지 않는 경우

4. 기타 여러가지 이유

대부분의 문제는 "에러 메세지(Error message)"를 보면 정확히 이유를 알  수 있지만, 의외로 초보자들은 문제 해결에 어려움 을 많이 겪습니다. 사실은 이 에러  해결하는 방법 중에 경험에 의존하는 것도 많기  때문에 말로 표현하기 어려운 것도 있습니다. 따라서 이 내용만  갖고도 상당기간 강좌를 진행할 수 있을 것입니다. 많은 연습과 질문이 필요할 것입니다.

<> 이미 개발된 프로그램을 읽고 분석하는 요령

-> 모든 프로그램을 자기가 전부 작성한다는 것은 너무 시간이 걸리는 일입니다. 따라서 유용하게 활용할 수 있는 공개된 소스 프로그램이 있다면 그것을 잘 수정,활용 하는 것이 좋은 방법입니다. 남의 프로그램을 읽고 분석하는 방법(요령)을 학습합니다. 이것은 여러분이 생각하는 것 이상으로 중요한 사항입니다.

================================================================================

DOS, BIOS Fuction call

================================================================================

<> 중급 이상 사용자들이 기본적으로 사용해야 할 기능입니다.

<> 이 내용을 활용하기 위해서는 "인터럽트"에 대한 이해가 필요합니다.

<> 이 전체 목록은 이 강좌에서 제공할 예정입니다.

================================================================================

컴퓨터 구조및 하드웨어

================================================================================

<> 초급자들이 컴퓨터에 대해 상식적으로 알아야 하는 기능을 C 와 연관지어 학습합니다.

<> 중급 이상자들이 전문적으로 프로그램하기 위해 알아야하는 내용.

-> 키보드, 모니터, 마우스, 프린터	-> CPU, RAM, ROM, CMOS

-> FDD, HDD, 모뎀,등등	-> 버스 구조와 옵션카드 들에 대한 이해

-> 기타, "레지스터"를 가지고 있는 컴퓨터 내부의 각종 칩에 대한 학습

================================================================================

어셈블러(assembler)

================================================================================

<> 중급 이상의  실력을 갖추면 프로그램의 실행속도등의 이유로 어셈블러를 이용하여 C와 링크(link) 하는 작업을 해야합니다. (C + 어셈블러)

<> 어셈블러  자체도 하나의 개별적인 언어이므로  많은 시간 학습이 필요합니다. 특히  어셈블러 실습을 위해서는, 위에서  언급한 "컴퓨터 하드 웨어" 에 대한 이해가 선행되어야 합니다. 만일 이 강좌를 정식으로  개설하면 그것은 더이상 "C 강좌"의 범주에 넣을 수 없을 것입니다. (마치 "BASIC"이 C 강좌가 될 수 없듯이) 그러나 "어셈블러" 가  언급되는 이유는 두 언어가 혼합되어 사용될 시 더욱 좋은 프로그램을 짤 수 있기 때문입니다.

---------------------------------------------------------------------------------

<마치며>

이상의 내용이 프로그래머가 되고자 하는 분이 기본적으로 알아야 할 내용입니다. 스스로 아는  부분과 모르는 부분을 명확히 구분하여 학습에 효율을 기하시기 바랍니다. 스스로 문제를 풀어보시고 모르는 내용과 또, 특히 안다고 착각하고 있었던 부분은 없는지 점검해 보세요.

E.2.2  2차 편집 후기

하이텔 직씨에서 빈둥대는 RAGNARoK 의 푸념

GLIBC 1.09 영문판을 갖고 있어서 그것과 비슷하게 편집하다가 결국 엄청난 시간을 투자했지만 남는 건 딱 한 가지뿐이군요.

□편집이 용이한 워드 프로세서를 만들고 말테닷.□

□안 그러면 미쳐버릴 것 같애....으아악~~~ --;□

HWP 2.1, 2.5, 3.0 모두 정품 구입했었죠. 그렇지만 이때까지 써 본 기능이라곤 소스를 축소한 후 출력한 것 뿐. 표니 뭐니... 거의 대부분 처음 구입할 때 몇번 뒤적일 때 이름 한번 본 것 뿐이라 기능하나 넣을때마다 기능설명서 보느라 눈이 뒤집힐거 같더군요. 완전히 능력의 한계를 느끼며 더 이상의 편집을 포기하고 올립니다. 이제 그럭저럭 볼만 하실겁니다. 누구 줘 버릴려다가 말았던 그 두꺼운 기능설명서를 내내 뒤적거린 엄청난 편집이었네요. (저에게만...--;)

그동안 워낙 속이 터져서 한 마디도 안 하고선 못 견딜거 같아 왕잡담 해 봤습니다.

오기가 발동하면 끝장 보려는 성질을 고쳐야 하는데....으그그.

□다시 편집 하나 봐라.... 내가 미쳤지...흑흑□

PS. 초반엔 편집을 업으로 삼으시는 분이 정말 대단하게 여기지더군요.

어떻게 프로그램 짜는거보다 편집이 더 어려운 걸까....어