Выделение локальных переменных в стеке и использование арифметики с указателями

0

Я прочитал, что в функции локальные переменные помещаются в стек, поскольку они определены после того, как параметры были помещены туда первым.

Это также упоминается здесь

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?

Теги:
memory-management
stack

2 ответа

1
Лучший ответ

Ваши предположения верны в теории (с точки зрения CS).

На практике нет никакой гарантии сделать арифметику указателя таким образом, ожидая этих результатов.

Например, ваше предположение "Все аргументы функции помещаются в стек" неверно: распределение функций в соответствии с реализацией (в зависимости от архитектуры, оно может использовать регистры или стек), а также компилятор свободен для распределите локальные переменные в регистрах, если это будет необходимо.

Также размер абзаца " int составляет 4 байта, поэтому добавление 4 к указателю идет в b "является ложным. Компилятор мог бы добавить отступы между a и b чтобы обеспечить сохранение памяти.

Вывод здесь: Не используйте низкоуровневые трюки, они определяются реализацией. Даже если вам нужно (независимо от наших рекомендаций) сделать это, вы должны знать, как работает компилятор и как он генерирует код.

  • 0
    Хорошо. Таким образом, вывод заключается в том, что то, что я прочитал, было очень упрощенным объяснением того, как это работает, и на практике есть много других факторов, которые усложняют его, поскольку он зависит от компилятора / архитектуры.
  • 0
    @LeNoob точно. Полезно знать, что такое «хитрости», как действительно работает код, который генерирует компилятор (то есть, как действительно работает ваша программа или что на самом деле означает код, написанный на C ++). Но на практике этот код сильно зависит от архитектуры и оптимизаций, выполняемых компилятором.
2

То, что говорили все остальные (т.е. "Не делай этого"), абсолютно верно. Не делай этого. Однако, чтобы ответить на ваш вопрос, 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 на любой процессорной архитектуре с любым вызовом, который я когда-либо видел, потому что стек обычно растет вниз.

  • 0
    Спасибо за то, что привели больше света в это Когда я начал искать в обратном направлении, я нашел значения в памяти. Значение следующего объявленного int b было в p-3 и другого int c в p -6 . Кажется, что -858993460 - 11001100 11001100 11001100 11001100 в двоичном формате, является некоторым значением заполнения или значением по умолчанию для незанятой области памяти.
  • 0
    @LeNoob: Из любопытства вы все еще видите 0xCCCCCCCC в этой позиции, если ваша функция не является main ? Кроме того, я подумал об одном действительном приложении подобного математического трюка с указателями - это называется разбиванием стека, и оно используется вредоносным ПО, чтобы связываться с кодом, который он обманывает, чтобы вызвать его. Помните поддельные AV-вредоносные программы, которые все и их собака получили несколько лет назад? Он сделал (злая версия) то, что вы делаете в main .
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню