C语言中的宏与函数
我经常看到使用宏比使用函数更好的例子C语言中的宏与函数,c,function,c-preprocessor,C,Function,C Preprocessor,我经常看到使用宏比使用函数更好的例子 有人能举例说明宏与函数相比的缺点吗?没有重复参数和代码的类型检查,这会导致代码膨胀。宏语法还可能导致许多奇怪的边缘情况,其中分号或优先顺序可能会造成阻碍。下面的链接演示了一些宏示例1: 鉴于: int square(int x) { return x * x; } int main() { int x = 2; int y = square(x++); // fine return 0; } 例2: 与之相比: struct foo {
有人能举例说明宏与函数相比的缺点吗?没有重复参数和代码的类型检查,这会导致代码膨胀。宏语法还可能导致许多奇怪的边缘情况,其中分号或优先顺序可能会造成阻碍。下面的链接演示了一些宏示例1: 鉴于:
int square(int x) {
return x * x;
}
int main() {
int x = 2;
int y = square(x++); // fine
return 0;
}
例2:
与之相比:
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)
它们需要在每行末尾添加一个\
宏无法“返回”任何内容,除非将其设置为单个表达式:
#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的expression语句,否则无法在宏中执行此操作。(编辑:虽然可以使用逗号运算符……但忽略了这一点……但其可读性可能仍然较低。)
操作顺序:(由@ouah提供) 定义最小值(a,b)(a 扩展到:
(x++ < y ? x++ : y)
(x & 0xFF < 42 ? x & 0xFF : 42)
(x&0xFF<42?x&0xFF:42)
但是
&
的优先级低于宏容易出错,因为它们依赖于文本替换,不执行类型检查。例如,此宏:
#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;
最后,宏可能很难调试,产生奇怪的语法错误或运行时错误,您必须进行扩展才能理解(例如,使用gcc-e),因为调试器无法单步调试宏,如本例所示:
#define print(x, y) printf(x y) /* accidentally forgot comma */
print("foo %s", "bar") /* prints "foo %sbar" */
内联函数和常量有助于避免宏的许多此类问题,但并不总是适用的。在故意使用宏来指定多态行为的情况下,可能很难避免意外的多态。C++具有许多特征,如模板,可以帮助在不使用宏的情况下以类型化的方式创建复杂的多态结构;请参阅Stroustrup的C++编程语言。 < P>宏的一个缺点是调试器读取源代码,而没有扩展的宏,因此在宏中运行调试器并不一定有用。不用说,您不能像使用函数一样在宏中设置断点。函数执行类型检查。这为您提供了一个额外的安全层。如有疑问,请使用函数(或内联函数)
然而,这里的答案主要是解释宏的问题,而不是简单地认为宏是邪恶的,因为愚蠢的事故是可能的。
你可以意识到陷阱并学会避免它们。然后,只有在有充分理由的情况下才使用宏
在某些特殊情况下,使用宏有好处,包括:
- 通用函数,如下所述,您可以有一个宏,可用于不同类型的输入参数
- 可变数量的参数可以映射到不同的函数,而不是使用C的
va_args
例如:
- 它们可以选择性地包含本地信息,例如调试字符串:
(\uuuuuuuuuu文件
,\uuuuuuu行
,\uuuu函数
)。检查前置/后置条件、assert
on failure,甚至静态断言,以便代码不会在不正确使用时编译(主要用于调试构建)
- 检查输入参数,您可以在输入参数上执行测试,例如在强制转换前检查其类型、大小、结构成员是否存在(对于多态类型可能有用)。
或检查数组是否满足某些长度条件。
请参阅:
- 虽然注意到函数执行类型检查,但C也会强制值(例如int/float)。在极少数情况下,这可能会有问题。可以编写比函数更精确的宏,这些宏与它们的输入参数有关。见:
- 它们用作函数的包装,在某些情况下,您可能希望避免重复自己,例如<代码>函数(FOO,“FOO”)
,您可以定义一个宏来扩展字符串func\u包装器(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、CLAN、EKOPATS和英特尔C++(但不是MSVC)支持的编译器扩展;
因此,宏的缺点是,您需要知道如何开始使用这些宏,并且它们没有得到广泛的支持
一个好处是,在这种情况下,您可以对许多不同的类型使用相同的square
函数。添加到这个答案中
宏被预处理器直接替换到程序中(因为它们基本上是预处理器指令),因此它们不可避免地会占用更多内存
#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;
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
#define print(x, y) printf(x y) /* accidentally forgot comma */
print("foo %s", "bar") /* prints "foo %sbar" */
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))
#define square(a_) __extension__ ({ \
typeof(a_) a = (a_); \
(a * a); })
#define MIN(a,b) ((a)<(b) ? (a) : (b))
int min = MIN(functionThatTakeLongTime(1),functionThatTakeLongTime(2));