Я прочитал, что в функции локальные переменные помещаются в стек, поскольку они определены после того, как параметры были помещены туда первым.
Это также упоминается здесь
5. Все аргументы функции помещаются в стек. 6.Начинаются инструкции внутри функции. 7. Локальные переменные помещаются в стек по мере их определения.
Поэтому я предполагаю, что если код C++ выглядит так:
#include "stdafx.h"
#include <iostream>
int main()
{
int a = 555;
int b = 666;
int *p = &a;
std::cout << *(p+1);
return 0;
}
и если целое число здесь имеет 4 байта, и мы вызываем пространство памяти в стеке, которое содержит первые 8 бит int
555
x, а затем "перемещает" еще 4 байта в верхнюю часть стека через *(p+1)
мы должны смотреть в память по адресу x + 4.
Однако вывод этого - -858993460
- это всегда так, независимо от того, какое значение имеет значение int b
. По-видимому, это стандартное значение. Конечно, я обращаюсь к памяти, которую я не должен иметь, поскольку это переменная b. Это был просто эксперимент.
Почему я не получаю ожидаемое значение или ошибку незаконного доступа?
Где мое предположение неправильно?
Что может представлять -858993460
?
Ваши предположения верны в теории (с точки зрения CS).
На практике нет никакой гарантии сделать арифметику указателя таким образом, ожидая этих результатов.
Например, ваше предположение "Все аргументы функции помещаются в стек" неверно: распределение функций в соответствии с реализацией (в зависимости от архитектуры, оно может использовать регистры или стек), а также компилятор свободен для распределите локальные переменные в регистрах, если это будет необходимо.
Также размер абзаца " int
составляет 4 байта, поэтому добавление 4 к указателю идет в b
"является ложным. Компилятор мог бы добавить отступы между a
и b
чтобы обеспечить сохранение памяти.
Вывод здесь: Не используйте низкоуровневые трюки, они определяются реализацией. Даже если вам нужно (независимо от наших рекомендаций) сделать это, вы должны знать, как работает компилятор и как он генерирует код.
То, что говорили все остальные (т.е. "Не делай этого"), абсолютно верно. Не делай этого. Однако, чтобы ответить на ваш вопрос, p+1
, скорее всего, укажет на указатель на стек стека вызывающего или сам обратный адрес. Системный указатель стека уменьшается, когда вы нажимаете на него что-то. Это зависит от реализации, официально говоря, но каждый указатель стека, который я когда-либо видел (это с 16-разрядной эры), был таким. Таким образом, если, как вы говорите, локальные переменные помещаются в стек при инициализации, &a
должно == &b + 1
.
Возможно, иллюстрация в порядке. Предположим, что я скомпилирую ваш код для 32-битного x86 без оптимизации, и указатель стека esp
составляет 20 (это маловероятно для записи), прежде чем я вызову вашу функцию. Это то, что память выглядит прямо перед строкой, в которой вы вызываете cout
:
4: 12 (value of p)
8: 666 (value of b)
12: 555 (value of a)
16: -858993460 (return address)
p+1
, так как p
является int*
, равно 16. Память в этом месте не защищена от чтения, потому что ей необходимо вернуться к вызывающей функции.
Обратите внимание, что этот ответ является академическим; возможно, что оптимизация компилятора или различия между процессорами вызвали неожиданный результат. Тем не менее, я бы не ожидал, что p+1
до == &b
на любой процессорной архитектуре с любым вызовом, который я когда-либо видел, потому что стек обычно растет вниз.
int
b было в p-3
и другого int c
в p -6
. Кажется, что -858993460
- 11001100 11001100 11001100 11001100
в двоичном формате, является некоторым значением заполнения или значением по умолчанию для незанятой области памяти.
0xCCCCCCCC
в этой позиции, если ваша функция не является main
? Кроме того, я подумал об одном действительном приложении подобного математического трюка с указателями - это называется разбиванием стека, и оно используется вредоносным ПО, чтобы связываться с кодом, который он обманывает, чтобы вызвать его. Помните поддельные AV-вредоносные программы, которые все и их собака получили несколько лет назад? Он сделал (злая версия) то, что вы делаете в main
.