preprocessor는 preprocessing directive (# character로 시작하는 명령어) 로 작동한다.
C program --Preprocessor--> Modified C program --Compiler--> Object code
Modified C program에서 directive는 모두 삭제된다. (line을 지워버리는게 아니라 Blank line 처리)
#include directive는 해당 파일을 열고 정보를 가져와 붙여넣고
(요즘엔 굳이 복사해서 붙여넣지 않고 헤더파일의 함수를 사용하는 정도의 기능만 하기도 한다),
#define directive는 macro들을 정의된 값으로 치환한다.
이렇게 directive만 처리하는게 아니라, comment가 있다면 그건 single space character로 치환한다.
(몇몇 preprocessor는 필요없는 space나 들여쓰기 한 부분을 없애버리기도 한다.)
주의 : preprocessor는 C에 대한 제한된 지식을 가지고 기능하므로 본인이 잘 봐야한다. 원본 파일에서 찾기 어려운 error가 있다면 preprocessing만 진행해보고 문제점 파악해보는 것도 좋다.
Preprocessing Directives
Rules apply to direcitves
\
character 사용하면 됨. (string이랑 같음)#define identifier replacement-list
replacement-list : any sequence of preprocessing tokens
(replacement list may include identifiers, keywords, numeric constants, character constants, string literals, operators, and punctuation)
replacement list 비어있어도 OK
Simple macro는 주로 constants의 이름을 짓는데 사용된다.
그 외 다른 용도
1. Making minor changes to the syntax of C (not good idea)
#define BEGIN {
#define END }
#define LOOP for(;;)
2. Renaming types (type definition 있음)
#define BOOL int
3. Controlling conditional compilation
#define DEBUG
conditional compilatoin을 control 하는데 중요한 역할을 할 수 있다.
#define identifier( x1, x2, ..., xn) replacement-list
x1, x2 : macro's parameters -> replacement-list에 많이 나와도 OK
identifier과 왼쪽 괄호 사이에는 space가 없어야 한다. space가 있으면 그냥 simple macro임
parameter list는 비어있어도 OK (비울거면 굳이 쓸 필요도 없지만, 함수와 닮아서 그렇게 씀)
#define getchar() getc(stdin)
대개는 간단한 함수처럼 사용함.
#define PRINT_INT(n) printf("%d\n", n)
preprocessing 중에 처리하기 때문에 compiler는 알아보지 못하는 operator다.
macro arguments를 string literal로 변환한다. (stringization)
parameterized macro의 replacement list에서만 쓸 수 있다.
ex)
#define PRINT_INT(n) printf(#n " = %d\n", n)
여기서 PRINT_INT(90);
을 하면
printf("90" " = %d\n", 90);
과 같다.
C에선 붙어있는 두 문자열은 하나로 합치니까 printf("90 = %d\n", 90);
이 된다.
preprocessing 중에 처리하기 때문에 compiler는 알아보지 못하는 operator다.
두개의 token을 붙여서 하나의 token으로 만든다. (token pasting)
macro parameter가 끼여있으면 argument로 replace된 후에 paste한다.
ex)
#define MK_ID(n) i##n
int MK_ID(1), MK_ID(2), MK_ID(3); --> int i1, i2, i3;
#define GENERIC_MAX(type) \
type type##_max(type x, type y) \
{ \
return x > y ? x : y; \
}
simple macro, parameterized macro 둘 다에 적용되는 rules
#define a 1
이라고 define해도 int abc;
가 int 1bc;
가 되지는 않는다.#undef identifier
identifier에 대한 현재 definition을 삭제한다.
macro definition에서 괄호를 쳐야하는 위치 두곳이 있다.
위 규칙을 안지킨다고해서 오류가 뜨는건 아니지만, 예상한 결과와 다른 결과가 나올 수 있으므로 지켜야한다.
1번 규칙을 지키지 않으면,
#define TWO_PI 2*3.14159 //replacement list에 괄호가 필요하지만 치지 않겠다.
conversion_factor = 360/TWO_PI;
라고 했을 경우,
conversion_factor = 360/2*3.14159
가 돼버려서 나누기가 먼저 계산되기 때문에 원하는 값을 얻지 못한다.
원하는 값 : 360/(2*3.14159)
2번 규칙을 지키지 않으면,
#define SCALE(x) (x*10) //parameter인 x에 괄호가 필요하지만 치지 않겠다.
j = SCALE(i+1);
라고 했을 경우,
j = (i+1*10);
가 돼버려서 곱하기가 먼저 계산되기 때문에 원하는 값을 얻지 못한다.
원하는 값 : (i+1)*10
#define ECHO(s) (gets(s), puts(s))
#define ECHO(s) { gets(s); puts(s); }
#define ECHO(s) \
do { \
gets(s); \
puts(s); \
} while(0)
Name | Description |
---|---|
__LINE__ | Line number of file being compiled |
__FILE__ | Name of file being compiled |
__DATE__ | Date of compilation(in the form "Mmm dd yyyy") |
__TIME__ | Time of compilation(in the form "hh:mm:ss") |
__STDC__ | 1 if the compiler conforms to the C standard (C89 or C99) |
DATE나 TIME은 compile된 시간을 알려주므로 실행할때마다 상단에 보이게 하면 해당 프로그램의 버전을 알 수 있다.
LINE이나 FILE은 error를 찾는데 쓰일 수 있다.
STDC는 표준 적용되는지 확인용으로 사용할 수 있다.
Name | Description |
---|---|
__STDC__HOSTED__ | 1 if this is a hosted implementation; 0 if is freestanding |
__STDC__VERSION__ | Version of C standard supported |
__STDC_IEC_559__ | 1 if IEC 60559 floating-point arithmetic is supported |
__STDC_IEC_559_COMPLEX__ | 1 if IEC 60559 complex arithmetic is supported |
__STDC_ISO_10646__ | yyyymmL if wchar_t values match the ISO 10646 standard of the specified year and month |
C에서 implementation이라 함은, C 프로그램을 실행시키는데 필요한 여러 software다(Compiler 포함).
C99에선 implementation을 hosted implementation(C99 standard를 지키는 프로그램을 accept하는 implementation)과 freestanding implementation(몇몇 header나 complex type은 지원하지 않아도 됨) 두가지 종류로 나눴다.
__STDC__VERSION__
: compiler가 인정하는 C 표준 버전을 확인한다. C99면 값은 199901L
을 가진다. 각각 standard와 amendment에 따라 다른 값을 가진다.
아래 세개 macro는 해당 조건을 충족하지 않으면 정의도 안돼있다.
comma는 남겨두고, argument 비우는거 OK
그럼 그냥 해당 argument 자리는 지워짐.
만약 해당 argument가 #
operator의 피연산자라면
""(empty string) -> 결과로 나옴
만약 해당 argument가 ##
operator의 피연산자라면
placemarker token이 그 자리에 들어가고, macro expansion이 끝난 후 placemarker token은 사라짐.
(variable number of function은 chapter 26.1)
이 특징을 사용하는 주된 이유는 입력받은 arguments를 printf나 scanf 같이 argument 개수가 다양한 다른 함수에 넘겨줄 수 있기 때문이다.
#define TEST(condition, ...) ((condition)? \
printf("Passed test: %s\n", #condition): \
printf(__VA_ARGS__))
...
: ellipsis 라고 한다. parameter list 마지막에 온다.
__VA_ARGS__
: ellipsis에 해당되는 모든 arguments를 나타낸다. macro의 replacement list 에만 올 수 있다.
macro가 아니라 preprocessor와 아무 관계 없지만, debugging하기 좋아서 여기서 소개한다.
__func__
indentifier는 현재 진행되는 함수 이름을 알려주는 identifier로,
각각의 함수가
static const char __func__[] = "function-name";
을 함수 body 제일 윗부분에 적어둔 것과 같은 효과를 가진다.
이걸 이용해서,
어떤 함수가 시작 돼고 끝났는지를 알 수 있고,
다른 함수를 호출할 때 어떤 함수가 그 함수를 호출했는지 argument로 넘겨줘서 알게 할 수도 있다.
program text의 일부분을 특정 조건에 따라 포함하거나 제외시킬 수 있다.
#if constant-expression
: 정의되어있고 + 0이 아니라면 true
#endif
: #if block의 끝을 표시한다.
ex)
//아래 DEBUG 값을 바꾸거나 정의하지 않음으로써 출력할지 말지 정함
#define DEBUG 1
#if DEBUG
printf("Value of i: %d\n", i);
#endif
해당 macro가 정의되어 있다면 1, 아니면 0 반환
#if defined(DEBUG)
#if defined DEBUG
꼭 괄호가 필요한 건 아님, #if 와 주로 같이 쓰인다.
정의가 됐냐 안됐냐의 문제이므로 값을 줄 필요는 없다.
얘도 위 operator처럼 해당 identifier가 macro로 정의 돼있는지 확인한다.
#ifdef indentifier
: 해당 identifier가 macro로 정의돼있다면 1
#ifndef indentifier
: 해당 identifier가 macro로 정의돼있지 않다면 1
defined operator가 있는데 왜 이게 필요하지?
: 원래 #ifdef
가 defined
보다 먼저였다. 여러 조건의 묶음을 만들거나 할 때 #ifdef
를 사용하기 복잡하므로 flexibility를 위해 defined
가 나중에 추가되었다.
#elif constant-expression
#else
기본 구조는,
#if
or #ifdef
or #ifndef
와
#endif
가 처음과 끝을 이뤄야 되고,
그 사이에 여러개의 elif
가 올 수 있다.
else
는 그 사이에 딱 하나만 올 수 있다.
그리고 #endif
뒤에 주석으로 #if
의 condition을 적어주던가 해서 시작을 찾기쉽게 해주면 좋다.
#if defined(WIN32)
...
#elif defined(MAC_OS)
...
#elif defined(LINUX)
...
#endif
#if __STDC__
function prototypes
#else
Old-style function declarations
#endif
#ifndef BUFFER_SIZE
#define BUFFER_SIZE 256
#endif
#if 0
Lines containing comments
#endif
이렇게 코드를 일시적으로 막는걸 conditioning out 이라고 부른다.
#error message //message는 아무 tokens이면 된다
주로 conditional compilation과 같이 쓰여서, 보통 compile하는 동안 있어서 안될 일들을 잡아내 에러 메세지를 띄우고 중지시킨다.
예를들어 int가 100,000 이상의 숫자를 저장하지 못하면 안될 경우에
아래와 같이 작성할 수 있다.
#if INT_MAX < 100000
#error int type is too small
#endif
#line n
이 이후에 오는 line의 숫자를 n, n+1, ... 이런 식으로 만듦.
__line__ macro의 값을 바꾼다.
#line n "file"
위 효과에 더해 파일 이름도 저 안에 있는 걸로 인식하게 한다.
__file__ macro의 값도 바꾼다.
n 이나 file은 macro로 정의할 수도 있다.
이게 왜 필요하지?
: yacc 예시(p.240)
간단하게 설명하자면, 중간에 다른 프로그램을 거쳐서 compile할 경우 마지막에 에러가 떴을 때 내가 원래 작성한 code를 기준으로 line 번호를 알려줘서 debugging하기 편하게 할 수 있다. #line directive는 프로그래머보다 C file을 output으로 가지는 프로그램이 주로 사용한다.
compiler에게 특정 행동을 취하도록 요청한다.
#pragma tokens
각각 compiler에 맞는 command를 사용해야 한다.(그렇지 않으면 ignore)
C99에는 3개의 standard pragma가 있다.
1. FP_CONTRACT
2. CX_LIMITED_RANGE
3. FENV_ACCESS
사용하려면 #pragma 뒤에 STDC 뒤에 얘네를 작성해야 함.(그리고 얘네 뒤엔 또 ON or OFF or DEFAULT(자세한건 웹서핑))
_Pragma ( string-literal )
string-literal의 것들을 destringizes(C99에서 사용하는 용어) 해버린다. \"
는 "
로 바꾸고, \\
는 \
로 바꾸고 등등
즉, _Pragma("abc")
는 #pragma abc
와 같다.
얘도 operator가 따로 있는 이유는 macro 정의 안에서도 쓰이거나 할 수 있기 때문
# 만 덩그러니 있으면 null directive로, conditional compilation block을 확인하기 좋다
macro로 만들어야하는 constant의 가이드라인이 있나?
경험에 의하면 0과 1을 제외한 모든 numeric constant는 macro가 돼야 한다. character나 string literal의 경우는 (1)한번 이상 쓰이고 (2)수정될 가능성이 있는 경우 macro로 하면 좋다.(저자는 이렇게 하는거고 참고정도만 하자)
"
나 \
가 포함돼 있으면 # oprator는 어떻게 작동?
반대로 작동한다. "
은 \"
으로 만들고, \
은 \\
으로 만든다.
#define CONCAT(x, y) x##y
라고 했을 때 CONCAT(a,CONCAT(b,c)) 라고 하면 원하는 결과가 안나온다, 왜?
## 주변에 있는 parameter는 한번에 expand되지 않는다. 따라서 결과로 aCONCAT(b,c)가 나오게 되고 오류가 뜰 것이다.(aCONCAT 이라는 함수나 macro가 없으면)
#도 마찬가지로 한번에 expand되지 않는데, #define STR(x) #x
라고 했을 때, #define N 10
에서 N을 넣으면 "10"
이 되는게 아니라 "N"
이 된다.
이를 해결하는 방법은 #과 ##을 포함하지 않는 두번째 함수를 하나 더 만드는 것이다. ex) #define CONCAT2(x,y) CONCAT(x,y)
macro 특징에 보면, 전처리할때 macro가 없어질 때 까지 rescan한다고 하는데, 서로서로 엉켜있으면 무한 루프가 만들어지나?
아니다. original macro가 expansion 도중에 다시 나오면 이는 다시 치환되지 않는다.
이를 이용할 수도 있다.
#undef sqrt
#define sqrt(x) ((x)>=0 ? sqrt(x) : 0)
이렇게 하면 sqrt macro가 한번 0 미만인 경우 0으로 만들어주고,
그 다음에 또 sqrt가 나오면 이는 치환 안되고 sqrt 라이브러리 함수로 처리된다.
이렇게 라이브러리 함수와 같은 이름의 macro를 정의하는 것은 가능하다.(chapter 21.1)
hosted implementation / freestanding implementation ??
hosted의 경우 input, output이나 다른 필요한 기능을 제공하는 OS가 있어야 한다. freestanding은 OS가 없는 경우, OS의 kernel을 작성하는 경우, embedded system의 software를 작성하는 경우 등에 사용된다.
preprocessor는 editor 수준일 줄 알았는데, 연산 기능도 되네?
생각보다 preprocessor는 정교하다. compiler처럼 계산하진 않지만, 상수를 계산하는 정도는 충분히 해낸다.
conditioning out 했는데도 그 라인 안에서 오류가 뜬다.
그 라인이 완전히 무시되지는 않는다. comments의 경우 preprocessing directives보다 더 빨리 처리된다. 그래서 그 사이에 (1)unterminated comment가 있으면 error가 뜰 수 있고, (2)unpaired sigle quote or double quote 가 있으면 UB이다.