#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
Последние комментарии
- OlegL, 17 декабря 2023 года в 15:00 → Перекличка 21
- REDkiy, 8 июня 2023 года в 9:09 → Как «замокать» файл для юниттеста в Python? 2
- fhunter, 29 ноября 2022 года в 2:09 → Проблема с NO_PUBKEY: как получить GPG-ключ и добавить его в базу apt? 6
- Иванн, 9 апреля 2022 года в 8:31 → Ассоциация РАСПО провела первое учредительное собрание 1
- Kiri11.ADV1, 7 марта 2021 года в 12:01 → Логи catalina.out в TomCat 9 в формате JSON 1
Что интересно, если в классе B не делать метод принт виртуальным, то ошибка не воспроизводиться.
Эта же трабла воспроизводиться на:
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++) не воспроизводится.
Тогда ошибка в GCC, видимо…
Не очень логично, что проблема в 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 как базовая.
Попробую транслировать в асемблеровский код на сане и на федоре и посмотреть в чём разница. Хотя может быть проблема в либах… даже скорее всего.
На самом деле у Вас есть ошибка в коде. Прочитайте про деструкторы и наследование еще раз.
должен работать, хотя я его и не проверял.
sas прав. Всё работает.
А почему виртуальные диструкторы так на это влияют, они ведь пустые? у меня же нет никаких полей в классе, соответственно, ничего удалять и не нужно…
Схема диструктора для указателя b.
Если сделать конструктор виртульным, то вызов будет происходить:
~C
~B
~A
Если не ставить виртульным, но оставить виртульным метод принт, то:
~B
Если не стивить вообще ничего виртульным, то:
~B
Два последних случая одинаковы, разница лишь в том, что для третьего случая таблица виртульных методов не будет создана вообще.
Второй вариант и третий буду выглядеть в операторе delete примерно так:
if(b)
{
b->~B()
free(b);
}
И в строчке номер два как раз и падает в кору, видимо.
Поиясните, пожалуйста, я не очень дохожу почему здесь падает в кору?
Посмотри тут: http://home.wanadoo.nl/efx/c++-faq/dtors.html очень полезный фак, в нем я нашел ответы фактически на все свои вопросы.
Я в C++ не силен, только в C, но по-моему так:
Указатель — штука типозависимая!
Мои текущие результаты изучения проблемы.
Я написал программу, в которой я перегрузил операторы 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;
}
C++ использует статические проверки типов (то есть только то что можно проверить на этапе компиляции), и поэтому не может знать что *b — типа `class C:public B’, и поэтому он вызывает статический конструктор класса B, который, естественно, не делает никаких runtime проверок и удаляет объект *b как объект класса B. Можешь попробовать вручную привести b к (class C*) и удалить результат. Тогда не будет никаких запорченных куч.
Наличие же виртуального конструктора проблему исправляет: указатель *b передаётся деструктору класса C, так как в таблице виртуальных методов *b есть указатель на деструктор. А уж деструктор C, знает, с каким смещением от b (переданного ему неявным аргументом) начинается блок выделенный new.
Спасибо!
rgo, скажи, пожалуйста, где ты узнал об этом и что можно почитать по с++, что бы там описывались тонкости этого языка?
Наверно, ты здесь имел в виду виртульные деструкторы.
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;
будут иметь один и тот же адрес.
Используйте поисковики. Об этом написано в куче мест.
Если вы считаете, что об этом написано в куче мест, то ткните меня, пожалуйста, на ответ следущего вопроса:
Имеется прога, классы используются точно такие же.
Здесь выделяется память под объект класса 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 нет?
Если вы правы, и этого добра полно в инете, то я больше никогда не буду задавать вопросы по си++, а всегда буду их искать в инете. ;-)
ЗЫ: Хотя в общем-то я так и делал, но на этот вопрос я не нашёл ответа.
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
Еще?
Тонкости можно почитать а Страубструпа ;).
А если интересуют тонкости реализации, то не знаю. Я сам допёр, из общих соображений. Услышал про необходимость виртуальных деструкторов, увидел разные указатели. Подумал как может выглядеть asm код, который генерит g++ и допёр. Опыт работы с дизассемблером не пропьёшь :).
Это точно, но к сожалению я им не обладаю :-(
>>Имеется прога, классы используются точно такие же.
>>Здесь выделяется память под объект класса C и присваивается указателю типа A.
В данной конкретной программе Вы работаете не с классами а со структурами
>>Вопрос: почему при распечатке b и (С*)b выводятся разные адреса памяти, а при распечатке a и (С*)a нет?
Ответ на вопрос в строке
>>struct C: A, B
начало памяти выделенной под структуру С совпадает с началом структуры А,
попробуйте
struct C: B, A
вывод программы изменится, b и (С*)b будут показывать на одно и то же.
В C++ между структурой и классом нет существенной разницы. Структура — это класс, где все члены имеют квалификатор доступа public. Вот и все различия. RTFM ;)
Насчёт, struct C: B, A. Я говорил, про класс, который стоит не первый в списке наследования, и не важно какое он несёт имя, A или B, не в этом суть.
Если бы это было так, то ключевые слова virtual перед методом print ничего бы не меняли. А если нет виртуальных методов, то вывод для b и (С*)b одинаковый, хоть он стоит первый, хоть второй… хоть сотый в списке наследования.
В выходные собираюсь проштудировать эти ссылки, но дадю 99% что там нет ответа на мой вопрос…
>>А если нет виртуальных методов, то вывод для 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>
Да, ты прав! Спасибо большое!!!
Я понял, почему без виртульных функций у меня печатались одинаковые адреса, а у тебя нет. Плюс ты мне ещё помог разобраться почему static_cast рулет по сравнению с обычным преобразованием типов(C*). Компилятор мне выдал ошибки, что нельзя переводить типы, так как объекты не полиморфные.
В моей проге, если не делать методы виртуальными, то печатаются одинаковые адреса памяти, потому что они реально совпадают, так как нет членов класс, то есть данных, а у тебя есть. И поэтому прога без виртульных методов не падала в кору.
Круто!!! Спасибо!!!
Ну что, будем признаваться, что ответ есть в интернете и после «Прочитайте еще раз про деструкторы и наслдование» этих 2-х страниц обсуждения не надо было?
Или конкретные цитаты приводить?
На будущее Вам надо внимательнее читать матчасть и побольше думать о прочитанном (что как и почему).
Тестовые программы нужны для подтверждения умозаключений, а не наоборот.
согласен