Назад | Начало урока | Вперед
Содержание

Глава 8

Паразитные дикие и зависшие указатели

Источником наиболее сложных для обнаружения ошибок являются паразитные указатели. ПУ образуется в тех случаях, когда после оператора delete освободившего участок памяти, не следует переприсвоение указателя. При попытке использовать такой указатель, результат будет непредсказуем. (ведь облась памяти может содержать что угодно) если очень повезет то программа просто зависнет.

Будьте осторожны при использовании указателей для которых вызывался оператор delete. Указатель по прежнему будет содержать адрес области памяти, но по этому адресу уже могут находиться другие данные. Поэтому во избежание неприятностей после освобождения указателя присваивайте ему значение 0. Это обезопасит указатель.

Примечание:
Паразитные указатели называют еще дикими или зависшими указателями.

Предупреждение:
Не запускайте следующую программу, если только не хотите испытать удачу. В этой программе намеренно создается паразиьный указатель.
// Listing 8.9 // Demonstrates a stray pointer

typedef unsigned short int USHORT;
#include <iostream>

int main() {

USHORT * pInt = new USHORT;
*pInt = 10;
std::cout << "*pInt: " << *pInt << std::endl;
delete pInt;

long * pLong = new long;
*pLong = 90000;
std::cout << "*pLong: " << *pLong << std::endl;
*pInt = 20; // Ой, ой он же удален!!

std::cout << "*pInt: " << *pInt << std::endl;
std::cout << "*pLong: " << *pLong << std::endl;
delete pLong;
return 0;

}

Результат :

*pInt: 10
*pLong: 9000
*pInt: 20
*pLong: 65556

Анализ программы:

// Listing 8.9 // Demonstrates a stray pointer

typedef unsigned short int USHORT;
#include <iostream>

int main() {


Объявляем указатель на место под тип переменной в динамической памяти и инициализируем (заносим в динамическую память значение) его.

USHORT * pInt = new USHORT;
*pInt = 10;

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

std::cout << "*pInt: " << *pInt << std::endl;
delete pInt;

Вот тут то указатель и оказался зависшим-паразитным!

Объявляем новый указатель на место под тип переменной в динамической памяти и инициализируем (заносим в динамическую память значение) его. Затем выводим на экран это значение из динамической памяти через указатель на это место.(на эту переменную)

long * pLong = new long;
*pLong = 90000;
std::cout << "*pLong: " << *pLong << std::endl;

В следующей строке по адресу pInt записывается значение 20, но эта операция некорректна, так как выделенная для этого указателя память была освобождена, и теперь там возможно находятся чужие данные, и мы этой операцией уничтожим их!Что может привести к катастрофическим последствиям!!

*pInt = 20; // uh oh, this was deleted!

Далее это новое значение из динамич. памяти выводится на экран.

std::cout << "*pInt: " << *pInt << std::endl;

Как и ожидалось оно равно 20. Далее выводится значение на которое указывает указатель pLong

std::cout << "*pLong: " << *pLong << std::endl;

К удивлению там совсем не то что мы ожидали увидеть!(объяснение стр 233)

delete pLong;
return 0;

}

Правильно эта программа должна быть написана так: // Listing 8.9 // Demonstrates a stray pointer

typedef unsigned short int USHORT;
#include <iostream>

int main() {

USHORT * pInt = new USHORT;
*pInt = 10;
std::cout << "*pInt: " << *pInt << std::endl;
delete pInt;

long * pLong = new long;
*pLong = 90000;
std::cout << "*pLong: " << *pLong << std::endl;

//присваиваем указателю адрес нового места в динамической памяти

pInt = new USHORT;//вот строка, которой не хватало!

// Теперь можно поместить туда новое значение!

*pInt = 20;

std::cout << "*pInt: " << *pInt << std::endl;
std::cout << "*pLong: " << *pLong << std::endl;
delete pLong;
return 0;

}

Часто задаваемые вопросы

В чем разница между пустым и паразитным указателем?

при удалении указателя освобождается лишь память на которую он указывает, а сам указатель продолжает существовать. Такой указатель называется паразитным.

Если присвоить такому указателю нулевое значение, написав myPtr = 0; , то паразитный указатель станет пустым или нулевым указателем.

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

Использование паразитного или нулевого указателя (например указать myPtr = 5;)недопустимо, поскольку это приведет к серьезной ошибке. Применение пустого указателя вызовет ошибку во время компиляции, а применение паразитного - ошибку во время выполнения программы. В этом и состоит еще одно преимущество пустого указателя - некорректные операции с ним выявляются компилятором на раннем этапе и проще исправляются.

Вверх

Указатели и константы

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

const int*pOne; int*const pTwo; const int*const pThree;

pOne;-указатель на константу типа int. Следовательно значение на которое он указывает не может быть изменено.

pTwo;-является константой- указателем на тип int. Само целое число может быть изменено, но адрес в указателе pTwo -нет.

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

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

const int*p1;//указатель на константу типа int
int*const p2;//p2 константа, она не может указывать ни на что иное

Постоянные в качестве указателей и функций-членов

В уроке 6 ООП использование ключевого слова const при объявлении функций-членов уже рассматривалось.

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

Если указатель объявлен на объект, являющийся постоянным, то функции которые можно вызвать с его помощью, так же должны быть постоянными.

Посмотрите программу:


// Listing 8.10 // Using pointers with const methods

#include <iostream> using namespace std;

class Rectangle {

public:
Rectangle();
~Rectangle();

void SetLength(int length) {

itsLength = length;
}
int GetLength() const {
return itsLength;
}
void SetWidth(int width) {
itsWidth = width;
}

int GetWidth() const {

return itsWidth;
}

private:
int itsLength;
int itsWidth;

};

Rectangle::Rectangle() {

itsWidth = 5;
itsLength = 10;
}

Rectangle::~Rectangle() {

}

int main() {

Rectangle* pRect = new Rectangle;
const Rectangle * pConstRect = new Rectangle;
Rectangle * const pConstPtr = new Rectangle;

cout << "pRect width: " << pRect->GetWidth()
<< " feet\n";
cout << "pConstRect width: " << pConstRect->GetWidth()
<< " feet\n";
cout << "pConstPtr width: " << pConstPtr->GetWidth()
<< " feet\n";

pRect->SetWidth(10);
// pConstRect->SetWidth(10);
pConstPtr->SetWidth(10);

cout << "pRect width: " << pRect->GetWidth()
<< " feet\n";
cout << "pConstRect width: " << pConstRect->GetWidth()
<< " feet\n";
cout << "pConstPtr width: " << pConstPtr->GetWidth()
<< " feet\n";
return 0;

}


Результат:

pRect width: 5 feet
pConstRect width: 5 feet
pConstPtr width: 5 feet
pRect width: 10 feet
pConstRect width: 5 feet
pConstPtr width: 10 feet

Анализ:

Строка 14 -объявление постоянного метода-члена GetWidth()

int GetWidth() const {

return itsWidth;
}
Строка 32 -объявляет указатель на объект класса Rectangle

Rectangle* pRect = new Rectangle;

Строка 33 -указатель pConstRect на постоянный объект этого же класса

const Rectangle * pConstRect = new Rectangle;

36-41 значения переменных класса выводятся на экран


cout << "pRect width: " << pRect->GetWidth()
<< " feet\n";
cout << "pConstRect width: " << pConstRect->GetWidth()
<< " feet\n";
cout << "pConstPtr width: " << pConstPtr->GetWidth()
<< " feet\n";

43 -указатель pRect используется для присвоения ширине прямоугольника значения 10.

pRect->SetWidth(10);

44 -была попытка использовать указатель pConstRect,

// pConstRect->SetWidth(10);

но он был объявлен как указатель на константу, const Rectangle * pConstRect = new Rectangle;

в связи с чем наличие функций-членов, не являющихся постоянными, здесь недопустимо;

void SetWidth(int width) {

itsWidth = width;
}//непостоянный метод

поэтому эту строку пришлось закомментировать.

45 -вызов функции SetWidth() для указателя pConstPtr.

pConstPtr->SetWidth(10);

Этот указатель объявлен как постоянный указатель на объект,

Rectangle * const pConstPtr = new Rectangle;

то есть он не может указывать ни на что, кроме объекта Rectangle, который постоянным не является.(здесь указателю нельзя присвоить другой адрес, но можно присвоить другое значение переменной на которую он указывает)

Указатели const и this

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


Защищайте объекты доступные по ссылке ключевым словом const если они не должны изменяться. Объекты , подлежащие изменению, объявляйте без ключевого слова const. Небольшие объекты , которые не должны изменяться, могут быть переданы как значения.
Не удаляйте один и тот же указатель дважды


Назад | Начало урока | Вверх | Вперед
Содержание

Hosted by uCoz