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


Автор: Mr_Nuke 6.4.2007, 21:38
Собственно проблема такая. 
Дана следующая программа:

Код

#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <iostream.h>

//Класс описывает строку, память выделяется динамически, имеется два конструктора, диструктор

class str
{ char *s;
int n;
public:
str(){n=0;s=NULL;}
str(char *st)
{s=new char [sizeof(st)];
strcpy(s,st);
}


//Функция суммирует два объекта класса Str, результат записывает в S

void form(str x, str y)
{
s= new char [((sizeof x.s) + (sizeof y.s))];
char *t1=NULL;int n1=0;
for (t1=y.s; *t1!='\t';t1++, n1++);
n1--;
strcpy (s, x.s);
strcpy (s+n1, y.s);
}

//печать и диструктор

void print()
{printf ("\n");
for (char *i=s; *i!='\t'; i++)
printf ("%c", *i);
}

~str()
{delete s;
s=NULL; n=0;
}
};


//Главная программа. Диструктор вызывается не явно, однако всетаки вызывается именно данный диструктор, а не стандартный. 

void main()
{ str X ("abs");
   str Y ("123456");
   str Z;
   Z.form(X,Y);
}


----------------------------------

Суть Проблемы: 
Происходит суммирование, результат записывается в Z, Однако информация, находившая в X и Y потеряна. Вместо нее записаны какие то символы.

Испробовано: 
  • Заменить строку "void form(str x, str y)" на "void form (str &x, str &y)", тоесть работать не с копиями переменных, а с самими перменными. Проблема исчезла.
  • Убрали Диструктор. Проблема Исчезла.
Вопрос: 
Почему возникает такая проблема, и какими еще способами ее можно исправить.


Исходник приведен ниже
[*]

Заранее спасибо!



M
chipset
Тег [code=cpp] рулез форева.
СУВ, Chipset.

Автор: zkv 6.4.2007, 21:43
для начала, это неверно
Цитата(Mr_Nuke @  6.4.2007,  21:38 Найти цитируемый пост)
s= new char [((sizeof x.s) + (sizeof y.s))];

правильно:
s= new char [ strlen( x.s ) +  strlen( y.s) ];
пардон, вернее так:
s= new char [ strlen( x.s ) +  strlen( y.s)  + 1];
Цитата

Заменить строку "void form(str x, str y)" на "void form (str &x, str &y)", тоесть работать не с копиями переменных, а с самими перменными. Проблема исчезла.

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

Убрали Диструктор. Проблема Исчезла.

повезло просто скорее всего. 

Автор: Mr_Nuke 6.4.2007, 22:14
я не так давно начал изучать Объектно ориентированное... не объяснишь про конструктор копирования пару сторк? 

Автор: Damarus 6.4.2007, 22:40
Цитата(Mr_Nuke @  6.4.2007,  21:38 Найти цитируемый пост)
s=new char [sizeof(st)];

sizeof(st) обычно будет 4.

Автор: zkv 6.4.2007, 22:45
Цитата(Mr_Nuke @  6.4.2007,  22:14 Найти цитируемый пост)
про конструктор копирования пару сторк?  

при передаче в функцию объекта по значению создается копия объекта (есть еще пара таких случаев ;-)), для этого вызывается конструктор копирования, если пользователь не определил этот самый конструктор, то его  создает сам компилятор. 
Конструктор копирования по умолчанию (вроде так он называется), просто копирует значения всех членов-данных. 
У тебя в этом случае копируется указатель на строку, а не сама строка, те объект в памяти остается один, а указатель на него копируется, что может привести к неприятным последствиям, если ты обратишься к этому объекту после вызова деструктора хотя бы в одном из объектов. 
Скорее всего не это привело к ошибке в твоей программе, там и без этого интересностей хватает smile

Например:
Цитата(Mr_Nuke @  6.4.2007,  21:38 Найти цитируемый пост)
for (t1=y.s; *t1!='\t';t1++, n1++);

видимо вместо '\t' ты имел ввиду '\0'? Наверное длину строки определял? Это можно сделать функцией strlen(). 

Да, в твоем случае конструктор копирования должен выглядеть примерно так:
Код

str( const str &obStr )
{
  n=obStr.n;//или this->n = obStr.n; далее можно также через this
  s= new char[ n + 1];
  strcpy( s,  obStr.s ); 
}


Хотя надо еще исправить этот косяк:

Цитата(Mr_Nuke @  6.4.2007,  21:38 Найти цитируемый пост)
str(char *st)
{s=new char [sizeof(st)];

на
Код

str( const char *st )
{
  n = strlen( st );
  s=new char [ n + 1 ];
  strcpy( s, st );  
}


Еще советую научиться нормально оформлять код (невозможно читать, место экономишь?), и выбирать более осмысленные имена идентификаторам. Ну еще неплохо сразу научить себя разделять объявление класса и реализацию его методов.
   Удачи!

Автор: threef 7.4.2007, 00:24
не забудь поправит деструктор 
Код

...
  delete [] s; 
 
иначе будет утечка памяти.
  delete s;
освобождает память размером с один символ 

Автор: Daevaorn 7.4.2007, 01:25
Цитата(threef @  7.4.2007,  01:24 Найти цитируемый пост)
освобождает память размером с один символ 

не хочу быть особо въедливым, но это совсем не факт. в большинстве случаев удалится всё-таки вся память, а вот с вызовом деструкторов будут проблемы. а вообще это UB

Автор: Xenon 7.4.2007, 01:37
Daevaorn, это просто UB без каких-либо предположений smile

Автор: Mr_Nuke 7.4.2007, 10:09
Код

for (t1=y.s; *t1!='\t';t1++, n1++); 
видимо вместо '\t' ты имел ввиду '\0'? Наверное длину строки определял? Это можно сделать функцией strlen(). 


Да, определял я действительно длинну строки. Ток когда я смотрел через debug-watch, в конце строки почему то стояло '\t'

На счет оформления кода - скопировал пост с сайта, где до этого размещал свою задачку.... там сайт автоматом делал вот такое вот выравнивание. Я тут первый раз, и как то не пришло в голову, что тут по другому. Поэтому и прикрепил cpp файл.  

и что такое UB?

На счет остальных советов - буду сидеть, разбираться. Отпишусь, если будет что непонятно. Всем огромное спасибо!

Автор: Daevaorn 7.4.2007, 10:17
Цитата(Mr_Nuke @  7.4.2007,  11:09 Найти цитируемый пост)
и что такое UB?

http://en.wikipedia.org/wiki/Undefined_behavior

Автор: Mr_Nuke 7.4.2007, 16:55
И еще один вопросик. Что делает "const"?
Чем отличается "str(char *st)" от "str( const char *st )" ?

Автор: vinter 7.4.2007, 20:13
Цитата(Mr_Nuke @  7.4.2007,  16:55 Найти цитируемый пост)
И еще один вопросик. Что делает "const"?

оно делает как раз то, что это слово означает smile , а именно делает строку(в данном случае) константной

Автор: bsa 7.4.2007, 21:28
Цитата(Mr_Nuke @ 7.4.2007,  16:55)
И еще один вопросик. Что делает "const"?
Чем отличается "str(char *st)" от "str( const char *st )" ?

Это больше касается качества кода. Т.е. объявляя функцию с константным указателем или ссылкой в качестве параметра, ты гарантируешь всем, кто ее использует (а так же компилятору, который компилирует твой код), что ты не изменяешь переменную, на которую указывает указатель или ссылка. Другими словами, если ты по ошибке напишешь это:
Код
int str(const char *st) {
...
     st[10] = '\0';
...
}
То компилятор тебя пошлет. А если напишешь так:
Код
int str(char *st) {
...
     st[10] = '\0';
...
}
То компилятор не пискнет.

Больше того, у методов классов тоже есть модификатор const, который означает, что данный метод не меняет аттрибуты класса.
По мимо всего прочего, данные модификаторы помогают при написании программы.
Например, ты пишешь класс, у которого есть метод size():
Код
class MyClass {
...
    unsigned size() const;
...
};
По идее, он не должен менять содержимое класса, а просто должен посчитать занимаемый данными объем памяти. Ты его реализовал, но компилятор выдал кучу ругательств... И ты выясняешь, что где-то в куче кода, банально перепутал источник с приемником (например). Т.е. ты выловил ошибку на этапе компиляции. А отлов отладчиком занимает намного больше времени.

Автор: Mr_Nuke 8.4.2007, 19:11
Все супер! все работает!  smile 
остался последний вопрос: про конструктор копирования.

Код

str( const str &obStr )
{
  this->n=obStr.n;
  s= new char[ n + 1];
  strcpy( s,  obStr.s );
}


первая строчка "&obStr". все слово является какой то переменной или str - это имя класса? если так, то почему оно с большой буквы? 
Или это указатель на копируемый объект?

Автор: zkv 8.4.2007, 19:18
Цитата(Mr_Nuke @  8.4.2007,  19:11 Найти цитируемый пост)
первая строчка "&obStr". все слово является какой то переменной или str - это имя класса? если так, то почему оно с большой буквы? 

obStr - это идентификатор (типа object класса str
знак & - указывает, что объект передается по ссылке. По значению объект в конструктор копирования не передать, так как для этого надо вызвать конструктор копирования smile, поэтому передаем его по ссылке, а модификатор const говорит о том,  что мы обещаем его не менять внутри ;-)
Цитата(Mr_Nuke @  8.4.2007,  19:11 Найти цитируемый пост)
то почему оно с большой буквы

ну так захотелось smile

Автор: Mr_Nuke 8.4.2007, 19:36
на счет конструктора разобрался! smile  только вот еще одно)
Везде, где мы выделяем память, мы выделяем на 1 байт(или бит), тоесть дописываем +1 к необходимой длинне;
как нас учили, это делается для того, что бы руками потом дописывать "\0", показывая конец строки. но мы этого не делаем. 
Первый вопрос: почему тогда информация сохраняется верной? ведь мы выделили память на 1 байт больше, чем записали, т.е. один байт остается свободным. почему туда не записывается какой либо мусор?

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

Если не писать +1, то в конструкторе копирования, в нужную нам строчку дописывается какой то символ в конце. Так куда он пишется, если мы не выделяли место под этот символ? 


Автор: zkv 8.4.2007, 19:44
Цитата(Mr_Nuke @  8.4.2007,  19:36 Найти цитируемый пост)
и если мы всеравно ничего не дописываем

кто сказал что мы ничего туда не записываем?
В следующем примере это сделает функция strcpy().
Код

str( const str &obStr )
{
  this->n=obStr.n;
  s= new char[ n + 1];
  strcpy( s,  obStr.s );
}

Остальные вопросы снимаются, как я понимаю smile

Автор: Mr_Nuke 8.4.2007, 19:52
Всё! всем огромное спасибо! smile  на данный момент вопросов больше нету! Если че бут непонятно -  еще отпишусь  smile 

zkv и bsa! вам отдельное спасибо! если бы мог - влепил бы вам по плюсику в репутацию) но пока у меня постов маловато  smile 

Автор: Earnest 9.4.2007, 20:10
Цитата(threef @  7.4.2007,  01:24 Найти цитируемый пост)
иначе будет утечка памяти.
  delete s;
освобождает память размером с один символ  

Нет, освобождает ровно столько, сколько по адресу s выделено. Другое дело, что деструктор вызывается только для одного объекта (первого символа), и именно этим отличается от delete[] s - последний вызовет деструктор для каждого символа. Но в данном случае это не существенно - это же просто символы.
Не надо быть святее папы Римского smile

Добавлено через 3 минуты и 6 секунд
И никакое это не UB, а вполне стандартное поведение

Автор: Xenon 9.4.2007, 20:20
Earnest, это UB.
http://rsdn.ru/Forum/Info.aspx?name=FAQ.cpp.delete[]

Автор: Earnest 9.4.2007, 20:25
Xenon, у меня по твоей ссылке пустая страница открывается...
Но тем не менее, что тут может быть UB - удалится весь выделенный блок или нет?

Автор: Daevaorn 9.4.2007, 20:29
Цитата(Earnest @  9.4.2007,  21:25 Найти цитируемый пост)
у меня по твоей ссылке пустая страница открывается...

там последнии [] - часть ссылки
Цитата(Earnest @  9.4.2007,  21:25 Найти цитируемый пост)
Но тем не менее, что тут может быть UB - удалится весь выделенный блок или нет?

всё

Автор: Earnest 9.4.2007, 20:50
Вот, вроде нашла, что ты имел ввиду, Xenon:

Цитата

2 If the operand has a class type, the operand is converted to a pointer type by calling the above-mentioned
conversion function, and the converted operand is used in place of the original operand for the remainder of
this section. In either alternative, if the value of the operand of delete is the null pointer the operation
has no effect. In the first alternative (delete object), the value of the operand of delete shall be a pointer
to a non-array object or a pointer to a sub-object (1.8) representing a base class of such an object (clause
10). If not, the behavior is undefined. In the second alternative (delete array), the value of the operand of
delete shall be the pointer value which resulted from a previous array new-expression. If not, the
behavior is undefined.

Оно?
Только здесь написано, что UB, если вызвать delete для array object pointer. 
И еще про "If the operand has a class type". Конечно UB - память удалится, а часть объектов не разрушится - в зависимости от объектов может случиться что угодно. 
Так будет, если удалить без [] массив, скажем string...
Только простые символы (и вообще встроенные типы) так рассматривать не стоит... Ну сам подумай, какое UB тут может быть?

Автор: Daevaorn 9.4.2007, 20:52
Цитата(Earnest @  9.4.2007,  21:50 Найти цитируемый пост)
 Ну сам подумай, какое UB тут может быть?

ну допустим у меня на микроконтролере память под массивы отдельно от остальной для оптимизации.

Автор: Xenon 9.4.2007, 20:56
Теперь, думаю, сомнений нет? smile
Цитата
[16.12] Can I drop the [] when deleteing array of some built-in type (char, int, etc)?
No! 
Sometimes programmers think that the [] in the delete[] p only exists so the compiler will call the appropriate destructors for all elements in the array. Because of this reasoning, they assume that an array of some built-in type such as char or int can be deleted without the []. E.g., they assume the following is valid code: 

    void userCode(int n)
    {
      char* p = new char[n];
      // ...
      delete p;     // <— ERROR! Should be delete[] p !
    } 
But the above code is wrong, and it can cause a disaster at runtime. In particular, the code that's called for delete p is operator delete(void*), but the code that's called for delete[] p is operator delete[](void*). The default behavior for the latter is to call the former, but users are allowed to replace the latter with a different behavior (in which case they would normally also replace the corresponding new code in operator new[](size_t)). If they replaced the delete[] code so it wasn't compatible with the delete code, and you called the wrong one (i.e., if you said delete p rather than delete[] p), you could end up with a disaster at runtime.

Автор: Earnest 9.4.2007, 21:05
Ну да, да, это аргумент, действительно...
Я, конечно, имела в виду встроенные операторы, а не переопределенные. 
Только я сразу пристрелю программиста в моей группе, если он вздумает переопределить delete и delete[] несовместимо. 
Да и вообще переопределить delete для встроенных типов.
Ей-богу, вреда от каких переопределений в долгосрочной перспективе гораздо больше чем пользы.
Если уж приспичило выделять память для интов через ж. коленом, так делать это нужно явно, шоб ясно было видно: MySuperCrazyIntAlloc() \ MySuperCrazyIntFree() !

Но формально ты прав.

Автор: JackYF 10.4.2007, 01:02
Цитата(Earnest @  9.4.2007,  21:05 Найти цитируемый пост)
MySuperCrazyIntAlloc() \ MySuperCrazyIntFree() !


Мне тут скоро писать лабу-курсак... Там как раз надо хоть раз выделить память собственным аллокатором... Я все думал, как назвать  smile 
оффтоп, конечно.



Цитата(Earnest @  9.4.2007,  21:05 Найти цитируемый пост)
Только я сразу пристрелю программиста в моей группе

зачем же так сразу... может, он решит пошутить smile

Автор: Earnest 10.4.2007, 07:43
 smile 
Цитата(JackYF @  10.4.2007,  02:02 Найти цитируемый пост)
зачем же так сразу... может, он решит пошутить

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

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