Programing

C의 매크로 대 기능

lottogame 2020. 9. 2. 20:27
반응형

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;
}

No type checking of parameters and code is repeated which can lead to code bloat. The macro syntax can also lead to any number of weird edge cases where semi-colons or order of precedence can get in the way. Here's a link that demonstrates some macro evil


When in doubt, use functions (or inline functions).

However answers here mostly explain the problems with macros, instead of having some simple view that macros are evil because silly accidents are possible.
You can be aware of the pitfalls and learn to avoid them. Then use macros only when there is a good reason to.

There are certain exceptional cases where there are advantages to using macros, these include:

  • Generic functions, as noted below, you can have a macro that can be used on different types of input arguments.
  • Variable number of arguments can map to different functions instead of using C's va_args.
    eg: https://stackoverflow.com/a/24837037/432509.
  • They can optionally include local info, such as debug strings:
    (__FILE__, __LINE__, __func__). check for pre/post conditions, assert on failure, or even static-asserts so the code won't compile on improper use (mostly useful for debug builds).
  • Inspect input args, You can do tests on input args such as checking their type, sizeof, check struct members are present before casting
    (can be useful for polymorphic types).
    Or check an array meets some length condition.
    see: https://stackoverflow.com/a/29926435/432509
  • While its noted that functions do type checking, C will coerce values too (ints/floats for example). In rare cases this may be problematic. Its possible to write macros which are more exacting then a function about their input args. see: https://stackoverflow.com/a/25988779/432509
  • Their use as wrappers to functions, in some cases you may want to avoid repeating yourself, eg... func(FOO, "FOO");, you could define a macro that expands the string for you func_wrapper(FOO);
  • When you want to manipulate variables in the callers local scope, passing pointer to a pointer works just fine normally, but in some cases its less trouble to use a macro still.
    (assignments to multiple variables, for a per-pixel operations, is an example you might prefer a macro over a function... though it still depends a lot on the context, since inline functions may be an option).

Admittedly, some of these rely on compiler extensions which aren't standard C. Meaning you may end up with less portable code, or have to ifdef them in, so they're only taken advantage of when the compiler supports.


Avoiding multiple argument instantiation

Noting this since its one of the most common causes of errors in macros (passing in x++ for example, where a macro may increment multiple times).

its possible to write macros that avoid side-effects with multiple instantiation of arguments.

C11 Generic

If you like to have square macro that works with various types and have C11 support, you could do this...

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))

Statement expressions

This is a compiler extension supported by GCC, Clang, EKOPath & Intel C++ (but not MSVC);

#define square(a_) __extension__ ({  \
    typeof(a_) a = (a_); \
    (a * a); })

So the disadvantage with macros is you need to know to use these to begin with, and that they aren't supported as widely.

One benefit is, in this case, you can use the same square function for many different types.


one drawback to macros is that debuggers read source code, which does not have expanded macros, so running a debugger in a macro is not necessarily useful. Needless to say, you cannot set a breakpoint inside a macro like you can with functions.


Adding to this answer..

Macros are substituted directly into the program by the preprocessor (since they basically are preprocessor directives). So they inevitably use more memory space than a respective function. On the other hand, a function requires more time to be called and to return results, and this overhead can be avoided by using macros.

Also macros have some special tools than can help with program portability on different platforms.

Macros don't need to be assigned a data type for their arguments in contrast with functions.

Overall they are a useful tool in programming. And both macroinstructions and functions can be used depending on the circumstances.


Functions do type checking. This gives you an extra layer of safety.


I did not notice, in the answers above, one advantage of functions over macros that I think is very important:

Functions can be passed as arguments, macros cannot.

Concrete example: You want to write an alternate version of the standard 'strpbrk' function that will accept, rather than an explicit list of characters to search for within another string, a (pointer to a) function that will return 0 until a character is found that passes some test (user-defined). One reason you might want to do this is so that you can exploit other standard library functions: instead of providing an explicit string full of punctuation, you could pass ctype.h's 'ispunct' instead, etc. If 'ispunct' was implemented only as a macro, this wouldn't work.

There are lots of other examples. For example, if your comparison is accomplished by macro rather than function, you can't pass it to stdlib.h's 'qsort'.

An analogous situation in Python is 'print' in version 2 vs. version 3 (non-passable statement vs. passable function).


If you pass function as an argument to macro it will be evaluated every time. For example, if you call one of the most popular macro:

#define MIN(a,b) ((a)<(b) ? (a) : (b))

like that

int min = MIN(functionThatTakeLongTime(1),functionThatTakeLongTime(2));

functionThatTakeLongTime will be evaluated 5 times which can significantly drop perfomance

참고URL : https://stackoverflow.com/questions/9104568/macro-vs-function-in-c

반응형