【问题标题】:Do "#define" and inline behave the same?"#define" 和 inline 的行为是否相同?
【发布时间】:2011-07-05 14:46:14
【问题描述】:

我在其中一个标题中有一些简短的定义,如下所示:

#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 是否会作为 320 存储在“int a”中,然后计算会起作用,还是会与定义的行为相同?

一个更好的例子是调用:

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

【问题讨论】:

  • 2 * ROUND_DOWN(178, 32) 会让事情不再起作用。请改用#define ROUND_DOWN(a,b) ((a)-(a)%(b))(注意额外的括号)。结论:无论如何都不会表现得一样。
  • 用你的宏试试ROUND_DOWN(x++, y),看看会发生什么。
  • @Fred:或者不知道会发生什么,因为 UB 会导致 invisible 恶魔飞出你的鼻子。

标签: c++ c c-preprocessor inline-functions


【解决方案1】:

“#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 只是对编译器的一个建议。是否内联扩展函数由编译器决定。

宏将始终被扩展。

- 调试怎么样?

内联函数可以很容易地调试,因为你可以在内联函数定义处设置一个断点,然后一步步进入调试方法。

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

【讨论】:

  • 您的MAXdefines 已损坏。试试MAX(5 &amp; 3, 3 &amp; 3),它不会给出预期的结果3,而是1
  • 作为一个简单的规则,当你定义一个宏时,always在你的参数周围加上括号,always
  • @tristopia:代码示例指出如果使用不当,宏会导致邪恶,您的评论指出了同样的问题,它们并不是要教如何编写宏。
  • 可能是,但 OP 的问题是宏和内联之间的区别。您帖子中列出的错误案例确实解决了宏的一些问题,我指出的问题在您的回答中没有解决。顺便说一句,我没有对你的帖子投反对票。
【解决方案2】:

首先,您几乎应该假设所有常量表达式都在编译时进行评估,因此当您运行程序时,乘法永远不会被执行。

其次,你不能完全依赖inline 产生任何影响,这只是对编译器的提示,而不是要求。

但是即使函数没有内联,表达式也不会被计算两次,因为参数传递要求在函数体运行之前对其进行计算。

【讨论】:

  • 对不起,我的坏例子,我不做常数计算,我的代码更像“RoundDown((x+x2)*zoom, tile_width);”,如果编译器确实看到了我的函数作为内联,它会计算两次还是存储在参数中?
  • @Kaije:表达式将被评估一次。时期。函数的内容,inline 与否,对其参数的评估次数没有任何影响。
【解决方案3】:

#defines 是简单的文本替换,因此(如您所见)您可能需要注意括号等。inline 参数会被正常解析。

有一个关于条件的related issue

【讨论】:

    【解决方案4】:

    名义上,函数参数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 是一个整数,由于缺少序列点,宏具有未定义的行为。

    【讨论】:

      【解决方案5】:

      在您给出的带有常量的示例中,在任何合理的编译器上,两个版本都会在编译时计算常量。

      假设您实际上是在询问传入变量的情况,我希望编译器的优化器在两种情况下都生成相同的代码(如果保存结果更有效,它不会进行两次乘法运算。最后,内联函数确实为编译器提供了进行实际函数调用的选项,如果它可以提高性能。

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

      【讨论】:

        猜你喜欢
        • 2010-12-12
        • 2011-04-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多