Determinar a ordem de inicialização estática após a compilação?
-
11-07-2019 - |
Pergunta
Em C ++, eu sei que o compilador pode escolher para inicializar objetos estáticos em qualquer ordem que escolher (sujeito a algumas restrições), e que, em geral, você não pode escolher ou determinar a ordem de inicialização estática.
No entanto, uma vez que um programa foi compilado, o compilador tem de ter tomado uma decisão sobre o que pedir para inicializar esses objetos em. Existe alguma maneira de determinar, a partir de um programa compilado com símbolos de depuração, em que ordem construtores estáticos será chamado?
O contexto é esta: Eu tenho um programa considerável que é subitamente segfaulting antes de main () quando é construído sob um novo conjunto de ferramentas. Ou este é um problema de inicialização static, ou é algo de errado com uma das bibliotecas que está sendo carregado. No entanto, quando eu depurar com gdb, o local do acidente é simplesmente relatado como um endereço de matéria sem qualquer informação simbólica ou registo de chamadas. Gostaria de decidir qual destes dois problemas, é colocando um ponto de interrupção no construtor do objeto primeiro estaticamente inicializado, mas eu não sei como dizer qual objeto que é.
Solução
Matthew Wilson fornece uma maneira de responder a essa pergunta em nesta seção (Safari Books Online subscrição exigida) de Imperfect C ++ . (Bom livro, por sinal.) Para resumir, ele cria um cabeçalho CUTrace.h
que cria uma instância estática de uma classe que imprime o nome do arquivo do arquivo de origem, incluindo (usando o __BASE_FILE__
macro pré-processador fora do padrão), quando criado, em seguida, ele inclui CUTrace.h
em cada arquivo-fonte.
Isto requer uma recompilação, mas o #include "CUTrace.h" podem ser facilmente adicionados e removidos através de um script, por isso não deve ser muito difícil de configurar.
Outras dicas
Em G ++ no Linux, construtor estático e ordenação destructor é determinada por ponteiros de função nas seções .ctors e .dtors. Note-se que com suficiente depuração disponível, você pode realmente obter um registo de chamadas:
(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
Esta é a depuração símbolos para libc e libstdc ++ instalado. Como você pode ver, o acidente ocorreu aqui no construtor foo :: foo () para a foo_inst objeto estático.
Se você quiser entrar no processo de inicialização, você poderia, então, definir um ponto de interrupção na __do_global_ctors_aux e passo através de sua desmontagem, suponho. Ou apenas esperar por ele para bater para obter o backtrace como o acima.
Você poderia inicializar variáveis ??binárias no espaço estático e pontos de quebra colocar essas chamadas de função?
extern "C" int breakOnMe () { return 0 };
int break1 = breakOnMe ();
float pi = 3.1415;
int break2 = breakOnMe ();
myClass x = myClass (1, 2, 3);
Então, em gdb
break breakOnMe
prazo antes de executar o programa. Isso deve fazer pausa gdb antes de cada nas inicializações estáticas.
Eu acho que deve funcionar .. Eu estou um pouco enferrujado em gdbbing.
Você pode encontrar a ordem do TUs estão sendo inicializado usando modelos como destacado por este questão . Ele requer um pouco de mudança do código para cada uma das TUs que você está interessado em:
// 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
A idéia básica é que cada TU terá um único endereço diferente para dummyGlobal e assim o modelo terá uma instanciação diferente em cada TU. A inicialização dos resultados de membro estático na chamada para "showCountAndFile", que, em seguida, imprime SRC_FILE (definido no TU) e o valor atual de cnt
que irá, portanto, mostrar a ordem.
Você poderia usá-lo da seguinte forma:
static const char * SRC_FILE=__FILE__;
#include "order.h"
int main ()
{
}
g ++ fornece alguma ajuda com isso.
A sua não é portátil, mas estou certo neste momento que não é o seu principal problema.
http://gcc.gnu.org/onlinedocs /gcc/C_002b_002b-Attributes.html#C_002b_002b-Attributes
Na verdade, através do uso de Singletons você pode controlar a ordem de inicialização de / static global de objetos de forma bastante eficaz em C ++.
Por exemplo, digamos que você tem:
class Abc
{
public:
void foo();
};
e um objeto correspondente definida no escopo global:
Abc abc;
Em seguida, você tem uma classe:
class Def
{
public:
Def()
{
abc.foo();
}
};
que também tem um objeto definido no escopo global:
Def def;
Nesta situação, você não tem controle sobre a ordem de inicialização e se def é inicializado primeiro, então é provável que o programa irá falhar porque ele está chamando o método foo () em um Abc que ainda não foi inicializado.
A solução é ter uma função no escopo global fazer algo como isto:
Abc& abc()
{
static Abc a;
return a;
}
e depois Def seria algo parecido com:
class Def
{
public:
Def()
{
abc().foo();
}
};
Desta forma, abc é sempre garantida a ser inicializado antes de ser usado, porque isso irá acontecer durante a primeira chamada da função abc (). Da mesma forma, você deve fazer o mesmo com Def objeto global para torná-lo não terá quaisquer dependências de inicialização inesperados também.
Def& def()
{
static Def d;
return d;
}
Se você precisa controlar rigorosamente a ordem de inicialização, além de simplesmente ter certeza que tudo é inicializado antes de ser usado, coloque todos os objetos globais em um singleton global da seguinte forma.
struct Global
{
Abc abc;
Def def;
};
Global& global()
{
static Global g;
return g;
}
e fazer referências a esses itens como segue:
//..some code
global().abc.foo();
//..more code here
global().def.bar();
Independentemente de qual deles recebe chamadas em primeiro lugar, as regras membro de inicialização C ++ garante que os objetos do ABC e DEF são inicializados na ordem em que são definidos na classe global.