Выполнение программы — различия между версиями
Phil (обсуждение | вклад) |
Phil (обсуждение | вклад) |
||
| Строка 77: | Строка 77: | ||
Так как <tex>g</tex> задана конвенция вызова <tex>\_\_stdcall</tex>, после ее вызова вершина стека не будет сдвигаться. Однако, <tex>f</tex> задана конвенция вызова <tex>\_\_cdecl</tex>, поэтому аргумент не удалится со стека при вызове <tex>ret</tex>, и при следующем запросе адреса возврата будет выдан адрес <tex>239</tex>, скорее всего, не являющийся валидным. | Так как <tex>g</tex> задана конвенция вызова <tex>\_\_stdcall</tex>, после ее вызова вершина стека не будет сдвигаться. Однако, <tex>f</tex> задана конвенция вызова <tex>\_\_cdecl</tex>, поэтому аргумент не удалится со стека при вызове <tex>ret</tex>, и при следующем запросе адреса возврата будет выдан адрес <tex>239</tex>, скорее всего, не являющийся валидным. | ||
==Размещение локальных переменных== | ==Размещение локальных переменных== | ||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | ===Архив=== | ||
| + | Рассмотрим процесс выделения памяти в куче на примере простой программы: | ||
| + | |||
| + | [[Файл:vmmap_before.png|right|thumb|200px|Память не выделена]] | ||
| + | [[Файл:vmmap_after.png||right|thumb|200px|Выделена память под массив]] | ||
| + | [[Файл:vmmap_delete.png||right|thumb|200px|Память освобождена]] | ||
| + | |||
| + | int main() | ||
| + | { | ||
| + | int *a = new int [1000000]; | ||
| + | delete [] a; | ||
| + | return 0; | ||
| + | } | ||
| + | |||
| + | До выполнения | ||
| + | int *a = new int [1000000]; | ||
| + | куча в адресном пространстве программы занимает <tex>1152kB</tex>, а после выполнения команды - <tex>5060kB</tex>. Значит, память была выделена именно в куче, и объем выделенной памяти равен <tex>3908kB</tex>, что почти в точности соответствует объему массива <tex>int</tex> из <tex>1000000</tex> элементов при выделении <tex>4B</tex> на элемент. | ||
| + | |||
| + | После выполнения | ||
| + | delete [] a; | ||
| + | размер кучи снова равен <tex>1152kB</tex>, как и до выделение памяти под массив. | ||
| + | |||
| + | |||
| + | {{TODO|t=Показать в адресном пространстве ран-тайм, библиотеки ядра}} | ||
Версия 20:35, 11 июля 2011
Содержание
Последовательное выполнение
При старте процесса создается единица выполнения кода — поток. Потоку передается точка входа — адрес нахождения первой команды программы в адресном пространстве. Можно считать, что поток передает команду и ее аргументы процессору, тот выполняет ее и сообщает результат, после этого поток переходит на следующую команду с помощью счетчика команд. Каждая команда в адресном пространстве занимает несколько байт, не обязательно постоянное число.
TODO: запилить пруфлинк на то, как на самом деле
TODO: можно написать про переключение потоков
Вызов функций
Адрес возврата
Представим, что в нашем коде есть функция, не принимающая аргументов, ничего не возвращающая, и ничего не выполняющая:
void f()
{
}
Для ее вызова в адресном пространстве будет использоваться команда , которая переведет поток на начало функции , а для возврата обратно в место, откуда функция была вызвана — команда . Возникает вопрос: как найти адрес команды, к которой надо перейти после выполнения ? Можно сохранить этот адрес в переменной, но, в таком случае, нельзя будет вызывать функции из других функций, или создавать рекурсии:
void f()
{
}
void g()
{
f();
}
Поэтому, в адресном пространстве существует стек, на который при вызове функции кладется адрес возврата. Команда удаляет текущий адрес возврата с вершины стека, и устанавливает счетчик команд по этому адресу.
Передача параметров
Представим, что теперь нам необходимо вызвать чуть более сложную функцию:
void f(int a, int b)
{
}
Передавать аргументы внутрь функции удобно, также используя стек. Во всех конвенциях вызова C++ сначала передаются аргументы в обратном порядке, а потом - адрес возврата, то есть, вызов будет исполнен как
push 566 push 239 call f
, где — команда "положить на стек n". Адрес возврата положится на стек при вызове команды .
Конвенции вызова
Отличия конвенций
Заметим, что мы положили аргументы на стек, но не удалили их оттуда, поэтому при следующем вызове будет взят неверный адрес возврата. В С++ существует несколько конвенций вызова функций, рассмотрим 2 из них: и .
При использовании аргументы удалятся из стека при выполнении команды , а при использовании после возврата из функции необходимо переместить вершину стека командой . Рассмотрим пример:
void __cdecl f(int a, int b)
{
}
void __cdecl g(int a, int b, int c)
{
}
int main()
{
f(2, 3);
g(1, 2, 3);
return 0;
}
При выполнении этого кода можно будет сэкономить на количестве команд, не удаляя аргументы и со стека:
push 3 push 2 call f push 1 call g add esp, 12
Конфликт конвенций
По умолчанию в С++ используются конвенции вызова и , которая используется для методов класса и отличается тем, что кладет один аргумент в регистр процессора.
Посмотрим, что произойдет, если искусственно создать конфликт конвенций вызова:
void __decl f(int a)
{
}
int main()
{
void __stdcall (*g)(int);
g = (void __stdcall (*)(int)) &f;
g(239);
return 0;
}
Так как задана конвенция вызова , после ее вызова вершина стека не будет сдвигаться. Однако, задана конвенция вызова , поэтому аргумент не удалится со стека при вызове , и при следующем запросе адреса возврата будет выдан адрес , скорее всего, не являющийся валидным.
Размещение локальных переменных
Архив
Рассмотрим процесс выделения памяти в куче на примере простой программы:
int main()
{
int *a = new int [1000000];
delete [] a;
return 0;
}
До выполнения
int *a = new int [1000000];
куча в адресном пространстве программы занимает , а после выполнения команды - . Значит, память была выделена именно в куче, и объем выделенной памяти равен , что почти в точности соответствует объему массива из элементов при выделении на элемент.
После выполнения
delete [] a;
размер кучи снова равен , как и до выделение памяти под массив.
TODO: Показать в адресном пространстве ран-тайм, библиотеки ядра
