编译后确定静态初始化顺序?

时间:2009-08-03 20:12:11

标签: c++ gdb initialization g++ segmentation-fault

在C ++中,我知道编译器可以选择以它选择的任何顺序初始化静态对象(受一些约束),并且通常你不能选择或确定静态初始化顺序。

但是,一旦编译了一个程序,编译器就必须决定初始化这些对象的顺序。有没有办法从带有调试符号的编译程序中确定什么顺序静态构造函数将被调用?

上下文是这样的:我有一个相当大的程序,当它在一个新的工具链下构建时,会突然在main()之前进行segfaulting。这是一个静态初始化顺序问题,或者它正在加载的某个库有问题。但是,当我使用gdb进行调试时,崩溃位置只是报告为原始地址而没有任何符号信息或回溯。我想通过在第一个静态初始化对象的构造函数中放置一个断点来决定这两个问题中的哪一个,但我不知道如何判断它是哪个对象。

6 个答案:

答案 0 :(得分:11)

在Linux上的G ++中,静态构造函数和析构函数排序由.ctors和.dtors部分中的函数指针决定。请注意,如果有足够的调试可用,您实际上可以获得回溯:

(gdb) bt
#0  0xb7fe3402 in __kernel_vsyscall ()
#1  0xb7d59680 in *__GI_raise (sig=6)
    at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
#2  0xb7d5cd68 in *__GI_abort () at abort.c:88
#3  0x08048477 in foo::foo() ()
#4  0x0804844e in __static_initialization_and_destruction_0(int, int) ()
#5  0x0804846a in global constructors keyed to foo_inst ()
#6  0x0804850d in __do_global_ctors_aux ()
#7  0x08048318 in _init ()
#8  0x080484a9 in __libc_csu_init ()
#9  0xb7d4470c in __libc_start_main (main=0x8048414 <main>, argc=1,
    ubp_av=0xbfffcbc4, init=0x8048490 <__libc_csu_init>,
    fini=0x8048480 <__libc_csu_fini>, rtld_fini=0xb7ff2820 <_dl_fini>,
    stack_end=0xbfffcbbc) at libc-start.c:181
#10 0x08048381 in _start () at ../sysdeps/i386/elf/start.S:119

这是安装了libc和libstdc ++的调试符号。正如您所看到的,崩溃发生在静态对象foo_inst的foo :: foo()构造函数中。

如果你想进入初始化过程,你可以在__do_global_ctors_aux上设置断点并逐步完成它的反汇编,我想。或者只是等待它崩溃以获得如上所述的回溯。

答案 1 :(得分:7)

Matthew Wilson提供了一种方法来回答this section Imperfect C++ {需要的Safari Books Online订阅)中的这个问题。 (顺便说一句好书。)总而言之,他创建了一个CUTrace.h标题,用于创建类的静态实例,该类打印包含源文件的文件名(使用非标准预处理器宏__BASE_FILE__)在创建时,他在每个源文件中都包含CUTrace.h

这需要重新编译,但#include“CUTrace.h”可以通过脚本轻松添加和删除,因此设置起来应该不会太难。

答案 2 :(得分:2)

您可以在静态空间中初始化虚拟变量,并在这些函数调用上放置断点吗?

extern "C" int breakOnMe () { return 0 };

int break1 = breakOnMe ();
float pi = 3.1415;
int break2 = breakOnMe ();
myClass x = myClass (1, 2, 3);

然后在gdb运行break breakOnMe之前执行程序。这应该使gdb在静态初始化之前暂停。

我认为这应该有用..我在gdbbing上有点生疏。

答案 3 :(得分:1)

您可以使用此question突出显示的模板查找使用模板初始化TU的顺序。它需要对您感兴趣的每个TU进行一小段代码更改:

// order.h
//

#ifndef INCLUDED_ORDER
#define INCLUDED_ORDER

#include <iostream>

inline int showCountAndFile (const char * file)
{
  static int cnt = 0;
  std::cout << file << ": " << cnt << std::endl;
  ++cnt;
  return cnt;
}

template <int & i>
class A {
  static int j;
};

template <int & i>
int A<i>::j = showCountAndFile (SRC_FILE);

namespace
{
  int dummyGlobal;
}
template class A<dummyGlobal>;

#endif

基本思想是每个TU将为dummyGlobal提供不同的唯一地址,因此模板将在每个TU中具有不同的实例。静态成员的初始化导致调用“showCountAndFile”,然后打印出SRC_FILE(在TU中设置)和当前值cnt,这将显示顺序。

您可以按如下方式使用它:

static const char * SRC_FILE=__FILE__;
#include "order.h"

int main ()
{
}

答案 4 :(得分:0)

g ++为此提供了一些帮助 它不便携,但我相信这不是你的主要问题。

http://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Attributes.html#C_002b_002b-Attributes

答案 5 :(得分:0)

实际上,通过使用Singletons,您可以在C ++中非常有效地控制全局/静态对象的初始化顺序。

例如,假设你有:

class Abc
{
public:
    void foo();
};

以及在全局范围内定义的相应对象:

Abc abc;

然后你有一个班级:

class Def
{
public:
    Def()
    {
        abc.foo();
    }
};

还有一个在全局范围内定义的对象:

Def def;

在这种情况下,您无法控制初始化顺序,如果首先初始化def,那么您的程序可能会崩溃,因为它正在调用尚未初始化的Abc上的foo()方法。

解决方案是在全局范围内使用这样的函数:

Abc& abc()
{
    static Abc a;
    return a;
}

然后Def看起来像:

class Def
{
public:
    Def()
    {
        abc().foo();
    }
};

这样,abc始终保证在使用之前初始化,因为这将在第一次调用abc()函数时发生。同样,你应该对Def全局对象做同样的事情,以使它不会有任何意外的初始化依赖。

Def& def()
{
    static Def d;
    return d;
}

如果您需要严格控制初始化的顺序,只需确保在使用之前初始化所有内容,请将所有全局对象放在Global单例中,如下所示。

struct Global
{
    Abc abc;
    Def def;
};

Global& global()
{
    static Global g;
    return g;
}

并按如下方式引用这些项目:

//..some code
global().abc.foo();
//..more code here
global().def.bar();

无论哪个首先获得调用,C ++成员初始化规则将保证abc和def对象按照它们在Global类中定义的顺序进行初始化。