Часто сталкиваюсь с ситуацией, когда какая-то непонятно откуда берущаяся ошибка
при чём возникающая спонтанно связана с тем, что я например какой нибудь класс, массив или структуру выделяю на стеке то есть
SomeFunc()
{
CSomeClass obj;
obj.SomeMethod();
}
Как только изменяю это на
CSomeClass * pObj = new CSomeClass;
pObj->SomeMethod();
Проблемы изчезают. При чём обычно бывает так, что в начале работают оба варианта,
но потом по мере добавления какого-то кода где-нибудь в программе вдруг
первый вариант начинает себя вести совершенно непредсказуемо.
Полная ерунда при чём не обнаруживаемая ничем. Ни компилятором ни на стадии выполнения.
Доходит до того что компилятор даже смещения элементов класса не правильно вычисляет и пишет совершенно в другую область памяти.
Как с этим бороться? Существует ли какой то способ определённо проверить правильно ли
выделяется память на стеке или чем это регулируется хотя бы чтобы знать максимально допустимый размер.
Также иногда проблема возникает не только со стеком но и когда в глобальной области видимости то есть в статической памяти объявляется переменная класса или структуры или массива. Тоже иногда результаты совершенно неожиданные выходят.
Как обезопасить себя от подобных сюрпризов. Неужели отказаться вообще от подобного метода выделения памяти?
Здравствуйте, Аноним, Вы писали:
А>Часто сталкиваюсь с ситуацией, когда какая-то непонятно откуда берущаяся ошибка А>при чём возникающая спонтанно связана с тем, что я например какой нибудь класс, массив или структуру выделяю на стеке
1) Укажи компилятор, платформу, можно и настройки вообще-то...
2) Как ты узнал, что смещения полей неверные?
3) Вообще-то всё выглядит как расстрел памяти. Ну, то есть, ты в своих объектах пишешь "не совсем туда" от чего-то, а потом это всё непредсказуемо дохнет. Если объекты на стеке, то данные лежать в памяти подряд и в них легко попасть при расстреле, а если на куче, то данные лежат в памяти более разреженно, и при расстреле видимые эффекты проявляются не сразу...
Такая вот моя ИМХА
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Иногда сталкиваюсь с тем, что коллеги пытаются сохранить где-нибудь адрес объекта на стеке и пытаться дёргать его за методы тогда, когда самого объекта уже давно нет. Не твой случай?
А>Как обезопасить себя от подобных сюрпризов. Неужели отказаться вообще от подобного метода выделения памяти?
нет, просто научиться писать программы без ошибок. или уметь их находить.
потенциально опасные места где возникают проблемы с памятью это конструкторы и деструкторы.
пересмотри их внимательно, как там выделяется и освобождается память
Здравствуйте, av, Вы писали:
av>Иногда сталкиваюсь с тем, что коллеги пытаются сохранить где-нибудь адрес объекта на стеке и пытаться дёргать его за методы тогда, когда самого объекта уже давно нет. Не твой случай?
Нет. Такое как раз быстро и легко обнаруживается. Такое бывает например когда в функцию передают не указатель
а сам объект. Или иногда когда функция которая должна возвратить полученный указатель возвращает его тоже в указатель,
а нужно возврашать в указатель на указатель
Мои случаи другие.
Я говорю о том как в Си (Си++) во первых не просто "на глаз" ориентироваться (как я и делаю)
а достоверно знать и получать уведомление либо от компилятора, либо ешё как-то что
переменная расположенная на стеке или в статичнской памяти неправильно выделяется. То есть слишком большая.
НАпример если я объявляю в стеке (какая разница где в конструкторе власса или просто обычную структуру в теле
функции) переменную какого-то типа.
Если это небольшой тип никаких проблем не возникает. А если этот тип превышает некий размер то возникают
ошибки при чём обнаруживаемые только в ходе пошагового выполлнения программы.
То есть невозможно знать наверняка будет программа работать правильно или нет. Потому в последнее время
я стал вообще избегать выделения памяти на стеке, кроме самых маленьких объектов.
Но это не очень удобно. Например можно забыть вызывать "delete" после того как объект не нужен.
Или вызывать до того как создан. Или после того как уже уничтожен и.т.п. В любом случае автоматическое
выделение памяти гораздо удобнее.
Иногда также бывает, что при загрузки какой-то DLL статические переменные объявленные на глобальном уровне также
оказываются "слишком большие" и LoadLibrary вылетает с ошибкой "недостаточно памяти".
Вот например какой-то банальный массив структур объявляю на глобальном уровне:
SONESTRUCTTYPE rgSomeArray[SizeOfSomeArray];
До определённого этапа идёт всё нормально. Но стоит немного модифицировать тип структуры например добавив поля.
Тут же естественно увеличивается потребность в памяти. И точно не знаешь момент когда произойдёт облом.
Потому получается что теперь на этапе инициализации DLL лучше масив выделять динамически.
Так тут хотя бы можно отследить пытаясь загрузить DLL. Причём иногда бывает что дебаг версия работает нормально,
а релиз версия нет. По понятным причинам.
А вот со стеком проблемы гораздо хуже.
И это не ошибки в конструкторах. Как раз если в конструкторах выделять память для объектов динамически то никаких проблем нет.
Только освобождать нужно незабыть правильно в деструкторах.
Проблема это когда в самом объявлении класса находится некий объект при чём даже не очень большой (до килобайта). При чём сам объект класса может быть
создан динамически но при этом всё равно проявляются дикие глюки.
Например вот недавно я попытался создать объект некого класса который был объявлен как что-то типа условно:
class SomeClass
{
SomeClass1 SC1;
SomeClass2 SC2;
int var;
}
Переменая "var" когда к ней обращались методы класса вычислялась по одному смещению (я нажал Аlt+8, а также окощка памяти открыл).
Например по адресу base+0x090
А вот когда к ней обращался уже объект класса типа Obj.var,
то он её пытался считать по совсмем другом адресу например base+0x120
Когда я это всё переделал на
class SomeClass
{
SomeClass1 * pSC1;
SomeClass2 * pSC2;
int var;
}
SomeClass::SomeClass()
{
pSC1 = new SomeClass1;
pSC2 = new SomeClass2;
}
Всё заработало нормально.
Другая ситуация была когда уже в самом конструкторе какого-то класса я написал
SomeClass::SomeClass()
{
SOMESTRUCT s;
}
Когда программа выполнялась по F5 то есть через отладчик никаких проблем не возникало.
Но когда запускается программа то именно из-за этой строчки выскакивает какое-то исключение,
что что-то там неладное с кучей (хипом).
Когда я переписал на
SomeClass::SomeClass()
{
SOMESTRUCT * pS = new SOMESTRUCT;
}
Всё заработало нормально.
Может кто-нибудь подскажет в чём тут проблемы? И как научится этих ситуаций избегать вообще?
Потому что обратил внимание, что в последнее время все ошибки у меня возникают практически только из-за этого и много времени уходит на их поиск.
Здравствуйте, Аноним, Вы писали:
А>Доходит до того что компилятор даже смещения элементов класса неправильно вычисляет и пишет совершенно в другую область памяти.
Трудно посоветовать что-либо конкретное, но вот эта фраза наводит на размышления. Во-первых, как ты узнал, что смещения неправильные? Программа должна писаться так, чтобы не зависела от смещений. Может ты как-то "вручную" пытаешься попасть в нужное поле?
Во-вторых, неправильные смещения могут легко получиться если в разных файлах программы используются разные #pragma pack'и. То есть, h-файл включается в cpp-файлы с разным выравниванием. Например, перед этим h-файлом включается другой, который меняет выравнивание. В результате объект класса создается с одним выравниванием, а обращение к нему выполняется с другим. Чтобы этого не было, #pragma pack должны быть только в h-файлах и только в форме #pragma pack(x, push) — #pragma pack(pop)
Re[2]: Переменные на стеке
От:
Аноним
Дата:
13.09.07 11:15
Оценка:
Здравствуйте, SWW, Вы писали:
SWW>Здравствуйте, Аноним, Вы писали:
А>>Доходит до того что компилятор даже смещения элементов класса неправильно вычисляет и пишет совершенно в другую область памяти.
SWW>Трудно посоветовать что-либо конкретное, но вот эта фраза наводит на размышления. Во-первых, как ты узнал, что смещения неправильные? Программа должна писаться так, чтобы не зависела от смещений. Может ты как-то "вручную" пытаешься попасть в нужное поле? SWW>Во-вторых, неправильные смещения могут легко получиться если в разных файлах программы используются разные #pragma pack'и. То есть, h-файл включается в cpp-файлы с разным выравниванием. Например, перед этим h-файлом включается другой, который меняет выравнивание. В результате объект класса создается с одним выравниванием, а обращение к нему выполняется с другим. Чтобы этого не было, #pragma pack должны быть только в h-файлах и только в форме #pragma pack(x, push) — #pragma pack(pop)
Нет это совсем не тот случай. Потому что хэдеры используются одни и те же.
К тому же речь идёт о громадной разнице в сотни байт.
Не знаю откуда эти глюки бывают. Вот и пытаюсь выясть.
E>1) Укажи компилятор, платформу, можно и настройки вообще-то... E>2) Как ты узнал, что смещения полей неверные?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Awaken, Вы писали:
А>>Как обезопасить себя от подобных сюрпризов. Неужели отказаться вообще от подобного метода выделения памяти?
A>нет, просто научиться писать программы без ошибок. или уметь их находить. A>потенциально опасные места где возникают проблемы с памятью это конструкторы и деструкторы.
...и оператор присваивания
Автору топика:
Дело явно не в стеке и не в куче.
Просто у тебя коряво написан твой класс.
Приведи его тут, и тебе подскажут где конкретно проблема
Здравствуйте, SWW, Вы писали:
SWW>Здравствуйте, Аноним, Вы писали:
А>>Доходит до того что компилятор даже смещения элементов класса неправильно вычисляет и пишет совершенно в другую область памяти.
[...] SWW>Во-вторых, неправильные смещения могут легко получиться если в разных файлах программы используются разные #pragma pack'и. То есть, h-файл включается в cpp-файлы с разным выравниванием. Например, перед этим h-файлом включается другой, который меняет выравнивание. В результате объект класса создается с одним выравниванием, а обращение к нему выполняется с другим. Чтобы этого не было, #pragma pack должны быть только в h-файлах и только в форме #pragma pack(x, push) — #pragma pack(pop)
Дельный совет. У меня был такой печальный опыт...
Судя по всему, ты используещь MSVC. Тогда бы я еще посоветовал включить проверки размещения/освобождения памяти. Это группа функций которые начинаются на _Crt. Я это делаю следующим образом, в самом начале программы:
// Get current flagint tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
// Turn on leak-checking bit
tmpFlag |= _CRTDBG_LEAK_CHECK_DF;
// Call _CrtCheckMemory at every allocation and deallocation request
// and some other options (***)
//tmpFlag |= _CRTDBG_CHECK_ALWAYS_DF | _CRTDBG_DELAY_FREE_MEM_DF;
// Turn off CRT block checking bit
tmpFlag &= ~_CRTDBG_CHECK_CRT_DF;
// Set flag to the new value
_CrtSetDbgFlag( tmpFlag );
В твоем случае я бы открыл комментарий в строке следующией за (***). Но тогда программа будет сильно тормозить.
Стоит изучить и понять что такое жизенный цикл объекта и аккуратно с этим обходиться.
А размещение объектов на стеке, на мой личный взгляд, самые безопасный способ избежать забыть освободить объект.
Re[3]: А всё-таки что за платформа?
От:
Аноним
Дата:
13.09.07 11:53
Оценка:
Здравствуйте, Erop, Вы писали:
E>>1) Укажи компилятор, платформу, можно и настройки вообще-то...
Тот что с Вижуал Студио 2005. Не помню точто версии.
E>>2) Как ты узнал, что смещения полей неверные?
Ну посмотрел в отладчике куда оно пишет и откуда читает например переменную обного и того же объекта класа.
Здравствуйте, Аноним, Вы писали:
А>Ну посмотрел в отладчике куда оно пишет и откуда читает например переменную обного и того же объекта класа. А>В разных местах по разному. А>А что?
Ну хотелось понять что имеется в виду под "разные смещения"
А какой типичный размер объектов при этом при всём? Они в стэк влазят?
Вторая тема более странная. У тебя какие опции стоят. Всякие "мутные" типа инкрементальнйо компиляции снял?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, Аноним, Вы писали:
А>>Ну посмотрел в отладчике куда оно пишет и откуда читает например переменную обного и того же объекта класа. А>>В разных местах по разному. А>>А что?
E>Ну хотелось понять что имеется в виду под "разные смещения" E>А какой типичный размер объектов при этом при всём? Они в стэк влазят?
Вот бы мне и хотелося б узнать как определить влезает объект в стек или нет.
А на счёт разных смещений это я уже вроде бы разобрался.
Я то как раз подумал, что это из-за того что объект класса сильно большой и на стеке не помещается
потому вместо
class SomeClass
{
СSomeClass1 SC1;
СSomeClass2 SC2;
int var;
}
Написал:
class SomeClass
{
СSomeClass1 * pSC1;
СSomeClass2 * pSC2;
int var;
}
SomeClass::SomeClass()
{
pSC1 = new СSomeClass1;
pSC2 = new СSomeClass2;
}
А потом я подумал, что не может этого быть. Ведь классы в принципе маленькие. Они всего-то несколько переменных содержат и методов (то есть указателей на функции, которые занимают не более 4 байт)
И оказывается ,что в типе некоторого класса была включенна некая структура, длина которой зависит от значения диррективы _WIN32_IE, которая в настройках компиляции была прописанна для отдельного модуля. Вот и получилось что модуль класса обращается к переменной var по одному смещению, а при использывании этого класса в другом модуле оно вычисляется по другому и выходит полная ерунда.
Но всё-таки интересно узнать. Как же всё-таки определить что переменная данного типа слишком большая для выделения памяти в стеке?
Еще в догонку, моя практика показывает что самый быстрый способ поиска подобных ошибок, заключается в комментировании того, что написал 10мин, час, 10 часов назад
Что вы прицепились к стеку. Он тут точно не причем. Да, на IA под винды размер выделения ограничен в 4096 байт (размер страницы), но это актуально только если вы пишите на асме. Например при смещении esp больше чем на 4096 байт и последующей попытке затолкать че-нить в стек вы получите исключение. Но компилятор C++ автоматически измеряет размер локальных объектов и если он превышает 4096 байт, он вызывает специальную процедуру, которая смещает указатель на значения по 4096 байт и производит обращение к памяти после каждого смещения, дабы винда ловила исключения и довыделяла память, поэтому при работе со стеком на C++ у вас может быть только Stack Overflow или Stack Corrupted.
Короче вы просто где-то покоцали память, или вызываете процедуру с неправильным calling convention.
Здравствуйте, Аноним, Вы писали:
А>Но всё-таки интересно узнать. Как же всё-таки определить что переменная данного типа слишком большая для выделения памяти в стеке?
Ну хорошо в целом никак. Но ты можешь отмотать стэка с запасом на твоей платфороме (это опции линкера exe)
Всё-таки объекты класса обычно какого-то обозримого размера и место заканчивается из-за непредвиденно глубокой рекурсии. Ну а её можно уже как-то и ограничить
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
А>Когда программа выполнялась по F5 то есть через отладчик никаких проблем не возникало.
А>Но когда запускается программа то именно из-за этой строчки выскакивает какое-то исключение, А>что что-то там неладное с кучей (хипом).
А>Когда я переписал на
А>
А>SomeClass::SomeClass()
А>{
А>SOMESTRUCT * pS = new SOMESTRUCT;
А>}
А>
А>Всё заработало нормально.
А>Может кто-нибудь подскажет в чём тут проблемы? И как научится этих ситуаций избегать вообще?
Суть: объект создается на стеке, но все его "тяжелые" (с точки зрения размера памяти) поля создаются в куче. Применение auto_ptr автоматически будет контролировать время жизни этих "тяжелых" полей, дабы избежать утечек.
Re[7]: А всё-таки что за платформа?
От:
Аноним
Дата:
13.09.07 13:38
Оценка:
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, Аноним, Вы писали:
А>>Но всё-таки интересно узнать. Как же всё-таки определить что переменная данного типа слишком большая для выделения памяти в стеке?
E>Ну хорошо в целом никак. Но ты можешь отмотать стэка с запасом на твоей платфороме (это опции линкера exe)
Только AboutStackTop::Get() нельзя будет звать из статических конструкторов...
Ну и хмуро непортабл это всё конечно
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
_>Еще в догонку, моя практика показывает что самый быстрый способ поиска подобных ошибок, заключается в комментировании того, что написал 10мин, час, 10 >часов назад