2013-12-11 55 views
0

我有一个代码:再论用C静初始化顺序++

... 
namespace X 
{ 
    const std::string Foo = "foo"; 
    inline std::string getFoo() 
    { 
     return Foo; 
    } 
} 
... 

a.cpp:

#include "a.h" 
... 
namespace X 
{ 
    const string Default_Foo = getFoo(); 
} 
... 

当然也有一个项目的更多文件,其中包括啊

程序在开始时导致段错误。调查表明:

  1. 美孚几个拷贝在程序中创建
 
    bash-4.2# nm -oC a.out | grep Foo 
    a.out:3c162c50 b X::Foo 
    a.out:3c162990 b X::Foo 
    a.out:3c1641b0 b X::Foo 

2.During初始化Default_Foo调用的getFoo(),它不从a.cpp拿已经初始化符编译单元,而是将Foo从另一个编译单元中取出,这是偶然的,尚未初始化。这明显导致段错误。

有人可以给我一个这样的行为推理吗? 未来最好的防御策略是什么?

最后,我最感兴趣的是为什么getFoo()使用另一个编译单元中的Foo。

+0

代码中没有任何静态变量 - 是什么让你认为这是问题?请尝试发布一个暴露该错误的完整的最简单示例。 –

+3

@BjörnPollex虽然没有变量声明为“静态”,但存在具有静态存储持续时间的变量。我猜这就是他所指的。 – Agentlien

+0

@Agentlien:对 - 我不知道这个规则。 –

回答

3

您的程序违反了一个定义规则(ODR)。内联函数可以在一个以上的编译单元中定义,但要求它们必须一致地定义,即每个定义由相同的词位序列组成,并且该序列中的每个使用的标识符必须表示同一个对象。

在你的情况下,标识符Foo表示不同编译单元中的不同对象(命名空间级对象与所述改性剂具有const通过默认的内部连接,从而每个编译单元有它自己的X::Foo)。

您用于构建程序的工具可能(但不是必需)诊断违规。在你的情况下,他们没有。链接器只是挑选了它所遇到的第一个定义X::getFoo,而没有仔细考虑它。

2

X::Foo几个副本创建,因为您声明和定义页眉X::Foo。因此,当您将其纳入资源时,您会获得X::Foo的许多声明和定义。其实,这通常会导致在由于多重定义链接时错误,但你避免具有X::Foo内部链接。 (请参阅legends2k注释说明)

静态初始化本身只有在编译单元内才有顺序。在多个编译对象之间获得适当的静态初始化顺序确实是一个问题。有编译器特定的扩展来实现这一点,但请注意它们有严重的限制。

的方式提供的代码我看不出为什么X::getFoo()通话不会被内联,从而消除了动态符号解析任何机会。 X::Foo本身不应该被名称搞乱。您应该检查符号名称/搬迁表用于生成的二进制文件,看是否X::getFoo()X::Foo动态解决。

One解决方案,以避免问题的是避免全局静态变量。相反,使用这种方法:

static const std::string& getFoo() 
{ 
    static const std::string val = "abc"; 
    return val; 
} 

现在给你的诺言:

  • VAL将在第一次调用初始化getFoo()
  • 它是线程安全的
+2

如果'a.h'包含在多个编译单元中,由于有多个定义,你会得到一个链接器错误。 –

+0

我最感兴趣的是为什么getFoo()使用另一个编译单元中的Foo –

+6

@BjörnPollex:在C++中['const'意味着内部链接](http://stackoverflow.com/a/998457/183120)。 – legends2k

0

讨论后这个问题在另一个论坛中,我倾向于认为问题的根源在于事实,即编译器为每个翻译单元创建多个getFoo()副本,每个翻译单元都指向t o来自这个单位的Foo。在链接期间,链接器认为所有的getFoo()都是等价的,并且获得第一个,这不能保证是来自a.cpp转换单元的。

欢迎任何评论。

+0

正确的是,如果在'X :: getFoo()'之前没有'inline'关键字会发生什么。当然,可能出现这种情况,即“内联”提示被忽略,例如,由于链接选项;否则方法看起来很小,拒绝内联。 – nyrl

+0

没有给出额外的编译或链接选项。 –

+0

除了您的代码无效(请参阅Andrey的答案)之外,您可以检查到底发生了什么。至于我,主要问题是'X :: getFoo()'调用是否被内联。内联是一个提示,因此可能会被忽略。您可以通过查看汇编代码来检查调用是否实际内联。如果电话不是内联的,那么你在另一个论坛*上得到了正确的解释。如果呼叫被内联,那么......这是完全奇怪的。 – nyrl