Pourquoi le segment .bss est-il requis?

120

Ce que je sais, c'est que les variables globales et statiques sont stockées dans le .datasegment et que les données non initialisées sont dans le .bsssegment. Ce que je ne comprends pas, c'est pourquoi avons-nous un segment dédié pour les variables non initialisées? Si une variable non initialisée a une valeur affectée au moment de l'exécution, la variable existe-t-elle encore dans le .bsssegment uniquement?

Dans le programme suivant, aest dans le .datasegment, et best dans le .bsssegment; Est-ce exact? Veuillez me corriger si ma compréhension est fausse.

#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 ()
{
   ;
}  

Pensez également à suivre le programme,

#include <stdio.h>
#include <stdlib.h>
int var[10];  /* Uninitialized so in .bss */
int main ()
{
   var[0] = 20  /* **Initialized, where this 'var' will be ?** */
}
Qui suis je
la source
3
Vous pouvez lire BSS comme Better Save Space .
smwikipedia

Réponses:

89

La raison est de réduire la taille du programme. Imaginez que votre programme C s'exécute sur un système embarqué, où le code et toutes les constantes sont enregistrés dans une vraie ROM (mémoire flash). Dans de tels systèmes, une "copie vers le bas" initiale doit être exécutée pour définir tous les objets de durée de stockage statique, avant l'appel de main (). Cela ressemblera généralement à ce pseudo:

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

memset(.bss, 
       0, 
       all_implicitly_initialized_objects);

Où .data et .bss sont stockés dans la RAM, mais init_value est stocké dans la ROM. S'il s'agissait d'un segment, la ROM devait être remplie de beaucoup de zéros, ce qui augmentait considérablement la taille de la ROM.

Les exécutables basés sur la RAM fonctionnent de la même manière, bien qu'ils n'aient bien sûr pas de véritable ROM.

En outre, memset est probablement un assembleur en ligne très efficace, ce qui signifie que la copie de démarrage peut être exécutée plus rapidement.

Lundin
la source
7
Pour clarifier: la seule différence entre .data et .bss est qu'au démarrage, le "copy-down" peut être exécuté séquentiellement, donc plus rapidement. S'il n'était pas divisé en deux segments, l'initialisation devrait sauter les spots RAM appartenant aux variables non initialisées, perdant ainsi du temps.
CL22
80

Le .bsssegment est une optimisation. Le .bsssegment entier est décrit par un seul nombre, probablement 4 octets ou 8 octets, qui donne sa taille dans le processus en cours, alors que la .datasection est aussi grande que la somme des tailles des variables initialisées. Ainsi, le .bssrend les exécutables plus petits et plus rapides à charger. Sinon, les variables pourraient être dans le .datasegment avec une initialisation explicite à des zéros; le programme aurait du mal à faire la différence. (En détail, l'adresse des objets dans .bssserait probablement différente de l'adresse si elle était dans le .datasegment.)

Dans le premier programme, aserait dans le .datasegment et bserait dans le .bsssegment de l'exécutable. Une fois le programme chargé, la distinction devient sans importance. Au moment de l'exécution, boccupe des 20 * sizeof(int)octets.

Dans le deuxième programme, l' varespace est alloué et l'affectation main()modifie cet espace. Il se trouve que l'espace pour a varété décrit dans le .bsssegment plutôt que dans le .datasegment, mais cela n'affecte pas la façon dont le programme se comporte lors de l'exécution.

Jonathan Leffler
la source
16
Par exemple, envisagez d'avoir de nombreux tampons non initialisés de 4096 octets de longueur. Souhaitez-vous que tous ces tampons 4k contribuent à la taille du binaire? Ce serait beaucoup d'espace gaspillé.
Jeff Mercado
1
@jonathen killer: Pourquoi tout le segment bss est-il décrit par un seul chiffre ??
Suraj Jain
@JonathanLeffler Je veux dire que toute variable statique initialisée à zéro va dans bss. Donc, sa valeur ne devrait-elle pas être juste zéro? Et pourquoi ne leur donne-t-on pas d'espace sur la section .data, comment cela peut-il le ralentir?
Suraj Jain
2
@SurajJain: le nombre stocké est le nombre d'octets à remplir avec des zéros. À moins qu'il n'y ait de telles variables non initialisées, la longueur de la section bss ne sera pas nulle, même si tous les octets de la section bss seront zéro une fois le programme chargé.
Jonathan Leffler
1
La section .bss de l'exécutable est simplement un nombre. La section .bss dans l'image de processus en mémoire est normalement la mémoire adjacente à la section .data et souvent la section .data d'exécution est combinée avec le .bss; aucune distinction n'est faite dans la mémoire d'exécution. Parfois, vous pouvez trouver où le bss a commencé ( edata). Concrètement, le .bss n'existe pas en mémoire une fois la mémoire image terminée; les données mises à zéro sont une simple partie de la section .data. Mais les détails varient selon les o / s etc.
Jonathan Leffler
15

Depuis le langage d'assemblage pas à pas: Programmation avec Linux par Jeff Duntemann, concernant la section .data :

La section .data contient les définitions de données des éléments de données initialisés. Les données initialisées sont des données qui ont une valeur avant que le programme ne commence à s'exécuter. Ces valeurs font partie du fichier exécutable. Ils sont chargés en mémoire lorsque le fichier exécutable est chargé en mémoire pour exécution.

La chose importante à retenir à propos de la section .data est que plus vous définissez d'éléments de données initialisés, plus le fichier exécutable sera volumineux et plus il faudra de temps pour le charger du disque en mémoire lorsque vous l'exécuterez.

et la section .bss :

Tous les éléments de données n'ont pas besoin d'avoir des valeurs avant que le programme ne commence à s'exécuter. Lorsque vous lisez des données à partir d'un fichier disque, par exemple, vous devez disposer d'un emplacement pour les données après leur arrivée depuis le disque. Les tampons de données comme celui-ci sont définis dans la section .bss de votre programme. Vous mettez de côté un certain nombre d'octets pour un tampon et donnez un nom au tampon, mais vous ne dites pas quelles valeurs doivent être présentes dans le tampon.

Il existe une différence cruciale entre les éléments de données définis dans la section .data et les éléments de données définis dans la section .bss: les éléments de données de la section .data ajoutent à la taille de votre fichier exécutable. Les éléments de données de la section .bss ne le font pas. Un tampon qui occupe 16 000 octets (ou plus, parfois beaucoup plus) peut être défini dans .bss et n'ajouter presque rien (environ 50 octets pour la description) à la taille du fichier exécutable.

Mihai
la source
9

Eh bien, tout d'abord, ces variables de votre exemple ne sont pas non initialisées; C spécifie que les variables statiques non initialisées par ailleurs sont initialisées à 0.

Donc, la raison de .bss est d'avoir des exécutables plus petits, économisant de l'espace et permettant un chargement plus rapide du programme, car le chargeur peut simplement allouer un tas de zéros au lieu d'avoir à copier les données du disque.

Lors de l'exécution du programme, le chargeur de programme chargera .data et .bss en mémoire. Les écritures dans des objets résidant dans .data ou .bss ne vont donc qu'en mémoire, elles ne sont à aucun moment vidées vers le binaire sur le disque.

Janneb
la source
5

Le System V ABI 4.1 (1997) (spécification AKA ELF) contient également la réponse:

.bssCette section contient des données non initialisées qui contribuent à l'image mémoire du programme. Par définition, le système initialise les données avec des zéros lorsque le programme commence à s'exécuter. La section occupe pas d' espace de fichier, comme indiqué par le type de section, SHT_NOBITS.

dit que le nom de la section .bssest réservé et a des effets spéciaux, en particulier il n'occupe aucun espace fichier , donc l'avantage par dessus .data.

L'inconvénient est bien sûr que tous les octets doivent être définis sur 0lorsque le système d'exploitation les met en mémoire, ce qui est plus restrictif, mais un cas d'utilisation courant, et fonctionne bien pour les variables non initialisées.

La SHT_NOBITSdocumentation du type de section répète cette affirmation:

sh_sizeCe membre donne la taille de la section en octets. Sauf si le type de section est SHT_NOBITS, la section occupe des sh_size octets dans le fichier. Une section de type SHT_NOBITSpeut avoir une taille différente de zéro, mais elle n'occupe aucun espace dans le fichier.

Le standard C ne dit rien sur les sections, mais nous pouvons facilement vérifier où la variable est stockée sous Linux avec objdumpet readelf, et conclure que les globaux non initialisés sont en fait stockés dans le .bss. Voir par exemple cette réponse: Qu'arrive - t-il à une variable déclarée non initialisée en C?

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

L'article de wikipedia .bss fournit une belle explication historique, étant donné que le terme est du milieu des années 1950 (yippee mon anniversaire ;-).

À l'époque, chaque bit était précieux, donc toute méthode de signalisation d'espace vide réservé était utile. Ce ( .bss ) est celui qui est bloqué.

Les sections .data sont réservées aux espaces qui ne sont pas vides, mais contiennent (vos) valeurs définies.

Philip Oakley
la source