Gdzie są przechowywane zmienne statyczne w C i C ++?

180

W jakim segmencie (.BSS, .DATA, inny) pliku wykonywalnego przechowywane są zmienne statyczne, aby nie powodowały kolizji nazw? Na przykład:


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

Jeśli skompiluję oba pliki i połączę je z głównym, który wielokrotnie wywołuje fooTest () i barTest, instrukcje printf zwiększają się niezależnie. Ma to sens, ponieważ zmienne foo i bar są lokalne dla jednostki tłumaczeniowej.

Ale gdzie przydzielane jest miejsce?

Dla jasności założono, że masz zestaw narzędzi, który wyprowadzałby plik w formacie ELF. Dlatego uważam , że w pliku wykonywalnym musi być trochę miejsca zarezerwowanego dla tych zmiennych statycznych.
Dla celów dyskusji załóżmy, że używamy łańcucha narzędzi GCC.

Benoit
źródło
1
Większość osób mówi ci, że powinny być przechowywane w sekcji .DATA zamiast odpowiadać na twoje pytanie: gdzie dokładnie w sekcji .DATA i jak możesz znaleźć gdzie. Widzę, że już zaznaczyłeś odpowiedź, więc już wiesz, jak ją znaleźć?
lukmac
dlaczego zainicjowane i niezainicjowane są umieszczone w różnych sekcjach: linuxjournal.com/article/1059
mhk
1
Pamięć przydzielona do zmiennych globalnych / statycznych w czasie wykonywania nie ma nic wspólnego z ich rozpoznawaniem nazw, co dzieje się podczas kompilacji / łączenia. Po zbudowaniu pliku wykonywalnego - nie ma już nazw.
valdo
2
To pytanie jest pozbawione sensu, ponieważ opiera się na fałszywym założeniu, że „kolizja nazw” niewyportowanych symboli jest czymś, co może istnieć. Fakt, że nie ma uzasadnionego pytania, może wyjaśnić, jak tragiczne są niektóre odpowiedzi. Trudno w to uwierzyć, więc niewiele osób to dostało.
underscore_d

Odpowiedzi:

131

To, dokąd zmierza Twoja statystyka, zależy od tego, czy są one inicjowane zerem . dane statyczne zainicjowane zerem idą do .BSS (Block Started by Symbol) , dane niezainicjalizowane zerem idą do .DATA

Don Neufeld
źródło
50
Przez „niezainicjowany niezerowy” prawdopodobnie masz na myśli „zinicjalizowany, ale z czymś innym niż 0”. Ponieważ w C / C ++ nie ma czegoś takiego jak „niezainicjowane” dane statyczne. Wszystko statyczne jest domyślnie inicjowane na zero.
AnT
21
@ Don Neufeld: twoja odpowiedź w ogóle nie odpowiada na pytanie. Nie rozumiem, dlaczego jest to akceptowane. Ponieważ zarówno „foo”, jak i „bar” nie są inicjowane zerem. Pytanie brzmi, gdzie umieścić dwie zmienne statyczne / globalne o tej samej nazwie w .bss lub .data
lukmac
Użyłem implementacji, w których weszły dane statyczne, które zostały wyraźnie zainicjowane zerem .data, oraz dane statyczne bez inicjatora .bss.
MM
1
@MM W moim przypadku, czy element statyczny jest niezainicjowany (domyślnie zainicjowany na 0), czy jawnie zainicjowany na 0, w obu przypadkach sumuje się w sekcji .bss.
cbinder
Czy te informacje są specyficzne dla określonego typu pliku wykonywalnego? Zakładam, ponieważ nie określiłeś, że dotyczy to przynajmniej plików wykonywalnych ELF i Windows PE, ale co z innymi typami?
Jerry Jeremiah
116

Po załadowaniu programu do pamięci jest on podzielony na różne segmenty. Jednym z segmentów jest segment danych . Segment danych jest dalej podzielony na dwie części:

Zainicjowany segment danych: Tutaj przechowywane są wszystkie dane globalne, statyczne i stałe.
Niezainicjowany segment danych (BSS): Wszystkie niezainicjowane dane są przechowywane w tym segmencie.

Oto schemat wyjaśniający tę koncepcję:

wprowadź opis zdjęcia tutaj


tutaj jest bardzo dobry link wyjaśniający te pojęcia:

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

karn
źródło
Powyższa odpowiedź mówi, że 0 zainicjowanych przechodzi do BSS. Czy 0 zainicjowanych oznacza niezainicjowany czy 0 jako takich? Jeśli to oznacza 0 jako takie, to myślę, że powinieneś uwzględnić to w swojej odpowiedzi.
Viraj
Dane stałe nie są przechowywane w segmencie .data, ale w .const segmencie sekcji tekstowej.
user10678,
Zamiast tego („ Zainicjowany segment danych : wszystkie globalne, statyczne i stałe dane są tutaj przechowywane. Niezainicjowany segment danych (BSS) : Wszystkie niezainicjowane dane są przechowywane w tym segmencie.”), Myślę, że powinien powiedzieć: zainicjowany segment danych : Wszystkie globalne i statyczne zmienne, które zostały zainicjowane na niezerową wartość, a wszystkie dane stałe przechowywane są tutaj. segmentu niezainicjowaną danych (BSS) : Wszystkie globalne i statyczne zmienne, które były albo Niezainicjowany lub zainicjowany do zera są przechowywane w tym segmencie. ”).
Gabriel Staples
Zauważ też, że o ile rozumiem, „zainicjowane dane” mogą składać się ze zainicjowanych zmiennych i stałych . Na mikrokontrolerze (np. STM32) zmienne inicjowane są domyślnie przechowywane w pamięci Flash i kopiowane do pamięci RAM podczas uruchamiania , a zainicjowane stałe są pozostawiane i przeznaczone do odczytu tylko z pamięci Flash , wraz z tekstem , który zawiera sam program i jest pozostawiony tylko
Gabriel Staples
Z tego diagramu zbieram więc to, że zmienne, które są globalne lub statyczne (ponieważ zmienne statyczne zachowują się jak zmienne globalne w czasie trwania), nie są ani na stercie, ani na stosie, ale raczej są alokowane w pamięci poza tymi dwoma. Czy to prawda? Podejrzewam, że mógłbym jeszcze raz rzucić okiem na skrypt łączący STM32, aby bardziej zbadać przydział pamięci.
Gabriel Staples
32

W rzeczywistości zmienną jest krotka (pamięć, zakres, typ, adres, wartość):

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

Zasięg lokalny może oznaczać lokalny dla jednostki translacyjnej (pliku źródłowego), funkcji lub bloku, w zależności od tego, gdzie jest zdefiniowany. Aby zmienna była widoczna dla więcej niż jednej funkcji, na pewno musi znajdować się w obszarze DANYCH lub BSS (w zależności od tego, czy odpowiednio została zainicjowana jawnie, czy nie). Następnie jest odpowiednio dopasowywany do wszystkich funkcji lub funkcji w pliku źródłowym.

yogeesh
źródło
21

Miejsce przechowywania danych będzie zależało od implementacji.

Jednak znaczenie statyczne to „powiązanie wewnętrzne”. Tak więc symbol jest wewnętrzny dla jednostki kompilacji (foo.c, bar.c) i nie można się do niego odwoływać poza tą jednostką kompilacji. Tak więc nie może być kolizji nazw.

Seb Rose
źródło
Nie. static keyworld ma przeciążone znaczenia: w takim przypadku static jest modyfikatorem pamięci, a nie modyfikatorem połączenia.
ugasoft
4
ugasoft: statyką poza funkcją są modyfikatory powiązań, wewnątrz są modyfikatory pamięci, w których na początku nie może wystąpić kolizja.
wnoise,
12

w obszarze „globalnym i statycznym” :)

W C ++ istnieje kilka obszarów pamięci:

  • sterta
  • bezpłatny sklep
  • stos
  • globalny i statyczny
  • const

Zobacz tutaj szczegółową odpowiedź na swoje pytanie:

Poniżej podsumowano główne odrębne obszary pamięci programu C ++. Zauważ, że niektóre nazwy (np. „Kupa”) nie pojawiają się jako takie w wersji roboczej [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.
ugasoft
źródło
12

Nie wierzę, że nastąpi kolizja. Użycie statycznego na poziomie pliku (funkcje zewnętrzne) oznacza zmienną jako lokalną dla bieżącej jednostki kompilacji (pliku). Nigdy nie jest widoczny poza bieżącym plikiem, więc nigdy nie musi mieć nazwy, której można używać zewnętrznie.

Używanie statycznego wewnątrz funkcji jest inne - zmienna jest widoczna tylko dla funkcji (statycznej lub nie), jej wartość jest zachowywana w wywołaniach tej funkcji.

W efekcie statyczny robi dwie różne rzeczy w zależności od tego, gdzie się znajduje. W obu przypadkach widoczność zmiennych jest jednak ograniczona w taki sposób, że można łatwo zapobiec konfliktom przestrzeni nazw podczas łączenia.

Powiedziawszy to, wierzę, że będzie przechowywany w DATAsekcji, która zwykle ma zmienne, które są inicjowane na wartości inne niż zero. Jest to oczywiście szczegół implementacji, a nie coś nakazanego przez standard - zależy tylko na zachowaniu, a nie na tym , jak rzeczy są wykonywane pod przykryciem.

paxdiablo
źródło
1
@paxdiablo: wspomniałeś o dwóch typach zmiennych statycznych. Do którego z nich odnosi się ten artykuł ( en.wikipedia.org/wiki/Data_segment )? Segment danych zawiera także zmienne globalne (które są dokładnie przeciwne do zmiennych statycznych). 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, ma to związek z widocznością. W segmencie mogą znajdować się rzeczy lokalne dla jednostki kompilacyjnej, inne są w pełni dostępne. Jeden przykład: pomyśl o każdej jednostce komputerowej, która wnosi blok do segmentu DANYCH. Wie, gdzie wszystko jest w tym bloku. Publikuje także adresy tych rzeczy w bloku, do którego ma mieć dostęp inne jednostki obliczeniowe. Linker może rozpoznać te adresy w czasie łącza.
paxdiablo,
11

Jak samemu to znaleźć objdump -Sr

Aby faktycznie zrozumieć, co się dzieje, musisz zrozumieć relokację linkera. Jeśli nigdy tego nie dotykałeś, najpierw przeczytaj ten post .

Przeanalizujmy przykład ELF Linux x86-64, aby zobaczyć go sami:

#include <stdio.h>

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

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

Połącz z:

gcc -ggdb -c main.c

Dekompiluj kod za pomocą:

objdump -Sr main.o
  • -S dekompiluje kod z oryginalnym pomieszanym źródłem
  • -r pokazuje informacje o relokacji

Wewnątrz dekompilacji fwidzimy:

 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

i .data-0x4mówi, że przejdzie do pierwszego bajtu .datasegmentu.

-0x4Jest tam, ponieważ używamy RIP Adresowanie względne, więc %ripw instrukcji i R_X86_64_PC32.

Jest to wymagane, ponieważ RIP wskazuje na następującą instrukcję, która rozpoczyna 4 bajty, po 00 00 00 00których następuje przeniesienie. Wyjaśniłem to bardziej szczegółowo na stronie : https://stackoverflow.com/a/30515926/895245

Następnie, jeśli zmodyfikujemy źródło i = 1i wykonamy tę samą analizę, stwierdzimy, że:

  • static int i = 0 trwa .bss
  • static int i = 1 trwa .data
Ciro Santilli
źródło
7

Oto jak (łatwo zrozumieć):

stos, stos i dane statyczne

Yousha Aleayoub
źródło
6

To zależy od używanej platformy i kompilatora. Niektóre kompilatory przechowują bezpośrednio w segmencie kodu. Zmienne statyczne są zawsze dostępne tylko dla bieżącej jednostki tłumaczeniowej, a nazwy nie są eksportowane, dlatego kolizje nazw nigdy nie występują.

trotterdylan
źródło
5

Dane zadeklarowane w jednostce kompilacyjnej trafią do .BSS lub .Data danych wyjściowych tych plików. Zainicjowane dane w BSS, niezainicjowane w DANYCH.

Różnica między danymi statycznymi i globalnymi polega na włączeniu informacji o symbolu do pliku. Kompilatory zwykle zawierają informacje o symbolach, ale jako takie zaznaczają tylko informacje globalne.

Linker szanuje tę informację. Informacje o symbolach dla zmiennych statycznych są odrzucane lub zniekształcane, dzięki czemu do zmiennych statycznych można nadal odwoływać się w jakiś sposób (za pomocą opcji debugowania lub symboli). W żadnym przypadku nie ma to wpływu na jednostki kompilacji, ponieważ linker najpierw rozpoznaje odniesienia lokalne.

itj
źródło
3

Próbowałem z objdump i gdb, oto wynik, co otrzymuję:

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

oto wynik 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

To znaczy, że twoje cztery zmienne znajdują się w sekcji danych o tej samej nazwie, ale z różnym przesunięciem.

Dan
źródło
Jest o wiele więcej niż to. Nawet istniejące odpowiedzi nie są kompletne. Żeby wspomnieć o czymś innym: miejscowi wątkowie.
Adriano Repetti
2

zmienna statyczna przechowywana w segmencie danych lub segmencie kodu, jak wspomniano wcześniej.
Możesz być pewien, że nie zostanie on przydzielony na stosie lub stosie.
Nie ma ryzyka kolizji, ponieważ staticsłowo kluczowe definiuje zakres zmiennej, która ma być plikiem lub funkcją, w przypadku kolizji istnieje kompilator / linker, który Cię ostrzega.
Ładny przykład

Ilya
źródło
1

Odpowiedź może bardzo zależeć od kompilatora, więc prawdopodobnie chcesz edytować swoje pytanie (to znaczy, nawet pojęcie segmentów nie jest wymagane przez ISO C ani ISO C ++). Na przykład w systemie Windows plik wykonywalny nie nosi nazw symboli. Jeden „foo” byłby odsunięty 0x100, drugi być może 0x2B0, a kod z obu jednostek tłumaczeniowych jest kompilowany znając przesunięcia dla „ich” foo.

MSalters
źródło
0

oba będą przechowywane niezależnie, jednak jeśli chcesz to wyjaśnić innym programistom, możesz zawinąć je w przestrzenie nazw.

Robert Gould
źródło
-1

wiesz już, że albo przechowuje w bss (blok zaczyna się od symbolu) zwany także niezainicjowanym segmentem danych lub w zainicjowanym segmencie danych.

weźmy prosty przykład

void main(void)
{
static int i;
}

powyższa zmienna statyczna nie jest inicjowana, więc trafia do niezainicjowanego segmentu danych (bss).

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

i oczywiście został zainicjowany przez 10, więc przechodzi do zainicjowanego segmentu danych.

Anurag Bhakuni
źródło