Por que o segmento .bss é obrigatório?

120

O que eu sei é que as variáveis ​​globais e estáticas são armazenadas no .datasegmento e os dados não inicializados estão no .bsssegmento. O que não entendo é por que temos segmento dedicado para variáveis ​​não inicializadas? Se uma variável não inicializada tiver um valor atribuído em tempo de execução, a variável ainda existe .bssapenas no segmento?

No programa seguinte, aestá no .datasegmento e bestá no .bsssegmento; isso está correto? Por favor, corrija-me se meu entendimento estiver errado.

#include <stdio.h>
#include <stdlib.h>

int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9};
int b[20]; /* Uninitialized, so in the .bss and will not occupy space for 20 * sizeof (int) */

int main ()
{
   ;
}  

Além disso, considere o seguinte programa,

#include <stdio.h>
#include <stdlib.h>
int var[10];  /* Uninitialized so in .bss */
int main ()
{
   var[0] = 20  /* **Initialized, where this 'var' will be ?** */
}
Quem sou eu
fonte
3
Você pode ler BSS como Better Save Space .
smwikipedia

Respostas:

89

O motivo é reduzir o tamanho do programa. Imagine que seu programa em C seja executado em um sistema embarcado, onde o código e todas as constantes são salvos em verdadeira ROM (memória flash). Em tais sistemas, um "copy-down" inicial deve ser executado para definir todos os objetos de duração de armazenamento estático, antes que main () seja chamado. Normalmente será assim pseudo:

for(i=0; i<all_explicitly_initialized_objects; i++)
{
  .data[i] = init_value[i];
}

memset(.bss, 
       0, 
       all_implicitly_initialized_objects);

Onde .data e .bss são armazenados na RAM, mas init_value é armazenado na ROM. Se fosse um segmento, a ROM teria que ser preenchida com muitos zeros, aumentando significativamente o tamanho da ROM.

Os executáveis ​​baseados em RAM funcionam de maneira semelhante, embora, é claro, não tenham uma ROM verdadeira.

Além disso, o memset é provavelmente um montador embutido muito eficiente, o que significa que a cópia de inicialização pode ser executada mais rapidamente.

Lundin
fonte
7
Para esclarecer: a única diferença entre .data e .bss é que na inicialização, o "copy-down" pode ser executado sequencialmente, portanto mais rápido. Se não fosse dividido em dois segmentos, a inicialização teria que pular os pontos de RAM pertencentes às variáveis ​​não inicializadas, perdendo tempo.
CL22
80

O .bsssegmento é uma otimização. Todo o .bsssegmento é descrito por um único número, provavelmente 4 bytes ou 8 bytes, que dá seu tamanho no processo em execução, enquanto a .dataseção é tão grande quanto a soma dos tamanhos das variáveis ​​inicializadas. Assim, .bsstorna os executáveis ​​menores e mais rápidos de carregar. Caso contrário, as variáveis ​​podem estar no .datasegmento com inicialização explícita para zeros; o programa seria pressionado para dizer a diferença. (Em detalhes, o endereço dos objetos em .bssprovavelmente seria diferente do endereço se estivesse no .datasegmento.)

No primeiro programa, aestaria no .datasegmento e bestaria no .bsssegmento do executável. Depois que o programa é carregado, a distinção torna-se irrelevante. Em tempo de execução, bocupa 20 * sizeof(int)bytes.

No segundo programa, varé alocado espaço e a atribuição em main()modifica esse espaço. Acontece que o espaço para varfoi descrito no .bsssegmento e não no .datasegmento, mas isso não afeta a maneira como o programa se comporta durante a execução.

Jonathan Leffler
fonte
16
Por exemplo, considere ter muitos buffers não inicializados de 4096 bytes de comprimento. Você gostaria que todos esses buffers de 4k contribuíssem para o tamanho do binário? Isso seria muito espaço desperdiçado.
Jeff Mercado
1
@jonathen killer: Por que o segmento bss inteiro é descrito por um único número ??
Suraj Jain de
@JonathanLeffler Quero dizer, todas as variáveis ​​estáticas inicializadas com zero vão em bss. Portanto, seu valor não deveria ser apenas zero? E também por que eles não recebem espaço na seção .data, como isso pode torná-lo lento?
Suraj Jain de
2
@SurajJain: o número armazenado é o número de bytes a serem preenchidos com zeros. A menos que não existam essas variáveis ​​não inicializadas, o comprimento da seção bss não será zero, embora todos os bytes da seção bss sejam zero assim que o programa for carregado.
Jonathan Leffler
1
A seção .bss no executável é simplesmente um número. A seção .bss na imagem do processo na memória é normalmente uma memória adjacente à seção .data e frequentemente a seção .data do tempo de execução é combinada com .bss; não há distinção feita na memória de tempo de execução. Às vezes, você pode descobrir onde o bss começou ( edata). Em termos práticos, o .bss não existe na memória depois que a imagem do processo é concluída; os dados zerados são parte simples da seção .data. Mas os detalhes variam dependendo do o / s etc.
Jonathan Leffler
15

Do passo a passo da linguagem Assembly: Programação com Linux por Jeff Duntemann, sobre a seção .data :

A seção .data contém definições de dados de itens de dados inicializados. Dados inicializados são dados que possuem um valor antes de o programa começar a ser executado. Esses valores fazem parte do arquivo executável. Eles são carregados na memória quando o arquivo executável é carregado na memória para execução.

O importante a lembrar sobre a seção .data é que quanto mais itens de dados inicializados você definir, maior será o arquivo executável e mais demorará para carregá-lo do disco para a memória ao executá-lo.

e a seção .bss :

Nem todos os itens de dados precisam ter valores antes de o programa começar a ser executado. Quando você está lendo dados de um arquivo de disco, por exemplo, você precisa ter um lugar para os dados irem depois de virem do disco. Buffers de dados como esse são definidos na seção .bss do seu programa. Você reserva um número de bytes para um buffer e dá um nome a ele, mas não diz quais valores devem estar presentes no buffer.

Há uma diferença crucial entre os itens de dados definidos na seção .data e os itens de dados definidos na seção .bss: os itens de dados na seção .data aumentam o tamanho do seu arquivo executável. Os itens de dados na seção .bss não. Um buffer que ocupa 16.000 bytes (ou mais, às vezes muito mais) pode ser definido em .bss e não adicionar quase nada (cerca de 50 bytes para a descrição) ao tamanho do arquivo executável.

mihai
fonte
9

Bem, em primeiro lugar, essas variáveis ​​em seu exemplo não são inicializadas; C especifica que as variáveis ​​estáticas não inicializadas de outra forma são inicializadas com 0.

Portanto, o motivo do .bss é ter executáveis ​​menores, economizando espaço e permitindo um carregamento mais rápido do programa, já que o carregador pode apenas alocar um monte de zeros em vez de ter que copiar os dados do disco.

Ao executar o programa, o carregador do programa carregará .data e .bss na memória. As gravações em objetos que residem em .data ou .bss vão apenas para a memória, não são enviadas para o binário no disco em nenhum momento.

Janneb
fonte
5

O System V ABI 4.1 (1997) (especificação AKA ELF) também contém a resposta:

.bssEsta seção contém dados não inicializados que contribuem para a imagem da memória do programa. Por definição, o sistema inicializa os dados com zeros quando o programa começa a ser executado. A seção não ocupa espaço no arquivo, conforme indicado pelo tipo de seção SHT_NOBITS,.

diz que o nome da seção .bssé reservado e tem efeitos especiais, em particular, não ocupa espaço de arquivo , assim a vantagem sobre .data.

A desvantagem é, claro, que todos os bytes devem ser definidos 0quando o sistema operacional os coloca na memória, o que é mais restritivo, mas é um caso de uso comum, e funciona bem para variáveis ​​não inicializadas.

A SHT_NOBITSdocumentação do tipo de seção repete essa afirmação:

sh_sizeEste membro fornece o tamanho da seção em bytes. A menos que o tipo de seção seja SHT_NOBITS, a seção ocupa sh_size bytes no arquivo. Uma seção do tipo SHT_NOBITSpode ter um tamanho diferente de zero, mas não ocupa espaço no arquivo.

O padrão C não diz nada sobre as seções, mas podemos verificar facilmente onde a variável está armazenada no Linux com objdumpe readelf, e concluir que os globais não inicializados são de fato armazenados no .bss. Veja, por exemplo, esta resposta: O que acontece com uma variável declarada e não inicializada em C?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fonte
3

O artigo da Wikipedia .bss fornece uma boa explicação histórica, visto que o termo é de meados da década de 1950 (yippee meu aniversário ;-).

Antigamente, cada bit era precioso, então qualquer método para sinalizar um espaço vazio reservado era útil. Este ( .bss ) é o que travou .

Seções .data são para espaço que não está vazio, em vez disso, terá (seus) valores definidos inseridos nele.

Philip Oakley
fonte