добрый день. хочу узнать насчет бага, который проявляется в ходе тестинга небольшой ОО-системы. есть класс, в конструкторе вызывается один из его методов, в котором происходит выделение под этот член памяти и его инициализация. все это само собой завернуто в try/catch, с отловлей std::bad_alloc. вот, собссно, кусочек: логика такова: Код (Text): CMyClass()::CMyClass(const char *filePath) { if (!initMember(filePath)) throw CMyClassException(); } bool CMyClass::initMember(const char *filePath) { ...//blabla std::size_t sz = blabla//открыли файл по filePath, получили его размер в sz try { _pData = new[sz]; } catch(std::bad_alloc) { printf("CMyclass: unable to allocate memory\n"); return false; } //blabla return true; } CMyClass::~CMyClass() { if (_pData) delete [] _pData; } далее в одном в объекте класса ClassA вызывается один из методов др. класса, ClassB, в котором и происходит выделение объектов класса CMyCLass и дальнейшее с ними оперирование. так вот, здесь все прекрасно работает. но для проверки я решил в этом методе у ClassB вставить такой код: Код (Text): bool ClassB::foo() { //blabla try { for ( uint i = 1; i < 1000; ++i ) CMyClass test("bigFile.txt"); //bigFile.txt весит ~2MB } catch(const CMyClass::CMyClassException &e) { return false; } return true; } этот метод вызывается неск. раз из вышестоящего класса ClassA, и на втором вызове где-то на ~600 итерации срабатывает catch и ловится CMyClass::CMyClassException из CMyClass test. далее программа пытается корректно завершить работу(освобождаю все под-системы, ClassA итд). все завершается ок, в логах вижу записи о том, что произошло исключение типа CMyClass::CMyClassException, и далее о том, что все действия по освобождению ресурсов были выполнены _верно_. НО в консоль вываливается строчка: ,т.е. якобы происходит дважды освобождение какого-то выделенного ресурса. выяснил, что если закомментить delete _pData; в деструкторе у CMyClass, то эта строчка уже не вываливается. как может происходить дважды delete _pData в деструкторе, если после возбуждения исключения в конструкторе CMyClass само создание этого объекта уже невыполняется, а следовательно, и вызова далее деструктора нет? или же я еще что-то упустил, и здесь все норм. непонятно мне, где я ошибся, и что в первую очередь перепроверить, чтобы этот баг исправить. ps: юзаю компилер g++ (GCC) 3.4.6.
есть мнение, что но это описывается к случаю: Код (Text): char *p = NULL; try { for(; ;) p = new char[100000]; или это как раз мой случай несмотря на отсутствие new в цикле?
При удалении памяти - ставь указатель в NULL: Код (Text): delete [] array; array = NULL; К слову оператор delete проверяет указатель на нулевое значение, так что самому проверять - просто код будет длиннее.
AsmGuru62, уже так и есть у меня. в смысле, в деструкторе так и сделано. но тем не менее баг остается. сейчас для эксперимента переписал эту часть с выделением памяти в временном массиве char *temp = new char[sz];, и оттуда копированием всех данных в мембер класса, переписанный уже как вектор std::vector<char> _data; а затем удаляю память выделенную для темпового буффера. т.е. теперь сырые данные сами по себе уже не хранятся в char*, а хранятся в векторе, и следовательно, в деструкторе класса уже ничего не удаляю. но зато теперь после того как была выделена память под char *temp, и считана в нее, после копирования этих данных в мембер-вектор, иногда вылетает std::bad_alloc при попытке освободить эту память выделенную в темповую переменную. поставил обработку try/catch для удаления этой темповой переменной, и теперь программа стала по крайней мере _корректно_ реагировать на неудачу при конструировании объекта этого рассматриваемого класса и корректно завершаться без каких бы то ни было сообщений о багах итд (т.е. теперь все соответствует моей спроектированной логике для случаев с исключениями). но непонятно, почему иногда при попытке удалить char *temp вылетает std::bad_alloc? ведь память была успешно выделена, и в нее были успешно считаны данные. так чего ж освободить память эпизодически неудается? может кто прояснит, спасибо.
varnie Память под объект в случае исключения std::bad_alloc не выделяется, как и случае исключения в конструкторе. А значит в этом случае её уничтожать - получить ошибку. Эту проблему часто решают с помощью смарт указателей. После того как объект сконструирован, указатель передают смарту. Смарт автоматически разрушается при вызове деструктора, и если у него будет указатель, то он его разрушит, а если нет, то разрушиться только сам (указатель-то не успели передать).
Booster, это я знаю. но у меня вот как в д. момент реализовано: Код (Text): class CMyCLass { public: ...// bool loadFromFile(const char *fPath); private: vector<char> _data; }; bool CMyClass::loadFromFile(const char *fPath) { char *pData = NULL; ...//открыли файл fPath, получили его размер в size printf("size = %i\n", _size); printf("allocating _pData\n"); try { //Allocate data for the image and load it there. pData = new char[ sizeof(char) * size ]; } catch(std::bad_alloc ) { printf("bad_alloc error1!\n"); fclose( pFile ); return false; } catch(...) { printf("some error\n"); fclose( pFile ); return false; } printf("allocated ok\n"); try { fread( pData, sizeof(char), size, pFile ); ...//сделали что надо с pData //скопировали эти данные в мембер класса _data; for ( register long index = 0; index < size; ++index ) _data.push_back(pData[index]); //освободили память выделенную в темповом объекте printf("deleting pData\n"); delete [] pData; } catch(std::bad_alloc) { printf("bad_alloc error2!\n"); fclose(pFile); return false; } return true; } СMyClass::CMyClass(const char *fPath) { if (! loadFromFile) throw MyClassException(); } так вот, на определенном шаге при тестинге: Код (Text): for ( uint i = 1; i < 1000; ++i ) CMyClass test("test.txt"); вылетает std::bad_alloc и в консоль выводится: что ясно дает понять о том, в какой именно части кода это исключеине произошло. выходит, что при delete [] pData; так почему выделить память под pData получилось в этом случае, а удалить - нет, и вылетает это std::bad_alloc ?
А printf("deleting pData\n"); выводиться? Исключение точно не в _data.push_back(pData[index]);? Вот из msdn: IMHO delete исключений не генерит, по крайней мере С++.
Booster, ага, я тоже уже выяснил, что printf("deleting pData\n"); не выводится --> исключение в _data.push_back(pData[index]); происходит в ходе цикла. пытаюсь выяснить - почему.
сейчас пришел к двум вариантам. по логике одинаковы, но один из них производит вышеупомянутый баг.. вариант #1: Код (Text): bool CTexture::loadFromFile(const std::string &filePath) { if( filePath.empty() ) { return false; } if ( filePath.find(".tga") != std::string::npos ) { FILE *pFile = NULL; char tempColor; unsigned char bitCount; uint colorMode; std::size_t tgaSize; const static unsigned char unCompressHeader[12] = { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0}; unsigned char tgaHeader[12]; unsigned char header[6]; // Open the file. if ( (pFile = fopen(filePath.c_str(), "rb")) == NULL ) { return false; } // Read the header. fread( tgaHeader, 1, sizeof(tgaHeader), pFile ); if ( memcmp(unCompressHeader, tgaHeader, sizeof(unCompressHeader)) != 0 ) { fclose( pFile ); return false; } fread(header, 1, sizeof(header), pFile); _sizeX = header[1] * 256 + header[0]; _sizeY = header[3] * 256 + header[2]; bitCount = header[4]; colorMode = bitCount / 8; _size = tgaSize = _sizeX * _sizeY * colorMode; _transparent = ( colorMode == 4 ) ? true : false; char *pData = NULL; try { pData = new char[ sizeof(char) * tgaSize ]; if ( fread( pData, sizeof(char), _size, pFile ) != tgaSize) { fclose(pFile); return false; } for ( register std::size_t index = 0; index < _size; index += colorMode ) { tempColor = pData[index]; pData[index] = pData[index + 2]; pData[index + 2] = tempColor; } } catch(std::bad_alloc &) { printf("std::bad_alloc\n"); fclose(pFile); return false; } try { for ( register std::size_t index = 0; index < _size; ++index ) { //printf("index = %i\n", index); _data.push_back(pData[index]); } delete [] pData; } catch(std::bad_alloc) { delete [] pData; printf("std::bad_alloc2\n"); fclose(pFile); return false; } fclose( pFile ); printf("return true\n"); return true; } //not a TGA format return false; } вариант #2: Код (Text): bool CTexture::loadFromFileBUGGY(const std::string &filePath) { if (filePath.empty()) return false; if (filePath.find(".tga") != std::string::npos) { char tempColor; unsigned char bitCount; uint colorMode; static const unsigned char unCompressedHeader[12] = {0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0}; static std::vector<unsigned char> tgaHeader(12); static std::vector<unsigned char> header(6); std::ifstream f(filePath.c_str(), std::ios_base::in | std::ios_base::binary); assert(f); try { f.read((char*)&tgaHeader[0], 12); if ( !equal(tgaHeader.begin(), tgaHeader.end(), &unCompressedHeader[0] ) ) { return false; } f.read( (char*)&header[0], 6 ); _sizeX = header[1] * 256 + header[0]; _sizeY = header[3] * 256 + header[2]; assert(_sizeX && _sizeY); bitCount = header[4]; colorMode = bitCount / 8; _size = _sizeX * _sizeY * colorMode; _transparent = ( colorMode == 4 ) ? true : false; assert(_size); _data.resize( _size); f.read((char*)&_data[0], sizeof(char)*_size); for ( register std::size_t index = 0; index+2 < _size; index += colorMode ) { tempColor = _data[index]; _data[index] = _data[index + 2]; _data[index + 2] = tempColor; } } catch(std::bad_alloc &) { return false; } return true; } //not a TGA format return false; } в конструкторе этого класса вызываем этот метод loadFromFile(loadFromFile2): Код (Text): CTexture::CTexture(const std::string &filePath) throw(CTextureError) :_sizeX(0),_sizeY(0),_pData(NULL),_transparent(false),_texID(0),_size(0) { if ( ! loadFromFileBUGGY(filePath) ) //или же if ( ! loadFromFile(filePath) ) { printf("unable to load from file\n"); throw CTextureError("unable to load from file"); } //далее что надо делаем } так вот, если вызывать в конструкторе loadFromFile(filePath), то при возбуждении в нем исключения std::bad_alloc этот метод возвращает нам значение false, и мы здесь throw CTextureError("unable to load from file"), далее в вышестоящем классе ловим его, сигнализируем вышестоящему классу, и короче прога корректно завершает свою работу, с подчисткой всей выделенной под динамические объекты памяти. никаких ошибок free нету. если же вызывать в конструкторе loadFromFile2(filePath), то опять таки он возвращает нам значение false, т.к. в нем было выброшено std::bad_alloc, и мы также далее по иерархии получаем CTextureError("unable to load from file"), и корректно завершаем программу, освобождая все выделенные ресурсы. НО!!! но в этом случае уже валится в консоль: sc in free(): error: modified (page-) pointer хотя в логах проги я вижу что все последовательно было назад раскручено после исключения и все записи о освобождении ресурсов в деструкторах объектах также вижу. какбы все окей. так в чем фишка, различие в loadFromFile и loadFromFileBUGGY? если есть ошибки, ткните носом, буду признателен. спасибо!
все же я не успокоился и далее решил поразбираться. у меня есть опасения что этот "free: chunk is already free" происходит в связи с либой SDL, которую я юзаю для работы с графикой. #1 итого, вот такой тест прекрасно пробегается без каких бы то ни было ошибок: Код (Text): #include "CTexture.cpp" main() { try{ for ( uint i = 1; i < 500; ++i ) { printf("%i\n", i); game::CTexture test("./images/big_background.tga"); } } catch(const game::CTexture::CTextureError &e) { printf("texture creating error captured1\n"); return -1; } return 0; } создается и уничтожается именно 499 объектов, что я вижу по выводу значения i в консоль от 1 до 499. #2 если же я тестю вот этот пример, где я сначала инициализирую графику через либу SDL, а потом уже прокручиваю вышеописаный цикл, то опять таки по выводу в консоль вижу, что автоматический объект создался и удалился 499 раз, а затем вылетел еррор my_test in free(): error: junk pointer, too high to make sense: Код (Text): #include <SDL/SDL.h> #include <GL/gl.h> #include "CTexture.cpp" //мой класс int main() { if( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_TIMER ) == - 1 ) { printf( "Init error: %s\n", SDL_GetError() ); return -1; } atexit( SDL_Quit ); if ( SDL_SetVideoMode(1024,768,32, SDL_OPENGL | SDL_FULLSCREEN ) == NULL ) { printf( "Init error: %s\n", SDL_GetError() ); return -1; } SDL_WM_SetCaption( "scroller", NULL); glClearDepth(1.0); glDisable(GL_DEPTH_TEST); glShadeModel(GL_SMOOTH); glEnable(GL_TEXTURE_2D); glEnable(GL_POINT_SMOOTH); glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); try { for ( uint i = 1; i < 500; ++i ) { printf("%i\n", i); game::CTexture test("./images/big_background.tga"); } } catch(const game::CTexture::CTextureError &e) { printf("texture creating error captured1\n"); SDL_Quit(); return -1; } SDL_Quit(); return 0; } Загрузил под gdb полученный core-файл, и вот там какая инфа: никто не знает..?
Это может быть баг компилятора. У тебя нет возможности попробовать другие компиляторы? может ошибка в SDL. GCC не не умеет отлавливать ошибки так хорошо как например VC++. запись за конец массива например ни как не опознается. а как то я видел код от чувака: Код (Text): FILE*f = fopen(... fclose(f); delete f; и....работало.