不同的功能有不同的地址吗?

Yak*_*ont 51 c++ function-pointers one-definition-rule language-lawyer comdat-folding

考虑这两个功能:

void foo() {}
void bar() {}
Run Code Online (Sandbox Code Playgroud)

是保证&foo != &bar吗?

同样的,

template<class T> void foo() { }
Run Code Online (Sandbox Code Playgroud)

是保证&foo<int> != &foo<double>吗?


我知道折叠函数定义有两个连接器.

MSVC积极地COMDAT折叠函数,因此具有相同实现的两个函数可以转换为一个函数.作为副作用,这两个函数共享相同的地址.我的印象是这是非法的,但我无法找到标准中的哪些内容是非法的.

Gold链接器还可以折叠功能,包括a safeall设置. safe意味着如果采用了一个功能地址,它就不会折叠,all即使采用了地址也会折叠.因此safe,如果函数具有不同的地址,则黄金的折叠表现为.

虽然折叠可能是意料之外的,并且存在依赖于具有不同地址的不同(相同实现)函数的代码(因此折叠可能是危险的),在当前的C++标准下它实际上是非法的吗?(此时为C++ 14)(自然如果safe折叠是合法的)

Sha*_*our 30

它看起来像缺陷报告1400:函数指针相等处理这个问题,并且在我看来可以说可以进行这种优化,但正如评论所示,存在分歧.它说(强调我的):

根据5.10 [expr.eq]第2段,如果两个函数指针指向相同的函数,则它们仅相等.但是,作为优化,实现当前是具有相同定义的别名函数.目前尚不清楚标准是否需要明确处理此优化.

而回应是:

标准明确了要求,并且实现可以在"as-if"规则的约束内自由优化.

问题是询问两个问题:

  • 是否可以认为这些指针是平等的
  • 合并功能是否可以

根据评论,我看到对答复的两种解释:

  1. 这种优化是可以的,该标准在as-if规则下给予实现这种自由.在AS-如果规则是覆盖在部分1.9和手段的实施只相对于标准的要求,模拟观察到的行为.这仍然是我对答案的解释.

  2. 手头的问题完全被忽略了,声明只是说不需要对标准进行调整,因为as-if规则明确涵盖了这一点,但解释留给了读者一个练习.虽然我承认由于回复的简洁性,我不能忽视这种观点,但最终却是一种完全无益的回应.它似乎与其他NAD问题的答案不一致,据我所知,如果它们存在,我可以指出问题.

标准草案说的是什么

既然我们知道我们正在处理as-if规则,我们可以从那里开始并注意该部分1.8说:

除非对象是零字段或零大小的基类子对象,否则该对象的地址是它占用的第一个字节的地址.如果一个是另一个的子对象,或者如果至少一个是零大小的基类子对象并且它们是不同类型的,则不是位字段的两个对象可以具有相同的地址; 否则,他们应有不同的地址.4

并注意4说:

在"as-if"规则下,允许实现在同一个机器地址存储两个对象,或者如果程序无法观察到差异,则根本不存储对象

但该部分的说明是:

函数不是对象,无论它是否以对象的方式占用存储

虽然它不是规范性的,但是1在一个函数的上下文中对段落中的对象的要求没有意义,因此它与本说明一致.因此,我们明确限制别名对象,但有一些例外,但这种限制不适用于函数.

接下来我们有部分5.10 平等运营商说(强调我的):

[...]两个指针比较相等,如果它们都是空的,都指向相同的函数,或者两者都代表相同的地址(3.9.2),否则它们比较不相等.

它告诉我们两个指针是相等的,如果它们是:

  • 空指针
  • 指向相同的功能
  • 代表相同的地址

两个或两个代表相同的地址似乎给予足够的自由度,允许编译器为两个不同的函数设置别名,并且不需要指向不同函数的指针来比较不等.

意见

Keith Thompson做了一些很好的观察,我觉得值得加入答案,因为他们遇到了涉及的核心问题,他说:

如果程序打印&foo ==&bar的结果,那就是可观察的行为; 有问题的优化会改变可观察的行为.

我同意这一点,如果我们能够表明要求指针不相等而确实违反了as-if规则,但到目前为止我们无法证明这一点.

和:

[...]考虑一个程序,它定义空函数,并使用他们的地址的唯一值(考虑SIG_DFL,SIG_ERRSIG_IGN<signal.h中>/<csignal> ).为它们分配相同的地址会破坏这样的程序

正如我在评论中指出的那样,C标准要求这些宏生成不同的值,从7.14C11开始:

[...]扩展为具有不同值的常量表达式,这些值具有与第二个参数兼容的类型,以及信号函数的返回值,并且其值与任何可声明函数的地址不相等[...]

因此,虽然涵盖了这种情况,但也许还有其他情况会使这种优化变得危险.

更新

JanHubička是一位gcc开发人员,在GCC 5中撰写了博客文章链接时间和程序间优化改进,代码折叠是他所涉及的众多主题之一.

我让他评论是否将相同的函数折叠到同一个地址是否符合行为,他说这不符合行为,实际上这样的优化会破坏gcc自己:

它不符合转两个函数具有相同的地址,因此MSVC在这里非常积极.例如,这样做会破坏GCC本身,因为我的意外地址比较是在预编译的头代码中完成的.它适用于许多其他项目,包括Firefox.

事后看来,经过几个月阅读缺陷报告和思考优化问题后,我对委员会的回应更加保守.获取函数的地址是可观察的行为,因此折叠相同的函数将违反as-if规则.

  • 关闭但不完全:允许两个不同的函数共享相同的二进制代码(在as-if下),但是可能不能将两个不同的函数的地址进行比较并且相等.接近这种方法的一种方法是在函数的前面需要"stub"`noop`s或`jmp`s,所以每个都得到一个不同的地址,但是相同的主体(因为我相信一些非MSVC COMDAT折叠等价物做) (4认同)
  • 我发现委员会的回应不仅简洁,而且不透明.标准中的一个脚注说"只要结果是*,就好像已经遵守了要求,只要可以从程序的可观察行为中确定,"实施可以自由地忽略本国际标准的任何要求" .如果一个程序打印出`&foo ==&bar`的结果,那就是可观察到的行为; 有问题的优化会改变可观察的行为.我对响应的解读是指针必须比较不平等,但我怀疑这不是什么意思. (4认同)
  • @KeithThompson:看起来你的解释会允许一个函数与一个不同但相同的函数*合并,它们的地址不被取*,但是如果它们的地址比较为不同的话,那么每个取地址的函数都可以合并(在在指针具有比硬件地址更多的位的体系结构中,两个指针可能表示相同的物理地址但仍然看起来是不同的.如果意图是地址被采用的函数必须产生不同的地址,我想知道为什么它不*说*所以? (4认同)
  • @ShafikYaghmour:这不明确,但我认为这是常识.显然其他人不同意.问题是两个明确定义的函数是否可以是"相同的函数".一个共享两个函数代码的优化编译器能否以用户代码可见的方式使它们成为"相同的函数"?它可以为不同的对象做同样的事情吗?实际上,考虑一个定义空函数的程序,并将它们的地址用作唯一值(在`<signal.h>`/`<csignal>`中考虑`SIG_DFL`,`SIG_ERR`和`SIG_IGN`).为它们分配相同的地址会破坏这样的程序. (3认同)
  • @Yakk:这将与`address`的通用定义背道而驰,如果标准中没有不同的定义,它将占据主导地位. (2认同)

Jam*_*nze 11

是.从标准(§5.10/ 1):"同一类型的两个指针比较相等,当且仅当它们都为空时,都指向相同的函数,或者两者都代表相同的地址"

一旦它们被实例化,foo<int>并且foo<double>是两个不同的功能,所以上述内容也适用于它们.

  • @Deduplicator标准定义了它使用的所有术语.就标准而言,尽管一个部分标题的粗略措辞(在日常意义上使用字地址,而不是标准使用它的意义上),但功能根本没有地址. (7认同)
  • @MikeSeymour函数没有地址(在标准中). (6认同)
  • 这并不妨碍它们表示相同的地址,但指向不同的功能. (5认同)
  • @JamesKanze:除非他们这样做.参见例如13.4,"重载功能的地址". (5认同)
  • @LightnessRacesinOrbit您将找不到函数没有地址的语句.但是,只有对象占用存储空间,只有占用存储空间的东西才能有地址.另见§5.3.1/ 3; 函数上的一元`&`运算符返回一个"指针",而不是它的地址(与对象的情况不同). (5认同)
  • @Nawaz"相同的地址"与对象相关,其中对象表示要求它们具有地址.这是weasel的措辞,以避免一个超过数组末尾的人最终指向另一个对象的问题.这是"两个指向相同功能"的相关内容. (4认同)
  • @Yakk参见§1.8/ 6,它定义了一个对象的地址.任何地方的功能地址都没有,至少我能找到; 函数有"指针",但指向函数的指针就是这样; 它不是地址,而是编译器想要允许它找到函数的任何东西. (4认同)
  • @MikeSeymour:我强烈地将其解释为对象的相同地址.该短语涵盖了指针值的三种"模式".如果是这样,我会认为这是一个编辑缺陷.否则它是非常荒谬的. (3认同)
  • 我有点怀疑,似乎短语*或两者代表相同的地址*允许看到优化.也许这不是意图,但我可以看到它是如何以这种方式阅读的. (3认同)
  • @MikeSeymour这是一个有趣的标题; "地址"一词从未出现在该部分中.我不认为章节名称是规范性的(它们没有指定任何内容),但鉴于标准的其余部分,包括本节的内容,似乎不经意地选择了名称. (3认同)
  • @LightnessRacesinOrbit:不,不.使用"或"意味着如果它们代表相同的地址但两者都不指向相同的功能,它们将相等. (2认同)
  • 是否保证`foo <int>`和`foo <double>`是不同的函数,如果它们不依赖于模板参数? (2认同)
  • @JamesKanze:我很乐意这么说.实际上我的意思是我希望能够证明这一点. (2认同)
  • @LightnessRacesinOrbit 你走在正确的轨道上。在 C++ 中,只有“对象”有地址。你可以有一个指向函数的指针,但它不是一个地址。 (2认同)
  • @ luk32这当然是意图.每个实例化都会创建一个新函数.(我们在委员会中对此进行了讨论.目前来自微软的代表.) (2认同)

Pau*_*oke 7

所以问题部分显然是短语或两者都代表相同的地址(3.9.2).

IMO这部分显然是为了定义对象指针类型的语义.仅适用于对象指针类型.

这句话引用了第3.9.2节,这意味着我们应该看一下.3.9.2关于对象指针所代表的地址的讨论(以及其他).它没有谈论函数指针所代表的地址.其中,IMO只留下两种可能的解释:

1)该短语不适用于函数指针.这只剩下两个空指针和两个指向相同函数的指针,比较相等,这可能是我们大多数人所期望的.

2)这句话确实适用.因为它指的是3.9.2,它没有说明函数指针所代表的地址,所以我们可以使任何两个函数指针比较相等.这是非常意外的,当然渲染比较函数指针是完全无用的.

因此,虽然从技术角度来说可以证明(2)是一种有效的解释,IMO并不是一种有意义的解释,因此应予以忽视.既然不是每个人似乎都同意这一点,我也认为需要对标准进行澄清.


Ded*_*tor 3

5.10 相等运算符[expr.eq]

1 ==(等于)和!=(不等于)运算符从左到右分组。操作数应具有算术、枚举、指针或指向成员类型或类型的指针std::nullptr_t。运算符==!=都产生trueor false,即类型为 的结果bool在下面的每种情况下,在应用指定的转换后,操作数应具有相同的类型。
2如果至少一个操作数是指针,则对两个操作数执行指针转换 (4.10) 和限定转换 (4.4),以将它们转换为复合指针类型(第 5 条)。比较指针的定义如下:如果两个指针都为空、都指向同一个函数或都表示相同的地址(3.9.2),则它们比较相等,否则它们比较不相等。

让我们逐一讨论最后一点:

  1. 两个空指针比较相等。
    对你的理智有好处。
  2. 指向同一函数的两个指针比较相等。
    任何其他事情都会非常令人惊讶。
    这也意味着任何函数只有一个外联版本inline可能会被获取其地址,除非您想让函数指针比较变得异常复杂和昂贵。
  3. 两者代表相同的地址。
    现在这就是一切。放弃这个并简化if and only if为一个简单的if将留给解释,但这是一个明确的要求,使任何两个函数相同,只要它不以其他方式改变一致程序的可观察行为。