Gdzie w pamięci są moje zmienne przechowywane w C?


156

Biorąc pod uwagę, że pamięć jest podzielona na cztery segmenty: dane, stertę, stos i kod, gdzie są zmienne globalne, zmienne statyczne, stałe typy danych, zmienne lokalne (zdefiniowane i zadeklarowane w funkcjach), zmienne (w funkcji głównej), wskaźniki i dynamicznie przydzielana przestrzeń (za pomocą malloc i calloc) zostaje zapisana w pamięci?

Myślę, że zostałyby przydzielone w następujący sposób:

  • Zmienne globalne -------> dane
  • Zmienne statyczne -------> dane
  • Stałe typy danych -----> kod
  • Zmienne lokalne (zadeklarowane i zdefiniowane w funkcjach) --------> stos
  • Zmienne zadeklarowane i zdefiniowane w funkcji głównej -----> sterta
  • Wskaźniki (na przykład char *arr, int *arr) -------> sterta
  • Dynamicznie przydzielana przestrzeń (za pomocą malloc i calloc) --------> stos

Odnoszę się do tych zmiennych tylko z perspektywy C.

Proszę mnie poprawić, jeśli się mylę, ponieważ jestem nowy w C.


4
Typy nie są przechowywane w pamięci.

5
mainto tylko kolejna funkcja. Zmienne trafiają na stos, chyba że malloctak jak gdzie indziej.
simonc

4
wskaźniki są (zwykle) przechowywane na stosie. Pamięć, na którą wskazują (zwykle przydzielana przez malloc / calloc) znajduje się (zwykle) na stercie.
jpm

3
dynamicznie przydzielana przestrzeń (przy użyciu malloc, calloc) --------> heap
One Man Crew

3
zmienne zadeklarowane i zdefiniowane w funkcji głównej -----> stack
One Man Crew

Odpowiedzi:


217

Niektóre z nich masz rację, ale ktokolwiek napisał pytania, oszukał cię co najmniej w jednym pytaniu:

  • zmienne globalne -------> dane (poprawne)
  • zmienne statyczne -------> dane (poprawne)
  • stałe typy danych -----> kod i / lub dane. Rozważ literały łańcuchowe w sytuacji, gdy sama stała byłaby przechowywana w segmencie danych, a odniesienia do niej byłyby osadzone w kodzie
  • zmienne lokalne (zadeklarowane i zdefiniowane w funkcjach) --------> stos (poprawne)
  • zmienne zadeklarowane i zdefiniowane w mainfunkcji -----> heap również układają się w stos (nauczyciel próbował cię oszukać)
  • wskaźniki (np .: char *arr, int *arr) -------> dane sterty lub stosu, w zależności od kontekstu. C pozwala zadeklarować globalną lub staticwskaźnik, w którym to przypadku sam wskaźnik znalazłby się w segmencie danych.
  • dynamicznie przydzielane miejsca (za pomocą malloc, calloc, realloc) --------> stos kupie

Warto wspomnieć, że „stos” jest oficjalnie nazywany „klasą automatycznego przechowywania”.


6
Warto również wspomnieć, że sterta oficjalnie nie jest w ogóle nazywana. Przydzielona pamięć skądś pochodzi, w standardzie nie ma nazwy na to „skądś”.
Steve Jessop

6
W niektórych systemach (a mianowicie Linux i * BSD) istnieje również allocafunkcja, która działa podobnie do mallocalokacji stosu, ale działa.
Andreas Grapentin

Gdzie trafia zmienna const zadeklarowana wewnątrz metody?
Mahori

@Ravi W to samo miejsce idą pozostałe stałe (punkt # 3 powyżej).
dasblinkenlight

Używam GCC 4.8.1 i nie wydaje się, aby przechowywał zmienną const lokalną na główną w segmencie DANYCH. Poniżej znajduje się kod i mapa pamięci dla 3 takich programów: Kod 1: int main (void) {// char a [10] = "HELLO"; // 1 // const char a [10] = "HELLO"; // 2 powrót 0; } MAPA PAMIĘCI POWYŻEJ: dane tekstowe bss dec hex nazwa pliku 7264 1688 1040 9992 2708 a.exe MAPA PAMIĘCI DLA 2: dane tekstowe bss dec hex nazwa pliku 7280 1688 1040 10008 2718 a.exe MAPA PAMIĘCI DLA 3: dane tekstowe bss dec hex nazwa pliku 7280 1688 1040 10008 2718 a.exe
Mahori

124

Dla tych przyszłych gości, którzy mogą być zainteresowani poznaniem tych segmentów pamięci, piszę ważne punkty dotyczące 5 segmentów pamięci w C:

Niektóre ostrzeżenia:

  1. Za każdym razem, gdy wykonywany jest program w języku C, w pamięci RAM przydzielana jest część pamięci na wykonanie programu. Ta pamięć służy do przechowywania często wykonywanego kodu (danych binarnych), zmiennych programu itp. Poniższe segmenty pamięci mówią o tym samym:
  2. Zwykle istnieją trzy typy zmiennych:
    • Zmienne lokalne (zwane także zmiennymi automatycznymi w C)
    • Zmienne globalne
    • Zmienne statyczne
    • Możesz mieć globalne statyczne lub lokalne zmienne statyczne, ale powyższe trzy są typami nadrzędnymi.

5 segmentów pamięci w C:

1. Segment kodu

  • Segment kodu, zwany także segmentem tekstowym, to obszar pamięci zawierający często wykonywany kod.
  • Segment kodu jest często tylko do odczytu, aby uniknąć ryzyka przesłonięcia przez błędy programowania, takie jak przepełnienie bufora itp.
  • Segment kodu nie zawiera zmiennych programu, takich jak zmienna lokalna ( nazywana również zmienną automatyczną w C ), zmienne globalne itp.
  • W oparciu o implementację C segment kodu może również zawierać literały ciągu tylko do odczytu. Na przykład, gdy to zrobisz printf("Hello, world"), w segmencie kodu / tekstu zostanie utworzony ciąg „Hello, world”. Możesz to zweryfikować za pomocąsize polecenia w systemie operacyjnym Linux.
  • Dalsza lektura

Segment danych

Segment danych jest podzielony na dwie poniższe części i zazwyczaj znajduje się poniżej obszaru sterty lub w niektórych implementacjach powyżej stosu, ale segment danych nigdy nie leży między stertą a obszarem stosu.

2. Niezainicjowany segment danych

  • Ten segment jest również znany jako bss .
  • To jest część pamięci, która zawiera:
    1. Niezainicjowane zmienne globalne (w tym zmienne wskaźnikowe)
    2. Niezainicjowane stałe zmienne globalne .
    3. Niezainicjowane lokalne zmienne statyczne .
  • Każda globalna lub statyczna zmienna lokalna, która nie została zainicjowana, zostanie zapisana w niezainicjowanym segmencie danych
  • Na przykład: zmienna globalna int globalVar;lub statyczna zmienna lokalnastatic int localStatic; będą przechowywane w niezainicjowanym segmencie danych.
  • Jeśli zadeklarujesz zmienną globalną i zainicjujesz ją jako 0lubNULL to nadal przechodzi do niezainicjowanego segmentu danych lub bss.
  • Dalsza lektura

3. Zainicjowany segment danych

  • W tym segmencie znajdują się:
    1. Zainicjowane zmienne globalne (w tym zmienne wskaźnikowe)
    2. Zainicjowane stałe zmienne globalne .
    3. Zainicjowane lokalne zmienne statyczne .
  • Na przykład: zmienna globalna int globalVar = 1;lub statyczna zmienna lokalna static int localStatic = 1;będzie przechowywana w zainicjowanym segmencie danych.
  • Segment ten można dalej podzielić na zainicjowany obszar tylko do odczytu i zainicjowany obszar do odczytu i zapisu . Zainicjowane stałe zmienne globalne będą umieszczane w zainicjowanym obszarze tylko do odczytu, podczas gdy zmienne, których wartości można modyfikować w czasie wykonywania, będą umieszczane w zainicjowanym obszarze do odczytu i zapisu .
  • Rozmiar tego segmentu jest określany przez rozmiar wartości w kodzie źródłowym programu i nie zmienia się w czasie wykonywania .
  • Dalsza lektura

4. Segment stosu

  • Segment stosu służy do przechowywania zmiennych, które są tworzone wewnątrz funkcji ( funkcja może być funkcją główną lub funkcją zdefiniowaną przez użytkownika ), zmiennymi takimi jak
    1. Zmienne lokalne funkcji (w tym zmienne wskaźnikowe)
    2. Argumenty przekazane do funkcji
    3. Adres zwrotny
  • Zmienne przechowywane na stosie zostaną usunięte, gdy tylko zakończy się wykonywanie funkcji.
  • Dalsza lektura

5. Segment sterty

  • Ten segment ma wspierać dynamiczną alokację pamięci. Jeśli programista chce przeznaczyć trochę pamięci dynamicznie następnie w C to odbywa się za pomocą malloc, calloclubrealloc metod.
  • Na przykład, gdy int* prt = malloc(sizeof(int) * 2)wtedy osiem bajtów zostanie przydzielonych w stercie, a adres pamięci tej lokalizacji zostanie zwrócony i zapisany w ptrzmiennej. PlikptrZmienna będzie on albo segment stosu lub dane w zależności od sposobu ich deklarowanej / używane.
  • Dalsza lektura

Nie powinno to być zainicjowane zamiast niezainicjowanego w 3. Zainicjowany segment danych.
Suraj Jain

Re „przechowywane w niezainicjowanym segmencie danych” (wiele instancji): Czy masz na myśli „przechowywane niezainicjowane w segmencie danych” ?
Peter Mortensen

@PeterMortensen Mam na myśli obie rzeczy. „Każda globalna lub statyczna zmienna lokalna, która nie została zainicjowana, zostanie zapisana w niezainicjowanym segmencie danych”
hagrawal

jak możemy mieć globalną zmienną statyczną w C?

pod nagłówkiem „niektóre ostrzeżenia” znalazłem punkt „Możesz mieć globalne statyczne lub lokalne zmienne statyczne, ale powyższe trzy są typami nadrzędnymi”. w którym u odnosisz się do terminu „global static”. Chodzi mi o to, że zmienna statyczna nie może być globalna. tj. jeśli jakakolwiek zmienna ma być globalna, powinna być dostępna do zakończenia wykonywania programu. Proszę o wyjaśnienie i pomoc, jeśli się mylę.

11

Poprawione złe zdania

constant data types ----->  code //wrong

lokalne zmienne stałe -----> stos

zainicjowana globalna zmienna stała -----> segment danych

niezainicjowana globalna zmienna stała -----> bss

variables declared and defined in main function  ----->  heap //wrong

zmienne zadeklarowane i zdefiniowane w funkcji głównej -----> stos

pointers(ex:char *arr,int *arr) ------->  heap //wrong

dynamically allocated space(using malloc,calloc) --------> stack //wrong

wskaźniki (np: char * arr, int * arr) -------> rozmiar tej zmiennej wskaźnikowej będzie na stosie.

Weź pod uwagę, że przydzielasz pamięć o wielkości n bajtów (przy użyciu malloclub calloc) dynamicznie, a następnie ustawiasz zmienną wskaźnika, aby ją wskazywała. Teraz, gdy nbajty pamięci są w stercie, a zmienna wskaźnikowa wymaga 4 bajtów (jeśli maszyna 64-bitowa to 8 bajtów), które będą w stosie, aby przechowywać wskaźnik początkowy nbajtów fragmentu pamięci.

Uwaga: Zmienne wskaźnikowe mogą wskazywać pamięć dowolnego segmentu.

int x = 10;
void func()
{
int a = 0;
int *p = &a: //Now its pointing the memory of stack
int *p2 = &x; //Now its pointing the memory of data segment
chat *name = "ashok" //Now its pointing the constant string literal 
                     //which is actually present in text segment.
char *name2 = malloc(10); //Now its pointing memory in heap
...
}

dynamicznie przydzielane miejsce (przy użyciu malloc, calloc) --------> heap


wskaźniki mogą znajdować się na stosie lub stercie (patrz zwłaszcza: wskaźniki do wskaźników)
argentage

@airza: Teraz zaktualizowane. Właściwie aktualizowałem tylko te szczegóły :)
rashok

Czy na poniższej mapie pamięci możesz wskazać, gdzie jest stos i sterta? Nie jestem pewien, czy to poprawne pytanie, ponieważ stos i pamięć mogą mieć zastosowanie tylko w czasie wykonywania. MAPA PAMIĘCI: „dane tekstowe bss dec hex nazwa pliku 7280 1688 1040 10008 2718 a.exe”
Mahori

7

Popularna architektura pulpitu dzieli pamięć wirtualną procesu na kilka segmentów :

  • Segment tekstowy: zawiera kod wykonywalny. Wskaźnik instrukcji przyjmuje wartości z tego zakresu.

  • Segment danych: zawiera zmienne globalne (tj. Obiekty ze statycznym połączeniem). Podzielone na dane tylko do odczytu (takie jak stałe łańcuchowe) i niezainicjowane dane („BSS”).

  • Segment stosu: zawiera dynamiczną pamięć programu, tj. Wolną pamięć („stertę”) i lokalne ramki stosu dla wszystkich wątków. Tradycyjnie stos C i sterta C rosły do ​​segmentu stosu z przeciwnych końców, ale uważam, że praktyka ta została porzucona, ponieważ jest zbyt niebezpieczna.

Program AC zazwyczaj umieszcza obiekty ze statycznym czasem trwania w segmencie danych, dynamicznie przydzielane obiekty w wolnym magazynie i obiekty automatyczne na stosie wywołań wątku, w którym żyje.

Na innych platformach, takich jak stary tryb rzeczywisty x86 lub na urządzeniach wbudowanych, sytuacja może się radykalnie różnić.


„Uważam, że praktyka została porzucona, ponieważ jest zbyt niebezpieczna” - i uniemożliwia implementację wątków, ponieważ od tego czasu potrzeba więcej niż jednego stosu na program i nie mogą być wszystkie na końcu :-)
Steve Jessop

@SteveJessop: Tak, też tak myślałem. Ale wątki istniały od dawna - nie wiem, czy wszystkie stosy nici również odrosły do ​​tyłu, czy też wyrosłyby jak kupa ... zresztą teraz wszystko idzie w tym samym kierunku i są straże stron.
Kerrek SB

6

Odnoszę się do tych zmiennych tylko z perspektywy C.

Z punktu widzenia języka C liczy się tylko zasięg, zakres, powiązania i dostęp; Dokładny sposób mapowania elementów do różnych segmentów pamięci zależy od indywidualnej implementacji i będzie się różnić. Standardowy język nie mówić o segmentach pamięci w ogóle . Większość współczesnych architektur działa w większości w ten sam sposób; zmienne o zasięgu blokowym i argumenty funkcji zostaną przydzielone ze stosu, zmienne o zasięgu plikowym i statyczne zostaną przydzielone z segmentu danych lub kodu, pamięć dynamiczna zostanie przydzielona ze sterty, niektóre dane stałe będą przechowywane w segmentach tylko do odczytu itp.


3

Jedną rzeczą, o której należy pamiętać w przypadku przechowywania, jest zasada as-if . Kompilator nie musi umieszczać zmiennej w określonym miejscu - zamiast tego może umieścić ją w dowolnym miejscu tak długo, jak długo skompilowany program zachowuje się tak, jakby był uruchamiany na abstrakcyjnej maszynie C zgodnie z regułami abstrakcyjnej maszyny C. Dotyczy to wszystkich okresów przechowywania . Na przykład:

  • zmienną, do której nie można uzyskać dostępu do wszystkich, można całkowicie wyeliminować - nie ma ona pamięci ... nigdzie. Przykład - zobacz, jak jest 42w wygenerowanym kodzie asemblera, ale nie ma znaku 404.
  • zmienna z automatycznym czasem przechowywania, dla której adres nie jest zajęty, nie musi być w ogóle przechowywana w pamięci. Przykładem może być zmienna pętli.
  • zmienna, która jest constlub faktycznieconst nie musi być w pamięci. Przykład - kompilator może udowodnić, że foojest skuteczny consti wbudowuje jego użycie w kod. barma łącze zewnętrzne i kompilator nie może udowodnić, że nie zostałby zmieniony poza bieżącym modułem, dlatego nie jest wstawiany.
  • obiekt przydzielony mallocnie musi znajdować się w pamięci przydzielonej ze sterty! Przykład - zwróć uwagę, że kod nie ma wywołania malloci wartość 42 nie jest nigdy przechowywana w pamięci, jest przechowywana w rejestrze!
  • w ten sposób obiekt, który został przydzielony przez malloc i odniesienie jest tracony bez zwalniania obiektu bez free konieczności przecieku pamięci ...
  • obiekt przydzielony przez mallocnie musi znajdować się w stercie poniżej programu break ( sbrk(0)) w systemie Unixen ...

1

wskaźniki (np .: char * arr, int * arr) -------> heap

Nie, mogą znajdować się na stosie lub w segmencie danych. Mogą wskazywać w dowolnym miejscu.


Stwierdzenia dotyczące mainzmiennych alokowanych dynamicznie również są błędne
simonc

Nie tylko na stosie lub segmencie danych. Pomyśl o wskaźniku wskazującym na tablicę wskaźników. W tym przypadku wskaźniki w tablicy są przechowywane na stercie.
Sebi2020

1
  • Zmienne / zmienne automatyczne ---> sekcja stosu
  • Dynamicznie przydzielane zmienne ---> sekcja sterty
  • Zainicjalizowane zmienne globalne -> sekcja danych
  • Niezainicjalizowane zmienne globalne -> sekcja danych (bss)
  • Zmienne statyczne -> sekcja danych
  • Stałe łańcuchowe -> sekcja tekstowa / sekcja kodu
  • Funkcje -> sekcja tekstowa / sekcja kodu
  • Kod tekstowy -> sekcja tekstowa / sekcja kodowa
  • Rejestry -> rejestry procesora
  • Wejścia wiersza poleceń -> sekcja środowiska / wiersza poleceń
  • Zmienne środowiskowe -> sekcja środowiska / wiersza poleceń

Co to jest sekcja środowiska / wiersza poleceń? Czy istnieją w Linuksie?
Haoyuan Ge

-1

Minimalne uruchamialne przykłady w systemie Linux z analizą dezasemblacji

Ponieważ jest to szczegół implementacji, który nie jest określony przez standardy, przyjrzyjmy się tylko, co kompilator robi w określonej implementacji.

W tej odpowiedzi albo podam link do konkretnych odpowiedzi, które wykonują analizę, albo przedstawię analizę bezpośrednio tutaj i podsumuję wszystkie wyniki tutaj.

Wszystkie są w różnych wersjach Ubuntu / GCC, a wyniki są prawdopodobnie dość stabilne we wszystkich wersjach, ale jeśli znajdziemy jakieś różnice, określmy bardziej precyzyjne wersje.

Zmienna lokalna wewnątrz funkcji

Czy to mainczy jakakolwiek inna funkcja:

void f(void) {
    int my_local_var;
}

Jak pokazano na: Co oznacza <wartość zoptymalizowana na zewnątrz> w gdb?

  • -O0: stos
  • -O3: rejestruje, jeśli się nie rozlewają, stosuj inaczej

Aby dowiedzieć się, dlaczego stos istnieje, zobacz: Jaka jest funkcja instrukcji push / pop używanych w rejestrach w asemblerze x86?

Zmienne globalne i staticzmienne funkcyjne

/* BSS */
int my_global_implicit;
int my_global_implicit_explicit_0 = 0;

/* DATA */
int my_global_implicit_explicit_1 = 1;

void f(void) {
    /* BSS */
    static int my_static_local_var_implicit;
    static int my_static_local_var_explicit_0 = 0;

    /* DATA */
    static int my_static_local_var_explicit_1 = 1;
}

char * i char c[]

Jak pokazano na: Gdzie są przechowywane zmienne statyczne w C i C ++?

void f(void) {
    /* RODATA / TEXT */
    char *a = "abc";

    /* Stack. */
    char b[] = "abc";
    char c[] = {'a', 'b', 'c', '\0'};
}

DO ZROBIENIA, czy na stosie zostaną również umieszczone bardzo duże literały ciągów? Albo .data? A może kompilacja się nie udaje?

Argumenty funkcji

void f(int i, int j);

Musi przejść przez odpowiednią konwencję telefoniczną, np .: https://en.wikipedia.org/wiki/X86_calling_conventions dla X86, która określa konkretne rejestry lub lokalizacje stosu dla każdej zmiennej.

Następnie, jak pokazano w Co oznacza <wartość zoptymalizowana na zewnątrz> w gdb? , -O0a następnie wysysa wszystko do stosu, -O3próbując jednocześnie używać rejestrów tak często, jak to możliwe.

Jeśli jednak funkcja zostanie wstawiona, są traktowani tak, jak zwykli lokalni.

const

Uważam, że nie ma to znaczenia, ponieważ można to odrzucić.

I odwrotnie, jeśli kompilator jest w stanie określić, że niektóre dane nigdy nie są zapisywane, teoretycznie mógłby umieścić je w, .rodatanawet jeśli nie jest to const.

Analiza TODO.

Wskaźniki

Są to zmienne (zawierające adresy, które są liczbami), tak samo jak cała reszta :-)

malloc

Pytanie nie ma większego sensu malloc, ponieważ mallocjest funkcją, a w:

int *i = malloc(sizeof(int));

*i jest zmienną, która zawiera adres, więc przypada na powyższy przypadek.

Jeśli chodzi o to, jak malloc działa wewnętrznie, kiedy to nazywasz, jądro Linuksa oznacza określone adresy jako zapisywalne w swoich wewnętrznych strukturach danych, a kiedy program dotyka ich początkowo, pojawia się błąd i jądro włącza tabele stron, co umożliwia dostęp dzieje się bez segfaul: Jak działa stronicowanie x86?

Zauważ jednak, że jest to w zasadzie dokładnie to, co execwywołanie systemowe robi pod maską, kiedy próbujesz uruchomić plik wykonywalny: zaznacza strony, na które chce załadować, i zapisuje tam program, zobacz także: Jak jądro pobiera wykonywalny plik binarny działający pod linux? Z wyjątkiem tego, że execma pewne dodatkowe ograniczenia dotyczące miejsca, do którego należy załadować (np. Czy kodu nie można przenieść ).

Dokładne wywołanie systemowe używane mallocjest mmapwe współczesnych implementacjach 2020, a w przeszłości brkbyło używane: Czy malloc () używa brk () lub mmap ()?

Biblioteki dynamiczne

Zasadniczo pobierz się mmapdo pamięci: /unix/226524/what-system-call-is-used-to-load-libraries-in-linux/462710#462710

envinroment zmienne i mainsargv

Powyższy stos początkowy: /unix/75939/where-is-the-environment-string-actual-stored TODO, czemu nie w .data?

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.