我在一个标题中有一些简短的定义,如下所示:
#define ROUND_DOWN(a,b) (a)-(a)%(b)
例如
ROUND_DOWN(178,32) = 160
但如果我把这个传给它:
ROUND_DOWN(160*2, 32);
然后它被编译成这样?
(160*2)-(160*2)%(32),
这只是两次160*2的处理量。。
我想知道内联函数的行为方式是否相同?例如
inline int RoundDown(int a, int b)
{
return (a)-(a)%(b)
}
160*2会被存储在"int a"中作为320,然后计算就会起作用吗?还是它的行为与定义相同?
一个更好的例子是调用:
RoundDown((x+x2)*zoom, tile_width);
"#define"和内联的行为相同吗
不,他们没有!
宏和内联函数之间有许多区别。
-评估次数
作为参数传递给内联函数的表达式只计算一次。
在某些情况下,作为参数传递给宏的表达式可以进行多次求值。每次在宏中使用参数时,都会对该参数进行求值。
代码示例:
#define max(a,b) (a>b?a:b)
int main()
{
int a = 0;
int b = 1;
int c = max(a++, b++);
cout << a << endl << b << endl;
return 0;
}
其意图可能是打印1和2,但宏扩展为:
int c = a++ > b++ ? a++ : b++;
b增加两次,程序打印1和3。
-谁评估他们
内联函数由编译器求值,而宏则由预编译器在预编译时求值。
-类型检查
内联函数遵循在正常函数上强制执行的所有类型安全协议。将检查参数类型,并正确执行必要的转换。编译器在将内联函数放入符号表之前执行返回类型检查和函数签名
它们可以重载,以便对正确类型的数据执行正确类型的操作。
与内联函数相比,宏更容易出错。参数没有类型化(宏适用于任何算术类型的对象)。编译期间不进行错误检查。
代码示例:
#define MAX(a, b) ((a < b) ? b : a)
int main( void)
{
cout << "Maximum of 10 and 20 is " << MAX("20", "10") << endl;
return 0;
}
可以将字符串传递给进行整数运算的宏,而宏不会抱怨!
-建议还是命令
内联只是对编译器的一个建议。是否内联扩展函数由编译器决定。
宏将始终展开。
-调试怎么样
内联函数可以很容易地调试,因为您可以在内联函数定义处设置断点,并逐步进入方法中进行调试。
宏不能用于调试,因为它们是在预编译时展开的。
首先,您应该假设所有常量表达式都是在编译时求值的,这样在运行程序时乘法就永远不会被执行。
第二,你根本不能依赖inline
有任何效果,它只是编译器的一个提示,而不是一个要求。
但是,即使函数没有内联,也不会对表达式求值两次,因为参数传递要求在函数体运行之前对其求值。
#define
s是简单的文本替换,因此(正如您所注意到的)您可能需要小心使用括号等。inline
参数解析正常。
还有一个与条件相关的问题。
名义上,函数参数160*2
只计算一次,然后在函数体中使用结果,而宏计算160*2
两次。如果参数表达式有副作用,那么您可以看到[*]:ROUND_DOWN(printf("hi!n"), 1);
vs RoundDown(printf("hi!n"), 1);
在实践中,无论函数是内联的还是宏扩展的,它都只是表达式中的整数运算,没有任何副作用。优化编译器可以计算出整个宏/函数调用的结果,只需将答案粘贴在发出的代码中即可。因此,您可能会发现您的宏和内联函数会导致执行完全相同的代码,因此int a = ROUND_DOWN(160*2, 32);
和int a = RoundDown(160*2, 32);
可能都与int a = 320;
相同。
在没有副作用的情况下,优化还可以存储和重用中间结果。所以int c = ROUND_DONW(a*2, b);
最终可能会发出看起来像你写过的代码:
int tmp = a*2;
int c = tmp - tmp % b;
请注意,是否实际内联函数是编译器根据自己的优化规则做出的决定。这些规则可能会考虑函数是否标记为inline
,但很可能不会,除非您使用编译器选项强制内联或其他什么。
因此,假设有一个不错的编译器,就没有理由为此使用宏——尤其是对于你的宏,你只是在乞求有人来写:
int a = ROUND_DOWN(321, 32) * 2;
然后浪费几分钟时间想知道为什么结果是319。
[*]尽管不要得意忘形——对于一些有副作用的表达式,例如i++
,其中i
是整数,但由于缺少序列点,宏具有未定义的行为。
在您给出的常量示例中,在任何合理的编译器上,两个版本都将在编译时计算常量。
假设你真的在询问传递变量的情况,我希望编译器的优化器在这两种情况下都能生成相同的代码(如果保存结果更有效,它就不会进行两次乘法运算。最后,内联函数确实为编译器提供了一个选项,如果它能提高性能,可以进行实际的函数调用。
最后请注意,我不会担心这样的微优化,因为99%的微优化不会影响程序的性能——I/O将是您的瓶颈。