Почему книги говорят: "компилятор выделяет пространство для переменных в памяти"?
Почему книги говорят: "компилятор выделяет пространство для переменных в памяти". Разве это не исполняемый файл? Я имею в виду, например, если я пишу следующую программу,
#include <iostream>
using namespace std;
int main()
{
int foo = 0;
cout<<foo;
return 0;
}
и скомпилировать его и получить исполняемый файл (пусть это будет program.exe), теперь, если я запустил program.exe, этот исполняемый файл сам будет командовать для выделения некоторого пространства для переменной foo. Не так ли? Пожалуйста, объясните, почему книги продолжают говорить: "компилятор сделает это... сделайте это", тогда как на самом деле это скомпилированный исполняемый файл.
Добавляя к этому вопросу другой связанный вопрос, почему sizeof
называется оператором времени компиляции? Разве это не оператор времени выполнения?
Ответы
Ответ 1
Когда мы нанимаем архитектора для проектирования дома, он определяет размер комнат и т.д. и информирует рабочих (рабочих) об этом. Рабочие выполняют работу соответственно. Но все-таки мы бы сказали: "Архитектор устроил дом таким образом" и не "Работник сделал дом таким образом".
Работник просто выполняет шаги, определенные архитектором. Компилятор на самом деле выполняет всю работу по проверке и определению того, сколько памяти будет выделено и т.д. Во время выполнения, а затем последуют эти инструкции.
Ответ 2
Технически акт создания самого пространства выполняется во время выполнения, однако компилятор должен выяснить, сколько места зарезервировано в стеке в вашем случае для вашей переменной foo
.
Компилятор знает размер типа int
и поэтому может генерировать правильную инструкцию ассемблера, которая зарезервирует достаточно места в стеке, чтобы позволить foo
жить там.
Если вы посмотрите на созданный ниже ассемблер (используя MSVC2012) для программы, которую вы показали, я прокомментировал некоторые из них, чтобы показать вам, что происходит:
#include "stdafx.h"
#include <iostream>
using namespace std;
int main()
{
//Setup stack frame for main by storing the stack pointer from the calling function and
//reserving space for local variables and storing commonly used registers on the stack
002E4390 push ebp
002E4391 mov ebp,esp
// reserve space for local variables, which is 204 bytes here, no idea why so much.
// this is where the compiler calculated the size of your foo and added that to whatever else needs to be stored on the stack. Subtract from stack pointer (esp) because stack grows downward.
002E4393 sub esp,0CCh
002E4399 push ebx
002E439A push esi
002E439B push edi
002E439C lea edi,[ebp-0CCh] // load effective address of [ebp-0CCh], which I suspect would be your foo variable into edi register
002E43A2 mov ecx,33h
002E43A7 mov eax,0CCCCCCCCh
002E43AC rep stos dword ptr es:[edi] //fill block of memory at es:[edi] with stuff
int foo;
return 0;
002E43AE xor eax,eax //set eax to zero for return value
}
// restore everything back to how it was before main was called
002E43B0 pop edi
002E43B1 pop esi
002E43B2 pop ebx
002E43B3 mov esp,ebp
002E43B5 pop ebp
002E43B6 ret
Ответ 3
Это просто свободное использование терминологии. Конечно, компилятор не выделяет память для программы. Более точное описание заключается в том, что он сообщает во время выполнения, сколько памяти выделяется при запуске программы.
Пока программа фактически не запущена, она не находится в памяти (если она не загружается динамически, но даже это происходит во время выполнения, поэтому из области компилятора), поэтому нет памяти, о которой можно было бы говорить.
О том, что эти книги говорят о распределении переменных, размер которых известен во время компиляции, в отличие от динамического распределения cin >> x; int * y = new[x];
, где размер неизвестен.
Ответ 4
В нем говорится, что компилятор выделяет пространство для переменных в памяти, потому что в противном случае вам нужно выделять (и бесплатную!) память с помощью new/malloc
и т.д.
Ответ 5
Конечно, компилятор не "выделяет пространство для переменных". Компилятор генерирует код, который выделяет пространство для переменных в памяти.
т.е. если у вас есть
int foo;
foo = 1;
в исходном коде, компилятор может генерировать код типа
int* fooPtr = allocate sizeof(int)
*fooPtr = 1;
В архитектуре x86 обычно, что allocate
вещь будет одной командой сборки:
sub esp, 4 ; allocate 4 == sizeof(int) bytes on stack
; now the value of "esp" is equal to the address of "foo",
; i.e. it "fooPtr"
mov [esp], 1 ; *fooPtr = 1
Если у вас несколько локальных переменных, компилятор упакует их в структуру и распределяет их вместе:
int foo;
int bar;
bar = 1;
будет скомпилирован как
struct Variables { int foo; int bar; };
Variables* v = allocate sizeof(Variables);
v->bar = 1;
или
sub esp, 4+4 ; allocate sizeof(Variables) on stack
mov [esp + 4], 1 ; where 4 is offsetof(Variables, bar)
Ответ 6
Компилятор генерирует машинные инструкции и определяет, какие локальные переменные адреса памяти будут занимать. Каждой локальной переменной присваивается адрес относительно вершины стека, например, foo
будет считаться адресом памяти stack_pointer
. Если у вас есть переменная foo2
, она будет размещена по адресу stack_pointer + 4
, где 4 - размер int
.
При доступе к локальной переменной foo
компилятор заменит адрес, хранящийся в stack_pointer
. Аппаратное обеспечение имеет специальный регистр stack_pointer
, который всегда указывает на верхнюю часть текущего стека.
Компилятор знает, какой размер имеет каждая переменная, потому что он отвечает за поиск объявлений struct
или class
и выясняет, как он выложен в памяти. Таким образом, sizeof
известно во время компиляции и рассматривается как постоянное выражение. Известно, что такие примитивные типы, как int
, имеют определенный размер, например sizeof(int)
составляет 4.
Ответ 7
Предложите вам прочитать компиляцию. Сосредоточьтесь на фазе хранения, ваш запрос будет разрешен.
После того, как программа скомпилирует свой преобразованный код объекта, который является кодом языка ассемблера. каждая строка языковой программы высокого уровня переводится на многие шаги ассемблерного языка. Переведенная программа помещается в ассемблер, который выполняется.
Фаза назначения STORAGE в конструкции компилятора преобразуется в таблицу операций машины (инструкции MOT на уровне сборки). Это место, где выполняется распределение пространства для переменных/регистров. В С++ есть ключевое слово модификатора регистра.