Dove sono archiviate le variabili statiche in C e C ++?


180

In quale segmento (.BSS, .DATA, altro) di un file eseguibile sono memorizzate le variabili statiche in modo che non abbiano una collisione del nome? Per esempio:


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);
}                              }

Se compilo entrambi i file e li collego a un main che chiama ripetutamente fooTest () e barTest, le istruzioni printf aumentano in modo indipendente. Ha senso poiché le variabili foo e bar sono locali per l'unità di traduzione.

Ma dove viene allocato lo spazio di archiviazione?

Per essere chiari, il presupposto è che hai una toolchain che produrrebbe un file in formato ELF. Quindi, credo che ci debba essere dello spazio riservato nel file eseguibile per quelle variabili statiche.
A scopo di discussione, supponiamo che utilizziamo la toolchain GCC.


1
Molte persone ti stanno dicendo che dovrebbero essere archiviate nella sezione .DATA invece di rispondere alla tua domanda: dove esattamente nella sezione .DATA e come puoi trovare dove. Vedo che hai già segnato una risposta, quindi sai già come trovarla?
lukmac,

perché inizializzati e non inizializzati sono collocati in diverse sezioni: linuxjournal.com/article/1059
mhk

1
L'archiviazione allocata alle variabili globali / statiche in fase di runtime non ha nulla a che fare con la loro risoluzione dei nomi, che si verifica durante il tempo di compilazione / collegamento. Dopo che l'eseguibile è stato creato, non ci sono più nomi.
valdo,

2
Questa domanda non ha senso, essendo costruita sulla falsa premessa che la "collisione dei nomi" di simboli non esportati è una cosa che può esistere. Il fatto che non ci siano domande legittime potrebbe spiegare quanto alcune risposte siano terribili. È difficile credere che così poche persone abbiano capito.
underscore_d

Risposte:


131

La posizione della statistica dipende dal fatto che siano inizializzati a zero . i dati statici a zero inizializzazione vanno in .BSS (Block Started by Symbol) , i dati a zero non inizializzati vanno in .DATA


50
Con "non-0 inizializzato" si intende probabilmente "inizializzato, ma con qualcosa di diverso da 0". Perché non esistono dati statici "non inizializzati" in C / C ++. Tutto ciò che è statico viene inizializzato per impostazione predefinita.
AnT

21
@Don Neufeld: la tua risposta non risponde affatto alla domanda. Non capisco perché sia ​​accettato. Perché sia ​​"foo" che "bar" sono inizializzati senza 0. La domanda è dove posizionare due variabili statiche / globali con lo stesso nome in .bss o .data
lukmac

Ho usato implementazioni in cui sono stati inseriti dati statici esplicitamente inizializzati a zero .datae dati statici senza inizializzatore .bss.
MM

1
@MM Nel mio caso, se un membro statico è non inizializzato (inizializzato implicitamente su 0) o inizializzato esplicitamente su 0, in entrambi i casi è stato aggiunto nella sezione .bss.
Raccoglitore

Queste informazioni sono specifiche per un determinato tipo di file eseguibile? Presumo, dal momento che non hai specificato, che si applica almeno ai file eseguibili ELF e Windows PE, ma per quanto riguarda gli altri tipi?
Jerry Jeremiah,

116

Quando un programma viene caricato in memoria, è organizzato in diversi segmenti. Uno dei segmenti è il segmento DATI . Il segmento Dati è ulteriormente suddiviso in due parti:

Segmento dati inizializzato: qui vengono memorizzati tutti i dati globali, statici e costanti.
Segmento dati non inizializzato (BSS): tutti i dati non inizializzati sono memorizzati in questo segmento.

Ecco un diagramma per spiegare questo concetto:

inserisci qui la descrizione dell'immagine


ecco un ottimo collegamento che spiega questi concetti:

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


La risposta sopra dice che 0 inizializzato va in BSS. 0 inizializzato significa non inizializzato o 0 di per sé? Se significa 0 di per sé, penso che dovresti includerlo nella tua risposta.
Viraj,

I dati costanti non vengono memorizzati nel segmento .data ma all'interno del segmento .const della sezione di testo.
user10678

Invece di questo (" Segmento dati inizializzato : tutti i dati globali, statici e costanti sono memorizzati qui. Segmento dati non inizializzato (BSS) : Tutti i dati non inizializzati sono memorizzati in questo segmento."), Penso che dovrebbe dire questo: (" Segmento di dati inizializzato : tutte le variabili globali e statiche che sono state inizializzate su un valore diverso da zero e tutti i dati costanti, sono memorizzati qui Segmento di dati non inizializzato (BSS) : Tutte le variabili globali e statiche che non sono state inizializzate o inizializzate a zero, sono memorizzati in questo segmento. ").
Gabriel Staples,

Inoltre, per quanto ne so, i "dati inizializzati" possono consistere in variabili e costanti inizializzate . Su un microcontrollore (ad es. STM32), le variabili inizializzate vengono archiviate per impostazione predefinita nella memoria Flash e copiate nella RAM all'avvio , e le costanti inizializzate vengono lasciate e intese per essere lette, solo Flash , insieme al testo , che contiene il programma stesso e viene lasciato solo
Gabriel Staples,

Quindi quello che sto raccogliendo da questo diagramma è che le variabili che sono globali o statiche (poiché le variabili statiche agiscono come variabili globali in durata) non sono né sullo heap sullo stack, ma sono piuttosto allocate in memoria a parte entrambe. È giusto? Suppongo che potrei dare di nuovo un'occhiata a uno script del linker STM32 per studiare di più anche l'allocazione della memoria.
Gabriel Staples,

32

In effetti, una variabile è tupla (memoria, ambito, tipo, indirizzo, valore):

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

L'ambito locale può significare locale all'unità di traduzione (file sorgente), alla funzione o al blocco a seconda di dove è definita. Per rendere la variabile visibile a più di una funzione, deve essere sicuramente nell'area DATA o BSS (a seconda che sia inizializzata esplicitamente o meno, rispettivamente). Viene quindi impostato di conseguenza su tutte le funzioni o funzioni all'interno del file di origine.


21

La posizione di archiviazione dei dati dipenderà dall'implementazione.

Tuttavia, il significato di statico è "collegamento interno". Pertanto, il simbolo è interno all'unità di compilazione (foo.c, bar.c) e non può essere referenziato al di fuori di tale unità di compilazione. Quindi, non ci possono essere collisioni di nomi.


no. keyworld statico ha significati sovraccarichi: in tal caso statico è modificatore di memoria, non modificatore di collegamento.
ugasoft,

4
ugasoft: la statica al di fuori della funzione sono modificatori di collegamento, al suo interno sono modificatori di memoria in cui non ci possono essere collisioni con cui iniziare.
wnoise,

12

nell'area "globale e statica" :)

Esistono diverse aree di memoria in C ++:

  • mucchio
  • negozio gratuito
  • pila
  • globale e statico
  • const

Vedi qui per una risposta dettagliata alla tua domanda:

Di seguito vengono riepilogati i principali settori di memoria distinti di un programma C ++. Si noti che alcuni dei nomi (ad es. "Heap") non compaiono come tali nella bozza [standard].

     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

Non credo che ci sarà una collisione. L'uso di static a livello di file (funzioni esterne) contrassegna la variabile come locale per l'unità di compilazione corrente (file). Non è mai visibile al di fuori del file corrente, quindi non deve mai avere un nome che può essere utilizzato esternamente.

L'uso statico all'interno di una funzione è diverso: la variabile è visibile solo alla funzione (statica o no), è solo il suo valore che viene preservato attraverso le chiamate a quella funzione.

In effetti, static fa due cose diverse a seconda di dove si trova. In entrambi i casi, tuttavia, la visibilità delle variabili è limitata in modo tale da poter facilmente evitare conflitti nello spazio dei nomi durante il collegamento.

Detto questo, credo che sarebbe memorizzato nella DATAsezione, che tende ad avere variabili inizializzate su valori diversi da zero. Questo è, ovviamente, un dettaglio di implementazione, non qualcosa richiesto dalla norma - si preoccupa solo del comportamento, non di come le cose vengono fatte sotto le coperte.


1
@paxdiablo: hai menzionato due tipi di variabili statiche. A chi si riferisce a questo articolo ( en.wikipedia.org/wiki/Data_segment )? Il segmento di dati contiene anche le variabili globali (che sono esattamente opposte in natura a quelle statiche). 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, ha a che fare con la visibilità. Ci possono essere oggetti memorizzati in un segmento che sono locali a un'unità di compilazione, altri che sono completamente accessibili. Un esempio: pensa a ciascuna unità comp che contribuisce con un blocco al segmento DATA. Sa dove si trova tutto in quel blocco. Pubblica anche gli indirizzi di quelle cose nel blocco a cui desidera avere accesso ad altre comp-unit. Il linker può risolvere quegli indirizzi al momento del link.
paxdiablo,

11

Come trovarlo tu stesso objdump -Sr

Per capire effettivamente cosa sta succedendo, è necessario comprendere il trasferimento del linker. Se non l'hai mai toccato, considera di leggere prima questo post .

Analizziamo un esempio ELF x86-64 di Linux per vederlo da soli:

#include <stdio.h>

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

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

Compilare con:

gcc -ggdb -c main.c

Decompilare il codice con:

objdump -Sr main.o
  • -S decompila il codice con l'origine originale mescolata
  • -r mostra le informazioni di trasferimento

All'interno della decompilazione di fvediamo:

 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

e .data-0x4dice che andrà al primo byte del .datasegmento.

Il -0x4c'è perché utilizziamo RIP indirizzamento relativo, così la %ripnell'istruzione e R_X86_64_PC32.

È necessario perché RIP punta alla seguente istruzione, che inizia 4 byte dopo di 00 00 00 00che è quello che verrà trasferito. L'ho spiegato in modo più dettagliato su: https://stackoverflow.com/a/30515926/895245

Quindi, se modifichiamo l'origine i = 1e facciamo la stessa analisi, concludiamo che:

  • static int i = 0 prosegue .bss
  • static int i = 1 prosegue .data


6

Dipende dalla piattaforma e dal compilatore che stai utilizzando. Alcuni compilatori memorizzano direttamente nel segmento di codice. Le variabili statiche sono sempre accessibili solo all'unità di traduzione corrente e i nomi non vengono esportati, pertanto non si verificano mai collisioni dei nomi dei motivi.


5

I dati dichiarati in un'unità di compilazione andranno in .BSS o .Data dell'output dei file. Dati inizializzati in BSS, non inizializzati in DATA.

La differenza tra dati statici e globali sta nell'inclusione delle informazioni sui simboli nel file. I compilatori tendono a includere le informazioni sui simboli ma contrassegnano solo le informazioni globali in quanto tali.

Il linker rispetta queste informazioni. Le informazioni sui simboli per le variabili statiche vengono scartate o alterate in modo che le variabili statiche possano ancora essere referenziate in qualche modo (con opzioni di debug o simboli). In nessun caso le unità di compilazione possono essere influenzate quando il linker risolve prima i riferimenti locali.


3

L'ho provato con objdump e gdb, ecco il risultato che ottengo:

(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.

ecco il risultato objdump

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

Quindi, vale a dire, le quattro variabili si trovano nella sezione dati con lo stesso nome, ma con offset diverso.


C'è molto di più. Anche le risposte esistenti non sono complete. Solo per citare qualcos'altro: thread locali.
Adriano Repetti,

2

variabile statica memorizzata nel segmento di dati o nel segmento di codice come menzionato prima.
Puoi essere sicuro che non verrà allocato su stack o heap.
Non vi è alcun rischio di collisione poiché la staticparola chiave definisce l'ambito della variabile come file o funzione, in caso di collisione c'è un compilatore / linker per avvisarti.
Un bell'esempio



1

La risposta potrebbe dipendere molto bene dal compilatore, quindi probabilmente vuoi modificare la tua domanda (voglio dire, anche la nozione di segmenti non è obbligatoria per ISO C né ISO C ++). Ad esempio, su Windows un eseguibile non porta nomi di simboli. Un 'pippo' verrebbe spostato di 0x100, l'altro forse 0x2B0, e il codice di entrambe le unità di traduzione viene compilato conoscendo gli offset per il "loro" pippo.


0

entrambi verranno archiviati in modo indipendente, tuttavia se si desidera chiarire ad altri sviluppatori, è possibile che si desideri racchiuderli in spazi dei nomi.


-1

sai già che memorizza in bss (inizio blocco con il simbolo) indicato anche come segmento di dati non inizializzato o in segmento di dati inizializzato.

facciamo un semplice esempio

void main(void)
{
static int i;
}

la variabile statica sopra non è inizializzata, quindi passa al segmento di dati non inizializzato (bss).

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

e ovviamente è inizializzato di 10, quindi passa al segmento di dati inizializzato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.