Определить статический порядок инициализации после компиляции?
В С++ я знаю, что компилятор может выбрать инициализацию статических объектов в любом порядке, который он выбирает (с учетом нескольких ограничений), и что в общем случае вы не можете выбрать или определить статический порядок инициализации.
Однако, как только программа была скомпилирована, компилятор должен был принять решение о том, в каком порядке инициализировать эти объекты. Есть ли способ определить из компилируемой программы с помощью отладочных символов в каком порядке статические конструкторы будут вызваны?
Контекст таков: у меня есть значительная программа, которая внезапно прервана перед main(), когда она построена под новой инструментальной цепочкой. Либо это статическая проблема с порядком инициализации, либо что-то не так с одной из библиотек, которые она загружает. Однако, когда я отлаживаю с помощью gdb, местоположение сбоя просто сообщается как необработанный адрес без какой-либо символической информации или обратной линии. Я хотел бы решить, какая из этих двух проблем заключается в размещении точки останова в конструкторе самого первого статически инициализированного объекта, но я не знаю, как определить, какой именно объект.
Ответы
Ответ 1
Мэтью Уилсон дает способ ответить на этот вопрос в в этом разделе (требуется подписка на подписку на Safari Books) Imperfect С++. (Кстати, хорошая книга). Подводя итог, он создает заголовок CUTrace.h
, который создает статический экземпляр класса, который печатает имя файла исходного файла (используя нестандартный макрос препроцессора __BASE_FILE__
) при создании, затем он включает CUTrace.h
в каждый исходный файл.
Для этого требуется перекомпиляция, но #include "CUTrace.h" можно легко добавить и удалить с помощью script, поэтому его не следует устанавливать слишком сложно.
Ответ 2
В 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 и libstdС++. Как вы можете видеть, авария произошла в конструкторе foo:: foo() для статического объекта foo_inst.
Если вы хотите вступить в процесс инициализации, вы можете установить точку останова на __do_global_ctors_aux и выполнить свою разборку, я полагаю. Или просто подождите, пока он сработает, чтобы получить обратную трассировку, как показано выше.
Ответ 3
Не могли бы вы инициализировать фиктивные переменные в статическом пространстве и поставить точки останова на эти вызовы функций?
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 pause перед каждым из статических инициализаций.
Я думаю, что это должно сработать. Я немного ржавый на gdbbing.
Ответ 4
Вы можете найти порядок инициализации TU, используя шаблоны, выделенные этим question. Это требует небольшого изменения кода для каждого из интересующих вас модулей:
// 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 ()
{
}
Ответ 5
g++ предоставляет некоторую помощь с этим.
Это не переносимо, но я уверен, что в данный момент это не ваша главная проблема.
http://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Attributes.html#C_002b_002b-Attributes
Ответ 6
На самом деле, используя Singletons, вы можете эффективно контролировать порядок инициализации глобальных/статических объектов на С++.
Например, скажем, что у вас есть:
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();
Независимо от того, кто первым получает вызовы, правила инициализации члена С++ гарантируют, что объекты abc и def инициализируются в том порядке, в котором они определены в глобальном классе.