C ++ очищенная память все еще доступна - PullRequest
2 голосов
/ 21 июня 2011

У меня есть вопрос, почему я могу получить доступ к определенным частям памяти, и я думаю, что это связано с тем, как я понимаю (или не понимаю), как компилятор сохраняет вещи в памяти. Вот пример кода, с которым я работаю:

Заголовочный файл:

#include <iostream>
using namespace std;

class A
{
public:
int value;
A (int newValue = 5)
{
    value = newValue;
    cout << "A()" << endl;
}
~A() { cout <<"~A()" << endl; }
void func1() {cout << "A::func1()" << endl; }

};


class B : public A
{
public:
B() { A::value = 0; cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
virtual void func1 () { cout << "B::func1()" << endl; }

};

class C : public B
{
public:
C() { cout << "C()" << endl; }
~C() { cout << "~C()" << endl; }
virtual void func1() { cout << "C::func1()" << endl; }
};

Файл .cpp:

int main()
{
int i;

cout << endl;
A* pA = new A (5);
B* pB = new B;
C* pC = new C;

pA->func1();
pB->func1();
pC->func1();

delete pA;
delete pB;
delete pC;

cout << endl;

    //here is my first question - why don't I get a compiler error here?
    //why does the compiler know what this is? Didn't I delete it?
A* ppA = pC;

    //here is my second question - why does this work?  
    //didn't I clear this memory?
ppA->func1();
B bObject;
B* ppB = &bObject;
ppB->func1();


cin >> i;

    return 0;
}

Мои вопросы прямо в комментариях - почему я не получаю ошибки в этих строках?

Если я изменю файл .h так, что func1() будет virtual в A, я получу нарушение прав доступа в этой строке, но все еще не будет ошибок во время компиляции.

Спасибо за любые объяснения:)

Ответы [ 4 ]

8 голосов
/ 21 июня 2011

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

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

7 голосов
/ 21 июня 2011

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

Ответ на ваш второй вопрос: он может не работать. Вы вошли в страшную землю с неопределенным поведением. Подумайте, Алиса в Стране Чудес, без кота, который вас поведет.

Обычно, когда вы вызываете operator delete по указателю, вы говорите ОС, что при необходимости она может перезаписать память. "Если нужно. Фактического стирания памяти не происходит, просто обещание, что вы больше не будете использовать этот блок памяти.

Таким образом, когда вы пытаетесь получить доступ к виртуальной таблице для вызова func1, данные все еще могут быть там. Или нет. В этом случае вы получите сбой (если вам повезет). Но вы это заслужили, вы нарушили свое обещание ОС, поэтому в этот момент все идет хорошо.

Редактировать: Обратите внимание, что func1 на самом деле никогда не использует this, поэтому, если бы он не был виртуальным, вы могли бы сказать ((A*)0)->func1(), и он бы работал нормально.

1 голос
/ 21 июня 2011

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

delete не обнуляет память для вас. Вы можете сделать это самостоятельно, если вам нужно. То, что было в этом блоке памяти, не изменяется до тех пор, пока оно не решит разместить там новое распределение. Использование указателя на удаленную память - очень плохая идея.

0 голосов
/ 21 июня 2011

Функции освобождения памяти, такие как удаление, освобождение, HeapFree не просто сотрут память. Они просто пометят их как «нераспределенные», и эти нераспределенные блоки памяти могут выполнить дополнительный запрос. Один и тот же блок памяти может использоваться для хранения нескольких вызовов выделения памяти или только одного вызова.

Это похоже на удаление (Shift + Deleting) файла - файл фактически не будет помечен как нули, он будет просто помечен как «свободное место». Как и в случае размещения файлов, для одного файла может использоваться несколько свободных пространств (и поэтому у нас есть «Дефрагментаторы»). Но при распределении памяти обслуживание выделения большого блока памяти не может быть выполнено несколькими блоками свободной памяти.

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