Macro vs Function in C

I often see instances in which using a macro is better than using a function.

Could someone explain me with an example the disadvantage of a macro compared to a function?

99401 次浏览

参数和代码的类型检查不会重复,这会导致代码膨胀。宏语法还会导致许多奇怪的边缘情况,分号或优先顺序可能会成为其中的障碍。下面的链接演示了一些宏 邪恶

例子一:

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

副作用是一个很大的问题。 这里有一个典型的例子:

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


min(x++, y)

扩展到:

(x++ < y ? x++ : y)

在同一个语句中,abc0增加两次(未定义行为)


Writing multi-line macros are also a pain:

#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 的 语句表达式。(编辑: 你可以使用逗号操作符... ... 忽略了... ... 但它可能仍然不太可读。)


Order of Operations: (courtesy of @ouah)

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


min(x & 0xFF, 42)

gets expanded to:

(x & 0xFF < 42 ? x & 0xFF : 42)

但是 &的优先级比 <低,所以 0xFF < 42先被评估。

宏很容易出错,因为它们依赖于文本替换并且不执行类型检查:

#define square(a) a * a

当与整数一起使用时,可以很好地工作:

square(5) --> 5 * 5 --> 25

but does very strange things when used with expressions:

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

Finally, macros can be difficult to debug, producing weird syntax errors or runtime errors that you have to expand to understand (e.g. with gcc -E), because debuggers cannot step through macros, as in this example:

#define print(x, y)  printf(x y)  /* accidentally forgot comma */
print("foo %s", "bar") /* prints "foo %sbar" */

内联函数和常量有助于避免宏的许多这类问题,但并不总是适用。如果宏被有意地用来指定多态行为,那么无意中的多态性可能难以避免。C + + 有许多特性,比如模板,可以帮助以类型安全的方式创建复杂的多态结构,而不需要使用宏; 详细信息请参阅 Stroustrup 的 C++程式语言

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.

函数可以进行类型检查,这为您提供了一个额外的安全层。

有疑问时,使用函数(或内联函数)。

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.

在某些 很特别案例中,使用宏具有优势,这些案例包括:

  • 如下所述,泛型函数可以有一个可用于不同类型输入参数的宏。
  • Variable number of arguments can map to different functions instead of using C's va_args.
    eg: https://stackoverflow.com/a/24837037/432509.
  • 它们可以 可以选择包含本地信息,例如调试字符串: < br > (__FILE____LINE____func__)。检查前/后条件,失败时的 assert,甚至静态断言,这样代码就不会在不正确的使用情况下编译(对于调试构建非常有用)。
  • 检查输入参数,您可以对输入参数进行测试,比如检查它们的类型、 sizeof、检查在转换 < br > (可用于多态类型)之前是否存在 struct成员。或检查数组是否满足某些长度条件。原文地址: http://stackoverflow/a/29926435/432509 https://stackoverflow.com/a/29926435/432509
  • 虽然它指出函数执行类型检查,但 C 也会强制执行值(例如 int/float)。在极少数情况下,这可能是有问题的。可以编写比关于输入参数的函数更精确的宏。见: https://stackoverflow.com/a/25988779/432509
  • 它们用作函数的包装器,在某些情况下,您可能希望避免重复使用它们,例如..。.0,您可以定义一个宏来展开 func_wrapper(FOO);的字符串
  • 当您想要操作调用程序局部作用域中的变量时,将指针传递给指针正常工作,但是在某些情况下使用宏仍然没有那么麻烦。< br > (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).

不可否认,其中一些依赖于编译器扩展,而这些扩展并不是标准的 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函数。

Adding to this answer..

宏被预处理器直接替换到程序中(因为它们基本上是预处理器指令)。因此,它们不可避免地使用了比各自函数更多的内存空间。另一方面,函数需要更多的时间来调用和返回结果,这种开销可以通过使用宏来避免。

宏还有一些特殊的工具,可以帮助提高程序在不同平台上的可移植性。

与函数相比,宏的参数不需要分配数据类型。

总的来说,宏指令和函数在编程中都是非常有用的工具,而且宏指令和函数都可以根据具体情况使用。

宏功能 :

  • Macro is Preprocessed
  • 无类型检查
  • Code Length Increases
  • 使用宏可以导致 副作用
  • 执行速度是 再快点
  • 在编译宏名称被宏值替换之前
  • 在多次出现小代码的地方很有用
  • 宏检查编译错误

功能特点 :

  • 函数是 编译完毕
  • 类型检查已完成
  • 代码长度保持为 一样
  • 没有副作用
  • 执行速度是 慢一点
  • 在函数调用期间,发生控制转移
  • 在多次出现大代码的地方很有用
  • 函数检查 编译错误

在上面的答案中,我没有注意到函数相对于宏的一个非常重要的优势:

函数可以作为参数传递,而宏不能。

具体例子: 您需要编写一个标准‘ strpbrk’函数的替代版本,它将接受一个(指向 a 的)函数,该函数将返回0,直到找到一个通过某些测试(用户定义)的字符,而不是在另一个字符串中搜索一个显式的字符列表。这样做的一个原因是,您可以利用其他标准库函数: 您可以传递 ctype.h 的‘ isunct’,而不是提供一个充满标点符号的显式字符串,等等。如果‘ isunct’只是作为一个宏来实现的,那么它就不会工作。

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'.

Python 中的一个类似情况是版本2与版本3中的“ print”(不可通过语句与可通过函数)。

如果将函数作为参数传递给宏,则每次都会对其进行计算。 例如,如果调用最流行的宏之一:

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

像这样

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

将被评估5次,这会显著降低性能