Wo sind statische Variablen in C und C ++ gespeichert?


180

In welchem ​​Segment (.BSS, .DATA, other) einer ausführbaren Datei werden statische Variablen gespeichert, damit sie keine Namenskollision haben? Beispielsweise:


foo.c:                         bar.c:
static int foo = 1;            static int foo = 10;
void fooTest() {               void barTest() {
  static int bar = 2;            static int bar = 20;
  foo++;                         foo++;
  bar++;                         bar++;
  printf("%d,%d", foo, bar);     printf("%d, %d", foo, bar);
}                              }

Wenn ich beide Dateien kompiliere und sie mit einer Hauptdatei verknüpfe, die wiederholt fooTest () und barTest aufruft, werden die printf-Anweisungen unabhängig voneinander erhöht. Sinnvoll, da die Variablen foo und bar lokal für die Übersetzungseinheit sind.

Aber wo ist der Speicher zugeordnet?

Um klar zu sein, wird davon ausgegangen, dass Sie eine Toolchain haben, die eine Datei im ELF-Format ausgeben würde. So ich glaube , dass es hat etwas Platz in der ausführbaren Datei für die statischen Variablen reserviert werden.
Nehmen wir zu Diskussionszwecken an, wir verwenden die GCC-Toolchain.


1
Die meisten Leute sagen Ihnen, dass sie im Abschnitt .DATA gespeichert werden sollen, anstatt Ihre Frage zu beantworten: Wo genau im Abschnitt .DATA und wie können Sie wo finden. Ich sehe, dass Sie bereits eine Antwort markiert haben, also wissen Sie bereits, wie Sie sie finden können?
Lukmac

warum initialisiert und nicht initialisiert werden in verschiedenen Abschnitten platziert: linuxjournal.com/article/1059
mhk

1
Der Speicher, der Ihren globalen / statischen Variablen zur Laufzeit zugewiesen wird, hat nichts mit ihrer Namensauflösung zu tun, die während der Build- / Link-Zeit auftritt. Nachdem die ausführbare Datei erstellt wurde, gibt es keine Namen mehr.
Valdo

2
Diese Frage ist bedeutungslos, da sie auf der falschen Prämisse beruht, dass eine "Namenskollision" nicht exportierter Symbole existieren kann. Die Tatsache, dass es keine legitime Frage gibt, könnte erklären, wie schlimm einige der Antworten sind. Es ist schwer zu glauben, dass so wenige Leute das verstanden haben.
underscore_d

Antworten:


131

Wohin Ihre Statik geht, hängt davon ab, ob sie mit Null initialisiert ist . Null-initialisierte statische Daten gehen in .BSS (Block Started by Symbol) , Nicht-Null-initialisierte Daten gehen in .DATA


50
Mit "nicht 0 initialisiert" meinen Sie wahrscheinlich "initialisiert, aber mit etwas anderem als 0". Weil es in C / C ++ keine "nicht initialisierten" statischen Daten gibt. Alles Statische ist standardmäßig auf Null initialisiert.
Am

21
@ Don Neufeld: Ihre Antwort beantwortet die Frage überhaupt nicht. Ich verstehe nicht, warum es akzeptiert wird. Weil sowohl 'foo' als auch 'bar' nicht 0 initialisiert sind. Die Frage ist, wo zwei statische / globale Variablen mit dem gleichen Namen in .bss oder .data
lukmac

Ich habe Implementierungen verwendet, bei denen statische Daten, die explizit mit Null initialisiert wurden .data, und statische Daten ohne Initialisierer eingegeben wurden .bss.
MM

1
@MM In meinem Fall, ob das statische Element nicht initialisiert (implizit auf 0 initialisiert) oder explizit auf 0 initialisiert ist, wurde es in beiden Fällen im Abschnitt .bss addiert.
cbinder

Sind diese Informationen spezifisch für einen bestimmten ausführbaren Dateityp? Ich gehe davon aus, dass es, da Sie nicht angegeben haben, zumindest für ausführbare ELF- und Windows PE-Dateien gilt, aber was ist mit anderen Typen?
Jerry Jeremiah

116

Wenn ein Programm in den Speicher geladen wird, ist es in verschiedene Segmente unterteilt. Eines der Segmente ist das DATA-Segment . Das Datensegment ist weiter in zwei Teile unterteilt:

Initialisiertes Datensegment: Hier werden alle globalen, statischen und konstanten Daten gespeichert.
Nicht initialisiertes Datensegment (BSS): Alle nicht initialisierten Daten werden in diesem Segment gespeichert.

Hier ist ein Diagramm, um dieses Konzept zu erklären:

Geben Sie hier die Bildbeschreibung ein


Hier ist ein sehr guter Link, der diese Konzepte erklärt:

http://www.inf.udec.cl/~leo/teoX.pdf


Die obige Antwort besagt, dass 0 initialisiert in BSS geht. Bedeutet 0 initialisiert nicht initialisiert oder 0 an sich? Wenn es an sich 0 bedeutet, sollten Sie es meiner Meinung nach in Ihre Antwort aufnehmen.
Viraj

Konstante Daten werden nicht im .data-Segment, sondern im .const-Segment des Textabschnitts gespeichert.
user10678

Stattdessen (" Initialisiertes Datensegment : Alle globalen, statischen und konstanten Daten werden hier gespeichert. Nicht initialisiertes Datensegment (BSS) : Alle nicht initialisierten Daten werden in diesem Segment gespeichert.") Sollte dies folgendermaßen lauten: (" Initialisiertes Datensegment : Hier werden alle globalen und statischen Variablen gespeichert, die auf einen Wert ungleich Null initialisiert wurden, sowie alle konstanten Daten. Nicht initialisiertes Datensegment (BSS) : Alle globalen und statischen Variablen, die entweder NICHT initialisiert oder initialisiert wurden bis Null, werden in diesem Segment gespeichert. ").
Gabriel Staples

Beachten Sie auch, dass "initialisierte Daten" meines Wissens aus initialisierten Variablen und Konstanten bestehen können . Auf einem Mikrocontroller (ex: STM32), initialisierten Variablen wird standardmäßig in gespeicherten Flash - Speicher und kopiert RAM beim Start und initialisierten Konstanten sind links in und sollte aus gelesen wird, nur Flash - , zusammen mit dem Text , der das enthält Programm selbst und bleibt nur in Flash.
Gabriel Staples

Aus diesem Diagramm geht also hervor, dass Variablen, die global oder statisch sind (da statische Variablen in ihrer Dauer wie globale Variablen wirken), weder auf dem Heap noch auf dem Stapel liegen, sondern im Speicher zugewiesen werden, abgesehen von beiden. Ist das richtig? Ich nehme an, ich könnte mir noch einmal ein STM32-Linker-Skript ansehen, um die Speicherzuordnung genauer zu untersuchen.
Gabriel Staples

32

Tatsächlich ist eine Variable ein Tupel (Speicher, Bereich, Typ, Adresse, Wert):

storage     :   where is it stored, for example data, stack, heap...
scope       :   who can see us, for example global, local...
type        :   what is our type, for example int, int*...
address     :   where are we located
value       :   what is our value

Lokaler Bereich kann lokal für die Übersetzungseinheit (Quelldatei), die Funktion oder den Block sein, je nachdem, wo er definiert ist. Um eine Variable für mehr als eine Funktion sichtbar zu machen, muss sie sich definitiv entweder im DATA- oder im BSS-Bereich befinden (abhängig davon, ob sie explizit initialisiert wurde oder nicht). Es wird dann entsprechend entweder allen Funktionen oder Funktionen in der Quelldatei zugeordnet.


21

Der Speicherort der Daten ist implementierungsabhängig.

Die Bedeutung von statisch ist jedoch "interne Verknüpfung". Daher befindet sich das Symbol innerhalb der Kompilierungseinheit (foo.c, bar.c) und kann außerhalb dieser Kompilierungseinheit nicht referenziert werden. Es kann also keine Namenskollisionen geben.


Nein. Die statische Schlüsselwelt hat überladene Bedeutungen: In einem solchen Fall ist statisch der Speichermodifikator und nicht der Verknüpfungsmodifikator.
Ugasoft

4
ugasoft: Die Statik außerhalb der Funktion sind Verknüpfungsmodifikatoren, innerhalb sind Speichermodifikatoren, bei denen zunächst keine Kollision auftreten kann.
wnoise

12

im Bereich "global und statisch" :)

In C ++ gibt es mehrere Speicherbereiche:

  • Haufen
  • kostenloser Laden
  • Stapel
  • global & statisch
  • const

Sehen Sie hier für eine detaillierte Antwort auf Ihre Frage:

Im Folgenden werden die wichtigsten unterschiedlichen Speicherbereiche eines C ++ - Programms zusammengefasst. Beachten Sie, dass einige der Namen (z. B. "Heap") im Entwurf [Standard] nicht als solche erscheinen.

     Memory Area     Characteristics and Object Lifetimes
     --------------  ------------------------------------------------

     Const Data      The const data area stores string literals and
                     other data whose values are known at compile
                     time.  No objects of class type can exist in
                     this area.  All data in this area is available
                     during the entire lifetime of the program.

                     Further, all of this data is read-only, and the
                     results of trying to modify it are undefined.
                     This is in part because even the underlying
                     storage format is subject to arbitrary
                     optimization by the implementation.  For
                     example, a particular compiler may store string
                     literals in overlapping objects if it wants to.


     Stack           The stack stores automatic variables. Typically
                     allocation is much faster than for dynamic
                     storage (heap or free store) because a memory
                     allocation involves only pointer increment
                     rather than more complex management.  Objects
                     are constructed immediately after memory is
                     allocated and destroyed immediately before
                     memory is deallocated, so there is no
                     opportunity for programmers to directly
                     manipulate allocated but uninitialized stack
                     space (barring willful tampering using explicit
                     dtors and placement new).


     Free Store      The free store is one of the two dynamic memory
                     areas, allocated/freed by new/delete.  Object
                     lifetime can be less than the time the storage
                     is allocated; that is, free store objects can
                     have memory allocated without being immediately
                     initialized, and can be destroyed without the
                     memory being immediately deallocated.  During
                     the period when the storage is allocated but
                     outside the object's lifetime, the storage may
                     be accessed and manipulated through a void* but
                     none of the proto-object's nonstatic members or
                     member functions may be accessed, have their
                     addresses taken, or be otherwise manipulated.


     Heap            The heap is the other dynamic memory area,
                     allocated/freed by malloc/free and their
                     variants.  Note that while the default global
                     new and delete might be implemented in terms of
                     malloc and free by a particular compiler, the
                     heap is not the same as free store and memory
                     allocated in one area cannot be safely
                     deallocated in the other. Memory allocated from
                     the heap can be used for objects of class type
                     by placement-new construction and explicit
                     destruction.  If so used, the notes about free
                     store object lifetime apply similarly here.


     Global/Static   Global or static variables and objects have
                     their storage allocated at program startup, but
                     may not be initialized until after the program
                     has begun executing.  For instance, a static
                     variable in a function is initialized only the
                     first time program execution passes through its
                     definition.  The order of initialization of
                     global variables across translation units is not
                     defined, and special care is needed to manage
                     dependencies between global objects (including
                     class statics).  As always, uninitialized proto-
                     objects' storage may be accessed and manipulated
                     through a void* but no nonstatic members or
                     member functions may be used or referenced
                     outside the object's actual lifetime.

12

Ich glaube nicht, dass es zu einer Kollision kommen wird. Durch die Verwendung von static auf Dateiebene (externe Funktionen) wird die Variable als lokal für die aktuelle Kompilierungseinheit (Datei) markiert. Es ist außerhalb der aktuellen Datei niemals sichtbar und muss daher niemals einen Namen haben, der extern verwendet werden kann.

Die Verwendung von static innerhalb einer Funktion ist anders - die Variable ist nur für die Funktion sichtbar (ob statisch oder nicht), nur ihr Wert bleibt bei Aufrufen dieser Funktion erhalten.

In der Tat macht statische zwei verschiedene Dinge, je nachdem, wo es ist. In beiden Fällen ist die Sichtbarkeit der Variablen jedoch so eingeschränkt, dass Sie beim Verknüpfen leicht Namespace-Konflikte vermeiden können.

Trotzdem glaube ich, dass es in dem DATAAbschnitt gespeichert wird, der dazu neigt, Variablen zu haben, die mit anderen Werten als Null initialisiert werden. Dies ist natürlich ein Implementierungsdetail, das nicht vom Standard vorgeschrieben wird - es geht nur um das Verhalten, nicht darum , wie die Dinge unter der Decke gemacht werden.


1
@paxdiablo: Sie haben zwei Arten von statischen Variablen erwähnt. Auf welchen von ihnen bezieht sich dieser Artikel ( en.wikipedia.org/wiki/Data_segment )? Das Datensegment enthält auch die globalen Variablen (die den statischen genau entgegengesetzt sind). So, how does a segment of memory (Data Segment) store variables that can be accessed from everywhere (global variables) and also those which have limited scope (file scope or function scope in case of static variables)?
Lazer

@eSKay, es hat mit Sichtbarkeit zu tun. In einem Segment können Dinge gespeichert sein, die lokal für eine Kompilierungseinheit sind, andere, auf die vollständig zugegriffen werden kann. Ein Beispiel: Stellen Sie sich vor, dass jede Comp-Unit einen Block zum DATA-Segment beiträgt. Es weiß, wo sich alles in diesem Block befindet. Es veröffentlicht auch die Adressen der Dinge in dem Block, auf die andere Comp-Units Zugriff haben sollen. Der Linker kann diese Adressen zur Linkzeit auflösen.
Paxdiablo

11

Wie man es selbst findet objdump -Sr

Um wirklich zu verstehen, was los ist, müssen Sie die Verlagerung von Linkern verstehen. Wenn Sie das noch nie angerührt haben, lesen Sie zuerst diesen Beitrag .

Lassen Sie uns ein Linux x86-64 ELF-Beispiel analysieren, um es selbst zu sehen:

#include <stdio.h>

int f() {
    static int i = 1;
    i++;
    return i;
}

int main() {
    printf("%d\n", f());
    printf("%d\n", f());
    return 0;
}

Kompilieren mit:

gcc -ggdb -c main.c

Dekompilieren Sie den Code mit:

objdump -Sr main.o
  • -S dekompiliert den Code mit der ursprünglichen Quelle vermischt
  • -r zeigt Umzugsinformationen

In der Dekompilierung von sehen fwir:

 static int i = 1;
 i++;
4:  8b 05 00 00 00 00       mov    0x0(%rip),%eax        # a <f+0xa>
        6: R_X86_64_PC32    .data-0x4

und das .data-0x4sagt, dass es zum ersten Byte des .dataSegments gehen wird.

Das -0x4ist da, weil wir die relative RIP-Adressierung verwenden, also die %ripin der Anweisung und R_X86_64_PC32.

Dies ist erforderlich, da RIP auf die folgende Anweisung verweist , die 4 Bytes startet und danach 00 00 00 00verschoben wird. Ich habe dies ausführlicher unter https://stackoverflow.com/a/30515926/895245 erklärt

Wenn wir dann die Quelle ändern i = 1und dieselbe Analyse durchführen, schließen wir Folgendes:

  • static int i = 0 geht weiter .bss
  • static int i = 1 geht weiter .data


6

Dies hängt von der Plattform und dem Compiler ab, die Sie verwenden. Einige Compiler speichern direkt im Codesegment. Statische Variablen sind immer nur für die aktuelle Übersetzungseinheit zugänglich und die Namen werden nicht exportiert, sodass die Kollisionen von Namensnamen niemals auftreten.


5

In einer Kompilierungseinheit deklarierte Daten werden in die Ausgabe von .BSS oder .Data dieser Dateien übertragen. Initialisierte Daten in BSS, nicht initialisiert in DATA.

Der Unterschied zwischen statischen und globalen Daten besteht in der Aufnahme von Symbolinformationen in die Datei. Compiler enthalten in der Regel die Symbolinformationen, markieren jedoch nur die globalen Informationen als solche.

Der Linker respektiert diese Informationen. Die Symbolinformationen für die statischen Variablen werden entweder verworfen oder entstellt, sodass auf statische Variablen weiterhin auf irgendeine Weise verwiesen werden kann (mit Debug- oder Symboloptionen). In keinem Fall können die Kompilierungseinheiten betroffen sein, da der Linker zuerst lokale Referenzen auflöst.


3

Ich habe es mit objdump und gdb versucht, hier ist das Ergebnis, was ich bekomme:

(gdb) disas fooTest
Dump of assembler code for function fooTest:
   0x000000000040052d <+0>: push   %rbp
   0x000000000040052e <+1>: mov    %rsp,%rbp
   0x0000000000400531 <+4>: mov    0x200b09(%rip),%eax        # 0x601040 <foo>
   0x0000000000400537 <+10>:    add    $0x1,%eax
   0x000000000040053a <+13>:    mov    %eax,0x200b00(%rip)        # 0x601040 <foo>
   0x0000000000400540 <+19>:    mov    0x200afe(%rip),%eax        # 0x601044 <bar.2180>
   0x0000000000400546 <+25>:    add    $0x1,%eax
   0x0000000000400549 <+28>:    mov    %eax,0x200af5(%rip)        # 0x601044 <bar.2180>
   0x000000000040054f <+34>:    mov    0x200aef(%rip),%edx        # 0x601044 <bar.2180>
   0x0000000000400555 <+40>:    mov    0x200ae5(%rip),%eax        # 0x601040 <foo>
   0x000000000040055b <+46>:    mov    %eax,%esi
   0x000000000040055d <+48>:    mov    $0x400654,%edi
   0x0000000000400562 <+53>:    mov    $0x0,%eax
   0x0000000000400567 <+58>:    callq  0x400410 <printf@plt>
   0x000000000040056c <+63>:    pop    %rbp
   0x000000000040056d <+64>:    retq   
End of assembler dump.

(gdb) disas barTest
Dump of assembler code for function barTest:
   0x000000000040056e <+0>: push   %rbp
   0x000000000040056f <+1>: mov    %rsp,%rbp
   0x0000000000400572 <+4>: mov    0x200ad0(%rip),%eax        # 0x601048 <foo>
   0x0000000000400578 <+10>:    add    $0x1,%eax
   0x000000000040057b <+13>:    mov    %eax,0x200ac7(%rip)        # 0x601048 <foo>
   0x0000000000400581 <+19>:    mov    0x200ac5(%rip),%eax        # 0x60104c <bar.2180>
   0x0000000000400587 <+25>:    add    $0x1,%eax
   0x000000000040058a <+28>:    mov    %eax,0x200abc(%rip)        # 0x60104c <bar.2180>
   0x0000000000400590 <+34>:    mov    0x200ab6(%rip),%edx        # 0x60104c <bar.2180>
   0x0000000000400596 <+40>:    mov    0x200aac(%rip),%eax        # 0x601048 <foo>
   0x000000000040059c <+46>:    mov    %eax,%esi
   0x000000000040059e <+48>:    mov    $0x40065c,%edi
   0x00000000004005a3 <+53>:    mov    $0x0,%eax
   0x00000000004005a8 <+58>:    callq  0x400410 <printf@plt>
   0x00000000004005ad <+63>:    pop    %rbp
   0x00000000004005ae <+64>:    retq   
End of assembler dump.

Hier ist das objdump-Ergebnis

Disassembly of section .data:

0000000000601030 <__data_start>:
    ...

0000000000601038 <__dso_handle>:
    ...

0000000000601040 <foo>:
  601040:   01 00                   add    %eax,(%rax)
    ...

0000000000601044 <bar.2180>:
  601044:   02 00                   add    (%rax),%al
    ...

0000000000601048 <foo>:
  601048:   0a 00                   or     (%rax),%al
    ...

000000000060104c <bar.2180>:
  60104c:   14 00                   adc    $0x0,%al

Das heißt, Ihre vier Variablen befinden sich in Datenabschnittereignissen mit demselben Namen, jedoch mit unterschiedlichem Versatz.


Es gibt viel mehr als das. Auch vorhandene Antworten sind nicht vollständig. Um nur etwas anderes zu erwähnen: Thread-Einheimische.
Adriano Repetti

2

statische Variable, die wie zuvor erwähnt im Datensegment oder Codesegment gespeichert ist.
Sie können sicher sein, dass es nicht auf Stapel oder Heap zugewiesen wird.
Es besteht kein Kollisionsrisiko, da staticSchlüsselwörter den Bereich der Variablen als Datei oder Funktion definieren. Im Falle einer Kollision gibt es einen Compiler / Linker, vor dem Sie gewarnt werden können.
Ein schönes Beispiel



1

Die Antwort hängt möglicherweise vom Compiler ab, daher möchten Sie wahrscheinlich Ihre Frage bearbeiten (ich meine, auch der Begriff der Segmente wird weder von ISO C noch von ISO C ++ vorgeschrieben). Unter Windows enthält eine ausführbare Datei beispielsweise keine Symbolnamen. Ein 'foo' wäre Offset 0x100, das andere vielleicht 0x2B0, und Code aus beiden Übersetzungseinheiten wird unter Kenntnis der Offsets für "ihr" foo kompiliert.


0

Sie werden beide unabhängig voneinander gespeichert. Wenn Sie jedoch anderen Entwicklern klar machen möchten, möchten Sie sie möglicherweise in Namespaces einbinden.


-1

Sie wissen bereits, dass es entweder in bss (Blockstart durch Symbol), auch als nicht initialisiertes Datensegment bezeichnet, oder im initialisierten Datensegment gespeichert wird.

Nehmen wir ein einfaches Beispiel

void main(void)
{
static int i;
}

Die obige statische Variable wird nicht initialisiert und geht daher in das nicht initialisierte Datensegment (bss).

void main(void)
{
static int i=10;
}

und natürlich wird es um 10 initialisiert, so dass es zum initialisierten Datensegment geht.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.