nixp.ru v3.0

23 января 2017,
понедельник,
07:37:19 MSK

DevOps с компанией «Флант»
Fatal написал 22 марта 2006 года в 15:58 (452 просмотра) Ведет себя как мужчина; открыл 123 темы в форуме, оставил 484 комментария на сайте.

#include

using namespace std;

class A

{

public:

virtual void print(){cout<<«A»<<endl;}

};

class B

{

public:

virtual void print(){cout<<«B»<<endl;}

};

class C: public A, public B

{

public:

void print(){cout<<«C»<<endl;}

};

int main()

{

B *b = new C;

A *a = new C;

b->print();

a->print();

delete b;

delete a;

return 0;

}

Подскажите, пожалуйста, почему, когда я скомпилирую этот код и запущу у меня прога падает в кору?

Выводится ошибка:

[15:48]~/practise$ ./t.out

C

C

* glibc detected * ./t.out: free(): invalid pointer: 0×0992300c *

======= Backtrace: =========

/lib/libc.so.6[0x683424]

/lib/libc.so.6(__libc_free+0×77)[0x68395f]

/usr/lib/libstdc++.so.6(_ZdlPv+0×21)[0x890669]

./t.out(__gxx_personality_v0+0×18a)[0x8048806]

/lib/libc.so.6(__libc_start_main+0xc6)[0x634de6]

./t.out(__gxx_personality_v0+0×61)[0x80486dd]

======= Memory map: ========

0042d000-0042e000 r-xp 0042d000 00:00 0

00602000-0061c000 r-xp 00000000 fd:00 719980 /lib/ld-2.3.5.so

0061c000-0061d000 r-xp 00019000 fd:00 719980 /lib/ld-2.3.5.so

0061d000-0061e000 rwxp 0001a000 fd:00 719980 /lib/ld-2.3.5.so

00620000-00744000 r-xp 00000000 fd:00 719981 /lib/libc-2.3.5.so

00744000-00746000 r-xp 00124000 fd:00 719981 /lib/libc-2.3.5.so

00746000-00748000 rwxp 00126000 fd:00 719981 /lib/libc-2.3.5.so

00748000-0074a000 rwxp 00748000 00:00 0

00752000-00774000 r-xp 00000000 fd:00 719987 /lib/libm-2.3.5.so

00774000-00775000 r-xp 00021000 fd:00 719987 /lib/libm-2.3.5.so

00775000-00776000 rwxp 00022000 fd:00 719987 /lib/libm-2.3.5.so

007d0000-007d9000 r-xp 00000000 fd:00 719988 /lib/libgcc_s-4.0.0-20050520.so.1

007d9000-007da000 rwxp 00009000 fd:00 719988 /lib/libgcc_s-4.0.0-20050520.so.1

007dc000-008bb000 r-xp 00000000 fd:00 2032089 /usr/lib/libstdc++.so.6.0.4

008bb000-008c0000 rwxp 000df000 fd:00 2032089 /usr/lib/libstdc++.so.6.0.4

008c0000-008c5000 rwxp 008c0000 00:00 0

08048000-08049000 r-xp 00000000 fd:00 1796993 /home/alek/practise/t.out

08049000-0804a000 rw-p 00000000 fd:00 1796993 /home/alek/practise/t.out

09923000-09944000 rw-p 09923000 00:00 0 [heap]

b7e00000-b7e21000 rw-p b7e00000 00:00 0

b7e21000-b7f00000 —p b7e21000 00:00 0

b7ff5000-b7ff7000 rw-p b7ff5000 00:00 0

b7ffc000-b7ffd000 rw-p b7ffc000 00:00 0

bfce7000-bfcfd000 rw-p bfce7000 00:00 0 [stack]

Аварийное завершение

Я использую:

gcc-4.0.0

Fedora 4

Fatal

Что интересно, если в классе B не делать метод принт виртуальным, то ошибка не воспроизводиться.

Fatal

Эта же трабла воспроизводиться на:

OpenBSD 3.8, gcc-3.3.5

[21:14]~> ./a.out

C

C

a.out in free(): error: modified (chunk-) pointer

Abort (core dumped)

На SunOS 5.9 (gcc-3.4.3) и WindowsXP (GUI среда Dev C++) не воспроизводится.

myst

Тогда ошибка в GCC, видимо…

Fatal

Не очень логично, что проблема в gcc

gcc-3.3.5 — воспроизводится (OpenBSD 3.8)

gcc-3.4.3 — не воспроизводится (SunOS 5.9)

gcc-4.0.0 — воспроизводится (Fedora 4)

самой ранней воспроизводится, потом более поздней не воспроизводится, потом в самой поздней воспроизводится. Я думаю, если бы в gcc, то проблема бы не скакала от версии к версии. Хотя в полне возможно, что для разработки gcc 4.0.0 была взята версия 3.3.X как базовая.

myst
Тогда ошибка в GCC, видимо…

Попробую транслировать в асемблеровский код на сане и на федоре и посмотреть в чём разница. Хотя может быть проблема в либах… даже скорее всего.

sas

На самом деле у Вас есть ошибка в коде. Прочитайте про деструкторы и наследование еще раз.

#include
using namespace std;
class A
{
public:
  virtual void print(){cout<<"A"<<endl;}
  virtual ~A() {}
};
class B
{
public:
  virtual void print(){cout<<"B"<<endl;}
  virtual ~B() {}
};
class C: public A, public B
{
public:
  void print(){cout<<"C"<<endl;}
};
int main()
{
  B *b = new C;
  A *a = new C;
  b->print();
  a->print();
  delete b;
  delete a;
  return 0;
}

должен работать, хотя я его и не проверял.

Fatal
#include

using namespace std;

class A

{

public:

 virtual void print(){cout<<«A»<<endl;}

};

class B

{

public:

 virtual void print(){cout<<«B»<<endl;}

};

class C: public A, public B

{

public:

 void print(){cout<<«C»<<endl;}

};

int main()

{

 B *b = new C;

 A *a = new C;

 b->print();

 a->print();

 delete b;

 delete a;

 return 0;

}

Подскажите, пожалуйста, почему, когда я скомпилирую этот код и запущу у меня прога падает в кору?

Выводится ошибка:

[15:48]~/practise$ ./t.out

C

C

* glibc detected * ./t.out: free(): invalid pointer: 0×0992300c *

======= Backtrace: =========

/lib/libc.so.6[0x683424]

/lib/libc.so.6(__libc_free+0×77)[0x68395f]

/usr/lib/libstdc++.so.6(_ZdlPv+0×21)[0x890669]

./t.out(__gxx_personality_v0+0×18a)[0x8048806]

/lib/libc.so.6(__libc_start_main+0xc6)[0x634de6]

./t.out(__gxx_personality_v0+0×61)[0x80486dd]

======= Memory map: ========

0042d000-0042e000 r-xp 0042d000 00:00 0

00602000-0061c000 r-xp 00000000 fd:00 719980     /lib/ld-2.3.5.so

0061c000-0061d000 r-xp 00019000 fd:00 719980     /lib/ld-2.3.5.so

0061d000-0061e000 rwxp 0001a000 fd:00 719980     /lib/ld-2.3.5.so

00620000-00744000 r-xp 00000000 fd:00 719981     /lib/libc-2.3.5.so

00744000-00746000 r-xp 00124000 fd:00 719981     /lib/libc-2.3.5.so

00746000-00748000 rwxp 00126000 fd:00 719981     /lib/libc-2.3.5.so

00748000-0074a000 rwxp 00748000 00:00 0

00752000-00774000 r-xp 00000000 fd:00 719987     /lib/libm-2.3.5.so

00774000-00775000 r-xp 00021000 fd:00 719987     /lib/libm-2.3.5.so

00775000-00776000 rwxp 00022000 fd:00 719987     /lib/libm-2.3.5.so

007d0000-007d9000 r-xp 00000000 fd:00 719988     /lib/libgcc_s-4.0.0-20050520.so.1

007d9000-007da000 rwxp 00009000 fd:00 719988     /lib/libgcc_s-4.0.0-20050520.so.1

007dc000-008bb000 r-xp 00000000 fd:00 2032089    /usr/lib/libstdc++.so.6.0.4

008bb000-008c0000 rwxp 000df000 fd:00 2032089    /usr/lib/libstdc++.so.6.0.4

008c0000-008c5000 rwxp 008c0000 00:00 0

08048000-08049000 r-xp 00000000 fd:00 1796993    /home/alek/practise/t.out

08049000-0804a000 rw-p 00000000 fd:00 1796993    /home/alek/practise/t.out

09923000-09944000 rw-p 09923000 00:00 0          [heap]

b7e00000-b7e21000 rw-p b7e00000 00:00 0

b7e21000-b7f00000 —p b7e21000 00:00 0

b7ff5000-b7ff7000 rw-p b7ff5000 00:00 0

b7ffc000-b7ffd000 rw-p b7ffc000 00:00 0

bfce7000-bfcfd000 rw-p bfce7000 00:00 0          [stack]

Аварийное завершение

Я использую:

gcc-4.0.0

Fedora 4

Fatal

sas прав. Всё работает.

А почему виртуальные диструкторы так на это влияют, они ведь пустые? у меня же нет никаких полей в классе, соответственно, ничего удалять и не нужно…

Fatal

Схема диструктора для указателя b.

Если сделать конструктор виртульным, то вызов будет происходить:

~C

~B

~A

Если не ставить виртульным, но оставить виртульным метод принт, то:

~B

Если не стивить вообще ничего виртульным, то:

~B

Два последних случая одинаковы, разница лишь в том, что для третьего случая таблица виртульных методов не будет создана вообще.

Второй вариант и третий буду выглядеть в операторе delete примерно так:

if(b)

{

b->~B()

free(b);

}

И в строчке номер два как раз и падает в кору, видимо.

Поиясните, пожалуйста, я не очень дохожу почему здесь падает в кору?

Longobard

Посмотри тут: http://home.wanadoo.nl/efx/c++-faq/dtors.html очень полезный фак, в нем я нашел ответы фактически на все свои вопросы.

anonymous

Я в C++ не силен, только в C, но по-моему так:

#include

using namespace std;

class A

{

public:

virtual void print(){cout<<«A»<<endl;}

};

class B

{

public:

virtual void print(){cout<<«B»<<endl;}

};

class C: public A, public B

{

public:

void print(){cout<<«C»<<endl;}

};

int main()

{

B *b = new B;

A *a = new A;

b->print();

a->print();

delete b;

delete a;

return 0;

}

Указатель — штука типозависимая!

Fatal

Мои текущие результаты изучения проблемы.

Я написал программу, в которой я перегрузил операторы delete и new. И ввёл макрос VDESTR, если его определить, то прога компилится с виртуальными диструкторами. И в этой программе печатаются адреса памяти, которые выделяются в операторе new, которые возвращаются new и которые освобождаются оператором delete.

Вот результаты:

При виртульных деструкторах:

0×8cb7008 — выделяется оператором new

0×8cb700c — возвращается new

0×8cb7008 — удаляется

При деструкторах по умолчанию:

0×8e10008

0×8e1000c

0×8e1000c

Видно, что при втором варианте удаление не того адреса, который возвратила функция малок в операторе нью.

Можно предположить почему нью возвращает не тот указатель, которые выделяется оператором нью.Скорее всего потому что для каждого объекта иерархии выделяется памят, а нью возвращает указатель на объект последнее класса в иерархии. В общем, конечно это детали реализации, но всё же это интересно.

И если у кого-нибудь есть замечания, мысли, пожалуйста, поделитесь.

#include

#include

using namespace std;

struct A

{

 virtual void print(){cout<<«A»<<endl;}

#ifdef VDESTR

 virtual ~A(){}

#endif

};

struct B

{

 virtual void print(){cout<<«B»<<endl;}

#ifdef VDESTR

 virtual ~B(){}

#endif

};

struct C: public A, public B

{

 virtual void print(){cout<<«C»<<endl;}

};

void *operator new(size_t size)

{

 void *p = malloc(size);

 cout<<p<<endl;

 return p;

}

void operator delete(void*p)

{

 cout<<p<<endl;

 free(p);

}

int main()

{

 B *b = new C;

 cout<<b<<endl;

 delete b;

 return 0;

}

rgo

C++ использует статические проверки типов (то есть только то что можно проверить на этапе компиляции), и поэтому не может знать что *b — типа `class C:public B’, и поэтому он вызывает статический конструктор класса B, который, естественно, не делает никаких runtime проверок и удаляет объект *b как объект класса B. Можешь попробовать вручную привести b к (class C*) и удалить результат. Тогда не будет никаких запорченных куч.

Наличие же виртуального конструктора проблему исправляет: указатель *b передаётся деструктору класса C, так как в таблице виртуальных методов *b есть указатель на деструктор. А уж деструктор C, знает, с каким смещением от b (переданного ему неявным аргументом) начинается блок выделенный new.

Fatal

Спасибо!

rgo, скажи, пожалуйста, где ты узнал об этом и что можно почитать по с++, что бы там описывались тонкости этого языка?

Fatal
rgo
… статический конструктор класса B…

Наличие же виртуального конструктора проблему исправляет…

Наверно, ты здесь имел в виду виртульные деструкторы.

1. А почему тогда не выпадает в кору, если сделать тип указателя будет класс A:

A *a = new C;

delete a;

2. Где можно почитать, чем отличаются указатели разных типов? то есть почему если сделать так:

B *b = new C;

cout<<b<<» «<<(C*)b<<endl;

то указатели будут иметь разные значения? Почему они ссылаются на разные участки памяти. Причём, в ниже приведённом коде a и (C*)a,

A *a = new C;

cout<<a<<» «<<(C*)a<<endl;

будут иметь один и тот же адрес.

sas
sas
Прочитайте про деструкторы и наследование еще раз.

Используйте поисковики. Об этом написано в куче мест.

Fatal
sas
Используйте поисковики. Об этом написано в куче мест.

Если вы считаете, что об этом написано в куче мест, то ткните меня, пожалуйста, на ответ следущего вопроса:

Имеется прога, классы используются точно такие же.

Здесь выделяется память под объект класса C и присваивается указателю типа A.

Печатает адрес этого указателя, затем приводятся к типу C и снова печатается адрес.

Потом выделяется память для указателя B и печатаются те же значения, то есть b и (C*)b.

#include

using namespace std;

struct A

{

virtual void print(){cout<<«A»<<endl;}

};

struct B

{

virtual void print(){cout<<«B»<<endl;}

};

struct C: A, B

{

virtual void print(){cout<<«C»<<endl;}

};

int main()

{

A *a = new C;

cout<<a<<» «<<(C*)a<<endl;

B *b = new C;

cout<<b<<» «<<(C*)b<<endl;

delete a;

delete (C*)b;

return 0;

}

Вопрос: почему при распечатке b и (С*)b выводятся разные адреса памяти, а при распечатке a и (С*)a нет?

Fatal

Если вы правы, и этого добра полно в инете, то я больше никогда не буду задавать вопросы по си++, а всегда буду их искать в инете. ;-)

ЗЫ: Хотя в общем-то я так и делал, но на этот вопрос я не нашёл ответа.

sas
Fatal
Если вы правы, и этого добра полно в инете, то я больше никогда не буду задавать вопросы по си++, а всегда буду их искать в инете. ;-)

ЗЫ: Хотя в общем-то я так и делал, но на этот вопрос я не нашёл ответа.

http://www.parashift.com/c++-faq-lite/virtual-functions.html#faq-20.7

http://www.informit.com/guides/content.asp?g=cplusplus&seqNum=196&rl=1

Еще?

rgo
Fatal
Спасибо!

rgo, скажи, пожалуйста, где ты узнал об этом и что можно почитать по с++, что бы там описывались тонкости этого языка?

Тонкости можно почитать а Страубструпа ;).

А если интересуют тонкости реализации, то не знаю. Я сам допёр, из общих соображений. Услышал про необходимость виртуальных деструкторов, увидел разные указатели. Подумал как может выглядеть asm код, который генерит g++ и допёр. Опыт работы с дизассемблером не пропьёшь :).

Fatal
rgo
Опыт работы с дизассемблером не пропьёшь :).

Это точно, но к сожалению я им не обладаю :-(

anonymous

>>Имеется прога, классы используются точно такие же.

>>Здесь выделяется память под объект класса C и присваивается указателю типа A.

В данной конкретной программе Вы работаете не с классами а со структурами

>>Вопрос: почему при распечатке b и (С*)b выводятся разные адреса памяти, а при распечатке a и (С*)a нет?

Ответ на вопрос в строке

>>struct C: A, B

начало памяти выделенной под структуру С совпадает с началом структуры А,

попробуйте

struct C: B, A

вывод программы изменится, b и (С*)b будут показывать на одно и то же.

Longobard
primus
В данной конкретной программе Вы работаете не с классами а со структурами

В C++ между структурой и классом нет существенной разницы. Структура — это класс, где все члены имеют квалификатор доступа public. Вот и все различия. RTFM ;)

Fatal
primus
>>struct C: A, B

начало памяти выделенной под структуру С совпадает с началом структуры А,

попробуйте

struct C: B, A

вывод программы изменится, b и (С*)b будут показывать на одно и то же.

Насчёт, struct C: B, A. Я говорил, про класс, который стоит не первый в списке наследования, и не важно какое он несёт имя, A или B, не в этом суть.

Если бы это было так, то ключевые слова virtual перед методом print ничего бы не меняли. А если нет виртуальных методов, то вывод для b и (С*)b одинаковый, хоть он стоит первый, хоть второй… хоть сотый в списке наследования.

Fatal
sas
http://www.parashift.com/c++-faq-lite/virtual-functions.html#faq-20.7

http://www.informit.com/guides/content.asp?g=cplusplus&seqNum=196&rl=1

Еще?

В выходные собираюсь проштудировать эти ссылки, но дадю 99% что там нет ответа на мой вопрос…

anonymous

>>А если нет виртуальных методов, то вывод для b и (С*)b одинаковый, хоть он стоит первый, хоть второй… хоть сотый в списке наследования

Пример кода?

Мне кажется происходит вот что:

B *b = new C;

1.создается объект типа С

2.указателю b присваевается адрес подобъекта (subobject) B, входящего в объект C

3.подобъект (subobject) типа A уже тоже существует

cout<<b<<» «<<(C*)b<<endl;

4.(C*)b — начало выделенной памяти

5.b — адрес подобъекта (subobject) B

Вот этот код подтвердит вышесказанное?

#include

using namespace std;

struct A

{

int a;

virtual ~A(){};

};

struct B

{

int b;

virtual ~B(){};

};

struct C: A, B

{

int c;

};

int main()

{

A *a = new C;

B *ab = dynamic_cast(a);

cout<<a<<» «<<(C*)a<<» «<< ab << endl;

B *b = new C;

A *ba = dynamic_cast<a>(b);</a>

<a>cout<<b<<» «<<(C*)b<<» «<< ba << endl;</a>

<a>cout << «size of A = » << sizeof(A)</a>

<a><<«\t» << «size of B = » << sizeof(B)</a>

<a><<«\t» <<"size of C = » << sizeof(C)</a>

<a><<«\t» << endl;</a>

<a>delete a;</a>

<a>delete b;</a>

<a>return 0;</a>

<a>}</a>

Fatal
primus
>>А если нет виртуальных методов, то вывод для b и (С*)b одинаковый, хоть он стоит первый, хоть второй… хоть сотый в списке наследования

Пример кода?

Мне кажется происходит вот что:

B *b = new C;

1.создается объект типа С

2.указателю b присваевается адрес подобъекта (subobject) B, входящего в объект C

3.подобъект (subobject) типа A уже тоже существует

Да, ты прав! Спасибо большое!!!

Я понял, почему без виртульных функций у меня печатались одинаковые адреса, а у тебя нет. Плюс ты мне ещё помог разобраться почему static_cast рулет по сравнению с обычным преобразованием типов(C*). Компилятор мне выдал ошибки, что нельзя переводить типы, так как объекты не полиморфные.

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

Круто!!! Спасибо!!!

sas
Fatal
В выходные собираюсь проштудировать эти ссылки, но дадю 99% что там нет ответа на мой вопрос…

Ну что, будем признаваться, что ответ есть в интернете и после «Прочитайте еще раз про деструкторы и наслдование» этих 2-х страниц обсуждения не надо было?

Или конкретные цитаты приводить?

На будущее Вам надо внимательнее читать матчасть и побольше думать о прочитанном (что как и почему).

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

Fatal
sas
Ну что, будем признаваться, что ответ есть в интернете и после «Прочитайте еще раз про деструкторы и наслдование» этих 2-х страниц обсуждения не надо было?

Или конкретные цитаты приводить?

На будущее Вам надо внимательнее читать матчасть и побольше думать о прочитанном (что как и почему).

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

согласен

ecobeingecobeing.ru
Экология и вегетарианство на благо всем живым существам Планеты.