Tenho um registro singleton que mapeia nomes para ponteiros de função. Também tenho um objeto registrador cujo construtor registra um ponteiro de função.
Meu objetivo é ter as funções e o registro em uma biblioteca estática, mas descobri que o vinculador omite os registros quando construído dessa forma.
Aqui está uma ilustração simplificada. Para resumir, substituí os ponteiros de função por ponteiros para inteiros globais e o contêiner associativo do registro por um vetor.
// registry.h
#include <vector>
std::vector<int const *> &GetRegistry();
// registry.cpp
#include "registry.h"
std::vector<int const *> &GetRegistry() {
static std::vector<int const *> registry;
return registry;
}
// thingadder.h
#include "registry.h"
class ThingAdder {
public:
explicit ThingAdder(int const *thing) {
GetRegistry().push_back(thing);
}
};
// things.cpp
#include "thingadder.h"
int g_thing1 = 1;
ThingAdder g_adder1(&g_thing1);
// main.cpp
#include "registry.h"
#include <print>
#include <vector>
int main() {
std::print("registry size: {}\n", GetRegistry().size());
return 0;
}
Se todos os arquivos forem compilados e vinculados como um único projeto, g_adder1
o construtor de adiciona g_global1
o endereço de ao registro, e o programa informa que o tamanho do registro é 1.
Mas quando tudo, exceto main.cpp, é criado em uma biblioteca estática, e então main.cpp é compilado e vinculado a essa biblioteca, o tamanho do registro relatado é 0. Parece que os globais em things.cpp foram omitidos pelo link.
Eu meio que entendo por que isso está acontecendo: nada fora de things.cpp faz referência direta ao objeto global nem ao seu registrador. Mas isso é verdade mesmo quando construído como um monolito. Eu não esperaria que incluir essa parte em uma biblioteca estática mudasse o comportamento.
Soluções?
A única solução que encontrei foi fazer com que main.cpp faça referência a um símbolo definido na unidade de tradução things.cpp. Na biblioteca em si, haverá mais arquivos de objetos a serem registrados, e não quero que cada usuário da biblioteca adicione uma referência a um símbolo para cada um deles.
Veja o wiki de tags do Stackoverflow para
static-libraries
.A partir disso, você verá a diferença entre inserir
things.o[bj]
diretamente em seu link e inserir uma biblioteca estática da qual um dos membros éthings.o[bj]
.Uma entrada de arquivo-objeto explicitamente é sempre vinculada incondicionalmente. O vinculador não a ignora por não definir referências não resolvidas (porque, se o fizesse, a vinculação nunca poderia ser iniciada). Arquivos-objeto que são membros de uma biblioteca estática de entrada são oferecidos ao vinculador conforme necessário . Um arquivo-objeto será extraído e vinculado somente se a vinculação fizer referência a um símbolo externo definido por esse membro da biblioteca.
Ao vincular seu
main.o[bj]
a uma biblioteca estática contendothings.o[bj]
eregistry.o[bj]
, a oferta dethings.o[bj]
é supérflua para a vinculação, porquemain.o[bj]
(o único arquivo de objeto explícito) não se refere a nada definido emthings.o[bj]
. A oferta deregistry.o[bj]
é necessária. Portanto,registry.o[bj]
é extraído e vinculado;things.o[bj]
é ignorado.Soluções?
1. Basta vincular os arquivos de objeto que você precisa
Por padrão, quando você deseja que seu programa contenha um arquivo-objeto específico, você o vincula explicitamente no sistema de compilação do programa: isso o torna um dos pontos de partida da vinculação. Você precisaria de algum motivo superveniente para colocá-lo primeiro em uma biblioteca estática. Existem tais motivos (por exemplo, servir à vinculação de um driver de teste unitário, bem como do programa), mas então você precisará considerar a diferença que isso faz na vinculação do programa.
2. /WHOLEARCHIVE | --whole-archive
Às vezes, você realmente deseja que todos os membros de uma biblioteca estática sejam vinculados à imagem de saída. Nesse caso, o vinculador fornece uma opção que o força a vincular todos os membros de uma biblioteca estática aos quais a opção se aplica, sejam eles necessários ou não. Para a Microsoft,
LINK
essa opção é ,/WHOLEARCHIVE
e para o GNU,ld
é--whole-archive
.Mas...
O principal caso de uso para esta opção é a vinculação de uma biblioteca dinâmica que deve ser simplesmente uma implementação dinâmica de alguma biblioteca estática. O motivo normal para manter uma biblioteca estática de
thing
s registráveis para vinculação com programas seria facilitar que diferentes programas registrem diferentes seleções deles, conforme necessário.Se você mantiver uma biblioteca estática de todos os arquivos de objetos de registro
thing
+thing
e vinculá-la a todo o arquivo com programas, eles vincularão todos os sthing
na biblioteca e seus registros, independentemente de o programa funcionalmente querer isso ou não.Uma etapa de compilação personalizada para um programa cliente pode evitar a vinculação de qualquer objeto inoperante, extraindo da biblioteca portmanteau apenas os arquivos de objeto necessários, usando o gerenciador de arquivos da sua cadeia de ferramentas e, em seguida, arquivando novamente os arquivos de objeto escolhidos em uma biblioteca estática específica do aplicativo que você vincula ao seu programa. Mas a biblioteca específica do aplicativo não serve para nada. Você pode simplesmente extrair os arquivos de objeto necessários e inseri-los na vinculação do programa.
3. Um arquivo de objeto parcialmente vinculado
@CraigEstey comenta que o GNU linker
ld
fornece a-r|--relocatable
opção que vinculará parcialmente (ou seja, incrementalmente) os arquivos de objeto de entrada em um único arquivo de objeto de saída, combinando todos eles, sem falhas em referências indefinidas.Esta opção está disponível para você no Windows em uma cadeia de ferramentas GCC para Windows, para criar um único arquivo-objeto que define qualquer ou todos
thing
os s à sua disposição e seus registros, mas não possui um equivalente para a Microsoftlink
. Portanto, para usá-la, você precisará compilar qualquer programa cliente com a cadeia de ferramentas GCC, não o da Microsoft, porque os arquivos-objeto C++ produzidos pelo GCC no Windows não são compatíveis em termos binários com os produzidos pela Microsoftcl
.O uso de um arquivo-objeto parcialmente vinculado combinando vários outros é, para os propósitos atuais, equivalente ao uso de uma biblioteca estática que contém os mesmos outros, à qual se aplica o arquivo inteiro. Portanto, está sujeito às mesmas considerações: se combinar mais arquivos-objeto do que um programa cliente precisa, você vincula o arquivo morto, e se for criado de forma personalizada para cada programa cliente, você pode simplesmente vincular os arquivos-objeto combinados nele.
4. Separe
thing
os s de seus registros no linkage .Como eu disse, normalmente se manteria uma biblioteca estática de
thing
s registráveis para que programas diferentes pudessem registrar diferentes. Você teria exatamente umthing
definido por arquivo de objeto membro. Nesse caso, você compilaria os registros dosthing
s necessários para um programa cliente em um ou mais arquivos de objeto que não estão na biblioteca e vincularia explicitamente esses arquivos de objeto ao programa cliente e à biblioteca estática. Cada registro se referirá aothing
que está sendo registrado e obrigará o vinculador a extrair os arquivos de objeto que definem osthing
s registrados da biblioteca estática, mas nenhum outro. Você forneceria à biblioteca estática um arquivo de cabeçalho que declara externamente todos os s registráveisthings
para inclusão no código-fonte do registro.