“#define”和“inline”有相同的行为吗?

3

我在其中一个头文件中有一些简短的定义,如下所示:

#define ROUND_DOWN(a,b)   (a)-(a)%(b)

e.g.

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的形式存储并进行计算,还是与define相同?

一个更好的例子是调用:

RoundDown((x+x2)*zoom, tile_width);

使用2 * ROUND_DOWN(178, 32)将会导致程序出现问题。请改用#define ROUND_DOWN(a,b) ((a)-(a)%(b))(注意额外的括号)。总之,两种方法的行为不会完全相同。 - user142019
尝试使用您的宏ROUND_DOWN(x++, y),看看会发生什么。 - Fred Larson
2
@Fred: 要么不要去看会发生什么,因为未定义行为可能会导致看不见的恶魔从你的鼻子里飞出来。 - Steve Jessop
5个回答

8

“#define”和inline有相同的行为吗?

不,它们并不相同!

宏和内联函数之间存在许多差异。

- 评估次数不同

作为内联函数参数传递的表达式只会被评估一次。

在某些情况下,作为宏参数传递的表达式可能会被评估多次。 每次在宏中使用一个参数时,该参数都会被评估。

代码示例:

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

可以将字符串传递给执行一些整数运算的宏,而宏不会抱怨!

- 是建议还是命令?

内联函数只是向编译器提出的建议。是否展开函数是编译器的决定。

宏始终会被展开。

- 调试方面呢?

内联函数可以轻松调试,因为可以在内联函数定义处设置断点,逐步调试方法。

宏不能用于调试,因为它们在预编译时展开。


你的MAX定义有问题。尝试使用MAX(5 & 3, 3 & 3),它不会给出预期结果3,而是1 - Patrick Schlüter
1
作为一个简单的规则,在定义宏时,一定要在参数周围加上括号,一定要 - Patrick Schlüter
@tristopia:代码示例指出了宏的恶劣影响,如果使用不当,您的评论也指出了同样的问题,它们并不意味着要教授如何编写宏。 - Alok Save
可能是这样,但OP的问题是宏和内联之间的区别。您在帖子中列出的错误情况确实解决了一些宏的问题,但我指出的问题在您的回答中没有得到解决。顺便说一句,我没有对您的帖子进行投票。 - Patrick Schlüter

5

首先,您应该基本上假设所有常量表达式都在编译时进行求值,因此乘法永远不会存活到您运行程序时被执行。

其次,您不能指望inline有任何效果,它只是对编译器的提示,而不是要求。

但即使函数未被内联,表达式也不会被评估两次,因为参数传递需要在函数体运行之前对其进行评估。


抱歉,我的示例不好,我不进行常量计算,我的代码更像是“RoundDown((x+x2)*zoom, tile_width);”,如果编译器将我的函数视为内联,它会计算两次还是存储在参数中? - Kaije
@Kaije:表达式将被评估一次。函数的内容,无论是inline还是不是,都不会对其参数被评估的次数产生任何影响。 - greyfade

3

#define是简单的文本替换,所以(正如你所注意到的)你可能需要小心括号等细节。 inline参数会被正常解析。

在条件方面有一个相关问题,请参考相关问题


2
理论上,函数参数 160*2 仅被计算一次,然后在函数体中使用其结果,而宏则会计算 160*2 两次。如果参数表达式具有副作用,则可看到以下内容[*]:ROUND_DOWN(printf(“hi!\ n”),1); 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是整数,由于缺乏序列点,该宏具有未定义的行为。


1

在你提供的常量示例中,任何合理的编译器都会在编译时计算出常量的值。

假设你实际上是在询问传递变量的情况,我预计编译器的优化器将在两种情况下生成相同的代码(如果保存结果更有效,则不会进行两次乘法运算)。最后,内联函数确实为编译器提供了一个选项,即在需要提高性能时进行实际的函数调用。

最后请注意,我不会担心这样的微观优化,因为99%的情况下它对程序的性能没有影响 - I/O将成为瓶颈。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接