이전 챕터들에서는 C 라이브러리를 단편적으로 살펴보았다. 이 챕터에서는 그 라이브러리 전체에 대해 초점을 맞춘다.
21.1 Using the Library
C89 표준 라이브러리는 15개의 파트로 나눠져 있으며, 각각의 파트는 헤더에 의해 설명된다.
C99 표준 라이브러리에는 9개의 헤더가 추가되어 총 24개의 헤더가 있다.
<assert.h> |
<inttypes.h>* |
<signal.h> |
<stdlib.h> |
<comlex.h>* |
<iso646.h>* |
<stdarg.h> |
<string.h> |
<ctype.h> |
<limits.h> |
<stdbool.h>* |
<tgmath.h>* |
<errno.h> |
<locale.h> |
<stddef.h> |
<time.h> |
<fenv.h>* |
<math.h> |
<stdint.h>* |
<wchar.h>* |
<float.h> |
<setjmp.h> |
<stdio.h> |
<wctype.h>* |
*C99 only
대부분의 컴파일러는 광범위한 라이브러리가 있고 위 표에 나오지 않는 헤더도 많이 존재한다. 추가적인 헤더는 표준이 아니므로, 다른 컴파일러에서도 사용 가능하다고 생각할 수 없다. 그런 헤더들이 제공하는 함수는 종종 특정 컴퓨터나 운영체제에만 적용된다.
표준 헤더에는 주로 함수 원형, 타입 정의, 매크로 정의 등이 들어 있다. 어떤 파일에서 특정 헤더 안의 함수를 호출하거나 정의된 타입, 매크로를 사용하려면 파일 첫 부분에서 그 헤더를 include해야 한다. 여러 개의 표준 헤더를 include 할 때 그 순서는 상관이 없다. 또 표준 헤더를 두번 이상 include해도 된다.
Restrictions on Names Used in the Library
표준 헤더를 include한 파일에서는 몇가지 규칙을 따라야 한다.
첫째, 헤더 안에서 정의된 매크로를 다른 목적으로 사용할 수 없다. 예를 들어 <stdio.h>를 include하면 NULL이라는 이름은 이미 선언되었기 때문에 다른 용도로 쓸 수 없다.
둘째, file scope를 갖는 라이브러리의 name들(특히 typedef names)은 파일 레벨에서 재정의될 수 없다. 따라서 한 파일에서 <stdio.h>를 include하면 size_t를 file scope의 identifier로 정의할 수 없다. <stdio.h>에서 size_t를 typedef 이름으로 정의하기 때문이다.
위 제한은 꽤 당연해 보이지만, 다음과 같은 제약도 존재한다. 다음 규칙들이 항상 강제적인 것은 아니지만, 지키지 않을 경우 프로그램을 포터블하지 못하게 만들 수 있다.
밑줄+대문자로 시작하거나, 밑줄+밑줄로 시작하는 identifier는 라이브러리 내에서만 사용될 목적으로 예약되어 있다. 이런 형태의 이름을 어떤 목적으로도 사용해서는 안 된다.
밑줄로 시작하는 identifer는 file scope를 갖는 identifier들과 tag들로 예약되어 있다. 함수 내에서 사용할 것이 아니라면 이러한 이름을 자신만의 목적으로 사용해선 안 된다.
표준 헤더 내의 external linkage를 갖는 모든 identifier는 external linkage를 갖는 identifier로 예약되어 있다. 특히 표준 라이브러리의 모든 함수 이름은 예약되어 있다. 따라서 한 파일에서 <stdio.h>를 include하지 않는다고 하더라도, printf라는 이름을 갖는 external function을 정의해서는 안 된다.
이 규칙들은 프로그램 내의 모든 파일에, 어떤 헤더를 include했는지에 관계없이 적용된다. 또 현재 라이브러리에 존재하는 이름들 뿐 아니라, 미래에 사용될 수 있게 남겨진 이름에 대해서도 적용된다. 예를 들어 str + 소문자로 시작되는 이름들이 미래에 <string.h> 헤더에 추가될 때를 대비해 예약되어 있다.
Functions Hidden by Macros
표준 라이브러리 내의 어떤 함수 이름들은, 함수로도 정의되어 있지만 동시에 parameterized 매크로로 정의된 경우도 있다.
예를 들어, getchar 함수는 <stdio.h> 헤더 내에 선언된 라이브러리 함수이다. 그 원형은
int getchar(void);
그런데 <stdio.h>는 종종 getchar를 다음과 같은 매크로로도 정의한다.
#define getchar() getc(stdin)
getchar 호출시 기본값으로 매크로 호출로 간주된다(매크로 이름은 preprocessing시 대체되기 때문에).
대부분의 경우 진짜 함수 대신 매크로를 사용하는 것이 속도 측면에서 유리하므로 매크로를 기쁘게 사용하겠지만 가끔은 함수 자체를 원할 때도 있다 (아마 실행 코드의 크기를 최소하하기 위해서).
그럴 필요가 있을 때는 macro 정의를 제거하거나
#include <stdio.h>
#undef getchar
다음과 같이 이름 주위에 괄호를 씌워서 매크로로 인식하지 않게 만든다.
ch = (getchar)(); /* instead of ch = getchar(); */
매크로 이름 뒤에 왼쪽 괄호가 없기 때문에, preprocessor는 이것을 parameterized macro로 인식하지 않는다. 대신 컴파일러는 getchar를 함수로 인식하게 된다.
21.2 C89 Library Overview
<assert.h> Diagnostics (24.1)
assert 매크로만 들어 있다. 이 매크로는 프로그램 내에서 자체적으로 체크할 수 있게 해준다. 만약 체크에 실패하면, 프로그램이 종료된다.
<ctype.h> Character Handling (23.5)
문자들을 분류하고, 소문자를 대문자로 변환하거나 그 역인 함수들을 제공한다.
<errno.h> Errors (24.2)
lvalue인 errno("error number")를 제공한다. 이것은 어떤 라이브러리 함수 호출 이후에 테스트되어 호출 도중에 에러가 발생했는지를 알 수 있게 해 준다.
<float.h> Characteristics(특징) of Floating Types (23.1)
floating 타입들의 특징을 설명하는 매크로를 제공한다. 타입들의 범위, 정확도 같은 것들.
<limits.h> Sizes of Integer Types (23.2)
integer 타입(character 타입들 포함)들의 특징을 설명하는 매크로를 제공한다. 최대, 최소값 같은 것들.
<locale.h> Localization (25.1)
국가나 지리적 영역에 프로그램의 행동이 적응할 수 있게 돕는 함수를 제공한다. 국가/지역 한정적인 것들에는 숫자 표현 방식(소수점에 쓰이는 문자), character set, 시간과 날짜의 표현 방식 같은 것들이 있다.
<math.h> Mathematics (23.3)
수학 함수들을 제공한다. 삼각함수, 하이퍼볼릭, exponential, 로그, power, 반올림, 절대값, 나머지 함수 같은 것들.
<setjmp.h> Nonlocal Jumps (24.4)
setjmp, longjmp 함수를 제공한다. setjmp는 프로그램의 한 지점을 표시한다. longjmp는 나중에 그 지점으로 돌아올 수 있다.
<signal.h> Signal Handling (24.3)
예외적인 상황들(signals) - 중단, 런타임 에러같은 - 을 다루는 함수들을 제공한다. signal 함수는 나중에 signal이 발생하면 호출되는 함수를 설치한다. raise 함수는 signal을 발생시킨다.
<stdarg.h> Variable Arguments (26.1)
가변 인자를 갖는 함수(ex. printf, scanf)를 쓸 수 있게 해 주는 도구를 제공한다.
<stddef.h> Common Definitions (21.4)
자주 사용되는 타입과 매크로의 정의를 제공한다.
<stdio.h> Input/Output (22.1-22.8)
입출력 함수들의 커다란 묶음을 제공한다. 순차 액세스 파일과 무작위 액세스 파일 모두에 대한 조작을 포함한다.
<stdlib.h> General Utilities (26.2)
다른 헤더에 속하지 않는 함수들을 포함하는 "잡동사니"이다. 이 헤더의 함수들에는 문자열을 숫자로 변환, 의사 난수 생성, 메모리 관리 작업, 탐색과 정렬, 멀티바이트 문자와 와이드 문자 간의 변환 같은 것들이 있다.
<string.h> String Handling (23.6)
문자열 조작에 관한 함수들 - 복사, 연결, 비교, 탐색 - 을 제공한다. 뿐만 아니라 임의의 메모리 블록을 조작하는 함수들도 제공한다.
<time.h> Date and Time (26.3)
시간과 날짜를 결정하고, 시간을 조작하고, 시간을 출력하도록 포맷팅하는 함수들을 제공한다.
21.3 C99 Library Changes
C99로 바뀌면서 생긴 커다란 변화 중 몇몇은 표준 라이브러리에 영향을 주었다. 그 변화들은 세가지 그룹으로 나뉜다:
추가된 헤더들: C99 표준 라이브러리는 C89에 없었던 9개의 헤더가 추가되었다. 이 중 세개(<iso646.h>, <wchar.h>, <wctype.h>)는 1995년에 C89가 개정되었을 때 이미 추가되었다. 다른 여섯개(<complex.h>, <fenv.h>, <inttypes.h>, <stdbool.h>, <stdint.h>, <tgmath.h>)는 C99에서 새롭게 추가되었다.
추가된 매크로와 함수들: C99 표준에서는 기존 헤더들에 매크로와 함수가 추가되었다. 주로 <float.h>, <math.h>, <stdio.h>에 추가되었다. <math.h>에 추가된 것들은 아주 광범위해서 23.4에서 따로 다룬다.
강화된 기존 함수: 기존에 있던 몇몇 함수들(printf와 scanf를 포함해서)이 C99에서 추가적인 능력을 갖게 되었다.
<complex.h> Complex Arithmetic (27.4)
complex와 I 매크로를 정의한다. 이들은 복소수를 다룰 때 유용하게 사용된다. 또 복소수 연산을 수행하는 함수들을 제공한다.
<fenv.h> Floating-Point Environment (27.6)
Provides access to floating-point status flags and control modes. For example, a program might test a flag to see if overflow occured during a floating-point operation or set a control mode to specify how rounding should be done.
<inttypes.h> Format Conversion of Integer Types (27.2)
<stdint.h>에 선언된 정수형 타입을 input/output하기 위한 포맷 스트링에 사용되는 매크로가 들어 있다. 또 greatest-width integer를 위한 함수를 제공한다.
<iso646.h> Alternative Spellings (25.3)
몇몇 연산자(&, |, ~, !, ^가 포함된 것들)를 나타내는 매크로를 정의한다. 해당 문자들이 local character set에 포함되지 않은 환경의 프로그램에서 유용하게 사용된다.
<stdbool.h> Boolean Type and Values (21.5)
bool, true, false 매크로를 정의한다. 또 이 매크로들이 정의되었는지를 테스트하는 매크로를 정의한다.
<stdint.h> Integer Types (27.1)
특정 폭을 갖는 정수 타입을 선언하고 관련된 매크로(각각 타입의 최대값과 최소값 같은)를 정의한다. 또 특정 타입의 정수 상수를 만드는 parameterized macro를 정의한다.
<tgmath.h> Type-Generic Math (27.5)
C99에는, 같은 작업을 수행하지만 타입이 다른, 다른 버전의 수학 함수가 <math.h>와 <complex.h> 헤더에 들어 있다. <tgmat.h> 헤더 안의 "type-generic" 매크로는 자신에게 전달된 인자의 타입을 탐지해서 그것을 <math.h> 또는 <complex.h> 헤더에 있는 적합한 함수를 호출한다.
<wchar.h> Extended Multibyte and Wide-Character Utilities (25.5)
와이드 캐릭터 입출력과 와이드 문자열 조작을 위한 함수들을 제공한다.
<wctype.h> Wide-Character Classification and Mapping Utilities (25.6)
<ctype.h>의 와이드 캐릭터 버전이다. 와이드 캐릭터를 분류하는 함수와 대/소문자를 변환하는 함수들을 제공한다.
21.4 The <stddef.h> Header: Common Definitions
<stddef.h> 헤더는 자주 사용되는 타입과 매크로의 정의를 제공한다. 함수의 선언은 없다.
Types
ptrdiff_t. 두 포인터에 대해 뺄셈을 했을 때 결과의 타입이다.
size_t. sizeof 연산자가 리턴하는 타입이다.
wchar_t. 모든 지역에서 사용되는 가능한 문자를 표현할 수 있을 정도로 넓은 타입이다.
세가지 이름은 모두 정수형 타입이다. ptrdiff_t는 signed type, size_t는 unsigned type이어야만 한다. wchar_t는 25.2에서 자세히 다룬다.
Macros
NULL. null pointer
offsetof. parameterized macro로, 인자는 두 개이다. type(구조체 타입)과 member-designator(구조체의 멤버).
offsetof는 구조체의 시작 지점과 지정한 멤버 사이에 몇 바이트가 있는지 계산한다.
struct s {
char a;
int b[2];
float c;
};
offsetof(struct s, a)는 무조건 0이다. C는 구조체의 첫번째 멤버가 구조체 자체와 같은 주소에 있는 것을 보장하기 때문이다. b와 c의 offset은 어떻게 될 지 확실하게 말할 수 없다. 한가지 가능성은 offsetof(struct s, b)가 1(a가 1바이트이므로)이고, offsetof(struct s, c)는 9(int가 32-bit라는 가정 하에)가 되는 것이다. 하지만 어떤 컴파일러에서는 구조체에 "구멍"을 남기기 때문에, offsetof의 값에 영향을 준다. 예를 들어 컴파일러가 a 뒤에 3바이트의 구멍을 남긴다면, b와 c의 offset은 각각 4와 12가 된다. 이렇게 컴파일러에 따라 offsetof의 값이 달라지는 점이 중요하다. 모든 컴파일러에서 정확한 offset을 계산해 주기 때문에, 포터블한 프로그램을 만들 수 있게 해 준다.
만약 s 구조체의 c 멤버는 제외하고 a, b멤버만 파일에 저장하고 싶다고 하자. fwrite함수로 sizeof(struct s) 바이트를 저장해서 구조체 s 전체를 저장하는 대신에, offsetof(structs, c) 바이트만큼만 저장하면 된다.
<stddef.h>안의 타입과 매크로 몇개는 다른 헤더에도 나타난다. 예를 들어 NULL 매크로는 <locale.h>, <stdio.h>, <stdlib.h>, <string.h>, <time.h>, C99의 <wchar.h> 헤더에도 들어 있다. 그 결과 <stddef.h>를 include 해야 하는 프로그램은 거의 없다.
21.5 The <stdbool.h> Header (C99): Boolean Type and Values
<stdbool.h> 헤더는 네개의 매크로를 정의한다.
bool (defined to be _Bool)
true (defined to be 1)
false (defined to be 0)
__bool_true_false_are_defined (defined to be 1)
bool, true, false 매크로를 사용하는 예시는 이미 많이 보아왔다.
__bool_true_false_are_defined 매크로의 사용 가능성은 제한적이다. 프로그램에서는 자기 자신 버전의 bool, true, false를 정의하려는 시도를 하기 전에 전처리기 지시문(#if나 #ifdef)으로 이 매크로를 테스트할 수도 있다.
Q&A
Q: "standard header file"이라는 용어 대신 "standard header"라는 용어가 사용되고 있는데, "file"이라는 단어를 사용하지 않은 이유는?
A: C 표준에 따르면, "standard header"가 파일이 될 필요는 없다. 대부분의 컴파일러는 표준 헤더를 파일로 저장하기는 하지만, 헤더들은 컴파일러 자체에 빌드되어도 된다.
Q: 14.3에서는 parameterized macro를 함수 대신 사용할 때의 단점을 설명하고 있다. 이 관점에서 표준 라이브러리 함수의 대체로 매크로를 제공하는 것이 위험하지 않은가?
A: C 표준에 따르면, 라이브러리 함수를 대체하는 parameterized macro는 괄호로 "완전히 보호되어야 하고", 그 값을 단 한 번만 측정해야만 한다. 이 규칙들로 인해 14.3에 언급된 대부분의 문제들을 피해갈 수 있다.