不同函数有不同的地址

Do distinct functions have distinct addresses?

本文关键字:地址 函数      更新时间:2023-10-16

考虑以下两个函数:

void foo() {}
void bar() {}

是否保证&foo != &bar?

相似的,

template<class T> void foo() { }

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


我知道有两个链接器可以将函数定义折叠在一起。

MSVC积极地COMDAT折叠函数,因此具有相同实现的两个函数可以变成一个函数。作为副作用,这两个函数共享相同的地址。在我的印象中,这是非法的,但我找不到在标准中哪里是非法的。

金连接器也折叠功能,具有safeall设置。safe表示如果一个函数地址被取走了,它不会被折叠,而all表示即使取走了地址也会折叠。所以金的折叠safe表现得好像函数有不同的地址。

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

它看起来像缺陷报告1400:函数指针相等处理这个问题,在我看来,做这个优化是可以的,但正如评论所表明的,存在分歧。它说(强调mine):

根据5.10 [expr。eq]段落2,只有两个函数指针如果它们指向同一个函数,则比较相等. 然而,作为一个优化,实现目前正在混叠函数那有相同的定义。目前尚不清楚标准是否需要是否显式处理此优化

,反应是:

标准明确了要求,的实施是明确的在"as-if"规则的约束下自由优化.

这个问题是关于两个问题的:

  • 是否可以将这些指针视为相等
  • 是否可以合并函数

根据注释,我看到了对响应的两种解释:

  1. 这个优化是可以的,标准在as-if规则下给予实现这种自由。as-if规则将在1.9节中介绍,它意味着实现只需要模拟与标准要求相关的可观察行为。这仍然是我对回应的解释。

  2. 这个问题被完全忽略了,声明只是说不需要对标准进行调整,因为显然as-if规则涵盖了这一点,但解释留给读者作为练习。虽然我承认由于回答的简洁,我不能否定这种观点,但它最终是一个完全没有帮助的回答。这似乎也与其他NAD问题的反应不一致,据我所知,这些问题指出了问题是否存在。

标准草案怎么说

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

除非对象是位域或基类的子对象为0大小,该对象的地址是它的第一个字节的地址占据了。两个不是位域的对象可以有相同的如果其中一个是另一个的子主语,或者至少有一个是大小为零的基类子对象,它们具有不同的类型;4

和注释4说:

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

但是那个部分的注释说:

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

虽然不规范,但1段中对对象的要求在函数的上下文中没有意义,因此它与本注释一致。因此,除了一些例外,我们明确地限制了混叠对象,但这种限制不适用于函数。

接下来是5.10相等运算符,它说(强调mine):

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

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

  • 空指针
  • 指向相同的函数
  • 表示相同的地址

或两者表示相同的地址似乎给了编译器足够的自由度来允许别名两个不同的函数,并且不需要指向不同函数的指针来进行不相等的比较。

观察

Keith Thompson做了一些很好的观察,我觉得这些观察很值得补充,因为它们涉及到核心问题,他说:

如果程序输出&foo == &bar,这是可观察的行为;问题中的优化改变了可观察的行为。

我同意这一点,如果我们可以证明指针不相等的要求,这确实违反了as-if规则,但到目前为止我们还不能证明这一点。

:

[…考虑一个定义空函数并使用它们的程序地址作为唯一值(考虑SIG_DFL),SIG_ERRSIG_IGN& lt; signal.h>/& lt; csignal>)。给它们分配相同的地址会打破这样的程序

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

[…扩展为具有不同值的常量表达式有类型兼容的第二个参数,并返回值的信号函数,其值不等于任意可声明函数的地址[…]

因此,尽管这种情况已经讨论过了,但可能还有其他情况会使这种优化变得危险。

更新

Jan hubika是一名gcc开发者,他写了一篇博客文章:GCC 5中的链接时间和过程间优化改进,代码折叠是他所涉及的众多主题之一。

我请他评论将相同的函数折叠到相同的地址是否符合行为,他说这是不符合行为,确实这样的优化会破坏gcc本身:

把两个函数变成相同的地址是不符合的,所以MSVC在这里是相当激进的。例如,这样做会破坏GCC本身,因为令我惊讶的是,地址比较是在预编译的头代码中完成的。它适用于许多其他项目,包括Firefox。

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

更新2

也可以参考这个llvm-dev讨论:

这是link.exe中一个众所周知的违反一致性的错误;LLVM不应该通过引入类似的bug使事情变得更糟。聪明的连接器(例如,我认为黄金和黄金)将具有相同的功能仅当除一个以外的所有函数符号仅用作调用的目标(而不是实际观察地址)。是的,这个不符合常规的行为(很少)在实践中破坏事情。看到这个研究论文。

是。出自标准(§5.10/1):"两个相同的指针键入compare equal当且仅当它们都为空,都为点到相同的函数,或者两者都表示相同的地址">

一旦它们被实例化,foo<int>foo<double>是两个不同的函数,所以上面的内容也适用于它们。

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

在我看来,这部分显然是为了定义对象指针类型的语义。并且仅适用于对象指针类型。

这个短语引用了第3.9.2节,这意味着我们应该查看那里。3.9.2讨论了对象指针所代表的地址。它不讨论函数指针所代表的地址。在我看来,这只剩下两种可能的解释:

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

2)这个短语确实适用。由于它引用的是3.9.2,其中没有提到函数指针所表示的地址,因此可以使任意两个函数指针compare相等。这是非常出乎意料的,当然使得比较函数指针完全无用。

所以,虽然从技术上讲,(2)是一个有效的解释,但在我看来,它不是一个有意义的解释,因此应该忽略。既然不是每个人似乎都同意这一点,我也认为需要在标准中进行澄清。

5.10相等运算符[expr.eq]

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

让我们取最后一个位对位:

  1. 两个空指针比较相等。
    对你的理智有好处。
  2. 指向同一函数的两个指针比较相等。
    其他任何事情都会非常令人惊讶。
    这也意味着任何inline函数的只有一个脱行版本可能会占用它的地址,除非你想使函数指针比较过于复杂和昂贵。
  3. 两者表示相同的地址。
    这就是它的全部。去掉这个并将if and only if简化为一个简单的if将会留下解释,但这是一个明确的任务,使任何两个函数相同,只要不改变符合程序的可观察行为即可。