Помощь - Поиск - Пользователи - Календарь
Полная версия: тип string, утечка памяти
Форум «Всё о Паскале» > Современный Паскаль и другие языки > Ада и другие языки
Страницы: 1, 2
TarasBer
Работу с типом char* я тупо не смог осилить, так как так и не понял, при каких операциях надо выделять ему память, при каких не надо. Короче ужаснейший тип, и кто его придумал, и ну его нафиг.
Решил применить string - я так понял, что это аналог паскалевского. Но обнаружилось, что после динамического создания структуры, содержащей поле типа string, и её удаления кол-во занимаемой памяти меняется. Встатив этот кусок в бесконечный цикл, я обнаружил, что память, пожираемая программой, пульсирует от 10 до 50 MЬ.
Вторая проблема - при отладке на операциях с этим типом он лезет в свои модули, мне они неинтересны, как запретить отладчику в них влазить.
Блин, настроек по сравнению с Дельфой раза в 3 больше, на кой.
volvo
Цитата
так и не понял, при каких операциях надо выделять ему память, при каких не надо.
Выделять память надо всегда. Просто когда-то ее можно выделить самим фактом инициализации строки:
char *s = "my string";
, а иногда это приходится делать через new, то есть выделять память динамически.

Цитата
после динамического создания структуры, содержащей поле типа string, и её удаления кол-во занимаемой памяти меняется.
Можно посмотреть на структуру, и на то, как выделяется память? Что говорят средства отладки (скажем, тот же CodeGuard)?

Добавлено через 1 мин.
Цитата
Вторая проблема - при отладке на операциях с этим типом он лезет в свои модули, мне они неинтересны, как запретить отладчику в них влазить.
Не делать "Step Into", а пользоваться вместо этого "Step Over", тогда отладчик будет выполнять всю строку, а не по отдельным операциям.
TarasBer
Цитата(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
Цитата
Но просто 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
Цитата(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
Цитата
А лишние точки с запятой, не вызывающие ошибок компиляции - разве вредят?
До поры до времени - не вредят... Пока ты не напишешь какую-нибудь конструкцию, в которой эта самая точка с запятой, скажем, образует пустой цикл, пустую ветку if/else или еще что-нибудь такое же трудноуловимое. Синтаксис С очень многое позволяет "намудрить", поэтому лучше избавляться от привычки ставить лишние символы, даже если они не вызывают ошибок компиляции.

Цитата
Интересно.
Ну, тогда смотри: при выполнении delete, если добавить [], как я сделал, происходит вот что: сначала для каждого элемента массива вызывается его деструктор, и только потом освобождается память, выделенная под сам массив. Что происходит в случае замены string на int? А ничего страшного не произойдет, даже если не вызвать деструктор для int, от него все равно толку нет, утечки не будет даже без его вызова. В случае string все серьезнее: это полноправный класс, который имеет конструктор, выделяющий память. И экземпляр этого класса требуется удалить, иначе сам экземпляр остается висеть в памяти, а указатель на него потеряется.

Поэтому всегда, когда выделяешь память динамически под массив, либо состоящий из не POD-типов (Plain Old Data, данные в стиле С), либо содержащий такие типы (как у тебя - структура, которая содержит non-POD type), то освобождай ее всегда с использованием delete [] p_arr;, чтобы быть уверенным в том, что для каждого элемента вызовется деструктор, и только потом удалится сам массив.
TarasBer
Цитата(volvo @ 18.09.2009 20:05) *


Ну, тогда смотри: при выполнении delete, если добавить [], как я сделал, происходит вот что: сначала для каждого элемента массива вызывается его деструктор, и только потом освобождается память


Это, я так понял, только для не-POD типов, потому что для массива объектов для каждого элемента деструктор надо вызывать руками?
Решил вместо char* работать с AnsiString, а то у всех компонентов свойства как раз такого типа.
volvo
Цитата
Это, я так понял, только для не-POD типов, потому что для массива объектов для каждого элемента деструктор надо вызывать руками?
Угу, именно поэтому...

Кстати, а что за задачу ты решаешь? Может, будет выгоднее не выделять самому динамически память под массив, а воспользоваться либо vector<AnsiString> либо vector<S>? Тут ведь мало того, что не надо выделять память вручную, так еще и удалять вектор не надо, он описывается статически, следовательно при выходе из области видимости самоликвидируется (причем корректно, перед этим вызвав деструктор каждого своего элемента).
TarasBer
Цитата(volvo @ 18.09.2009 21:53) *

Кстати, а что за задачу ты решаешь?


Я решаю задачу освоения нового для меня и довольно хитрого языка, за который к тому же много где платят, в отличие от Дельфей (увы, реальность примерно такова). Пока впечатление отвратительное.
На примере калькулятора со встроенным парсером и ещё некоторыми наворотами (он уже знает, что 2+2*2 равно 6). Тем более, что опыт распознавания выражений имею.
Собсна та структура, содержащая строчку - это описание функций, содержащее как раз и имя, и ссылку на операцию, и ещё много чего.

Цитата

Может, будет выгоднее не выделять самому динамически память под массив, а воспользоваться либо vector<AnsiString> либо vector<S>? Тут ведь мало того, что не надо выделять память вручную, так еще и удалять вектор не надо, он описывается статически, следовательно при выходе из области видимости самоликвидируется (причем корректно, перед этим вызвав деструктор каждого своего элемента).


Ну я так понял, что фишка С++ в полной открытости всех действий перед программистом. И мне кажется, что использовать в нём подобные вещи - всё равно что покупать джип для езды по городу.
volvo
Фишка С++ - не в том, чтобы не писать свой велосипед каждый раз, да еще и чинить его потом на каждом повороте, а в том, что ты берешь отлаженную сотнями и тысячами программистов библиотеку (я про STL) и просто реализуешь свою задачу. Нет, дело твое, конечно, но потом не говори, что написание программ на С++ занимает ОЧЕНЬ долгое время. Оно занимает просто долгое время, а не ОЧЕНЬ. ОЧЕНЬ - это из-за того, что ты опять с нуля реализуешь то, что уже есть в языке (хотелось бы напомнить, что STL - это часть Стандарта C++).

По поводу "джипов". Лучше я буду ездить везде на джипе, чем каждый раз менять транспортные средства (доехал до перекрестка, а там - лужа. Упс... Надо пересесть в амфибию, а то на велосипеде и утонуть можно). Хотя ко мне это относится меньше всего. Ада - язык универсальный, причем с Паскалевским синтаксисом, что снимает огромную часть проблем, да и оплачивается программирование на нем в разы (если не на порядки) выше, чем программирование на С++. И средств, подобных тому же STL-ю там не меньше, а даже больше... Так что я свой джип уже нашел. Передвигается одинаково уверенно и по Win, и по Lin, и по Embedded, и вообще практически по любым существующим территориям. Хочешь попробовать догнать? Но это уже оффтоп в данной теме.
TarasBer
Цитата(volvo @ 18.09.2009 22:29) *

Фишка С++ - не в том, чтобы не писать свой велосипед каждый раз, да еще и чинить его потом на каждом повороте, а в том, что ты берешь отлаженную сотнями и тысячами программистов библиотеку (я про STL) и просто реализуешь свою задачу.


Вот как раз для такого куда лучше бы подошёл какой-нибудь другой язык, пусть не с таким оптимизированным кодом, как говорят про C++, зато более дружелюбный к программисту. А иначе зачем вообще этот C++ нужен?
renesko1
С++ наоборот ИМХО понятен, если программа написана с высоким и правильным содержанием STL
компонентов.(к сожалению, мои программы этим пока не очень отличаются).
std::vector<car> вектор из машин)
TarasBer
Хьюстон, у нас опять проблемы.
При вводе корректных выражений утечка прекратилась, но при вводе "1/0" память опять потекла. Само деление обёрнуто в

try {
}
catch (...) {
};



Внутри трая само деление, внутри катча присвоение пустого значения.
volvo
Где память выделяется, и где она освобождается? Если сделать так:
	S *s = new S[8];
try
{
//
int i = 1, j = 0;
int x = i / j;
}
catch(...)
{
// Ну, по-хорошему, тут надо еще уточнить что за исключение
ShowMessage("Division by Zero");
}
delete [] s;
, то утечки не будет... Покажи, как делаешь ты...
TarasBer
Естественно, все выделения и освобождения находятся ВНЕ блока.
Чтобы указывать исключение, надо запоминать их список и названия, а операций, которые могут вызвать то или иное исключение, довольно много. Тот же логарифм, например. Проще ловить любое.
Полностью блок выглядит так:

TComplex operator / (TComplex X, TComplex Y) {
TComplex Z;
try {
if (X.IsValue && Y.IsValue) {
Z.IsValue = true;
long double D = 1 / (Y.Re * Y.Re + Y.Im * Y.Im);
Z.Re = D * (X.Re * Y.Re + X.Im * Y.Im);
Z.Im = D * (-X.Re * Y.Im + X.Im * Y.Re);
} else {
Z.IsValue = false;
};
} catch (...) {
Z.IsValue = false;
};
return Z;
};

volvo
Ну, хорошо... Это переопределение операции деления. Для того, чтобы у тебя была утечка, надо, чтобы память выделялась и не освобождалась, так? Вот я и хочу увидеть, как ты вызываешь эту самую операцию деления, и что происходит в ее ближайшем окружении, после чего у тебя теряется память...

Исключение поймано, IsValue результата установлено в false, все нормально. Дальше все зависит от того, как именно ты обрабатываешь этот IsValue...
TarasBer
Значит собсна вычисление выражения выглядит так:

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 - это указатель на тип-функцию:


typedef TComplex(TTreeFunc)(int Count, TTree **Args);

struct TFunc {
...
TTreeFunc *Operation;
};



Собсно сама операция, которая тут вызывается:


TComplex Div(int Count, TTree **Args) {
TComplex Result;
if (Count != 2) {
Result.IsValue = false;
} else {
Result = Args[0]->Data.Value / Args[1]->Data.Value;
};
return Result;
};



Тоже ничего не создаётся.
volvo
Цитата
Счётчик объектов TTree говорит, что после все действий объектов ноль - то есть тут тоже всё в порядке.
Сомневаюсь... Поскольку сведения у меня о твоей программе только частичные - попробовал сделать так:
TComplex Div(int Count, TComplex *Args) {
TComplex Result;
if (Count != 2) {
Result.IsValue = false;
} else {
Result = Args[0] / Args[1];
};
return Result;
};

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
Запуск с гуардом даёт аксесс виолейшн, программа намертво виснет, не желает завершаться по команде из среды, завершается только проводником, причём так как среда об этом не узнаёт, для перекомпиляции приходится перезапускать среду.
Я не пойму, что за такая проблема с TTree может быть, которая с 1/1 не вылезает, а с 1/0 вылезает.
volvo
Присоедини свой проект, я его прогоню в 2009... Можно в приват, если не хочешь выкладывать в общий доступ.
TarasBer
Заодно я проверил, что дело не в "пустом" значении - я вставил в процедуру деления простой возврат "пустого" значения, без трая. Утечка исчезла.

> Присоедини свой проект, я его прогоню в 2009... Можно в приват, если не хочешь выкладывать в общий доступ.

Да там нечего скрывать: я пока не настолько крут, чтобы мой код имело смысл скрывать от чужих глаз.
volvo
А ты знаешь, у меня не вылетает. У тебя вечный цикл while(1). Насколько я понимаю, ты его сделал только, чтобы проверить на утечки? Так вот по 10 минут работало без перерыва, ни одного байта памяти не прибавило... Вводил выражения 1/1 и 1/0... И в том и в другом случае прекрасно считает и результат пишется в Caption формы. В случае деления на 0 - как ты и требовал от функции она возвращает "?".
TarasBer
> Так вот по 10 минут работало без перерыва, ни одного байта памяти не прибавило...

Странно. Я этот момент проверяю при помощи виндовского Task Managera, или это неправильный метод?

У меня настолько палёный билдер? Я его с какой-то шары скачал, если честно.

> В случае деления на 0 - как ты и требовал от функции она возвращает "?".

Да.
TarasBer
И надо сказать, что хавает от не спеша, где-то полметра в секунду.
volvo
Ну не получается у меня, чтоб пожиралась память... Как только установилось определенное количество памяти (после нескольких первых итераций), так и стоит как вкопанное. Если что - можешь посмотреть видео: Calc.AVI (около 5Мб, кодек indeo 4.х)

За 30 секунд ничего не изменилось... Что-то у тебя с Билдером... Хотя, может быть это в BDS2009 получше работа с памятью, пускай кто-нибудь еще на BCB6 проверит...
TarasBer
А что это он сразу 8 метров отхватил? У меня в корректных случаях никогда до 5 не доходит. Или это уже особенности более позднего компилятора?
volvo
Это Дебаг-версия + CodeGuard... Если скомпилировать в Release и отключить все, что не надо - будет гораздо меньше.
TarasBer
Я правильно понимаю, что команда new для создания строки не может вызывать access violation в нормально работающей среде? А то у меня чем дальше, тем круче.
TarasBer
Мне удалось подключить CodeGuard.
Запустил, ввёл 1/0. Открыл лог, который переименован в тхт и прилагается.
Если я правильно его понял, то
5 вызовов malloc
5 вызовов free
(я решил по старинке писать).
Наборы адресов совпадают.
Ставлю бесконечный цикл, запускаю, диспетчер видит утечку.
Я сойду с ума.
TarasBer
Решил упростить свой случай до предела.
Написал в обработчике нажатия кнопки вот это:

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
Цитата
Может быть, дело в том, что создаётся объект-исключение, который надо как-то определять и удалять?
Ничего там не создается... Проверил и Билдером, и GCC - нет утечки. Что-то у тебя с Билдером явно не то. К сожалению, запустить тот EXE-шник, что ты присоединял в 21-ом посте, и проверить, дает ли он утечку, я не могу, он требует библиотеки от BCB6, а у меня их нет.
TarasBer
Тогда ещё вопрос - как надо компилировать программу, чтобы она запускалась на любом компе, где есть винда?
volvo
нехватка Vcl50.bpl
TarasBer
Понятно.
Альтернативный вариант - чистое API?

Екзешник с утечкой ещё интересен?
volvo
Цитата
Альтернативный вариант - чистое API?
И в этом случае возможны проблемы (при отсутствии Билдера в системе), какие-то библиотеки все равно привяжутся к проекту. Полностью работоспособно только приведенное по ссылке решение.

Цитата
Екзешник с утечкой ещё интересен?
Собственно, интересно было посмотреть, насколько утекает память. Да, за 12 секунд сожрало почти 30 Мб - это много. Попробуй все-таки другой Билдер, скорее всего проблема в этом, ибо я тестировал твой проект (без изменений) и на 2007 и на 2009. Ни там ни там нет утечек.
TarasBer
А просканировать как-нибудь тот екзешник, чтоб понять, где глюк, ну и сравнить побайтно с результатом компилирования в ВС2009 - реально?
volvo
Реально, только сравнивать надо не EXE-шники, а MAP-файлы (заставить Билдер генерировать MAP-файлы: фактически - ассемблерный код, и сравнивать результаты этой операции для обоих версий Билдера). Только это - чуть позже, у меня еще ремонт в квартире продолжается, я выхожу то с одного компьютера, то с другого, а все, что нужно для подобных детальных сравнений у меня установлено только в одном месте, я туда еще не добрался, комната завалена полностью. На следующей неделе, надеюсь, начну разгребать этот бардак, тогда уже и посмотрю, в чем разница...
TarasBer
А существует ли какой-либо механизм предсказания ошибки вычислений, помимо исключения?
То есть чтобы я, зная два числа, смог заранее, без исключения, сказать, поделится (умножится, сложится, вычтется) ли одно на другое, или нет?
TarasBer
Попытался заняться дальнейшим упрощением "неверного" кода.

void __fastcall TForm1::Button1Click(TObject *Sender)
{
int i = 1;
while(1) {
try {
int j = i / 0;
} catch (...) {
};
};
}


Утечка в коде, не содержащем НИ ОДНОГО указателя!
Писать сразу 1 / 0 не получится, компилятор палит.
Да, я уже скачал отдельно компилятор с другого места.
volvo
Цитата
Утечка в коде, не содержащем НИ ОДНОГО указателя!
Значит, в BCB6 такая "хорошая" работа с памятью... Проверь на более новом Билдере. Попробуй проверить на другом С++ компиляторе, есть ли утечка там? Я проверил только что в BDS2009 - результат аналогичен тому, что было выше - сначала плюс несколько килобайт, а потом - без увеличения.
TarasBer
Цитата(volvo @ 27.09.2009 14:47) *

Значит, в BCB6 такая "хорошая" работа с памятью...

О да, я крут, сел осваивать новый язык и тут же нашёл нехилый баг в компиляторе.
Цитата

Проверь на более новом Билдере. Попробуй проверить на другом С++ компиляторе, есть ли утечка там?

Я попробую найти другой С++ компилятор, но это не так просто.
volvo
Цитата
но это не так просто.
Чего сложного?
http://www.codeblocks.org/downloads/5 (IDE вместе с компилятором и отладчиком, около 20Мб)
volvo
Цитата
сел осваивать новый язык и тут же нашёл нехилый баг в компиляторе.
Нет, этот баг нашел не ты, его нашли другие... Ты просто на него нарвался. На delphigroups.info проскакивала информация об ошибке, связанной с синтаксисом catch(...) на старых Билдерах, и о том, что желательно использовать конструкцию catch(Exception&) вместо нее... Так что сначала попробуй код:
#include <exception>
// ...
int i = 1;
while(1)
{
try
{
int j = i / 0;
}
catch (Exception& e)
{
// ShowMessage(e.Message);
}

}
, и проверь, "утекает" ли по-прежнему память?
TarasBer

catch (Exception &E) {
...
delete &E;
};


Помогло. Без делита - утечка.
Мдааа.
TarasBer
А такой код, с удалением исключения, будет работать на новых билдерах?
volvo
Цитата
А такой код, с удалением исключения, будет работать на новых билдерах?
Нет, при попытке сделать
delete &E;
получишь Invalid pointer operation, и этим дело закончится.
TarasBer
Тогда какой #ифдеф на этот случай посоветуете? Я в них ноль, так как не приходилось сталкиваться с таким.
volvo
Насколько я помню номера компиляторов - так:
		catch (Exception& e)
{
// ...
#if defined(__BORLANDC__) && (__BORLANDC__ >= 0x560) && (__BORLANDC__ <= 0x564)
delete &e;
#endif
// e.Free();
}

0x560 - это "чистый" BCB6, и еще 4 - с разными Service Pack-ами
TarasBer
> && (__BORLANDC__ >= 0x560)

Ну, во-первых, у меня вроде 5.5, во-вторых, это условие говорит, что для более ранних версий этого бага не было?
volvo
Ты спрашивал, как определить Билдер 6 директивами компиляции? Я тебе показал, как это делается... Нужно включить ВСЕ глючные версии - убирай нижнее условие, оставляй только верхнюю границу...

Баг был на всех Билдерах, как минимум до BDS 2006, если не до BDS 2007, только потом был исправлен. А если у тебя 5.5, то посмотри внимательно на название темы, и пойми, что телепаты обходят этот форум стороной.
Это текстовая версия — только основной контент. Для просмотра полной версии этой страницы, пожалуйста, нажмите сюда.