在内联函数中访问静态全局变量

时间:2012-06-20 19:09:03

标签: c++ gcc

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

inl.h:

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

a.cc:

#include <stdio.h>

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

#include "inl.h"

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

b.cc:

#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关键字在这种情况下具有完全不同的含义。

2 个答案:

答案 0 :(得分:8)

您违反了单一定义规则。非静态函数fn在两个翻译单元中的定义不同。一个与 a.cc 中定义的id变量绑定,其中另一个与 b.cc 中的id变量绑定。这些定义文本相同,但这还不足以满足单定义规则,即使为声明为inline的函数设置了异常,也会导致未定义的行为。

您使用的是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的所有定义,并使用a初始化对象   常量表达式(5.19),以及的值(但不是地址)   使用了对象,对象在所有定义中具有相同的值   D;和
  •   
  • ...
  •   

您对fn的定义包含相同的令牌序列,但它们引用的id未在D中定义,在两个翻译单元中都不是同一个实体,并且在所有定义中没有相同的值。我认为C ++标准中没有规定内联函数隐式获取内部链接。 C ++11§7.1.1/ 7说明了这一点:

  

在没有存储类说明符的命名空间作用域中声明的名称具有外部链接,除非由于先前的声明而具有内部链接,并且未声明const。 / p>

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

答案 1 :(得分:3)

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