2014-12-08 5 views
5

Мой вопрос:указатель и динамической памяти распределение

int* x = new int; 
cout << x<<"\n"; 
int* p; 
cout << p <<"\n"; 
p = x; 
delete p; 
cout << p <<"\n"; 

Я написал это чисто сам понимать указатель и понять (также теряться в) динамический new и delete.

Мой XCode может составить программу и возвращать следующие результаты:

0x100104250 
0x0 
0x100104250 

Я знаю, что я могу назвать только удалить динамически выделенную память. Однако я назвал delete в p в вышеуказанной программе и скомпилирует.

Может ли кто-нибудь объяснить это мне? Почему я могу удалить p?

Кроме того, я обнаружил, если программа переходит в следующее:

int* x = new int; 
int* p; 
cout << p <<"\n"; 
delete p; 
cout << p <<"\n"; 

Тогда мой Xcode снова собирает и возвращает меня:.

0x0 
0x0 
Program ended with exit code: 0 

и теперь, я получил полностью потерял :( Может ли кто-нибудь объяснить мне это? Почему я мог удалить p, так как он не имеет ничего общего с x?


Поскольку Xcode успешно компилируется, я предполагаю, что указанные выше две программы верны для компьютера. Тем не менее, я думаю, что это снова утверждение «только удаление вызова в динамической выделенной памяти». Или, возможно, я не совсем понял, что такое указатель и что такое динамически распределенная память. Я нашел это post, когда искал в Интернете. Но я не думаю, что это похоже на мое дело.

Пожалуйста, помогите мне.


Я хотел бы задать еще один вопрос. Код here о бинарном дереве поиска. В строке 28-32 речь идет об удалении узла одним ребенком. Здесь я помещаю эту часть кода, если веб-ссылка не работает.

else if (root-> left == NULL) { struct Node * temp = root; root = root-> right; delete temp; }

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

Я не могу вначале связать родительский узел root с правым дочерним элементом root. , а затем удалить корневой узел, поскольку поддерево под корневым узлом также будет удалено. Поэтому я должен создать указатель темпа, указывая на слот памяти, на который указывает корень. Затем я связываю родительский узел с корнем с дочерним элементом root. , и теперь я могу безопасно удалить слот памяти, обозначенный «root» (т. Е. Темп, поскольку оба они указывают на одну и ту же память). Таким образом, я освобождаю память, а также сохраняю связь между родителем и дочерними элементами. Кроме того, темп все еще присутствует и по-прежнему указывает на «этот» слот памяти. Должен ли я установить его в NULL после удаления?

Спасибо всем заблаговременно.

Yaofeng

+1

Вы "повезло", что 'p' является' 0' в вашем втором примере кода. Поскольку он не инициализирован, он может иметь любую ценность. Так как это '0' (aka' NULL'), то можно вызвать 'delete' (это полезно, чтобы избежать наличия миллиона проверок для' NULL', особенно при работе с условиями ошибки, при которых не удалось выполнить выделение, и вы хотите очистите остальные распределения - если все указатели сначала инициализируются на «NULL», тогда вы можете просто «удалить» все, не беспокоясь, какой из них не удалось выделить). –

+1

Просто совет, вы всегда должны инициализировать переменные указателя, такие как int * p = 0; или int * p = NULL; Это потому, что в сборке отладки это будет сделано для вас. Но в релиз-сборке это не будет сделано. Это может сэкономить вам много времени на отладку. – user743414

+1

@ user743414 Если вы не поддерживаете устаревший код, вы должны использовать C++ 11 и, следовательно, 'int * p = nullptr;'. (Эта часть) C++ 11 уже много лет поддерживается всеми основными компиляторами. – Angew

ответ

2

Да, вы можете вызвать delete только на память, которая была выделена с помощью new. Обратите внимание, что это адрес памяти (значение значения указателя), что имеет значение, а не переменная, хранящая указатель. Таким образом, ваш первый код:

int* x = new int; //(A) 
cout << x<<"\n"; 
int* p; 
cout << p <<"\n"; 
p = x; //(B) 
delete p; //(C) 
cout << p <<"\n"; //(D) 

Line (A) выделяет память динамически по некоторому адресу (0x100104250 в вашем примере вывода) и сохраняет этот адрес в переменной x. Память распределяется через new, что означает, что delete должен быть вызван по адресу 0x100104250.

Линия (B) присваивает адрес 0x100104250 (значение указателя x) в указатель p. Линия (C) затем вызывает delete p, что означает «освободить память, на которую указывает p». Это означает, что он вызывает delete по адресу 0x100104250, и все в порядке. Память по адресу 0x100104250была, выделенная через new, и поэтому неправильно распределяется через delete. Тот факт, что вы использовали другую переменную для хранения значения, не играет никакой роли.

Линия (D) просто распечатывает значение указателя. delete действует на память, на которую указывает указатель, а не на сам указатель. Значение указателя остается неизменным - оно все еще указывает на ту же память, что память больше не выделяется.


Второй пример отличается от других - вы звоните delete p когда p не инициализирован ни к чему. Это указывает на случайный кусок памяти, поэтому вызов delete на нем, конечно, является незаконным (технически он имеет «неопределенное поведение» и, скорее всего, сбой).

Кажется, что в вашем конкретном примере вы используете сборку отладки, и ваш компилятор «помогает» инициализировать локальные переменные до 0, если вы их не инициализируете самостоятельно. Таким образом, это фактически приводит к тому, что ваш второй пример не сбой, так как вызов delete на нулевом указателе действителен (и ничего не делает). Но у программы действительно есть ошибка, так как локальные переменные обычно не инициализируются неявно.

+0

Благодарим вас за подробный и быстрый ответ. Это сделало меня гораздо более удобным в отношении указателя. – Yaofeng

1
p = x; 

Это сделало бы p содержать такое же значение, как x (p будет указывать на тот же объект, как x). Поэтому в основном, когда вы говорите

delete p; 

Его делают удалить на адрес ссылается p, который так же, как и x. Следовательно, это совершенно справедливо, поскольку объект, на который ссылается этот адрес, выделяется с использованием new.

Второй случай: -

Co-указатель между прочим p устанавливается компилятором как NULL pointer (Вы не должны зависеть от этого). Таким образом, удаление этого указателя безопасно. Если бы это был не указатель NULL, вы бы, возможно, видели крушение.

+0

Да, иногда он падает. Я нашел его, когда пытался использовать разные коды. Я забыл включить его в мои вопросы. Большое спасибо за подтверждение мне этой информации. – Yaofeng

1

Хорошо, давайте посмотрим на документацию для оператора «delete». По http://en.cppreference.com/w/cpp/memory/new/operator_delete:

Вызывается Delete-выражения для освобождения памяти, ранее выделенную для одного объекта. Поведение реализации стандартной библиотеки этой функции не определено, если только ptr не является нулевым указателем или является указателем, ранее полученным из стандартной реализации библиотеки оператора new (size_t) или оператора new (size_t, std :: nothrow_t).

Так что же происходит следующее: вы вызываете новый оператор, который выделяет SizeOf (INT) байт для переменной INT в памяти. Эта часть в памяти ссылается на ваш указатель x. Затем вы создаете другой указатель p, который указывает на тот же адрес памяти. Когда вы вызываете delete, память отпускается. Оба p и x по-прежнему указывают на одну и ту же память адрес, за исключением того, что значение в этом месте теперь является мусором. Чтобы сделать это легче понять, я изменил свой код следующим образом:

#include <iostream> 
using namespace std; 

int main() { 
    int * x = new int;    // Allocate sizeof(int) bytes, which x references to 
    *x = 8;       // Store an 8 at the newly created storage 
    cout << x << " " << *x << "\n"; // Shows the memory address x points to and "8". (e.g. 0x21f6010 8) 
    int * p;       // Create a new pointer 
    p = x;       // p points to the same memory location as x 
    cout << p << " " << *p << "\n"; // Shows the memory address p points to (the same as x) and "8". 
    delete p;      // Release the allocated memory 
    cout << x << " " << p << " " 
     << *x << " " << *p << "\n"; // p now points to the same memory address as before, except that it now contains garbage (e.g. 0x21f6010 0) 
    return 0; 
} 

После запуска, я получил следующие результаты:

0x215c010 8 
0x215c010 8 
0x215c010 0x215c010 0 0 

Так что помните, с помощью удаления вы освободить память, но указатель по-прежнему указывает на тот же адрес. Вот почему обычно безопасная практика также устанавливает указатель на NULL. Надеюсь, теперь это немного лучше :-)

+0

Мне это очень помогает. Хотя мой Xcode возвращает 8 8 вместо 0 0. Но я думаю, что я понял суть. – Yaofeng

0

Перед ответами вам нужно понять следующие моменты.

  1. когда вы delete указатель, то нужно не быть назначены с 0. Это может быть любым.
  2. Вы можете удалить 0 или NULL без каких-либо вреда.
  3. Неопределенное поведение - это то, в чем может случиться что-то. Программа может привести к сбою, он может работать должным образом, как будто ничего не произошло, это может произвести некоторые случайные результаты и т.д.,

Однако, я назвал удалять р в приведенной выше программе, и он компилирует.

Может ли кто-нибудь объяснить это мне? Почему я могу удалить p?

Это потому, что вы назначаете адрес памяти, выделенный new через x. (p = x;) x (или p) является допустимым адресом памяти и может быть удален.

Сейчас x называется Dangling pointer. Потому что это указывает на память, которая больше не действительна. Доступ к x после удаления - это неопределенное поведение.

Может ли кто-нибудь объяснить мне это? Почему я могу удалить p, поскольку он не имеет ничего общего с x?

Это потому, что ваш p назначен 0. Следовательно, вы уходите с неопределенным поведением. Однако не гарантируется, что ваш неинициализированный указатель будет иметь значение 0 или NULL. На данный момент это работает нормально, но вы пробираетесь через неопределенное поведение здесь.

+0

Большое спасибо за обмен информацией об оборванных указателях. – Yaofeng

Смежные вопросы