Страницы

Поиск по вопросам

вторник, 9 октября 2018 г.

Как на самом деле работает стек функции в С++?

Уважаемые коллеги! Мне хочется понять как работает стек, на примере функции из трёх переменных.
void f2() { int B = 5; int *pB = &B; // задание адреса в указатель int &sB = B; //задание объекта в ссылку cout << "addres of B = " << &B << "
"; cout << "value of B = " << B << "
"; cout << "addres of pB = " << &pB << "
"; cout << "value of pB = " << pB << "
"; cout << "addres of sB = " << &sB << "
"; cout << "value of sB = " << sB << "
"; }
int main() { f2(); system("PAUSE"); return 0; }
Output:
addres of B = 0038F644 value of B = 5 addres of pB = 0038F638 value of pB = 0038F644 addres of sB = 0038F644 value of sB = 5
1. Непонятка между первой и третьей строкой. В адресном пространстве, как я полагал, адрес более новой переменной должен быть больше старой. На деле:
`0038F638 - 0038F644 = -C;`
То есть сдвиг на 12 позиций назад. Хотя один из программеров с большим опытом, говорил, что сдвиг пойдёт на 4 позиции вперёд - на размерность int, который выражен в байтах.
2. Адрес sB - вообще совпал с адресом B! А значение sB - совпал сo значением B! Мне казалось очевидным, что значение помещенное в sB должно быть равным адресу B. А адрес sB должен быть сдвинут на четыре позиции (sizeof(int)) вперёд, относительно адреса предыдущей переменной.
Вопросы:
Чем можно объяснить наблюдаемое поведение? Каким образом (на самом деле) происходит присвоение адреса следующей переменной, относительно предыдущей? Есть ли возможность распечатать стек более грамотным способом, чем это я сделал? (Я хочу видеть стек в виде трой записей - тип - значение - адрес).


Ответ

В адресном пространстве, как я полагал, адрес более новой переменной должен быть больше старой.
В большинстве "традиционных" платформ стек растет сверху-вниз: от старших адресов к младшим. Поэтому даже если бы компилятор размещал ваши переменные "по порядку", то не было бы ничего удивительного в том, что адрес более "новой" переменной меньше адреса более "старой".
Но на самом деле никакого порядка в размещении отдельных переменных нет и ваши сравнения адресов - бессмысленны.
В традиционной реализации память для всех локальных переменных функции выделяется сразу, одним "кадром стека" в начале работы функции. Внутри этого кадра стека компилятор еще на стадии компиляции разработает некую фиксированную карту расположения локальных переменных. При этом он может (и будет) располагать локальные переменные в этой карте совершенно произвольным образом, руководствуясь оптимизационными соображениями выравнивания, экономии памяти и т.д. и т.п. Поэтому ваш порядок объявления переменных не значит вообще ничего и разность адресов двух "соседних" переменных может быть какой угодно как по величине так и по знаку.
Адрес sB - вообще совпал с адресом B!
Какой еще "адрес sB"? sB - ссылка. Ссылочный тип не является объектным, концептуально памяти не занимает и адреса не имеет. Никакого "адреса sB" в языке С++ нет и быть не может. После объявления int &sB = B; выражение &sB будет давать именно адрес B, что вы и наблюдаете. Нет ничего удивительного в том, что адрес B совпадает с адресом B
Каким образом (на самом деле) происходит присвоение адреса следующей переменной, относительно предыдущей?
Никаким конкретно. Как компилятору захочется в данном конкретном случае - так и будет. Эти решения принимаются компилятором на основе логики, которая на уровне языка не видна.
Какой-то порядок в языке С++ существует (может существовать) только между элементами одного массива или полями одного класса.
Есть ли возможность распечатать стек более грамотным способом, чем это я сделал? (Я хочу видеть стек в виде трой записей - тип - значение - адрес).
На уровне языка - разумеется, нет.
А дальше можете поизучать формат отладочной информации, генерируемой вашим компилятором, и те API, которые ваша реализация предоставляет (если предоставляет) для доступа к этой отладочной информации. Там все это будет.

Комментариев нет:

Отправить комментарий