Форум «Всё о Паскале» _ Ада и другие языки _ тип string, утечка памяти
Автор: TarasBer 18.09.2009 19:35
Работу с типом char* я тупо не смог осилить, так как так и не понял, при каких операциях надо выделять ему память, при каких не надо. Короче ужаснейший тип, и кто его придумал, и ну его нафиг. Решил применить string - я так понял, что это аналог паскалевского. Но обнаружилось, что после динамического создания структуры, содержащей поле типа string, и её удаления кол-во занимаемой памяти меняется. Встатив этот кусок в бесконечный цикл, я обнаружил, что память, пожираемая программой, пульсирует от 10 до 50 MЬ. Вторая проблема - при отладке на операциях с этим типом он лезет в свои модули, мне они неинтересны, как запретить отладчику в них влазить. Блин, настроек по сравнению с Дельфой раза в 3 больше, на кой.
Автор: volvo 18.09.2009 20:49
Цитата
так и не понял, при каких операциях надо выделять ему память, при каких не надо.
Выделять память надо всегда. Просто когда-то ее можно выделить самим фактом инициализации строки:
char *s = "my string";
, а иногда это приходится делать через new, то есть выделять память динамически.
Цитата
после динамического создания структуры, содержащей поле типа string, и её удаления кол-во занимаемой памяти меняется.
Можно посмотреть на структуру, и на то, как выделяется память? Что говорят средства отладки (скажем, тот же CodeGuard)?
Добавлено через 1 мин.
Цитата
Вторая проблема - при отладке на операциях с этим типом он лезет в свои модули, мне они неинтересны, как запретить отладчику в них влазить.
Не делать "Step Into", а пользоваться вместо этого "Step Over", тогда отладчик будет выполнять всю строку, а не по отдельным операциям.
Автор: TarasBer 18.09.2009 21:45
Цитата(volvo @ 18.09.2009 17:49)
Выделять память надо всегда. Просто когда-то ее можно выделить самим фактом инициализации строки:
char *s = "my string";
, а иногда это приходится делать через new, то есть выделять память динамически.
Но просто s = "my string" уже не катит. Что делают с памятью strcpy и strcat - тоже непонятно.
Цитата
Можно посмотреть на структуру, и на то, как выделяется память? Что говорят средства отладки (скажем, тот же CodeGuard)?
Ну например:
Код
struct S { string Name; }; ... S *s; while(1){ s = new S[8]; delete s; };
Прикол в том, что если заменить string на int, то утечка полностью исчезает.
В кодгуарде пока не разобрался.
Цитата
Добавлено через 1 мин. Не делать "Step Into", а пользоваться вместо этого "Step Over", тогда отладчик будет выполнять всю строку, а не по отдельным операциям.
А если строка передаётся как параметр в функцию? Тогда степ овер пропусткает то, что делает функция, а это как раз не надо. А степ инто тут же залезает в дебри всякие.
Автор: volvo 18.09.2009 22:23
Цитата
Но просто s = "my string" уже не катит.
Естественно... Для работы со строками в стиле С существуют функции, начинающиеся на str... В данном случае тебе нужен strcpy.
Цитата
Что делают с памятью strcpy и strcat - тоже непонятно.
С чего бы? Ничего они не делают с памятью. В описании strcpy ясно написано, что "для того, чтобы избежать порчи памяти из-за переполнения, указатель destination должен указывать на область памяти достаточного объема". То есть, выделить необходимую память перед копированием - забота программиста.
То же самое касается и strcat, destination должен указывать на область памяти достаточного объема, чтобы вместить результирующую строку и завершающий символ '\0'.
Цитата
Прикол в том, что если заменить string на int, то утечка полностью исчезает.
Нет, прикол не в этом. А в том, что если ты выделяешь память под МАССИВ структур, то и удалять надо МАССИВ структур:
struct S { string Name; }; ... S *s; while(1){ s = new S[8]; delete [] s; } // здесь точка с запятой не нужна, кстати
А почему утечка исчезает при замене string на int - это отдельный разговор, сли интересно - я расскажу, а просто так не хочется по клавишам стучать.
Автор: TarasBer 18.09.2009 22:43
Цитата(volvo @ 18.09.2009 19:23)
С чего бы? Ничего они не делают с памятью. В описании strcpy ямсно написано, что "для того, чтобы избежать порчи памяти из-за переполнения, указатель destination должен указывать на область памяти достаточного объема". То есть, выделить необхпдимую память перед копированием - забота программиста.
И ВСЕ стандрртные функции, возвращающие char* - тоже требуют предварительного выделения памяти? Ладно, тогда вроде понятнее. Попробую эти char* ещё раз расписать. А то что все пишут на C и не жалуются, а я не всё не врубаюсь, всё время кажется, что меня Паскаль испортил.
Цитата
Нет, прикол не в этом. А в том, что если ты выделяешь память под МАССИВ структур, то и удалять надо МАССИВ структур:
Код
... delete [] s; } // здесь точка с запятой не нужна, кстати
Вот ведь оно как, так и знал, что какую-то закорючку забыл поставить. А лишние точки с запятой, не вызывающие ошибок компиляции - разве вредят?
Цитата
А почему утечка исчезает при замене string на int - это отдельный разговор, сли интересно - я расскажу, а просто так не хочется по клавишам стучать.
Интересно.
Хотя не, такой момент ещё непонятен. Если у формы есть свойство Caption, то надо писать Caption = IntToStr(i) или сначала S = IntToStr(i), а потом Caption = S, где S - промежуточная строка, под которую выделена память?
Автор: volvo 18.09.2009 23:05
Цитата
А лишние точки с запятой, не вызывающие ошибок компиляции - разве вредят?
До поры до времени - не вредят... Пока ты не напишешь какую-нибудь конструкцию, в которой эта самая точка с запятой, скажем, образует пустой цикл, пустую ветку if/else или еще что-нибудь такое же трудноуловимое. Синтаксис С очень многое позволяет "намудрить", поэтому лучше избавляться от привычки ставить лишние символы, даже если они не вызывают ошибок компиляции.
Цитата
Интересно.
Ну, тогда смотри: при выполнении delete, если добавить [], как я сделал, происходит вот что: сначала для каждого элемента массива вызывается его деструктор, и только потом освобождается память, выделенная под сам массив. Что происходит в случае замены string на int? А ничего страшного не произойдет, даже если не вызвать деструктор для int, от него все равно толку нет, утечки не будет даже без его вызова. В случае string все серьезнее: это полноправный класс, который имеет конструктор, выделяющий память. И экземпляр этого класса требуется удалить, иначе сам экземпляр остается висеть в памяти, а указатель на него потеряется.
Поэтому всегда, когда выделяешь память динамически под массив, либо состоящий из не POD-типов (Plain Old Data, данные в стиле С), либо содержащий такие типы (как у тебя - структура, которая содержит non-POD type), то освобождай ее всегда с использованием delete [] p_arr;, чтобы быть уверенным в том, что для каждого элемента вызовется деструктор, и только потом удалится сам массив.
Автор: TarasBer 19.09.2009 0:05
Цитата(volvo @ 18.09.2009 20:05)
Ну, тогда смотри: при выполнении delete, если добавить [], как я сделал, происходит вот что: сначала для каждого элемента массива вызывается его деструктор, и только потом освобождается память
Это, я так понял, только для не-POD типов, потому что для массива объектов для каждого элемента деструктор надо вызывать руками? Решил вместо char* работать с AnsiString, а то у всех компонентов свойства как раз такого типа.
Автор: volvo 19.09.2009 0:53
Цитата
Это, я так понял, только для не-POD типов, потому что для массива объектов для каждого элемента деструктор надо вызывать руками?
Угу, именно поэтому...
Кстати, а что за задачу ты решаешь? Может, будет выгоднее не выделять самому динамически память под массив, а воспользоваться либо vector<AnsiString> либо vector<S>? Тут ведь мало того, что не надо выделять память вручную, так еще и удалять вектор не надо, он описывается статически, следовательно при выходе из области видимости самоликвидируется (причем корректно, перед этим вызвав деструктор каждого своего элемента).
Автор: TarasBer 19.09.2009 1:07
Цитата(volvo @ 18.09.2009 21:53)
Кстати, а что за задачу ты решаешь?
Я решаю задачу освоения нового для меня и довольно хитрого языка, за который к тому же много где платят, в отличие от Дельфей (увы, реальность примерно такова). Пока впечатление отвратительное. На примере калькулятора со встроенным парсером и ещё некоторыми наворотами (он уже знает, что 2+2*2 равно 6). Тем более, что опыт распознавания выражений имею. Собсна та структура, содержащая строчку - это описание функций, содержащее как раз и имя, и ссылку на операцию, и ещё много чего.
Цитата
Может, будет выгоднее не выделять самому динамически память под массив, а воспользоваться либо vector<AnsiString> либо vector<S>? Тут ведь мало того, что не надо выделять память вручную, так еще и удалять вектор не надо, он описывается статически, следовательно при выходе из области видимости самоликвидируется (причем корректно, перед этим вызвав деструктор каждого своего элемента).
Ну я так понял, что фишка С++ в полной открытости всех действий перед программистом. И мне кажется, что использовать в нём подобные вещи - всё равно что покупать джип для езды по городу.
Автор: volvo 19.09.2009 1:29
Фишка С++ - не в том, чтобы не писать свой велосипед каждый раз, да еще и чинить его потом на каждом повороте, а в том, что ты берешь отлаженную сотнями и тысячами программистов библиотеку (я про STL) и просто реализуешь свою задачу. Нет, дело твое, конечно, но потом не говори, что написание программ на С++ занимает ОЧЕНЬ долгое время. Оно занимает просто долгое время, а не ОЧЕНЬ. ОЧЕНЬ - это из-за того, что ты опять с нуля реализуешь то, что уже есть в языке (хотелось бы напомнить, что STL - это часть Стандарта C++).
По поводу "джипов". Лучше я буду ездить везде на джипе, чем каждый раз менять транспортные средства (доехал до перекрестка, а там - лужа. Упс... Надо пересесть в амфибию, а то на велосипеде и утонуть можно). Хотя ко мне это относится меньше всего. Ада - язык универсальный, причем с Паскалевским синтаксисом, что снимает огромную часть проблем, да и оплачивается программирование на нем в разы (если не на порядки) выше, чем программирование на С++. И средств, подобных тому же STL-ю там не меньше, а даже больше... Так что я свой джип уже нашел. Передвигается одинаково уверенно и по Win, и по Lin, и по Embedded, и вообще практически по любым существующим территориям. Хочешь попробовать догнать? Но это уже оффтоп в данной теме.
Автор: TarasBer 19.09.2009 2:44
Цитата(volvo @ 18.09.2009 22:29)
Фишка С++ - не в том, чтобы не писать свой велосипед каждый раз, да еще и чинить его потом на каждом повороте, а в том, что ты берешь отлаженную сотнями и тысячами программистов библиотеку (я про STL) и просто реализуешь свою задачу.
Вот как раз для такого куда лучше бы подошёл какой-нибудь другой язык, пусть не с таким оптимизированным кодом, как говорят про C++, зато более дружелюбный к программисту. А иначе зачем вообще этот C++ нужен?
Автор: renesko1 19.09.2009 18:29
С++ наоборот ИМХО понятен, если программа написана с высоким и правильным содержанием STL компонентов.(к сожалению, мои программы этим пока не очень отличаются). std::vector<car> вектор из машин)
Автор: TarasBer 19.09.2009 22:55
Хьюстон, у нас опять проблемы. При вводе корректных выражений утечка прекратилась, но при вводе "1/0" память опять потекла. Само деление обёрнуто в
try { } catch (...) { };
Внутри трая само деление, внутри катча присвоение пустого значения.
Автор: volvo 19.09.2009 23:34
Где память выделяется, и где она освобождается? Если сделать так:
S *s = new S[8]; try { // int i = 1, j = 0; int x = i / j; } catch(...) { // Ну, по-хорошему, тут надо еще уточнить что за исключение ShowMessage("Division by Zero"); } delete [] s;
, то утечки не будет... Покажи, как делаешь ты...
Автор: TarasBer 20.09.2009 0:00
Естественно, все выделения и освобождения находятся ВНЕ блока. Чтобы указывать исключение, надо запоминать их список и названия, а операций, которые могут вызвать то или иное исключение, довольно много. Тот же логарифм, например. Проще ловить любое. Полностью блок выглядит так:
Ну, хорошо... Это переопределение операции деления. Для того, чтобы у тебя была утечка, надо, чтобы память выделялась и не освобождалась, так? Вот я и хочу увидеть, как ты вызываешь эту самую операцию деления, и что происходит в ее ближайшем окружении, после чего у тебя теряется память...
Исключение поймано, IsValue результата установлено в false, все нормально. Дальше все зависит от того, как именно ты обрабатываешь этот IsValue...
Автор: TarasBer 20.09.2009 1:23
Значит собсна вычисление выражения выглядит так:
void TTree::GetValue() { if (Data.Func) { for (int i = 0; i < Count; i++) { Child[i]->GetValue(); }; Data.Value = Data.Func->Operation(Count, Child); }; };
Тут пока так сделано, что если указатель Data.Func нулевой, то значит эта вершина содержит константу и содержимое Value и так уже правильное. Сам Data.Func, если не нулевой, ссылается на элемент из заранее созданного массива, то есть его создавать и удалять не надо. Счётчик объектов TTree говорит, что после все действий объектов ноль - то есть тут тоже всё в порядке. Operation - это указатель на тип-функцию:
void __fastcall TForm1::Button1Click(TObject *Sender) { TComplex *f = new TComplex[2];
TComplex c = Div(2, f); delete [] f;
ShowMessage(BoolToStr(c.IsValue, true)); }
Все нормально, утечек нет... Значит проблема-таки с TTree где-то. Подключи уже CodeGuard (у меня в Builder 2009 для этого надо установить Project -> Options -> C++ Compiler Debugging -> Enable CodeGuard в True, как это делается в BCB 6 - не помню), и посмотри, что он тебе говорит, где именно утечка?
Автор: TarasBer 20.09.2009 4:04
Запуск с гуардом даёт аксесс виолейшн, программа намертво виснет, не желает завершаться по команде из среды, завершается только проводником, причём так как среда об этом не узнаёт, для перекомпиляции приходится перезапускать среду. Я не пойму, что за такая проблема с TTree может быть, которая с 1/1 не вылезает, а с 1/0 вылезает.
Автор: volvo 20.09.2009 4:10
Присоедини свой проект, я его прогоню в 2009... Можно в приват, если не хочешь выкладывать в общий доступ.
Автор: TarasBer 20.09.2009 4:34
Заодно я проверил, что дело не в "пустом" значении - я вставил в процедуру деления простой возврат "пустого" значения, без трая. Утечка исчезла.
> Присоедини свой проект, я его прогоню в 2009... Можно в приват, если не хочешь выкладывать в общий доступ.
Да там нечего скрывать: я пока не настолько крут, чтобы мой код имело смысл скрывать от чужих глаз.
А ты знаешь, у меня не вылетает. У тебя вечный цикл while(1). Насколько я понимаю, ты его сделал только, чтобы проверить на утечки? Так вот по 10 минут работало без перерыва, ни одного байта памяти не прибавило... Вводил выражения 1/1 и 1/0... И в том и в другом случае прекрасно считает и результат пишется в Caption формы. В случае деления на 0 - как ты и требовал от функции она возвращает "?".
Автор: TarasBer 20.09.2009 16:10
> Так вот по 10 минут работало без перерыва, ни одного байта памяти не прибавило...
Странно. Я этот момент проверяю при помощи виндовского Task Managera, или это неправильный метод?
У меня настолько палёный билдер? Я его с какой-то шары скачал, если честно.
> В случае деления на 0 - как ты и требовал от функции она возвращает "?".
Да.
Автор: TarasBer 20.09.2009 18:07
И надо сказать, что хавает от не спеша, где-то полметра в секунду.
Эскизы прикрепленных изображений
Автор: volvo 20.09.2009 18:52
Ну не получается у меня, чтоб пожиралась память... Как только установилось определенное количество памяти (после нескольких первых итераций), так и стоит как вкопанное. Если что - можешь посмотреть видео: http://vlady.uzelok.net/video/calc.avi
За 30 секунд ничего не изменилось... Что-то у тебя с Билдером... Хотя, может быть это в BDS2009 получше работа с памятью, пускай кто-нибудь еще на BCB6 проверит...
Автор: TarasBer 20.09.2009 19:07
А что это он сразу 8 метров отхватил? У меня в корректных случаях никогда до 5 не доходит. Или это уже особенности более позднего компилятора?
Автор: volvo 20.09.2009 19:15
Это Дебаг-версия + CodeGuard... Если скомпилировать в Release и отключить все, что не надо - будет гораздо меньше.
Автор: TarasBer 20.09.2009 22:20
Я правильно понимаю, что команда new для создания строки не может вызывать access violation в нормально работающей среде? А то у меня чем дальше, тем круче.
Автор: TarasBer 23.09.2009 2:17
Мне удалось подключить CodeGuard. Запустил, ввёл 1/0. Открыл лог, который переименован в тхт и прилагается. Если я правильно его понял, то 5 вызовов malloc 5 вызовов free (я решил по старинке писать). Наборы адресов совпадают. Ставлю бесконечный цикл, запускаю, диспетчер видит утечку. Я сойду с ума.
Решил упростить свой случай до предела. Написал в обработчике нажатия кнопки вот это:
while (1) { int *i; int *j; i = (int*)malloc(sizeof(int)); j = (int*)malloc(sizeof(int)); *i = 1; *j = 0; int k; try { k = *i / *j; } catch (...) { k = 0; }; free(i); free(j); };
Утечка есть, причём неслабая. Может быть, дело в том, что создаётся объект-исключение, который надо как-то определять и удалять? И как это делать?
Автор: volvo 23.09.2009 17:57
Цитата
Может быть, дело в том, что создаётся объект-исключение, который надо как-то определять и удалять?
Ничего там не создается... Проверил и Билдером, и GCC - нет утечки. Что-то у тебя с Билдером явно не то. К сожалению, запустить тот EXE-шник, что ты присоединял в 21-ом посте, и проверить, дает ли он утечку, я не могу, он требует библиотеки от BCB6, а у меня их нет.
Автор: TarasBer 23.09.2009 18:16
Тогда ещё вопрос - как надо компилировать программу, чтобы она запускалась на любом компе, где есть винда?
И в этом случае возможны проблемы (при отсутствии Билдера в системе), какие-то библиотеки все равно привяжутся к проекту. Полностью работоспособно только приведенное по ссылке решение.
Цитата
Екзешник с утечкой ещё интересен?
Собственно, интересно было посмотреть, насколько утекает память. Да, за 12 секунд сожрало почти 30 Мб - это много. Попробуй все-таки другой Билдер, скорее всего проблема в этом, ибо я тестировал твой проект (без изменений) и на 2007 и на 2009. Ни там ни там нет утечек.
Автор: TarasBer 24.09.2009 0:31
А просканировать как-нибудь тот екзешник, чтоб понять, где глюк, ну и сравнить побайтно с результатом компилирования в ВС2009 - реально?
Автор: volvo 24.09.2009 1:52
Реально, только сравнивать надо не EXE-шники, а MAP-файлы (заставить Билдер генерировать MAP-файлы: фактически - ассемблерный код, и сравнивать результаты этой операции для обоих версий Билдера). Только это - чуть позже, у меня еще ремонт в квартире продолжается, я выхожу то с одного компьютера, то с другого, а все, что нужно для подобных детальных сравнений у меня установлено только в одном месте, я туда еще не добрался, комната завалена полностью. На следующей неделе, надеюсь, начну разгребать этот бардак, тогда уже и посмотрю, в чем разница...
Автор: TarasBer 25.09.2009 2:22
А существует ли какой-либо механизм предсказания ошибки вычислений, помимо исключения? То есть чтобы я, зная два числа, смог заранее, без исключения, сказать, поделится (умножится, сложится, вычтется) ли одно на другое, или нет?
Автор: TarasBer 27.09.2009 17:30
Попытался заняться дальнейшим упрощением "неверного" кода.
void __fastcall TForm1::Button1Click(TObject *Sender) { int i = 1; while(1) { try { int j = i / 0; } catch (...) { }; }; }
Утечка в коде, не содержащем НИ ОДНОГО указателя! Писать сразу 1 / 0 не получится, компилятор палит. Да, я уже скачал отдельно компилятор с другого места.
Автор: volvo 27.09.2009 17:47
Цитата
Утечка в коде, не содержащем НИ ОДНОГО указателя!
Значит, в BCB6 такая "хорошая" работа с памятью... Проверь на более новом Билдере. Попробуй проверить на другом С++ компиляторе, есть ли утечка там? Я проверил только что в BDS2009 - результат аналогичен тому, что было выше - сначала плюс несколько килобайт, а потом - без увеличения.
Автор: TarasBer 27.09.2009 18:02
Цитата(volvo @ 27.09.2009 14:47)
Значит, в BCB6 такая "хорошая" работа с памятью...
О да, я крут, сел осваивать новый язык и тут же нашёл нехилый баг в компиляторе.
Цитата
Проверь на более новом Билдере. Попробуй проверить на другом С++ компиляторе, есть ли утечка там?
Я попробую найти другой С++ компилятор, но это не так просто.
Автор: volvo 27.09.2009 18:09
Цитата
но это не так просто.
Чего сложного? http://www.codeblocks.org/downloads/5 (IDE вместе с компилятором и отладчиком, около 20Мб)
Автор: volvo 27.09.2009 18:40
Цитата
сел осваивать новый язык и тут же нашёл нехилый баг в компиляторе.
Нет, этот баг нашел не ты, его нашли другие... Ты просто на него нарвался. На delphigroups.info проскакивала информация об ошибке, связанной с синтаксисом catch(...) на старых Билдерах, и о том, что желательно использовать конструкцию catch(Exception&) вместо нее... Так что сначала попробуй код:
#include <exception> // ... int i = 1; while(1) { try { int j = i / 0; } catch (Exception& e) { // ShowMessage(e.Message); }
}
, и проверь, "утекает" ли по-прежнему память?
Автор: TarasBer 28.09.2009 17:20
catch (Exception &E) { ... delete &E; };
Помогло. Без делита - утечка. Мдааа.
Автор: TarasBer 28.09.2009 19:47
А такой код, с удалением исключения, будет работать на новых билдерах?
Автор: volvo 28.09.2009 23:01
Цитата
А такой код, с удалением исключения, будет работать на новых билдерах?
Нет, при попытке сделать
delete &E;
получишь Invalid pointer operation, и этим дело закончится.
Автор: TarasBer 28.09.2009 23:23
Тогда какой #ифдеф на этот случай посоветуете? Я в них ноль, так как не приходилось сталкиваться с таким.
0x560 - это "чистый" BCB6, и еще 4 - с разными Service Pack-ами
Автор: TarasBer 29.09.2009 13:35
> && (__BORLANDC__ >= 0x560)
Ну, во-первых, у меня вроде 5.5, во-вторых, это условие говорит, что для более ранних версий этого бага не было?
Автор: volvo 29.09.2009 14:04
Ты спрашивал, как определить Билдер 6 директивами компиляции? Я тебе показал, как это делается... Нужно включить ВСЕ глючные версии - убирай нижнее условие, оставляй только верхнюю границу...
Баг был на всех Билдерах, как минимум до BDS 2006, если не до BDS 2007, только потом был исправлен. А если у тебя 5.5, то посмотри внимательно на название темы, и пойми, что телепаты обходят этот форум стороной.
Автор: TarasBer 29.09.2009 17:45
Всё, больше вопросов нет, спасибо.
Автор: TarasBer 29.09.2009 19:29
Как заставить встроенные функции модуля math не выводить ошибку в случае исключения? Если исключения ловлю Я, то зачем их ловилку всунули в модуль?!
Автор: TarasBer 30.09.2009 0:28
Заменил матх на фастматх, обнаружил новый прикол. gcvt виснет, если ему подсунуть exp(999) в консольном приложении. Сколько ещё багов встроено в этот замечательный компилятор?