it-swarm.it

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

In quale segmento (.BSS, .DATA, altro) di un file eseguibile sono memorizzate le variabili statiche in modo che non abbiano una collisione di nomi? 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 nell'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, io credo che lì abbia ci sia uno spazio riservato nel file eseguibile per quelle variabili statiche.
Ai fini della discussione, supponiamo che utilizziamo la toolchain GCC.

162
Benoit

La destinazione delle statistiche dipende dal fatto che siano inizializzati a zero o meno. i dati statici zero inizializzati vanno inseriti in . BSS (blocco avviato dal simbolo) , non i dati a zero inizializzati vanno in . DATA

119
Don Neufeld

Quando un programma viene caricato in memoria, è organizzato in diversi segmenti. Uno del segmento è Segmento DATA. Il segmento Dati è ulteriormente suddiviso in due parti:

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

Ecco un diagramma per spiegare questo concetto:

enter image description here


ecco un ottimo collegamento che spiega questi concetti:

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

102
karn

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

31
yogeesh

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.

21
Seb Rose

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.

L'uso statico all'interno di una funzione è diverso: la variabile è visibile solo per la funzione, è 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 altri casi, tuttavia, limita la visibilità della variabile per evitare scontri nello spazio dei nomi,

Detto questo, credo che sarebbe memorizzato in DATA che tende ad avere una variabile inizializzata. Il BSS originariamente stava per byte-set- <something> che conteneva variabili che non erano state inizializzate.

13
paxdiablo

Come trovarlo tu stesso con objdump -Sr

Per capire effettivamente cosa sta succedendo, è necessario comprendere il trasferimento del linker. Se non l'hai mai toccato, considera prima leggi 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 f vediamo:

 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 il .data-0x4 dice che andrà al primo byte di .data segmento.

Il -0x4 esiste perché stiamo utilizzando l'indirizzamento relativo RIP, quindi %rip nelle istruzioni e R_X86_64_PC32.

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

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

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

nell'area "globale e statica" :)

ci sono diverse aree di memoria in C++

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

vedi qui per una risposta dettagliata alla tua domanda

9
ugasoft

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 il motivo per cui non si verificano mai collisioni di nomi.

6
trotterdylan

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.

5
itj

Ecco come (facile da capire):

stack, heap and static data

3
Yousha Aleayoub

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 <[email protected]>
   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 <[email protected]>
   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.

2
Dan

Bene, questa domanda è un po 'troppo vecchia, ma dal momento che nessuno indica alcuna informazione utile: controlla il post di' mohit12379 'che spiega l'archivio di variabili statiche con lo stesso nome nella tabella dei simboli: http: //www.geekinterview. com/question_details/24745

2
lukmac

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

2
Ilya

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.

1
MSalters

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

0
Robert Gould