Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > где логика работы с указателями?


Автор: yours-tester 6.2.2005, 14:47
Начал изучать C++ с помощью VC6++ на winXP
Не могу найти ответ на интересующий меня вопрос.
Разобрал пример и он привёл меня в ступор.
А правильно ли я выбрал язык на котором хочу кое-что написать?
Что то какая то нелогичность получается в этом языке с указателями
пример приведён ниже, если не перегружать оператор присваивания
(закоментировать первый #define),
то после команды Str3=Str2=Str1
Str1.s, Str2.s, Str3.s - указывают на одну и ту же строку
но это ещё пол беды, когда после Str1.destroy() вызывается Str2.destroy()
происходит ошибка приложения и Windows пытается отослать в Microsoft
сообщение об ошибке. Оно понятно что, Str2.s уже указывает,
на освобождённую память, но неужели оператор delete этого не может распознать и
корректно отработать этот случай?
Что за этим разве программист должен следить?


Вот учебный пример

Код

#include <iostream.h>
#include <string.h>
#define OVERLOAD_ASSIGNMENT
class Str
{
char *s;
int len;
public:
void Initialize(const char*);
void Initialize() {s=NULL; len=0;}
void ToUpper() {strupr(s);}
void Destroy() {delete s; s=NULL; len=0;}
void print();
#ifdef OVERLOAD_ASSIGNMENT
Str& operator=(const Str&);
#endif
};

void Str::print()
{
if (s)
{cout << s <<endl;}
else
{ cout <<"NULL"<<endl;};

}

void Str::Initialize(const char* InitStr)
{
len=strlen(InitStr);
s=new char[len+1];
strcpy(s, InitStr);
}

#ifdef OVERLOAD_ASSIGNMENT
Str& Str::operator=(const Str& OtherStr)
{
len=OtherStr.len;
delete s;
s=new char[len+1];
strcpy(s,OtherStr.s);
return *this;
}
#endif

void main()
{
Str str1, str2, str3;
str1.Initialize("Hi! I am a strig!!!");
str2.Initialize();
str3.Initialize();
cout <<"After initialization: "<<endl;
cout <<"First object is "; str1.print();
cout <<"Second object is "; str2.print();
cout <<"Third object is "; str3.print();
str3=str2=str1;
cout <<"After assignment"<<endl;
cout <<"First object contains"; str1.print();
cout <<"Second object contains"; str2.print();
cout <<"Third object contains"; str3.print();
str3.ToUpper();
cout <<"All S3 to Upper" <<endl;
cout <<"First object contains"; str1.print();
cout <<"Second object contains"; str2.print();
cout <<"Third object contains"; str3.print();

str1.Destroy();
str2.Destroy();
str3.Destroy();

}



Автор: S.A.P. 6.2.2005, 15:15
Цитата(yours @ 6.2.2005, 14:47)
но неужели оператор delete этого не может распознать и корректно отработать этот случай?
наверняка в опциях компилятора можно выбирать проверку перед уничтожением, но я этим никогда не интересовался, да и зачем? Мы для того и вырали C++, чтобы все руками делать smile . Так быстрее работает. Хочешь писать программы с кучей скрытых ошибок, которые потом исполняющая среда за тобой будет подбирать, фиксить, но тормозить систему, выбирай Java или C#.

Автор: srd 6.2.2005, 15:15
Ну и? Повторное освобождение уже освобождённой памяти является грубейшей ошибкой в любом языке программирования, поддерживающем работу с указателями, в том числе и в тех, которые имеют сборщик мусора (типа явы или си шарпа). Всё просто и логично.
Добавлено @ 15:18
Цитата(Perchilla @ 6.2.2005, 23:15)
наверняка в опциях компилятора можно выбирать проверку перед уничтожением

Отладочная версия run-time может обнаруживать повторное освобождение памяти и выдавать исключение. Релизная версия будет молча ломать кучу, из-за чего баг будет вылазить совсем в другом месте.

Автор: Domestic Cat 6.2.2005, 20:11
Цитата(srd @ 6.2.2005, 06:15)
в том числе и в тех, которые имеют сборщик мусора (типа явы или си шарпа). Всё просто и логично.


Не понял, как в менеджед языках можно повторно память освободить? smile

Автор: chipset 6.2.2005, 20:13
Перегрузи функцию delete и заставь её проверять NULL ли обьект перед удалением, и обнулять его в противном случае.
Мы так делаем smile
Добавлено @ 20:15
Так, на будущее... пользуйся плз тегами [code=cpp][/code], удобнее читать просто http://forum.sources.ru/smiles/Main/wink.gif

Автор: En_t_end 6.2.2005, 20:20
Вообще фигня полная !!! smile
Первый раз вижу такую тупую реализацию!

Автор: chipset 6.2.2005, 20:21
En_t_end, насчёт стиля ничего плохого сказать не могу...

Автор: Конструктор 6.2.2005, 20:39
chipset, кажысь стандартная реализация и так проверяет не NULL-ли объект, тока сама в NULL не ставит при удалении. Вроде где то я читывал что по стандарту совершенно безопасно применить delete к NULL указателю

Автор: yours-tester 6.2.2005, 20:59
Вообще то здесь delete применяется не к NULL указателю, а к перекрёстному указателю, который не равен NULL, но указывает на уже освобождённую память.
Вопрос в том, есть ли способ узнать, что не NULL указатель указывает на освобождённую память.
И не освобождать её вторично.
Отладчик ведь откуда то об этом знает и покрасил мне переменные в красный цвет.
А оператор delete почему не может узнать этого?

Автор: Конструктор 6.2.2005, 22:02
А зачем ему? Ему скорость важна, а если не важна то как и было замечено Java и C#

Автор: gepard 7.2.2005, 11:31
Конструктор
Цитата
chipset, кажысь стандартная реализация и так проверяет не NULL-ли объект, тока сама в NULL не ставит при удалении. Вроде где то я читывал что по стандарту совершенно безопасно применить delete к NULL указателю

Ошибаешься...
yours-tester
Цитата
Вообще то здесь delete применяется не к NULL указателю, а к перекрёстному указателю, который не равен NULL, но указывает на уже освобождённую память.

Значит - NULL. Указатель - это как человек, который указывает на область памяти пальцем. Он тычит и говорит: "Вот здесь". А раз он NULL, то он - 0...0 - это NULL, NULL - это 0...
Если ты пишешь:
Код

str3 = str2 = str1;
...
str1.Destroy();
str2.Destroy();
str3.Destroy();

Это значит, что Человек с именем str3 тычет пальцем на область памяти, куда тычат str1 и str2.
Если ты удалил эту область, то получается, что тыкать-то им некуда.
str1.Destroy(); - парень с именем str1 перестаёт тыкать пальцем на область памяти.
str2.Destroy(), str3.Destroy(); - str2 и str3 рады перестать тыкать, но они уже перестали и второй раз перестать не могут... smile

Автор: srd 7.2.2005, 11:53
Цитата(Domestic @ 7.2.2005, 03:11)
Не понял, как в менеджед языках можно повторно память освободить? smile

Ага, заврался я smile Имел в виду, что использование освобожденной памяти легко получить и в managed-языках. Т.е.:
Код

#include "stdafx.h"
#using <mscorlib.dll>
using namespace System;
__gc struct A
{
   int value;
};
int _tmain()
{
   A __gc *a = __gc new A;
   a->value = 5;
   a = 0;
   GC::Collect();
   GC::WaitForPendingFinalizers();
   a->value = 10;
return 0;
}

А вот повторного освобождения я не добился, как ни пытался.

Цитата
Цитата
 
chipset, кажысь стандартная реализация и так проверяет не NULL-ли объект, тока сама в NULL не ставит при удалении. Вроде где то я читывал что по стандарту совершенно безопасно применить delete к NULL указателю

Ошибаешься...

Да нет, с точки зрения стандарта применять delete к нулевому указателя безопасно.
Код

// можно писать так
int *a = 0;
delete a;
// или так
delete 0;


Автор: S.A.P. 7.2.2005, 12:06
Цитата(srd @ 7.2.2005, 11:53)
Да нет, с точки зрения стандарта применять delete к нулевому указателя безопасно.
Код

// можно писать так
int *a = 0;
delete a;
// или так
delete 0;

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

Автор: srd 7.2.2005, 12:58
Если мы говорим про Си++, то в стандарте есть понятие 0 как нулевого указателя, но нет ничего про NULL. Это просто наследине из Си-шной CRT. Более того, компилятор всегда может интерпретировать 0 как нулевой указатель, а вот NULL в разных реализациях может быть определён как 0, как (void *)0, как (long *)0 и т.п. Это может создать проблемы. Так что, что лучше - ещё вопрос.

Автор: gepard 7.2.2005, 14:51
srd
Твоя правда...
Но только в том случае, если указатель точно "0".
Добавлено @ 14:54
Если delete вызывать после delete, то будет лаг...

Автор: S.A.P. 7.2.2005, 22:19
Цитата(srd @ 7.2.2005, 12:58)
Если мы говорим про Си++, то в стандарте есть понятие 0 как нулевого указателя, но нет ничего про NULL
http://www.open-std.org/jtc1/sc22/open/n2356/diff.html#diff.null

А вот то, что 0 применительно к указателям конвертируется в NULL, пожалуй, соглашусь, но все равно лучше все таки юзать NULL для указателей. Так спокойней, да и мало ли какая реализация C++ попадется smile .

Автор: srd 8.2.2005, 03:13
В приведённой тобой ссылке как раз и говорится, что макрос NULL, определённый в заголовках <clocale>, <cstddef>, <cstdio>, <cstdlib>, <cstring>, <ctime> или <cwchar> - это зависящая от реализации константа для нулевого указателя. Обрати внимание, что все эти заголовки из CRT. Если есть под рукой стандарт, посмотри, что там говорится про null-pointer, а не про макрос NULL.

Цитата
А вот то, что 0 применительно к указателям конвертируется в NULL

0 не конвертируется в NULL, он интерпретируется компилятором как нулевой указатель, точно так же, как интерпретируется как нулевой указатель конкретное значение макроса NULL. Проблема в том, что 0 - везде 0, а макрос NULL может отличаться в разных реализациях. Так что я с тобой не соглашусь, лучше всё-таки использовать просто 0.

Автор: Adil' 8.2.2005, 11:38
Цитата(srd @ 8.2.2005, 03:13)
Проблема в том, что 0 - везде 0, а макрос NULL может отличаться в разных реализациях. Так что я с тобой не соглашусь, лучше всё-таки использовать просто 0.
Дык в том то и дело, что в общем случае 0 вовсе не null-pointer, а NULL - это null-pointer независимо от реализации, и неважно, что у этого NULL "внутри"
Цитата(srd @ 8.2.2005, 03:13)
макрос NULL, ... - это зависящая от реализации константа для нулевого указателя
Вот именно, - это нулевой указатель, а как он определен - зависит от компилятора, и, может статься, это будет не 0 (хотя вероятность этого практически нулевая, извиняюсь за тавтологию smile )

Автор: srd 8.2.2005, 12:15
Цитата
Дык в том то и дело, что в общем случае 0 вовсе не null-pointer, а NULL - это null-pointer независимо от реализации, и неважно, что у этого NULL "внутри"

Боюсь вы меня не понимаете. В стандарте явно сказано, что 0, когда он применяется к указателям, всегда интерпретируется как нулевой указатель, не зависимо от того, чем нулевой указатель является в данной реализации и на данной машине в действительности, хоть 0xaabbccdd. А вот если вы напишете
Код

short *p = NULL;

то фиг вы это скомпилируете компилятором, где, например, NULL определён как
Код

#define NULL (long *)0

Кажется в документации wxWidgets приводился пример такого загадочного определения макроса NULL и вытекающие отсюда проблемы. Ещё раз, NULL достался в наследство от Си, а компилятор Си более равнодушно относится к присваиваниям указателей разных типов, потому там подобная проблема и не стояла.

Добавлено @ 12:20
Кстати, Страуструп в своём FAQ говорит, что в Си++ макрос NULL - всегда 0.
Цитата

Should I use NULL or 0?

In C++, the definition of NULL is 0, so there is only an aesthetic difference. I prefer to avoid macros, so I use 0. Another problem with NULL is that people sometimes mistakenly believe that it is different from 0 and/or not an integer. In pre-standard code, NULL was/is sometimes defined to something unsuitable and therefore had/has to be avoided. That's less common these days.

Смотрим http://www.research.att.com/~bs/bs_faq2.html#null

Автор: Adil' 8.2.2005, 12:22
Цитата(gepard @ 7.2.2005, 11:31)
Конструктор
Цитата
chipset, кажысь стандартная реализация и так проверяет не NULL-ли объект, тока сама в NULL не ставит при удалении. Вроде где то я читывал что по стандарту совершенно безопасно применить delete к NULL указателю
Ошибаешься...
И вовсе Конструктор и не ошибается...
Добавлено @ 12:31
Цитата(srd @ 8.2.2005, 12:15)
Кстати, Страуструп в своём FAQ говорит, что в Си++ макрос NULL - всегда 0.
Ну тогда вообще не о чем спорить smile

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)