O que há de errado em vincular estaticamente o STL em várias bibliotecas compartilhadas?

8

Aqui está o cenário:

  1. libA.so e libB.so se vinculam estaticamente à mesma STL.
  2. libA.so possui uma API pública que retorna uma std :: string.
  3. libB.so chama essa função e recebe uma cópia da string.
  4. Quando a cópia do libB.so da string sai do escopo, o destruidor da string é chamado.
  5. O aplicativo seg falha ao tentar liberar a sequência copiada.

Eu li em outro lugar que vincular estaticamente assim é ruim, mas eu gostaria de entender melhor por que é ruim. Alguém pode explicar por que a sequência acima falharia?

user1509041
fonte

Respostas:

8

Vamos pensar sobre isso com muito cuidado. O libA.so está estaticamente vinculado ao STL. No entanto, o STL não existe isoladamente, requer o tempo de execução C (CRT). Ambos residem na libstdc ++. Uma biblioteca estática. Isso significa que libA.so e libB.so possuem estruturas de dados CRT separadas. Em particular, o heap usado pelo libA.so é diferente do heap usado pelo libB.so. Alocar uma string do heap de tempo de execução da libA e tentar liberar o tempo de execução da libB simplesmente não funcionará porque o tempo de execução da libB não possui registros de alocação da string. A única maneira de destruir corretamente a string é chamando o destruidor no libA.so.

Alguém pode perguntar: mas libB.so recebe uma cópia da string, certo? Sim, está certo, mas quem alocou esta cópia? Ele foi alocado usando o construtor de cópia no contexto do tempo de execução da libA.

Dito isto, você ainda pode usar a sequência de libB.so. Você simplesmente não pode destruí-lo a partir daí.

Você também pode permitir que a libB receba um ponteiro para a string e, em seguida, crie uma cópia dela dentro do contexto do tempo de execução da libB. Essa cópia pode ser destruída pelo libB.

E é por isso que a vinculação estática às vezes é ruim.

Hadi Brais
fonte
1
Não estou claro a que "string" se refere aqui. Se a conversa é de um std::string, o problema simplesmente não existe: ou libB.sorecebe uma cópia da sequência, com a memória gerenciada em sua própria memória, ou recebe uma referência / ponteiro para a sequência libA.soe não tenta remover a sequência de sua própria memória.
Konrad Rudolph
1
@KonradRudolph Entre outros, as otimizações de (N) RVO criam situações em que a string retornada é construída por libA.so enquanto destruída por libB.so.
Sjoerd
Essa resposta é consistente com o que vi na prática, então acho que a parte que não entendo completamente é como o heap funciona para cada biblioteca. Inicialmente, pensei que o heap era uma coisa global singular no processo, mas a maneira como você o descreve faz parecer que existem vários heaps. Isso é apenas uma questão de como os alocadores (na libc?) São implementados ou há algo mais que controla isso (como o carregador)?
user1509041
A existência de um heap global ou vários heaps em um processo depende da implementação específica da CRT que está sendo usada. Eu já vi os dois casos. Você parece estar tendo vários montões. O carregador não tem nada a ver com montões.
Hadi Brais
1
@ AymanSalah Sim, isso deve funcionar bem. A versão do SO só importa quando você usa APIs específicas do SO, em vez de APIs padrão de idioma.
Hadi Brais
3

O STL é chamado de "estado cheio" (em oposição a ser "sem estado"), o que significa que possui algumas coisas estáticas no interior. Ao vincular o STL estaticamente ao libA.so e libB.so, você obtém duas instâncias da biblioteca STL na memória em tempo de execução (com duas cópias de material estático). Cada uma dessas duas cópias gerencia recursos alocados de forma independente e os recursos alocados em uma instância da biblioteca não podem ser liberados em outra

mvidelgauz
fonte