컴퓨터공부/C & C++ & STL

cfaq 8

achivenKakao 2009. 4. 5. 11:32

Q 10.4
여러 문장으로 이루어진 매크로를 만드는 좋은 방법 좀 알려 주세요.
Answer 이런 매크로를 만드는 일반적인 방법은 매크로 자체를 일반 함수처럼 쓸 수
있도록 하는 것입니다. 즉 호출하는 쪽에서 마지막 ‘;’을 직접 써 주게 하고
매크로의 몸통에서는 ‘;’을 따로 써 주지 않는 식으로 쓰는 것입니다. 예를
들면:
MACRO(arg1, arg2);
그러므로 매크로 정의는 단순히 여러 statement를 중괄호로 둘러싼 ‘compound
statement’ 형식으로 만들 수 없습니다. 왜냐하면 이 매크로가 호
출될 때 세미콜론이 추가적으로 붙는다면 if나 if/else 문장에서 에러가
발생할 수 있기 때문입니다. 다음 코드를 보기 바랍니다:
if (cond)
MACRO(arg1, arg2);
else /* some other code */
만약, 단순히 중괄호로 둘러싼 것으로 매크로가 확장된다면, 위 코드는 다
음과 같이 해석되어, 마지막 세미콜론 때문에 syntax error가 발생합니다:
if (cond)
{ stmt1; stmt2; };
else /* some other code */
전통적으로, 이 경우에는 다음과 같은 방법을 씁니다:

#define MACRO(arg1, arg2) do { \
/* declarations */ \
stmt1; \
stmt2; \
/* ... */ \
} while(0) /* (no trailing ; ) */

매크로를 부르는 쪽이 세미콜론을 붙이면, 매크로가 하나의 문장으로 해석
됩니다. (좋은 최적화를 제공하는 컴파일러라면 필요없는 테스트나, 조건
문에서 상수 0을 검사하는 일같은 것은 알아서 없애 줍니다. 그러나 lint
는 불평할 수 있습니다.)
또 다른 방법으로 다음과 같이 할 수도 있습니다:
#define MACRO(arg1, arg2) if (1) \
stmt1; \
stmt2; \
} else
그러나, 이 경우, 만약에 부르는 쪽에서 세미콜론을 빼먹을 경우, 전체 코
드가 엉망이 되 버릴 경우가 있어서 좋은 방법이 아닙니다.
매크로의 모든 문장이 매우 간단한 expression이라면, 즉 선언이나 루프가
없다면, 콤마(‘,’) 연산자를 써서 한 문장으로 만들 수 있습니다:
#define FUNC(arg1, arg2) (expr1, expr2, expr3)
예를 들어 질문 10.26의 DEBUG() 매크로를 보기 바랍니다. 이 테크닉은 또
매크로가 어떤 ‘값(value)’을 리턴할 수 있게 해 줍니다.
gcc와 같은 컴파일러는 이런 간단한 기능을 하는 함수가 매크로처럼 원하는
경우 확장(expand)할 수 있는 기능을, 비표준인, “inline” 키워드나 기타
방법을 써서 제공하기도 합니다.
Note 무슨 소리냐 하면 매크로를 정의할 때, 마지막에 세미콜론을 붙여 버린다면
아래와 같은 상황에서 에러가 발생할 수도 있다는 뜻입니다:
#define MULTI_STATEMNT_MACRO(x) do { \
stmt1; \
stmt2; \
} while(0);
if (some_condition)
MULTI_STATEMNT_MACRO(a);
else {
/* ... */
}

매크로를 확장해보면 세미콜론이 두 번 만들어지고, 따라서 else 부분에서
에러가 발생합니다.
References [H&S] x 3.3.2 p. 45
[CT&P] x 6.3 pp. 82.3
Note “inline”은 C99에서 표준이 되었습니다.

Q 10.7
헤더 파일에서 다른 파일을 #include 하는 것은 괜찮나요?
Answer 스타일에 관한 질문이군요. 따라서 상당한 논란의 여지가 있습니다.
많은 사람들이 “중첩된(nested) #include 파일”을 쓰지 않는 것이 좋다고
말합니다: 권위있는 Indian Hill Style Guide에서도 (질문 17.9 참고) 이런
쓰임새를 피하라고 씌여있습니다; 관련된 정의를 찾기가 훨씬 더 어렵기 때
문입니다; 또한 두번 #include하는 경우, 중복된 정의 에러가 (multipledefinition
error) 발생할 가능성이 높습니다; 또 수동으로 Makefile을 만들
경우, 상당히 복잡해질 가능성이 있습니다.
그러나 헤더 파일을 중첩하여 포함할 경우, 각각의 헤더 파일을 모듈화해
서(modular way), 헤더 파일에서 필요한 다른 헤더 파일을 #include함으
로써 수고를 덜어줄 수 있다는 장점도 있습니다; grep과 같은 툴을 (또는
tags 파일) 사용하면 정의가 어떤 파일에 되어 있느냐에 상관없이 쉽게 찾
을 수 있습니다. 다음과 같은 트릭을 쓰면, 헤더 파일이 여러 곳에서 포함
되었느냐에 상관없이, 딱 한번만 포함되게(idempotent) 할 수 있습니다:
#ifndef HFILENAME_USED
#define HFILENAME_USED
...header file contents...
#endif
(이 때, 각각의 헤더 파일에 각각 다른 매크로 이름을 사용합니다) 이 방
식은 헤더 파일이 꼭 한 번만 포함되도록 해 주므로 여러 번 #include하
더라도 문제가 발생하지 않습니다; 또한 자동으로 Makefile을 관리해주는
툴을 (큰 프로젝트를 관리할 때에는 꼭 필요합니다, 질문 18.1 참고) 사용
할 경우, 중첩된 #include를 처리해 주므로 좀 더 편합니다. 질문 17.10
을 참고하기 바랍니다.
Note GNU autoconf와 GNU automake는 Makefile을 관리해주는 아주 좋은 툴
입니다.
References [ANSI Rationale] x 4.1.2

Q 13.26
 올바른 라이브러리를 포함시키라고 했는데도 라이브러리 함수가 정의되어
있지 않다고 에러가 발생합니다.
Answer 대부분 linker들은, 주어진 오브젝트 파일과 라이브러리 파일들을 단 한번씩
만 조사하고, 함수 호출에 필요한 정의가 발견되면 함께 링크합니다. 만약
주어진 파일 목록에서 (한번 시도해서) 함수 정의를 찾지 못한다면, 심볼이
정의되어 있지 않다고 에러가 생깁니다. 따라서, linker에게 전달하는 오브
젝트 파일들과 라이브러리 파일들의 순서가 매우 중요합니다; 보통의 경우,
라이브러리 파일을 마지막에 써 두는 것이 좋습니다.
예를 들어, 대부분 UNIX 시스템에서 다음과 같이 명령을 실행하면, 보통
동작하지 않습니다:
cc -lm myproc.c
대신, -l 옵션을 마지막에 주면 동작합니다:
cc myproc.c -lm
라이브러리 파일을 먼저 주면, linker는 이 라이브러리에서 제공하는 정의
들이 나중에 쓰일지 쓰이지 않을 지 알 수가 없습니다. (아직 이 라이브러
리에 있는 함수를 쓰는 오브젝트 파일을 발견하지 못했기 때문) 질문 13.28
을 보기 바랍니다.