정적코드분석/코딩표준

C/C++ 언어 매크로 사용의 위험성-2편:의도하지 않은 결과

1편에서는 C/C++ 언어에서 매크로 사용 시, 타입 검사를 하지 않아서 위험하다는 글을 올렸습니다. 2편에서도 C/C++ 언어 매크로 사용 시 위험성에 대해서 적어보도록 하겠습니다.

의도하지 않은 결과(unexpected result)

매크로, 특히 함수처럼 사용하는 매크로(매크로 함수) 사용 시 인자를 주의해서 넣어야 합니다. 다음 매크로를 사용한다고 가정합니다.

#define DOUBLE(a) ((a) + (a))

이 매크로는 인자값 a를 두 번 더합니다. 2를 넣으면 4가, 5를 넣으면 10이 나오겠지요.?하지만 아래와 같이 인자를 넣으면 어떨까요?

/*test1.c*/
#include <stdio.h>

#define DOUBLE(a) ((a) + (a))

int main()
{
int a = 2;
int b = DOUBLE(++a);
printf("b is %d\n", b);
return 0;
}

예상 값은 a에 1을 더하니(++a) 값은 3이고, 이를 두 번 더하니 6이 나오겠지요??하지만 실제 값은

gcc -o test1 test1.c
./test1
b is 8

8이 나옵니다. (사실 전 7을 예상했다는…) 왜 그럴까요?

왜 이럴까요? (출처 : KBS1, 개그콘서트)
왜 이럴까요? (출처 : KBS1, 개그콘서트)

컴파일러가 전처리를 거친 파일(명령어 gcc -E test1.c -o test1.i) 을 한 번 보면 그 답이 나옵니다.

int main()
{
int a = 2;
int b = ((++a) + (++a));
printf("b is %d\n", b);
return 0;
}

전처리를 거치면 매크로 부분을 실제 코드로 치환하는데, 여기서 ++a 가 두 번 들어갑니다. ?컴파일러 내부적으로 트리 형태로 위 표현식을 가지고 있는데요, + 보다 ++a 두 번을 먼저 수행합니다.?그럼 x값이 4가 되죠. 이 x 값을 두 번 더하니 8이 됩니다.?보통 함수형 매크로의 인자에 ++a 와 같은 Side-effect 가 있는 수식을 넣으면 의도하지 않은 결과가 나올 수 있습니다.

그렇다면 매크로 인자만 주의하면 되나요? 아니죠. 매크로 정의도 잘 해야합니다. 다음 코드를 보겠습니다. 매크로?MULTIㄹㄹ 주의해서 봐주세요.

/*test2.c*/
#include <stdio.h>
#define MULTI(a, b) a * b

int main()
{
int a = 2;
int b = 10;
int c = MULTI(a + 2, b + 1)
printf("c is %d\n", c);
return 0;
}

a에 2를 더한 값(4)와 b에 1을 더한 값(11)을 곱했으니 44가 나와야겠죠?

gcc -o test2 test2.c
./test2.c
c is 23

이상합니다. 전처리 한 파일을 한 번 보겠습니다.

int main()
{
int a = 2;
int b = 10;
int c = a + 2 * b + 1;
printf("c is %d\n", c);
return 0;
}

c를 초기화하는 수식을 보면 저희가 예상했던 값이 안나오는 이유를 알 수 있습니다. ?a + 2 * b + 1 은 연산자 우선순위에 의거, 2 * b 가 먼저 계산되고(결과 : 20) 여기에 a와 1을 더하기 때문에 23이 나오는 것이지요. ?이런 문제를 막기 위해 함수형 매크로 정의 시, 아래와 같이 매크로 인자마다 괄호를 습관적으로 적어주면 문제의 소지를 없앨 수 있습니다.

#define MULTI(a, b) (a) * (b)

보통 이런 버그는 찾기도 어렵고, 컴파일러마다 그 결과 값이 다를 수 있기 때문에 고치기가 매우 까다롭습니다. 애당초 매크로를 사용하지 않거나, 잘 정의해서 이러한 버그 발생 가능성 자체를 봉쇄하는 것이 가장 좋은 방법입니다.

Leave a Reply

Leave a Reply

Your email address will not be published. Required fields are marked *