看起来
defect report 1400: Function pointer equality处理了这个问题,并且我认为它表明这样做是可以的,但是正如注释所指出的那样,人们存在分歧。它说(
我强调):
根据5.10 [expr.eq]第2段,“
只有指向同一函数的两个函数指针才相等”。然而,作为一种优化,
目前的实现将具有相同定义的函数别名化。不清楚标准是否需要显式处理此优化。
回答是:
标准对要求非常明确,
实现可以在“as-if”规则的约束下进行优化。
该问题涉及两个问题:
基于评论,我看到了两种解释回答:
这种优化是可以的,标准在“as-if规则”下赋予了实现这种自由。 “as-if规则”在第1.9节中有所涵盖,意味着实现只需模拟与标准要求相关的可观察行为。 这仍然是我对回答的解释。
问题完全被忽略了,该语句仅表示不需要调整标准,因为显然“as-if规则”涵盖了这一点,但是解释留给读者自行理解。 尽管我承认由于回答过于简洁,我无法驳回这个观点,但它最终变成了一个完全没有帮助的回答。 它似乎也与其他NAD问题的回答不一致,因为据我所知,如果存在问题,它们会指出问题。
草案标准的内容
既然我们知道正在处理“as-if规则”,那么我们可以从那里开始,并注意到第1.8节说:
除非一个对象是位域或零大小的基类子对象,否则该对象的地址是它占用的第一个字节的地址。如果一个对象不是位域,则如果它们中的一个是另一个对象的子对象,或者至少一个是零大小的基类子对象且它们是不同类型,则两个对象可能具有相同的地址;否则,它们必须具有不同的地址。(注4:根据“as-if”规则,如果程序无法观察到差异,则允许一个实现在同一机器地址上存储两个对象或不存储任何对象)。
注意4说明:
根据“as-if”规则,如果程序无法观察到差异,则允许一个实现在同一机器地址上存储两个对象或不存储任何对象。
但该部分的一条注释指出:
函数不是对象,无论它是否像对象一样占用存储空间。
虽然这不是规范性的,但段落1中所述对象的要求在函数上下文中毫无意义,因此它与该注释一致。因此,我们明确限制别名对象具有某些例外情况,但不应用于函数。
下面我们有第
5.10
节
等号运算符,其中说(
重点是我的):
[...]如果两个指针都为 null,或者都指向同一个函数,或者都表示相同的地址(3.9.2),则它们相等,否则它们不相等。
这告诉我们两个指针相等如果它们是:
或者都表示相同的地址 看起来给予编译器足够的自由度,允许别名两个不同的函数,并且不要求指向不同函数的指针不相等。
观察
Keith Thompson 做了一些很好的观察,我认为值得添加到答案中,因为它们涉及到核心问题,他说:
如果程序打印 &foo == &bar 的结果,那就是可观察的行为;所讨论的优化改变了可观察的行为。
我同意这种观点,如果我们能够证明指针需要是不相等的,那么这确实会违反“as-if rule”,但目前我们无法证明。
另外,考虑一个定义了空函数并将它们的地址用作唯一值的程序(可以想象在 / 中的SIG_DFL、SIG_ERR和SIG_IGN)。将它们赋予相同的地址将会破坏这样的程序。
正如我在评论中提到的,C标准要求这些宏生成具有不同值的常量表达式,这些值与signal函数的第二个参数和返回值兼容,并且它们的值与任何可声明函数的地址不相等。
因此,尽管这种情况已经被覆盖,但也许还有其他情况会使这种优化变得危险。
更新
Jan Hubička是一位gcc开发人员,他写了一篇博客文章
GCC 5中的链接时间和跨程序优化改进,代码折叠是他涉及的许多主题之一。
我问他是否将相同的函数折叠到相同的地址是否符合行为规范,他说这不是符合规范的行为,确实这样的优化会破坏gcc本身:
“将两个函数转换为具有相同地址是不符合规范的,因此MSVC在这方面非常激进。例如,这样做会破坏GCC本身,因为令我惊讶的是,在预编译标头代码中执行地址比较。它适用于许多其他项目,包括Firefox。”
事后看来,经过几个月阅读缺陷报告并思考优化问题,我对委员会的回应持更为保守的态度。获取函数的地址是可观测行为,因此折叠相同的函数将违反“as-if rule”原则。
第二次更新
也请参见此
llvm-dev 讨论:零长度函数指针相等性:
这是一个众所周知的违反符合性的link.exe漏洞;LLVM不应该通过引入类似的漏洞来使事情变得更糟。更聪明的链接器(例如,我认为lld和gold)仅在除一个函数符号以外的所有函数符号仅用作调用的目标(而不是实际观察地址)时才执行相同的函数组合。是的,这种非符合行为(很少)会在实践中导致故障。请参阅此研究论文。