Dlaczego książki mówią: „kompilator przydziela miejsce na zmienne w pamięci”?

18

Dlaczego książki mówią: „kompilator przydziela miejsce na zmienne w pamięci”. Czy nie jest to plik wykonywalny, który to robi? Mam na przykład na myśli, jeśli napiszę następujący program,

#include <iostream>
using namespace std;

int main()
{
   int foo;
   return 0;
}

i skompiluj go i uzyskaj plik wykonywalny (niech to będzie program.exe), teraz, jeśli uruchomię program.exe, ten plik wykonywalny sam poleci przydzielenie miejsca dla zmiennej foo. Prawda? Wyjaśnij, dlaczego książki wciąż mówią: „kompilator to zrobi ... zrób to”.

Peaceful Coder
źródło
11
o jakich książkach mówisz?
wirrbel,
4
Twoje „powiązane pytanie” powinno być osobnym pytaniem.
SShaheen,
sizeofPytanie znajduje się teraz na Dlaczego sizeof nazywamy operator kompilacji?
Kompilator generuje kod, który robi to lub to, co mówią. bezpośrednio lub pośrednio.
old_timer
Do Twojej wiadomości stackoverflow.com/questions/7372024/... i zauważ, że kompilator może zdecydować o zmianie lokalizacji postrzeganej zmiennej w pamięci ze względu na wyrównanie, np .: stackoverflow.com/questions/17774276/…
NoChance

Odpowiedzi:

20

Masz rację, że kompilator jako taki zniknął, gdy twój program faktycznie uruchomił się. A jeśli działa na innym komputerze, kompilator nie jest już nawet dostępny.

Myślę, że ma to na celu wyraźne rozróżnienie między pamięcią faktycznie przydzieloną przez twój własny kod. Kompilator wstawi kod do twojego programu, który dokonuje alokacji pamięci (np. Używając nowych, malloc lub podobnych poleceń).

Więc książki używają „kompilator robi to lub tamto” często mówiąc, że kompilator dodał kod, który nie jest wyraźnie wymieniony w twoich plikach kodu. To prawda, że ​​nie do końca tak się dzieje. Z tego punktu widzenia wiele rzeczy wymienionych w samouczkach byłoby błędnych, ale wymagałoby raczej szczegółowych wyjaśnień.

Thorsten Müller
źródło
Tak, w to wierzyłem. Dzięki za szybką odpowiedź!
The Peaceful Coder
12
kompilator przydziela zmienną foo na stosie, zastępując ją przesunięciem wskaźnika stosu podczas kompilacji. W ogóle nie jest to związane z alokacją sterty, którą wykonuje mallocet. glin.
wirrbel,
@holger: Twój sprzeciw jest oczywiście technicznie poprawny. Ale przestrzeń stosu jako taka musi być nadal przydzielona, ​​gdy program uruchomi się, zanim będzie można go użyć (co może się zdarzyć na różne sposoby, czasem w zależności od architektury procesora). Próbowałem znaleźć pewne szczegóły, jak to się dzieje, ale bez większego powodzenia.
thorsten müller
2
Myślę, że rozmiar stosu dla głównego wątku jest zarezerwowany przez linker, a następnie obsługiwany przez system operacyjny. W przypadku niestandardowych wątków jest bardziej podobny do alokacji sterty, tzn. Osoba dzwoniąca może ustalić rozmiar w czasie wykonywania.
wirrbel
4

To zależy od zmiennej. System operacyjny przydziela stertę, program przydzieli stos, a kompilator przydzieli miejsce dla globałów / statyki, tj. Są one wbudowane w sam plik exe. Jeśli przydzielisz 1 MB pamięci globalnej, rozmiar exe wzrośnie o co najmniej 1 MB

James
źródło
1
Nie o to chodzi w tym pytaniu.
Filip
2
w rzeczywistości jest bliżej pytania niż w przypadku innych odpowiedzi tutaj wymienionych.
wirrbel
@James Ah, to nie jest moje doświadczenie. Na przykład int test[256][1024]; int main(){ test[0][0]=2; return 0; } ten mały program ma przydzielony 1 MB, ale generuje tylko plik obiektowy o wielkości 1,4 Kb i plik wykonywalny o wielkości 8,4 Kb. Powinien jednak używać prawidłowej ilości pamięci RAM.
Garet Claborn,
1
Czy nie powinny to być tylko komendy alokacji przechowywane dla globałów? Jeśli na stałe zapisałeś wszystkie wartości przy użyciu takich prymitywów jak int lub char, rozmiar pliku wykonywalnego zdecydowanie by wzrósł o więcej niż liczba dodanych zmiennych. Takich jak int a1=1,a2=2,... aż do ... , a1048576=1048576;Tylko wtedy zdecydowanie dostaniesz coś większego niż 1mb.
Garet Claborn,
2
To wszystko umieszcza dane w sekcji BSS exe
James
4

co kompilator będzie zrobić, to wziąć swój kod i skompilować go do kodu maszynowego. To, co wspominasz, jest dobrym przykładem, w którym kompilator musi tylko tłumaczyć.

Na przykład kiedy piszesz

int foo;

Możesz to zobaczyć, gdy „Mówię kompilatorowi, aby [ w danych wyjściowych, które generuje ] zażądał od komputera zarezerwowania wystarczającej ilości pamięci RAM na int, do którego będę mógł później się odwoływać Kompilator prawdopodobnie użyje identyfikatora zasobu lub jakiegoś mechanizmu do śledzenia foo kod maszynowy, zamiast pisać asembler, możesz użyć foo w pliku tekstowym! Hurra !

Możesz więc spojrzeć na to, ponieważ kompilator pisze list ( lub może powieść / encyklopedię ) do wszystkich docelowych procesorów i urządzeń. List jest zapisywany w postaci sygnałów binarnych, które (ogólnie) mogą być tłumaczone na różne procesory poprzez zmianę celu. Każda „litera” i / lub kombinacja może wysyłać różnego rodzaju żądania i / lub dane - na przykład proszę przydzielić miejsce dla tej zmiennej, z której korzystał programista.

Garet Claborn
źródło
3

Powiedzenie „kompilator alokuje pamięć” może nie być faktyczne w dosłownym tego słowa znaczeniu, ale jest to metafora, która sugeruje we właściwy sposób.

Tak naprawdę dzieje się tak, że kompilator tworzy program, który przydziela własną pamięć. Tyle że to nie program przydziela pamięć, ale system operacyjny.

Tak naprawdę dzieje się tak, że kompilator tworzy program opisujący wymagania dotyczące pamięci, a system operacyjny bierze ten opis i używa go do przydzielania pamięci. Poza tym, że system operacyjny jest programem, a programy w rzeczywistości nic nie robią, opisują obliczenia wykonywane przez procesor. Tyle że procesor to tak naprawdę skomplikowany obwód elektroniczny, a nie antropomorficzny mały homonculus.

Ale sensowne jest myślenie o programach, kompilatorach i procesorach jako o małych ludziach mieszkających w komputerze, nie dlatego, że tak naprawdę są, ale dlatego, że jest to metafora, która dobrze pasuje do ludzkiego mózgu.

Niektóre metafory działają dobrze do opisywania rzeczy na jednym poziomie abstrakcji, ale nie działają również na innym poziomie. Jeśli myślisz na poziomie kompilatora, sensowne jest opisanie procesu generowania kodu, który spowoduje przydzielenie pamięci podczas kompilacji programu w rzeczywistości jako „alokację pamięci”. Jest na tyle blisko, że kiedy myślimy o tym, jak działa kompilator, mamy właściwy pomysł i nie jest tak długo rozwikłany, że zapominamy o tym, co robiliśmy. Jeśli spróbujemy użyć tej metafory na poziomie uruchomionego kompilowanego programu, wprowadzi to w dziwny sposób, co zauważyłeś.

Michael Shaw
źródło
0

To kompilator decyduje, gdzie przechowywać zmienną - może znajdować się w stosie lub w wolnym rejestrze. Niezależnie od decyzji magazynu podjętej przez kompilator, wygenerowany zostanie odpowiedni kod maszynowy umożliwiający dostęp do tej zmiennej i nie będzie można jej zmienić w czasie wykonywania. W tym sensie kompilator odpowiada za przydzielanie miejsca na zmienne, a końcowy program.exe po prostu ślepo działa w czasie wykonywania jak zombie.

Teraz nie myl tego z innym dynamicznym zarządzaniem pamięcią, takim jak malloc, nowy lub może to być własne zarządzanie pamięcią. Kompilatory zajmują się zmiennym przechowywaniem i dostępem, ale nie ma znaczenia, co rzeczywista wartość oznacza w innym frameworku / bibliotece. Na przykład:

byte* pointer = (byte*)malloc(...);

W czasie wykonywania malloc może zwrócić dowolną liczbę, ale kompilator nie dba o to, ważne jest tylko, gdzie ją zapisać.

Codism
źródło
0

Bardziej dokładnym sformułowaniem byłoby: - ​​„kompilator mówi modułowi ładującemu, aby zarezerwował miejsce dla zmiennych”

W środowisku C będą istniały trzy typy przestrzeni dla zmiennych: -

  • naprawiony blok zmiennych statycznych
  • Duży blok zmiennych „automatycznych” zwykle nazywany „stosem”. Funkcje przechwytują porcję przy wejściu i zwalniają po powrocie.
  • Duży blok zwany „stertą”, z którego przydzielana jest pamięć zarządzana przez program (przy użyciu malloc () lub podobnego interfejsu API zarządzania pamięcią.

W nowoczesnym systemie operacyjnym pamięć sterty nie będzie faktycznie zarezerwowana, ale przydzielana zgodnie z wymaganiami.

James Anderson
źródło
0

Tak, masz rację, w tym przypadku (deklarowanie zmiennej w funkcji) zdanie twojej książki jest prawdopodobnie nieprawidłowe: kiedy deklarujesz zmienną w funkcji, zostaje ona przydzielona na stosie po wejściu do funkcji. W każdym razie kompilator powinien zoptymalizować sytuację: jeśli funkcja nie jest rekurencyjna ( main()jest dobrym kandydatem do niej), można „przydzielić” jej czas kompilacji (w BSS).

(Jeśli jesteś ciekawy, gdzie znajdują się twoje zmienne, możesz to sprawdzić w nieprzyzwoity sposób (jeśli mimo to nie chcesz zbadać struktury pliku obj, dlaczego nie?), Możesz zadeklarować różne rodzaje zmiennych: stała, statyczne, dynamiczne, malloc()-alokowane itp. i wyświetlają ich adresy (użyj %Xformatera printf()dla lepszej czytelności). Zmienne znajdujące się na stosie będą miały bardzo różne adresy pamięci.)

ern0
źródło
0

Jedyną rzeczą wykonaną w czasie wykonywania będzie podbijanie stosu o określoną wartość. Tak więc kompilator decyduje wcześniej:

  • ile miejsca na stosie będzie potrzebne dla funkcji.
  • W jakiej odległości od wskaźnika stosu będzie zlokalizowana każda pojedyncza zmienna.

Można to nazwać „alokacją”, ale oczywiście w czasie kompilacji zajmuje miejsce tylko w modelu, który posiada kompilator uruchomionego programu.

Ingo
źródło