6

我遇到了一个奇怪的问题,我将其范围缩小到以下测试用例:

inl.h:

inline const char *fn() { return id; }

抄送:

#include <stdio.h>

static const char *id = "This is A";

#include "inl.h"

void A()
{
    printf("In A we get: %s\n", fn());
}

抄送:

#include <stdio.h>

static const char *id = "This is B";

#include "inl.h"

void B()
{
    printf("In B we get: %s\n", fn());
}

extern void A();

int main()
{
    A();
    B();
    return 0;
}

现在,当我用它编译它时,g++ -O1 a.cc b.cc它似乎可以正常工作。我得到:

In A we get: This is A
In B we get: This is B

但如果我编译g++ -O0 a.cc b.cc我得到:

In A we get: This is A
In B we get: This is A

请注意,我实际上是在这里尝试使用 C11 语义,但我使用的是 g++,因为 gcc 还不支持 C11。

现在据我所见,同时查看 C11 规范和 C++ 规范(C++11 和更早的规范——内联和静态全局变量的语义似乎没有改变),它应该做我想要的,使用失败-O0是gcc中的一个错误。

这是正确的,还是我在规范中缺少某些东西会导致这种未定义的行为?

编辑

常见的答案似乎声称fn需要声明static它才能工作。但根据 C99 规范的 6.7.4.6(C11 规范中的 6.7.4.7——不确定 C++ 规范):

如果翻译单元中函数的所有文件范围声明都包含不带 extern 的内联函数说明符,则该翻译单元中的定义是内联定义。内联定义不为函数提供外部定义,也不禁止在另一个翻译单元中进行外部定义。

所以由于这里没有明确的extern,这些应该是两个独立的内联函数,彼此没有交互。不需要明确static的。

使用显式静态解决了 C 的问题,但不适用于 C++ 内联成员函数,因为static在这种情况下关键字具有完全不同的含义。

4

2 回答 2

8

您违反了单一定义规则。非静态函数fn在两个翻译单元中的定义不同。一个与a.ccid中定义的变量绑定,而另一个与b.cc中的变量绑定。这些定义在文本上是相同的,但这不足以满足单一定义规则,即使为声明的函数设置了例外,所以你会得到未定义的行为。idinline

您使用的是 C++ 编译器,而不是 C 编译器,因此 C11 所说的任何内容都与您的 C++ 程序表现出的行为无关。在 C++11 中,标准(第 3.2/5 节)似乎说明了关于如何fn引用的规则id(强调和省略我的):

如果每个定义出现在不同的翻译单元中,并且定义满足以下要求,则程序中可以有多个定义……具有外部链接的内联函数(7.1.2)……。给定这样一个D在多个翻译单元中定义的实体,那么

  • 的每个定义D应由相同的标记序列组成;和
  • 在 的每个定义中D,根据 3.4 查找的相应名称应指在 的定义中定义的实体D,或应指同一实体,在重载决议 (13.3) 和部分模板特化 (14.8.3) 匹配之后),但const 如果对象在 的所有定义中具有相同的文字类型D,并且该对象使用常量表达式 (5.19) 初始化,并且值(但不是地址) 被使用,并且该对象在 的所有定义 中具有相同的值D;和

您的 的定义fn由相同的标记序列组成,但它们指的id是未在 中定义的D,在两个翻译单元中不是相同的实体,并且在所有定义中都没有相同的值。我在 C++ 标准中没有看到内联函数隐式获取内部链接的规定。C++11 §7.1.1/7 说:

在没有存储类说明符的命名空间范围内声明的名称具有外部链接,除非它由于先前的声明而具有内部链接并且如果它没有被声明const

如果您在某些优化级别或某些编译器的某些版本中获得了预期的行为,那么您只是得到了特别邪恶的未定义行为版本,尽管出现了错误,但事情似乎仍然有效。

于 2012-06-20T19:12:30.770 回答
3

因为fn()未声明static,正如@RobKennedy 指出的那样,您正在冒险进入UB 领域。在这种特定情况下,-O0可能会禁用内联,这意味着将保留一个发出的函数的非内联版本,而另一个将被丢弃,并且两个调用都将针对一个非内联版本。是否始终保留 A 版本可能取决于您在命令行上指定文件的顺序,或其他任何事情。-O1可能包括内联,在这种情况下,即使发出函数的非内联副本,这两个调用仍可能是内联的,这会给出(错误的)预期结果。

于 2012-06-20T19:33:24.297 回答