【问题标题】:Are the following inlined functions guaranteed to have the same implementation?以下内联函数是否保证具有相同的实现?
【发布时间】:2015-02-19 11:04:30
【问题描述】:
  • 是否保证以下函数具有相同的实现(即目标代码)?
  • 如果下面的Foo 是原始类型(例如int),这种情况会改变吗?
  • 这会随着Foo 的大小而改变吗?

按值返回:

inline Foo getMyFooValue() { return myFoo; }

Foo foo = getMyFooValue();

引用返回:

inline const Foo &getMyFooReference() { return myFoo; }

Foo foo = getMyFooReference();

就地修改:

inline void getMyFooInPlace(Foo &theirFoo) { theirFoo = myFoo; }

Foo foo;
getMyFooInPlace(foo);

【问题讨论】:

  • 函数本身发出的代码(例如,如果它们的地址被占用)显然是不同的。你的意思是如果调用站点的代码在内联后会改变吗?此外,其中之一与其他不同。
  • 如果您询问保证,无论如何都没有。只要可观察到的行为相同,编译器就可以做它想做的事情(例如,是否执行内联!)。例如,如果可以证明 myFoo 不会改变,它可以简单地发出使用直接值而不是引用真实内存的汇编程序。
  • 除了我在下面的正式回复之外,我了解您假设返回对原始类型的 const 引用可能会被实现为返回值的副本。虽然编译器可能会在只需要值的情况下通过高度优化和内联来做到这一点,但我认为如果有人获取getMyFooReference() 返回的地址(其他地方!),编译器可能总是发出真正的“引用”,即使当要求该值(即需要取消引用的地址),至少在低优化的情况下。
  • 我没有考虑获取参考的地址!很好的收获。

标签: c++ inline-functions


【解决方案1】:

是否保证以下函数具有相同的实现(即目标代码)?

不,该语言仅指定行为,而不是代码生成,因此由编译器决定具有相同行为的两段代码是否生成相同的目标代码。

如果下面的Foo 是原始类型(例如int),这种情况会改变吗?

如果是(或者,更一般地说,如果它可轻松复制),那么这三个都具有相同的行为,因此可以预期生成类似的代码。

如果它是一个非平凡的类类型,那么它取决于类的特殊功能做什么。每个调用这些函数的方式略有不同:

  • 第一个可能复制初始化一个临时对象(调用复制构造函数),复制初始化foo,然后销毁临时对象(调用析构函数);但更有可能的是它会忽略暂时的,变得等同于第二个。
  • 第二个将复制初始化foo(调用复制构造函数)
  • 第三个将默认初始化foo(调用默认构造函数),然后复制分配给它(调用赋值运算符)。

因此它们是否等效取决于默认初始化和复制分配是否具有与复制初始化等效的行为,以及(也许)创建和销毁临时对象是否有副作用。如果它们是等价的,那么您可能会得到类似的代码。

这会随着Foo 的大小而改变吗?

不,大小无关紧要。重要的是它是微不足道的(因此复制初始化和复制分配都只是复制字节)还是非平凡的(因此它们调用用户定义的函数,它们可能彼此等效,也可能不等效)。

【讨论】:

  • 使用 -O0 我预计会有差异,甚至可能不会内联。另外,如果有人想到获取 const ref 的地址(在此处未显示的代码中,但显然它只是一个 sn-p,对吗?)我希望 getMyFooReference() 的代码返回一个引用, 即汇编器中的地址。
  • @PeterSchneider:好点。我将问题解释为“这些函数 calls 是否具有相同的实现”;但值得注意的是,函数本身(如果没有内联,或者以暴露它们差异的方式使用)不会,因为它们可以有不同的行为。
  • @MikeSeymour,the language only specifies behaviour, not code generation。我同意,显然语言并不能确定将生成的代码,但它可以对可执行文件施加一些约束,而不仅仅是行为,例如 RVO 和移动语义。就个人而言,我和提问者有同样的问题。语言中的这种含糊不清似乎是可怕的和不必要的。如果我想在库代码中提供一个小且经常使用的函数,我想真正确定任何编译器都会以合理的方式内联该函数。
【解决方案2】:

标准草案 N3337 在 1.9.5 中包含以下规则:“符合标准的 [C++] 实现 [...] 应产生与抽象机的相应实例的可能执行之一相同的可观察行为相同的程序和相同的输入。”而在 1.9.9 中,它基本上将可观察行为定义为 I/O 和 volatile 的值。这意味着只要程序的 I/O 和 volatile 保持不变,实现就可以做它想做的事情。如果您没有 I/O 或 volatile,则程序不需要做任何事情(这使得基准测试很难通过高度优化得到正确)。

请注意,该标准对编译器应该发出什么代码完全没有提及。见鬼,它可能解释来源。

这回答了你的问题:不。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-07-27
    • 2021-10-21
    • 1970-01-01
    • 2013-11-17
    • 2011-11-13
    • 1970-01-01
    • 2011-01-24
    • 1970-01-01
    相关资源
    最近更新 更多