Warum ist das .bss-Segment erforderlich?

120

Was ich weiß ist, dass globale und statische Variablen im .dataSegment gespeichert sind und nicht initialisierte Daten im .bssSegment sind. Was ich nicht verstehe ist, warum wir ein dediziertes Segment für nicht initialisierte Variablen haben? Wenn einer nicht initialisierten Variablen zur Laufzeit ein Wert zugewiesen wurde, ist die Variable dann nur noch im .bssSegment vorhanden?

Im folgenden Programm abefindet sich im .dataSegment und bbefindet sich im .bssSegment; Ist das korrekt? Bitte korrigieren Sie mich, wenn mein Verständnis falsch ist.

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

Beachten Sie auch das folgende Programm:

#include <stdio.h>
#include <stdlib.h>
int var[10];  /* Uninitialized so in .bss */
int main ()
{
   var[0] = 20  /* **Initialized, where this 'var' will be ?** */
}
Wer bin ich
quelle
3
Sie können BSS als Better Save Space lesen .
Smwikipedia

Antworten:

89

Der Grund ist, die Programmgröße zu reduzieren. Stellen Sie sich vor, Ihr C-Programm läuft auf einem eingebetteten System, auf dem der Code und alle Konstanten in einem echten ROM (Flash-Speicher) gespeichert sind. In solchen Systemen muss ein anfängliches "Kopieren" ausgeführt werden, um alle statischen Speicherdauerobjekte festzulegen, bevor main () aufgerufen wird. Es wird normalerweise wie folgt aussehen:

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

memset(.bss, 
       0, 
       all_implicitly_initialized_objects);

Wobei .data und .bss im RAM gespeichert sind, init_value jedoch im ROM. Wenn es ein Segment gewesen war, musste das ROM mit vielen Nullen gefüllt werden, was die ROM-Größe signifikant erhöhte.

RAM-basierte ausführbare Dateien funktionieren ähnlich, obwohl sie natürlich kein echtes ROM haben.

Außerdem ist memset wahrscheinlich ein sehr effizienter Inline-Assembler, was bedeutet, dass das Herunterfahren des Starts schneller ausgeführt werden kann.

Lundin
quelle
7
Zur Verdeutlichung: Der einzige Unterschied zwischen .data und .bss besteht darin, dass beim Start das "Kopieren" nacheinander und damit schneller ausgeführt werden kann. Wenn es nicht in zwei Segmente aufgeteilt würde, müsste die Initialisierung die RAM-Spots überspringen, die zu den nicht initialisierten Variablen gehören, was Zeit verschwendet.
CL22
80

Das .bssSegment ist eine Optimierung. Das gesamte .bssSegment wird durch eine einzelne Zahl beschrieben, wahrscheinlich 4 Byte oder 8 Byte, die seine Größe im laufenden Prozess .dataangibt , während der Abschnitt so groß ist wie die Summe der Größen der initialisierten Variablen. Dadurch werden die .bssausführbaren Dateien kleiner und können schneller geladen werden. Andernfalls könnten sich die Variablen im .dataSegment mit expliziter Initialisierung auf Nullen befinden. Das Programm würde es schwer haben, den Unterschied zu erkennen. (Im Detail würde sich die Adresse der Objekte in .bsswahrscheinlich von der Adresse unterscheiden, wenn sie sich im .dataSegment befindet.)

Im ersten Programm awäre im .dataSegment und bwäre im .bssSegment der ausführbaren Datei. Sobald das Programm geladen ist, spielt die Unterscheidung keine Rolle mehr. Zur Laufzeit bnimmt 20 * sizeof(int)Bytes.

Im zweiten Programm varwird Speicherplatz zugewiesen, und die Zuweisung in main()ändert diesen Speicherplatz. Es kommt also vor, dass der Speicherplatz für varim .bssSegment und nicht im .dataSegment beschrieben wurde, dies hat jedoch keinen Einfluss auf das Verhalten des Programms beim Ausführen.

Jonathan Leffler
quelle
16
Angenommen, viele nicht initialisierte Puffer haben eine Länge von 4096 Byte. Möchten Sie, dass all diese 4k-Puffer zur Größe der Binärdatei beitragen? Das wäre viel Platzverschwendung.
Jeff Mercado
1
@ Jonathen Killer: Warum wird das gesamte BSS-Segment durch eine einzelne Zahl beschrieben?
Suraj Jain
@ JonathanLeffler Ich meine, alle null initialisierten statischen Variablen gehen in bss. Sollte sein Wert also nicht nur Null sein? Und warum wird ihnen im Abschnitt .data kein Platz eingeräumt? Wie kann dies dazu führen, dass er langsam wird?
Suraj Jain
2
@ SurajJain: Die gespeicherte Zahl ist die Anzahl der Bytes, die mit Nullen gefüllt werden sollen. Wenn es keine solchen nicht initialisierten Variablen gibt, wird die Länge des bss-Abschnitts nicht Null sein, obwohl alle Bytes im bss-Abschnitt nach dem Laden des Programms Null sind.
Jonathan Leffler
1
Der Abschnitt .bss in der ausführbaren Datei ist einfach eine Zahl. Der .bss-Abschnitt im speicherinternen Prozessabbild ist normalerweise ein Speicher neben dem .data-Abschnitt, und häufig wird der Laufzeit-.data-Abschnitt mit dem .bss-Abschnitt kombiniert. Im Laufzeitspeicher wird keine Unterscheidung getroffen. Manchmal können Sie herausfinden, wo das bss begonnen hat ( edata). In der Praxis ist die .bss-Datei nach Abschluss des Prozessabbilds nicht mehr im Speicher vorhanden. Die auf Null gesetzten Daten sind ein einfacher Teil des Abschnitts .data. Aber die Details variieren je nach Betrieb usw.
Jonathan Leffler
15

Von Assembly Language Step-by-Step: Programmieren mit Linux von Jeff Duntemanns in Bezug auf dem .data Abschnitt:

Der Abschnitt .data enthält Datendefinitionen initialisierter Datenelemente. Initialisierte Daten sind Daten, die einen Wert haben, bevor das Programm gestartet wird. Diese Werte sind Teil der ausführbaren Datei. Sie werden in den Speicher geladen, wenn die ausführbare Datei zur Ausführung in den Speicher geladen wird.

Das Wichtigste am Abschnitt .data ist, dass je mehr initialisierte Datenelemente Sie definieren, desto größer die ausführbare Datei ist und desto länger dauert das Laden von der Festplatte in den Speicher, wenn Sie sie ausführen.

und der Abschnitt .bss :

Nicht alle Datenelemente müssen Werte haben, bevor das Programm ausgeführt wird. Wenn Sie beispielsweise Daten aus einer Festplattendatei lesen, müssen Sie einen Speicherort für die Daten haben, nachdem diese von der Festplatte eingegangen sind. Solche Datenpuffer werden im Abschnitt .bss Ihres Programms definiert. Sie legen eine bestimmte Anzahl von Bytes für einen Puffer beiseite und geben dem Puffer einen Namen, sagen jedoch nicht, welche Werte im Puffer vorhanden sein sollen.

Es gibt einen entscheidenden Unterschied zwischen den im Abschnitt .data definierten Datenelementen und den im Abschnitt .bss definierten Datenelementen: Datenelemente im Abschnitt .data erhöhen die Größe Ihrer ausführbaren Datei. Datenelemente im Abschnitt .bss nicht. Ein Puffer, der 16.000 Bytes (oder mehr, manchmal viel mehr) belegt, kann in .bss definiert werden und der ausführbaren Dateigröße fast nichts hinzufügen (etwa 50 Bytes für die Beschreibung).

mihai
quelle
9

Zunächst einmal sind diese Variablen in Ihrem Beispiel nicht nicht initialisiert. C gibt an, dass statische Variablen, die nicht anderweitig initialisiert wurden, auf 0 initialisiert werden.

Der Grund für .bss sind kleinere ausführbare Dateien, die Platz sparen und ein schnelleres Laden des Programms ermöglichen, da der Loader nur eine Reihe von Nullen zuweisen kann, anstatt die Daten von der Festplatte kopieren zu müssen.

Beim Ausführen des Programms lädt der Programmlader .data und .bss in den Speicher. Schreibvorgänge in Objekten, die sich in .data oder .bss befinden, werden daher nur in den Speicher verschoben. Sie werden zu keinem Zeitpunkt in die Binärdatei auf der Festplatte geschrieben.

janneb
quelle
5

Das System V ABI 4.1 (1997) (AKA ELF-Spezifikation) enthält auch die Antwort:

.bssDieser Abschnitt enthält nicht initialisierte Daten, die zum Speicherbild des Programms beitragen. Per Definition initialisiert das System die Daten mit Nullen, wenn das Programm gestartet wird. Der Abschnitt belegt keinen Dateibereich, wie durch den Abschnittstyp angegeben SHT_NOBITS.

sagt, dass der Abschnittsname .bssreserviert ist und Spezialeffekte hat, insbesondere belegt er keinen Dateibereich , also den Vorteil gegenüber .data.

Der Nachteil ist natürlich, dass alle Bytes auf gesetzt werden müssen, 0wenn das Betriebssystem sie in den Speicher stellt. Dies ist restriktiver, aber ein häufiger Anwendungsfall und funktioniert gut für nicht initialisierte Variablen.

In der SHT_NOBITSDokumentation zum Abschnittstyp wird diese Bestätigung wiederholt:

sh_sizeDieses Mitglied gibt die Größe des Abschnitts in Bytes an. Sofern der Abschnittstyp nicht lautet SHT_NOBITS, belegt der Abschnitt sh_size Bytes in der Datei. Ein Abschnitt vom Typ SHT_NOBITSkann eine Größe ungleich Null haben, belegt jedoch keinen Platz in der Datei.

Der C-Standard sagt nichts über Abschnitte aus, aber wir können leicht überprüfen, wo die Variable unter Linux mit objdumpund gespeichert ist readelf, und daraus schließen, dass nicht initialisierte Globals tatsächlich in der Datei gespeichert sind .bss. Siehe zum Beispiel diese Antwort: Was passiert mit einer deklarierten, nicht initialisierten Variablen in C?

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

Der Wikipedia-Artikel .bss bietet eine schöne historische Erklärung, da der Begriff aus der Mitte der 1950er Jahre stammt (yippee mein Geburtstag ;-).

Früher war jedes Bit wertvoll, daher war jede Methode zur Signalisierung des reservierten leeren Raums nützlich. Dies ( .bss ) ist derjenige, der stecken geblieben ist.

.data- Abschnitte sind für Leerzeichen gedacht , die nicht leer sind, sondern in die (Ihre) definierten Werte eingegeben wurden.

Philip Oakley
quelle