나는 항상 매크로를 사용하는 것이 함수를 사용하는 것보다 나은 예와 사례를 보았습니다.
누군가 함수에 비해 매크로의 단점을 예로 설명해 줄 수 있습니까?
답변
매크로는 텍스트 대체에 의존하고 유형 검사를 수행하지 않기 때문에 오류가 발생하기 쉽습니다. 예를 들어,이 매크로 :
#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 를 참조하십시오.
답변
매크로 기능 :
- 매크로가 전 처리됨
- 유형 검사 없음
- 코드 길이 증가
- 매크로를 사용하면 부작용이 발생할 수 있습니다.
- 실행 속도가 더 빠름
- 컴파일 매크로 이름이 매크로 값으로 대체되기 전
- 작은 코드가 여러 번 나타나는 경우 유용합니다.
- 매크로가 컴파일 오류를 확인 하지 않음
기능 특징 :
- 함수가 컴파일 됨
- 유형 검사가 완료되었습니다.
- 코드 길이는 동일하게 유지
- 부작용 없음
- 실행 속도가 느림
- 함수 호출 중 제어 전송이 발생합니다.
- 큰 코드가 여러 번 나타나는 경우 유용합니다.
- 함수 검사 컴파일 오류
답변
부작용은 큰 것입니다. 다음은 일반적인 경우입니다.
#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
먼저 평가됩니다.
답변
예 1 :
#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;
}
예 2 :
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;
}
답변
확실하지 않은 경우 함수 (또는 인라인 함수)를 사용하십시오.
그러나 여기의 답변은 어리석은 사고가 가능하기 때문에 매크로가 악하다는 단순한 견해를 갖는 대신 대부분 매크로의 문제를 설명합니다.
함정을 인식하고이를 피하는 방법을 배울 수 있습니다. 그런 다음 적절한 이유가있을 때만 매크로를 사용하십시오.
특정있다 뛰어난 매크로를 사용하는 이점이있는 경우는 다음과 같습니다 :
- 아래에 언급 된 일반 함수는 다양한 유형의 입력 인수에 사용할 수있는 매크로를 가질 수 있습니다.
- 가변 개수의 인수는 C를 사용하는 대신 다른 함수에 매핑 할 수 있습니다
va_args
.
예 : https://stackoverflow.com/a/24837037/432509 . - 그들이 할 수있는 선택적 같은 디버그 문자열로 지역 정보를 포함 :
(__FILE__
,__LINE__
,__func__
). 사전 / 사후 조건,assert
실패 또는 정적 어설 션을 확인하여 부적절한 사용시 코드가 컴파일되지 않도록합니다 (대부분 디버그 빌드에 유용함). - 입력 인수를 검사합니다. 입력 인수에 대한 테스트를 수행 할 수 있습니다 (예 : 유형, sizeof,
struct
캐스팅 전에 멤버가 있는지 확인)
(다형성 유형에 유용 할 수 있음) .
또는 배열이 일부 길이 조건을 충족하는지 확인하십시오.
참조 : https://stackoverflow.com/a/29926435/432509 - 함수는 유형 검사를 수행하지만 C는 값도 강제합니다 (예 : 정수 / 수동). 드물게 문제가 될 수 있습니다. 입력 인수에 대한 함수보다 더 정확한 매크로를 작성할 수 있습니다. 참조 : https://stackoverflow.com/a/25988779/432509
- 함수에 대한 래퍼로 사용합니다. 어떤 경우에는 반복을 피하고 싶을 수 있습니다. 예를 들어 …
func(FOO, "FOO");
, 문자열을 확장하는 매크로를 정의 할 수 있습니다.func_wrapper(FOO);
- 호출자 로컬 범위에서 변수를 조작하려는 경우 포인터를 포인터로 전달하는 것은 정상적으로 작동하지만 경우에 따라 매크로를 사용하는 것이 문제가 적습니다.
(픽셀 당 작업의 경우 여러 변수에 대한 할당은 함수보다 매크로를 선호 할 수있는 예inline
입니다. 함수가 옵션 일 수 있으므로 여전히 컨텍스트에 많이 의존하지만 ) .
물론 이들 중 일부는 표준 C가 아닌 컴파일러 확장에 의존합니다. 즉, 이식성이 떨어지는 코드로 끝날 수 있거나 포함되어야 ifdef
하므로 컴파일러가 지원하는 경우에만 활용됩니다.
다중 인수 인스턴스화 방지
매크로에서 오류의 가장 일반적인 원인 중 하나이기 때문에 ( x++
예 : 매크로가 여러 번 증가 할 수있는 경우 전달 ) .
인수의 다중 인스턴스화로 부작용을 피하는 매크로를 작성할 수 있습니다.
C11 일반
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
여러 유형에 대해 동일한 기능을 사용할 수 있다는 것 입니다.
답변
매개 변수 및 코드의 유형 검사가 반복되지 않아 코드가 부풀어 질 수 있습니다. 매크로 구문은 세미콜론 또는 우선 순위가 방해가 될 수있는 이상한 경우로 이어질 수 있습니다. 다음은 일부 매크로 악 을 보여주는 링크입니다.
답변
매크로의 한 가지 단점은 디버거가 확장 된 매크로가없는 소스 코드를 읽으므로 매크로에서 디버거를 실행하는 것이 반드시 유용하지는 않다는 것입니다. 말할 필요도없이, 함수에서 할 수있는 것처럼 매크로 내부에 중단 점을 설정할 수 없습니다.