Определить статический порядок инициализации после компиляции? - PullRequest
11 голосов
/ 04 августа 2009

В C ++ я знаю, что компилятор может выбрать инициализацию статических объектов в любом порядке, который он выберет (с учетом нескольких ограничений), и что в общем случае вы не можете выбирать или определять порядок статической инициализации.

Однако после компиляции программы компилятор должен принять решение о том, в каком порядке инициализировать эти объекты. Есть ли способ определить, в каком порядке из скомпилированной программы с отладочными символами, в каком порядке Будут называться статические конструкторы?

Контекст таков: у меня есть крупная программа, которая неожиданно подвергается segfaulting перед main (), когда она собирается под новым набором инструментов. Либо это проблема статического порядка инициализации, либо что-то не так с одной из загружаемых библиотек. Однако, когда я отлаживаю с помощью gdb, место сбоя просто сообщается как необработанный адрес без какой-либо символической информации или обратной трассировки. Я хотел бы решить, какая из этих двух проблем заключается в размещении точки останова в конструкторе самого первого статически инициализированного объекта, но я не знаю, как определить, что это за объект.

Ответы [ 6 ]

11 голосов
/ 04 августа 2009

В G ++ в Linux упорядочение статических конструкторов и деструкторов определяется указателями функций в разделах .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 :: foo () для статического объекта foo_inst.

Если вы хотите ворваться в процесс инициализации, вы можете установить точку останова на __do_global_ctors_aux и выполнить ее разборку, я полагаю. Или просто подождите, пока он рухнет, чтобы получить обратный след, как указано выше.

7 голосов
/ 04 августа 2009

Мэтью Уилсон предоставляет способ ответить на этот вопрос в этом разделе (требуется подписка Safari Books Online) из Несовершенный C ++ . (Кстати, хорошая книга.) Подводя итог, он создает заголовок CUTrace.h, который создает статический экземпляр класса, который печатает имя файла включаемого исходного файла (используя нестандартный макрос препроцессора __BASE_FILE__) при создании, затем он включает CUTrace.h в каждый исходный файл.

Это требует перекомпиляции, но #include "CUTrace.h" может быть легко добавлено и удалено с помощью скрипта, поэтому его не должно быть слишком сложно настроить.

2 голосов
/ 04 августа 2009

Не могли бы вы инициализировать фиктивные переменные в статическом пространстве и поставить точки останова для этих вызовов функций?

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.

1 голос
/ 04 августа 2009

Вы можете найти порядок инициализации 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 ()
{
}
0 голосов
/ 06 марта 2011

На самом деле, с помощью Singletons вы можете довольно эффективно контролировать порядок инициализации глобальных / статических объектов в C ++.

Например, скажем, у вас есть:

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

и соответствующий объект, определенный в глобальной области видимости:

Abc abc;

Тогда у вас есть класс:

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

, который также имеет объект, определенный в глобальной области видимости:

Def def;

В этой ситуации у вас нет контроля над порядком инициализации, и если сначала инициализируется def, то, скорее всего, ваша программа вылетит, потому что она вызывает метод foo () в Abc, который еще не был инициализирован.

Решение состоит в том, чтобы функция в глобальном масштабе выполняла что-то вроде этого:

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

и тогда Def будет выглядеть примерно так:

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

Таким образом, abc всегда гарантированно инициализируется перед использованием, поскольку это произойдет во время первого вызова функции abc (). Аналогично, вы должны сделать то же самое с глобальным объектом Def, чтобы у него не было неожиданных зависимостей инициализации.

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

Если вам необходимо строго контролировать порядок инициализации в дополнение к простой проверке того, что все инициализировано перед его использованием, поместите все глобальные объекты в глобальный синглтон следующим образом.

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.

0 голосов
/ 04 августа 2009

g ++ предоставляет некоторую помощь в этом.
Это не портативно, но я уверен, что на данный момент это не ваша главная проблема.

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

...