답변:
매크로는 텍스트 대체에 의존하고 유형 검사를 수행하지 않기 때문에 오류가 발생하기 쉽습니다. 예를 들어,이 매크로 :
#define square(a) a * a
정수와 함께 사용하면 잘 작동합니다.
square(5) --> 5 * 5 --> 25
그러나 표현식과 함께 사용하면 매우 이상한 일을합니다.
square(1 + 2) --> 1 + 2 * 1 + 2 --> 1 + 2 + 2 --> 5
square(x++) --> x++ * x++ --> increments x twice
인수를 괄호로 묶는 것은 도움이되지만 이러한 문제를 완전히 제거하지는 못합니다.
매크로에 여러 문이 포함 된 경우 제어 흐름 구조에 문제가 발생할 수 있습니다.
#define swap(x, y) t = x; x = y; y = t;
if (x < y) swap(x, y); -->
if (x < y) t = x; x = y; y = t; --> if (x < y) { t = x; } x = y; y = t;
이 문제를 해결하기위한 일반적인 전략은 "do {...} while (0)"루프 안에 문을 넣는 것입니다.
이름은 같지만 의미가 다른 필드를 포함하는 두 개의 구조가있는 경우 동일한 매크로가 두 가지 모두에서 작동하며 이상한 결과가 나타날 수 있습니다.
struct shirt
{
int numButtons;
};
struct webpage
{
int numButtons;
};
#define num_button_holes(shirt) ((shirt).numButtons * 4)
struct webpage page;
page.numButtons = 2;
num_button_holes(page) -> 8
마지막으로, 매크로는 디버깅이 어려울 수 있으며, 이해하기 위해 확장해야하는 이상한 구문 오류 또는 런타임 오류 (예 : gcc -E 사용)가 발생할 수 있습니다. 디버거는 다음 예제와 같이 매크로를 단계별로 실행할 수 없기 때문입니다.
#define print(x, y) printf(x y) /* accidentally forgot comma */
print("foo %s", "bar") /* prints "foo %sbar" */
인라인 함수와 상수는 매크로와 관련된 이러한 문제를 방지하는 데 도움이되지만 항상 적용 가능한 것은 아닙니다. 매크로가 의도적으로 다형성 동작을 지정하는 데 사용되는 경우 의도하지 않은 다형성을 방지하기 어려울 수 있습니다. C ++에는 매크로를 사용하지 않고 형식이 안전한 방식으로 복잡한 다형성 구조를 만드는 데 도움이되는 템플릿과 같은 여러 기능이 있습니다. 자세한 내용은 Stroustrup의 The C ++ Programming Language 를 참조하십시오.
x++*x++
이 x
두 번 증가한다고 말할 수 없습니다 . 실제로 정의되지 않은 동작을 호출합니다. 즉, 컴파일러가 원하는 모든 작업을 자유롭게 수행 할 수 있습니다 x
. 즉, 두 번 또는 한 번 증가 하거나 전혀 증가 하지 않을 수 있습니다. 오류로 인해 중단되거나 악마가 코에서 날아갈 수도 있습니다 .
매크로 기능 :
기능 특징 :
부작용은 큰 것입니다. 다음은 일반적인 경우입니다.
#define min(a, b) (a < b ? a : b)
min(x++, y)
다음으로 확장됩니다.
(x++ < y ? x++ : y)
x
동일한 명령문에서 두 번 증가합니다. (및 정의되지 않은 동작)
여러 줄 매크로를 작성하는 것도 고통입니다.
#define foo(a,b,c) \
a += 10; \
b += 10; \
c += 10;
\
각 줄 끝에는이 필요합니다 .
매크로는 단일 표현식으로 만들지 않는 한 아무것도 "반환"할 수 없습니다.
int foo(int *a, int *b){
side_effect0();
side_effect1();
return a[0] + b[0];
}
GCC의 표현식 문을 사용하지 않는 한 매크로에서 그렇게 할 수 없습니다. (편집 : 쉼표 연산자를 사용할 수 있습니다 ... 간과 ...하지만 여전히 읽기 어렵습니다.)
운영 순서 : (@ouah 제공)
#define min(a,b) (a < b ? a : b)
min(x & 0xFF, 42)
다음으로 확장됩니다.
(x & 0xFF < 42 ? x & 0xFF : 42)
그러나 &
보다 낮은 우선 순위를가집니다 <
. 그래서 0xFF < 42
먼저 평가됩니다.
min(a & 0xFF, 42)
#define SQUARE(x) ((x)*(x))
int main() {
int x = 2;
int y = SQUARE(x++); // Undefined behavior even though it doesn't look
// like it here
return 0;
}
이므로:
int square(int x) {
return x * x;
}
int main() {
int x = 2;
int y = square(x++); // fine
return 0;
}
struct foo {
int bar;
};
#define GET_BAR(f) ((f)->bar)
int main() {
struct foo f;
int a = GET_BAR(&f); // fine
int b = GET_BAR(&a); // error, but the message won't make much sense unless you
// know what the macro does
return 0;
}
비교 :
struct foo {
int bar;
};
int get_bar(struct foo *f) {
return f->bar;
}
int main() {
struct foo f;
int a = get_bar(&f); // fine
int b = get_bar(&a); // error, but compiler complains about passing int* where
// struct foo* should be given
return 0;
}
확실하지 않은 경우 함수 (또는 인라인 함수)를 사용하십시오.
그러나 여기의 답변은 어리석은 사고가 가능하기 때문에 매크로가 악하다는 단순한 견해를 갖는 대신 대부분 매크로의 문제를 설명합니다.
함정을 인식하고이를 피하는 방법을 배울 수 있습니다. 그런 다음 적절한 이유가있을 때만 매크로를 사용하십시오.
특정있다 뛰어난 매크로를 사용하는 이점이있는 경우는 다음과 같습니다 :
va_args
. __FILE__
, __LINE__
, __func__
). 사전 / 사후 조건, assert
실패 또는 정적 어설 션을 확인하여 부적절한 사용시 코드가 컴파일되지 않도록합니다 (대부분 디버그 빌드에 유용함).struct
캐스팅 전에 멤버가 있는지 확인) func(FOO, "FOO");
, 문자열을 확장하는 매크로를 정의 할 수 있습니다.func_wrapper(FOO);
inline
입니다. 함수가 옵션 일 수 있으므로 여전히 컨텍스트에 많이 의존하지만 ) .물론 이들 중 일부는 표준 C가 아닌 컴파일러 확장에 의존합니다. 즉, 이식성이 떨어지는 코드로 끝날 수 있거나 포함되어야 ifdef
하므로 컴파일러가 지원하는 경우에만 활용됩니다.
매크로에서 오류의 가장 일반적인 원인 중 하나이기 때문에 ( x++
예 : 매크로가 여러 번 증가 할 수있는 경우 전달 ) .
인수의 다중 인스턴스화로 부작용을 피하는 매크로를 작성할 수 있습니다.
square
다양한 유형으로 작동하고 C11을 지원하는 매크로 를 갖고 싶다면 이렇게 할 수 있습니다.
inline float _square_fl(float a) { return a * a; }
inline double _square_dbl(float a) { return a * a; }
inline int _square_i(int a) { return a * a; }
inline unsigned int _square_ui(unsigned int a) { return a * a; }
inline short _square_s(short a) { return a * a; }
inline unsigned short _square_us(unsigned short a) { return a * a; }
/* ... long, char ... etc */
#define square(a) \
_Generic((a), \
float: _square_fl(a), \
double: _square_dbl(a), \
int: _square_i(a), \
unsigned int: _square_ui(a), \
short: _square_s(a), \
unsigned short: _square_us(a))
이것은 GCC, Clang, EKOPath 및 Intel C ++ (MSVC 아님)에서 지원하는 컴파일러 확장입니다 .
#define square(a_) __extension__ ({ \
typeof(a_) a = (a_); \
(a * a); })
따라서 매크로의 단점은 처음에는 이러한 매크로를 사용하는 방법을 알아야하며 널리 지원되지 않는다는 것입니다.
한 가지 이점은이 경우 square
여러 유형에 대해 동일한 기능을 사용할 수 있다는 것 입니다.
함수는 유형 검사를 수행합니다. 이것은 당신에게 추가적인 안전 층을 제공합니다.
이 답변에 추가 ..
매크로는 전처리기에 의해 프로그램에 직접 대체됩니다 (기본적으로 전 처리기 지시문이기 때문에). 따라서 필연적으로 각 기능보다 더 많은 메모리 공간을 사용합니다. 반면에 함수를 호출하고 결과를 반환하는 데 더 많은 시간이 필요하며 매크로를 사용하여 이러한 오버 헤드를 피할 수 있습니다.
또한 매크로에는 다른 플랫폼에서 프로그램 이식성을 도울 수있는 몇 가지 특수 도구가 있습니다.
매크로는 함수와 달리 인수에 대한 데이터 유형을 할당 할 필요가 없습니다.
전반적으로 프로그래밍에서 유용한 도구입니다. 그리고 상황에 따라 매크로 명령어와 함수를 모두 사용할 수 있습니다.
위의 답변에서 내가 매우 중요하다고 생각하는 매크로보다 함수의 장점 중 하나는 눈치 채지 못했습니다.
함수는 인수로 전달할 수 있지만 매크로는 전달할 수 없습니다.
구체적인 예 : 다른 문자열 내에서 검색 할 명시적인 문자 목록이 아닌 허용 할 표준 'strpbrk'함수의 대체 버전을 작성하려고합니다. 문자가 다음과 같을 때까지 0을 반환하는 (a에 대한 포인터) 함수 일부 테스트 (사용자 정의)를 통과 한 것으로 나타났습니다. 이렇게하려는 한 가지 이유는 다른 표준 라이브러리 함수를 이용할 수 있기 때문입니다. 구두점으로 가득 찬 명시적인 문자열을 제공하는 대신 ctype.h의 'ispunct'를 대신 전달할 수 있습니다. 'ispunct'가 다음과 같이 구현 된 경우 매크로라면 작동하지 않습니다.
다른 많은 예가 있습니다. 예를 들어, 함수가 아닌 매크로로 비교를 수행하는 경우 stdlib.h의 'qsort'에 전달할 수 없습니다.
파이썬에서 유사한 상황은 버전 2 대 버전 3에서 '인쇄'입니다 (비통과 성 문 대 통행 할 수있는 함수).