[c] C의 매크로 대 기능

나는 항상 매크로를 사용하는 것이 함수를 사용하는 것보다 나은 예와 사례를 보았습니다.

누군가 함수에 비해 매크로의 단점을 예로 설명해 줄 수 있습니까?



답변

매크로는 텍스트 대체에 의존하고 유형 검사를 수행하지 않기 때문에 오류가 발생하기 쉽습니다. 예를 들어,이 매크로 :

#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여러 유형에 대해 동일한 기능을 사용할 수 있다는 것 입니다.


답변

매개 변수 및 코드의 유형 검사가 반복되지 않아 코드가 부풀어 질 수 있습니다. 매크로 구문은 세미콜론 또는 우선 순위가 방해가 될 수있는 이상한 경우로 이어질 수 있습니다. 다음은 일부 매크로 을 보여주는 링크입니다.


답변

매크로의 한 가지 단점은 디버거가 확장 된 매크로가없는 소스 코드를 읽으므로 매크로에서 디버거를 실행하는 것이 반드시 유용하지는 않다는 것입니다. 말할 필요도없이, 함수에서 할 수있는 것처럼 매크로 내부에 중단 점을 설정할 수 없습니다.