C: gcc 에서 매크로를 함수처럼 쓰기
C 에서 매크로는 매우 강력한 기능을 제공한다. 하지만 몇 가지 아쉬운 점이 있는데, 매크로를 일반 함수처럼 쓰기에는 어려움이 있다는 것이다.
예를 들어, 어떤 정수 m 의 어떤 정수 n 제곱을 구하는 기능을 매크로로 구현하기란 쉽지 않다. 왜냐하면 거듭제곱의 결과값을 저장할 또 다른 변수가 필요하고, 반복문이 있어야 하며, 그 결과를 돌려주려면, 결국 복합문이 필요하기 때문이다. 물론 do{}while(0) 같은 기법을 써도 되지만, 아쉽게도 이것으로는 값을 되돌려줄 수가 없다.
gcc 는 이를 위해 statement expression 이라는 확장 기능을 제공한다. 위의 기능을 제공하는 매크로를 POW() 라고 하자. POW() 는 다음처럼 쓸 수 있다.
마지막 8 번째 줄에 있는 POW_m 의 값이 POW() 의 결과값이다. 하지만 이 기능은 위에서도 말했듯이 확장 기능이므로, -pedantic 옵션을 사용하면 다음과 같은 경고가 발생한다.
이를 방지하기 위해서는 다음처럼 __extension__ 을 사용하면 된다.
그런데 주의할 것은 이것 역시 매크로이기 때문에, 내부에서 쓰이는 변수들은 외부에서 쓰이는 변수와 겹치지 않아야 한다는 것이다. 2 번째, 3 번째 줄에서 POW_m 또는 POW_n 으로 선언한 이유가 그것이다.물론 외부에서 POW_m 또는 POW_n 이 쓰이고 있다면 문제가 될 수 있지만...
이 기능의 또다른 장점은 매크로의 매개변수를 오로지 한 번만 평가할 수 있다는 것이다. 예를 들어 MAX() 를 보자. MAX() 의 경우 일반적인 정의는 다음과 같다.
다음과 같은 코드를 생각해 보자.
우리의 기대는
이다. 하지만 실제 결과는
이다. 왜냐하면 MAX() 에서 매개변수가 여러번 평가되기 때문이다. MAX() 를 풀어보면 다음과 같다.
보다시피, b++ 가 두번 평가되기 때문에 MAX() 를 사용하고 나면, b 가 1 이 증가되는 것이 아니라 2 가 증가된다. 그리고 MAX() 의 결과값은 b++ 의 영향으로 b 보다 1 이 큰 값이 된다.
이를 방지하려면 MAX() 를 다음과 같이 써야 한다.
어떤 이는 차라리 static inline 함수를 쓰는 것이 낫지 않냐고 할 수 있다. 물론 C99 컴파일러라면 statinc inline 함수를 써도 상관이 없다. 하지만 매크로가 가지고 있는 특유의 기능, 예를 들어 # 이나 ## 같은 전처리기 명령이 필요하다면 또다른 선택지는 없을 것 같다.
Statement expression 에 대해 더 자세히 알고 싶으면 다음 링크를 참조하기 바란다.
아울러, gcc 의 모든 확장 기능에 대해 알고 싶다면 다음 링크를 참조하기 바란다.
예를 들어, 어떤 정수 m 의 어떤 정수 n 제곱을 구하는 기능을 매크로로 구현하기란 쉽지 않다. 왜냐하면 거듭제곱의 결과값을 저장할 또 다른 변수가 필요하고, 반복문이 있어야 하며, 그 결과를 돌려주려면, 결국 복합문이 필요하기 때문이다. 물론 do{}while(0) 같은 기법을 써도 되지만, 아쉽게도 이것으로는 값을 되돌려줄 수가 없다.
gcc 는 이를 위해 statement expression 이라는 확장 기능을 제공한다. 위의 기능을 제공하는 매크로를 POW() 라고 하자. POW() 는 다음처럼 쓸 수 있다.
1 2 3 4 5 6 7 8 9 | #define POW( m, n ) ({ \ int POW_m = (m); \ int POW_n = (n); \ \ while( POW_n-- > 0 ) \ POW_m *= POW_m; \ \ POW_m; \ }) |
마지막 8 번째 줄에 있는 POW_m 의 값이 POW() 의 결과값이다. 하지만 이 기능은 위에서도 말했듯이 확장 기능이므로, -pedantic 옵션을 사용하면 다음과 같은 경고가 발생한다.
warning: ISO C forbids braced-groups within expressions
이를 방지하기 위해서는 다음처럼 __extension__ 을 사용하면 된다.
1 2 3 4 5 6 7 8 9 | #define POW( m, n ) __extension__ ({ \ int POW_m = (m); \ int POW_n = (n); \ \ while( POW_n-- > 0 ) \ POW_m *= POW_m; \ \ POW_m; \ }) |
그런데 주의할 것은 이것 역시 매크로이기 때문에, 내부에서 쓰이는 변수들은 외부에서 쓰이는 변수와 겹치지 않아야 한다는 것이다. 2 번째, 3 번째 줄에서 POW_m 또는 POW_n 으로 선언한 이유가 그것이다.물론 외부에서 POW_m 또는 POW_n 이 쓰이고 있다면 문제가 될 수 있지만...
이 기능의 또다른 장점은 매크로의 매개변수를 오로지 한 번만 평가할 수 있다는 것이다. 예를 들어 MAX() 를 보자. MAX() 의 경우 일반적인 정의는 다음과 같다.
#define MAX(a, b) ((a) > (b) ? (a) : (b))
다음과 같은 코드를 생각해 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include <stdio.h> #define MAX( a, b ) (( a ) > ( b ) ? ( a ) : ( b )) int main( void ) { int a = 10; int b = 20; int c = MAX( a, b++ ); printf("a = %d, b = %d, c= %d\n", a, b, c ); return 0; } |
우리의 기대는
a = 10, b = 21, c = 20
이다. 하지만 실제 결과는
a = 10, b = 22, c = 21
이다. 왜냐하면 MAX() 에서 매개변수가 여러번 평가되기 때문이다. MAX() 를 풀어보면 다음과 같다.
MAX( a, b++ ) = (( a ) > ( b++ ) ? ( a ) : ( b++ ))
보다시피, b++ 가 두번 평가되기 때문에 MAX() 를 사용하고 나면, b 가 1 이 증가되는 것이 아니라 2 가 증가된다. 그리고 MAX() 의 결과값은 b++ 의 영향으로 b 보다 1 이 큰 값이 된다.
이를 방지하려면 MAX() 를 다음과 같이 써야 한다.
1 2 3 4 5 6 | #define MAX( a, b ) __extension__ ({ \ int MAX_a = (a); \ int MAX_b = (b); \ \ MAX_a > MAX_b ? MAX_a : MAX_b; \ }) |
어떤 이는 차라리 static inline 함수를 쓰는 것이 낫지 않냐고 할 수 있다. 물론 C99 컴파일러라면 statinc inline 함수를 써도 상관이 없다. 하지만 매크로가 가지고 있는 특유의 기능, 예를 들어 # 이나 ## 같은 전처리기 명령이 필요하다면 또다른 선택지는 없을 것 같다.
Statement expression 에 대해 더 자세히 알고 싶으면 다음 링크를 참조하기 바란다.
아울러, gcc 의 모든 확장 기능에 대해 알고 싶다면 다음 링크를 참조하기 바란다.
댓글
댓글 쓰기