Что делает C ++ в адресе памяти переменной, чтобы «освободить» ее? - PullRequest
0 голосов
/ 02 сентября 2018

Например.
Когда функция, имеющая локальную целочисленную переменную x, заканчивается, что C ++ делает со значением, хранящимся в ячейке памяти, соответствующей x?
Вставляет ли оно случайное значение?

Ответы [ 7 ]

0 голосов
/ 02 сентября 2018

Если вы создаете объект и размещаете указатель на него

Animal* animal =new Animal() ;

Он выделит область памяти в памяти. Это как если бы вы строили дом и давали адрес кому-то.

delete animal;

Отправится в эту область памяти и уничтожит ее. Как вы проследите дом по адресу и уничтожите его. Тогда значение, которое оно содержит, будет мусором. В качестве хорошей практики вы должны указать адрес дома nullptr.

animal = nullptr;
0 голосов
/ 15 сентября 2018

Объект не будет фактически удален из памяти даже после вызова delete . Это уязвимость, и конфиденциальная информация может быть пропущена через файлы дампа. Поэтому всегда рекомендуется заменять чувствительные объекты фиктивными и удалять их.

0 голосов
/ 02 сентября 2018

Теоретически это не указано, но на практике не происходит выделения для отдельных объектов базового типа, таких как int, double, T * и т. Д. В некоторых случаях указатель стека не изменяется, и компилятор просто повторно использует место для других переменных:

for (int i=0 ; i < 10 ; i++)
   foo(i);
for (int j=0 ; j < 10 ; j++)
   foo(j);

Скорее всего, компилятор будет использовать пространство i для выделения j. Это легко проверить на godbolt.org с помощью gcc :

.L2:
        mov     edi, ebx
        add     ebx, 1
        call    foo(int)
        cmp     ebx, 10
        jne     .L2
        xor     ebx, ebx
.L3:
        mov     edi, ebx
        add     ebx, 1
        call    foo(int)
        cmp     ebx, 10
        jne     .L3

Мало того, что циклы идентичны, они даже не используют стек. Вместо стека переменные i и j размещаются в `ebx. В этом примере распределение и освобождение полностью прозрачны и являются простым использованием или отсутствием использования регистров.


Более сложный пример будет делать то же самое в стеке:
int foo(int);
void bar(int*);
void bar()
{
    {
        int a[10];
        for (int i=0 ; i < 10 ; i++)
            a[i] = foo(i);
        bar(a);
    }
    {
        int b[10];
        for (int j=0 ; j < 10 ; j++)
            b[j] = foo(j);
        bar(b);
    }
}

Кроме того, консультируясь Второй пример Godbolt.org производит:

        xor     ebx, ebx ; <--- this is simply part of the next block
        sub     rsp, 48;   <--- allocating the stack space
.L2:
        mov     edi, ebx
        call    foo(int)
        mov     DWORD PTR [rsp+rbx*4], eax
        add     rbx, 1
        cmp     rbx, 10
        jne     .L2
        mov     rdi, rsp
        xor     ebx, ebx ; <--- this is simply part of the next block
        call    bar(int*)
.L3:
        mov     edi, ebx
        call    foo(int)
        mov     DWORD PTR [rsp+rbx*4], eax
        add     rbx, 1
        cmp     rbx, 10
        jne     .L3
        mov     rdi, rsp
        call    bar(int*)
        add     rsp, 48  ; <-- deallocating the stack space

Здесь также код идентичен для двух случаев. В стеке нет освобождения или распределения переменных. Линия:

        a[i] = foo(i);

переводится на

        mov     DWORD PTR [rsp+rbx*4], eax

, который просто записывает данные относительно указателя стека (rsp). Он в основном находит содержимое a в соответствии с его положением относительно указателя стека. Указатель стека не обновляется между двумя блоками кода, он только передается в bar() путем копирования указателя стека в rdi:

        mov     rdi, rsp
        call    bar(int*)


Как я показал, обычно во время работы блока не происходит выделения и освобождения. Как правило, в начале и конце функции указатель стека обновляется для отражения переменных.

В отличие от простых целочисленных значений, типы с деструкторами более сложны, но я не буду вдаваться в подробности, потому что это не было задано в вопросе.

0 голосов
/ 02 сентября 2018

Как говорит user3344003, переменная x размещается в стеке, именно там указатель стека указывает. Если вы не знаете, что такое указатель стека, вам нужно прочитать его. Когда функция заканчивается, ссылка на (все) локальные переменные теряется, включая x. Теперь память, используемая в качестве стека, используется повторно, поэтому значения всех локальных переменных, включая x, теряются как из-за потери их ссылок (адресов памяти в стеке), так и из-за того, что память используется повторно для других переменных или целей.

0 голосов
/ 02 сентября 2018

Это поведение не определено в C ++, но каждая известная мне реализация делает что-то подобное.

Компилятор определяет, сколько байтов необходимо для локальных переменных в функции. Компилятор создает код пролога, который делает что-то вроде этого:

  SUB #NUMBEROFNEEDEDBYTES, SP

Это выделяет хранилище из стека.

Внутри компилятор присваивает некоторое смещение каждой переменной. Допустим,

 X=12

Тогда

 12(SP) 

становится адресом X.

 ADD #4, 12(SP)

эквивалентно

X += 4 ;

В конце функции компилятор создает код эпилога, который выполняет что-то вроде:

 ADD #NUMBEROFNEEDEDBYTES, SP

Чтобы освободить память.

X тогда имеет начальное значение того, что произошло в стеке в этом месте. Все, что вы положили в X, остается для следующей функции.

0 голосов
/ 02 сентября 2018

Это очень сильно зависит от реализации.

В большинстве случаев ничего не делается. Вы освобождаете (который изменяет указатель стека). Вы не изменяете память в этом случае.

В некоторых очень неясных случаях вы можете найти разные вещи. В одном примере RTOS RTEMS может быть настроен на запись в память при освобождении. Это могло бы написать что-то определенное как 0xCDCDCDCD или 0xDEADBEEF. Это может быть полезно при отладке проблем с памятью в этих системах RTOS, потому что это легко определить, когда вы используете плохую память. Но это очень редко .

0 голосов
/ 02 сентября 2018

Что, вероятно, происходит - ничего. Удаление чего-либо требует ресурсов, поэтому вместо этого он просто настраивает указатель стека, который указывает на эту память. Это приведет к перезаписи в следующий раз, когда эта память будет использована.

Подобную вещь можно увидеть при использовании переменной без ее инициализации.

int i;

i будет содержать «мусорные» данные, этот «мусор» относится ко времени, когда использовалось это конкретное место в памяти. Может быть старая фотография, текстовый файл или ж / д.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...