Yak*_*ont 51 c++ function-pointers one-definition-rule language-lawyer comdat-folding
考虑这两个功能:
void foo() {}
void bar() {}
是保证&foo != &bar吗?
同样的,
template<class T> void foo() { }
是保证&foo<int> != &foo<double>吗?
我知道折叠函数定义有两个连接器.
MSVC积极地COMDAT折叠函数,因此具有相同实现的两个函数可以转换为一个函数.作为副作用,这两个函数共享相同的地址.我的印象是这是非法的,但我无法找到标准中的哪些内容是非法的.
Gold链接器还可以折叠功能,包括a safe和all设置.  safe意味着如果采用了一个功能地址,它就不会折叠,all即使采用了地址也会折叠.因此safe,如果函数具有不同的地址,则黄金的折叠表现为.
虽然折叠可能是意料之外的,并且存在依赖于具有不同地址的不同(相同实现)函数的代码(因此折叠可能是危险的),在当前的C++标准下它实际上是非法的吗?(此时为C++ 14)(自然如果safe折叠是合法的)
Sha*_*our 30
它看起来像缺陷报告1400:函数指针相等处理这个问题,并且在我看来可以说可以进行这种优化,但正如评论所示,存在分歧.它说(强调我的):
根据5.10 [expr.eq]第2段,如果两个函数指针指向相同的函数,则它们仅相等.但是,作为优化,实现当前是具有相同定义的别名函数.目前尚不清楚标准是否需要明确处理此优化.
而回应是:
标准明确了要求,并且实现可以在"as-if"规则的约束内自由优化.
问题是询问两个问题:
根据评论,我看到对答复的两种解释:
这种优化是可以的,该标准在as-if规则下给予实现这种自由.在AS-如果规则是覆盖在部分1.9和手段的实施只相对于标准的要求,模拟观察到的行为.这仍然是我对答案的解释.
手头的问题完全被忽略了,声明只是说不需要对标准进行调整,因为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_ERR和SIG_IGN 在<signal.h中>/<csignal> ).为它们分配相同的地址会破坏这样的程序
正如我在评论中指出的那样,C标准要求这些宏生成不同的值,从7.14C11开始:
[...]扩展为具有不同值的常量表达式,这些值具有与第二个参数兼容的类型,以及信号函数的返回值,并且其值与任何可声明函数的地址不相等[...]
因此,虽然涵盖了这种情况,但也许还有其他情况会使这种优化变得危险.
更新
JanHubička是一位gcc开发人员,在GCC 5中撰写了博客文章链接时间和程序间优化改进,代码折叠是他所涉及的众多主题之一.
我让他评论是否将相同的函数折叠到同一个地址是否符合行为,他说这不符合行为,实际上这样的优化会破坏gcc自己:
它不符合转两个函数具有相同的地址,因此MSVC在这里非常积极.例如,这样做会破坏GCC本身,因为我的意外地址比较是在预编译的头代码中完成的.它适用于许多其他项目,包括Firefox.
事后看来,经过几个月阅读缺陷报告和思考优化问题后,我对委员会的回应更加保守.获取函数的地址是可观察的行为,因此折叠相同的函数将违反as-if规则.
Jam*_*nze 11
是.从标准(§5.10/ 1):"同一类型的两个指针比较相等,当且仅当它们都为空时,都指向相同的函数,或者两者都代表相同的地址"
一旦它们被实例化,foo<int>并且foo<double>是两个不同的功能,所以上述内容也适用于它们.
所以问题部分显然是短语或两者都代表相同的地址(3.9.2).
IMO这部分显然是为了定义对象指针类型的语义.仅适用于对象指针类型.
这句话引用了第3.9.2节,这意味着我们应该看一下.3.9.2关于对象指针所代表的地址的讨论(以及其他).它没有谈论函数指针所代表的地址.其中,IMO只留下两种可能的解释:
1)该短语不适用于函数指针.这只剩下两个空指针和两个指向相同函数的指针,比较相等,这可能是我们大多数人所期望的.
2)这句话确实适用.因为它指的是3.9.2,它没有说明函数指针所代表的地址,所以我们可以使任何两个函数指针比较相等.这是非常意外的,当然渲染比较函数指针是完全无用的.
因此,虽然从技术角度来说可以证明(2)是一种有效的解释,IMO并不是一种有意义的解释,因此应予以忽视.既然不是每个人似乎都同意这一点,我也认为需要对标准进行澄清.
5.10 相等运算符
[expr.eq]1
==(等于)和!=(不等于)运算符从左到右分组。操作数应具有算术、枚举、指针或指向成员类型或类型的指针std::nullptr_t。运算符==和!=都产生trueorfalse,即类型为 的结果bool。在下面的每种情况下,在应用指定的转换后,操作数应具有相同的类型。
2如果至少一个操作数是指针,则对两个操作数执行指针转换 (4.10) 和限定转换 (4.4),以将它们转换为复合指针类型(第 5 条)。比较指针的定义如下:如果两个指针都为空、都指向同一个函数或都表示相同的地址(3.9.2),则它们比较相等,否则它们比较不相等。
让我们逐一讨论最后一点:
inline可能会被获取其地址,除非您想让函数指针比较变得异常复杂和昂贵。if and only if为一个简单的if将留给解释,但这是一个明确的要求,使任何两个函数相同,只要它不以其他方式改变一致程序的可观察行为。