free(): error: chunk is already free. где я ошибся?

Тема в разделе "LANGS.C", создана пользователем varnie, 19 ноя 2007.

  1. varnie

    varnie New Member

    Публикаций:
    0
    Регистрация:
    2 янв 2005
    Сообщения:
    1.785
    добрый день.

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

    есть класс, в конструкторе вызывается один из его методов, в котором происходит выделение под этот член памяти и его инициализация. все это само собой завернуто в try/catch, с отловлей std::bad_alloc. вот, собссно, кусочек:

    логика такова:
    Код (Text):
    1. CMyClass()::CMyClass(const char *filePath)
    2. {
    3.     if (!initMember(filePath))
    4.        throw CMyClassException();
    5. }
    6.  
    7. bool CMyClass::initMember(const char *filePath)
    8. {
    9.     ...//blabla
    10.     std::size_t sz = blabla//открыли файл по filePath, получили его размер в sz
    11.     try
    12.     {
    13.         _pData = new[sz];
    14.     }
    15.     catch(std::bad_alloc)
    16.     {
    17.         printf("CMyclass: unable to allocate memory\n");
    18.         return false;
    19.     }
    20.     //blabla
    21.     return true;
    22. }
    23.  
    24. CMyClass::~CMyClass()
    25. {
    26.     if (_pData)
    27.       delete [] _pData;
    28. }
    далее в одном в объекте класса ClassA вызывается один из методов др. класса, ClassB, в котором и происходит выделение объектов класса CMyCLass и дальнейшее с ними оперирование.
    так вот, здесь все прекрасно работает.
    но для проверки я решил в этом методе у ClassB вставить такой код:

    Код (Text):
    1. bool ClassB::foo()
    2. {
    3.     //blabla
    4.     try
    5.     {  
    6.     for ( uint i = 1; i < 1000; ++i )
    7.         CMyClass test("bigFile.txt"); //bigFile.txt весит ~2MB
    8.     }
    9.     catch(const CMyClass::CMyClassException &e)
    10.     {  
    11.     return false;
    12.     }
    13.  
    14.     return true;  
    15. }
    этот метод вызывается неск. раз из вышестоящего класса ClassA, и на втором вызове где-то на ~600 итерации срабатывает catch и ловится CMyClass::CMyClassException из CMyClass test. далее программа пытается корректно завершить работу(освобождаю все под-системы, ClassA итд). все завершается ок, в логах вижу записи о том, что произошло исключение типа CMyClass::CMyClassException, и далее о том, что все действия по освобождению ресурсов были выполнены _верно_.

    НО в консоль вываливается строчка:
    ,т.е. якобы происходит дважды освобождение какого-то выделенного ресурса.
    выяснил, что если закомментить delete _pData; в деструкторе у CMyClass, то эта строчка уже не вываливается. как может происходить дважды delete _pData в деструкторе, если после возбуждения исключения в конструкторе CMyClass само создание этого объекта уже невыполняется, а следовательно, и вызова далее деструктора нет? или же я еще что-то упустил, и здесь все норм.

    непонятно мне, где я ошибся, и что в первую очередь перепроверить, чтобы этот баг исправить.

    ps: юзаю компилер g++ (GCC) 3.4.6.
     
  2. varnie

    varnie New Member

    Публикаций:
    0
    Регистрация:
    2 янв 2005
    Сообщения:
    1.785
    есть мнение, что
    но это описывается к случаю:
    Код (Text):
    1.   char *p = NULL;
    2.   try
    3.   {
    4.     for(; ;)
    5.     p = new char[100000];
    или это как раз мой случай несмотря на отсутствие new в цикле?
     
  3. censored

    censored New Member

    Публикаций:
    0
    Регистрация:
    5 июл 2005
    Сообщения:
    1.615
    Адрес:
    деревня "Анонимные Прокси"
    А разве не catch(std::bad_alloc &) ?
     
  4. varnie

    varnie New Member

    Публикаций:
    0
    Регистрация:
    2 янв 2005
    Сообщения:
    1.785
    censored,
    да, ошибся. сейчас исправил, но баг по-прежнему остался.
     
  5. AsmGuru62

    AsmGuru62 Member

    Публикаций:
    0
    Регистрация:
    12 сен 2002
    Сообщения:
    689
    Адрес:
    Toronto
    При удалении памяти - ставь указатель в NULL:
    Код (Text):
    1. delete [] array;
    2. array = NULL;
    К слову оператор delete проверяет указатель на нулевое значение, так что самому проверять - просто код будет длиннее.
     
  6. varnie

    varnie New Member

    Публикаций:
    0
    Регистрация:
    2 янв 2005
    Сообщения:
    1.785
    AsmGuru62,
    уже так и есть у меня. в смысле, в деструкторе так и сделано. но тем не менее баг остается.

    сейчас для эксперимента переписал эту часть с выделением памяти в временном массиве char *temp = new char[sz];, и оттуда копированием всех данных в мембер класса, переписанный уже как вектор std::vector<char> _data; а затем удаляю память выделенную для темпового буффера. т.е. теперь сырые данные сами по себе уже не хранятся в char*, а хранятся в векторе, и следовательно, в деструкторе класса уже ничего не удаляю.
    но зато теперь после того как была выделена память под char *temp, и считана в нее, после копирования этих данных в мембер-вектор, иногда вылетает std::bad_alloc при попытке освободить эту память выделенную в темповую переменную. поставил обработку try/catch для удаления этой темповой переменной, и теперь программа стала по крайней мере _корректно_ реагировать на неудачу при конструировании объекта этого рассматриваемого класса и корректно завершаться без каких бы то ни было сообщений о багах итд (т.е. теперь все соответствует моей спроектированной логике для случаев с исключениями).
    но непонятно, почему иногда при попытке удалить char *temp вылетает std::bad_alloc? ведь память была успешно выделена, и в нее были успешно считаны данные. так чего ж освободить память эпизодически неудается?

    может кто прояснит,
    спасибо.
     
  7. Booster

    Booster New Member

    Публикаций:
    0
    Регистрация:
    26 ноя 2004
    Сообщения:
    4.860
    varnie
    Память под объект в случае исключения std::bad_alloc не выделяется, как и случае исключения в конструкторе. А значит в этом случае её уничтожать - получить ошибку. Эту проблему часто решают с помощью смарт указателей. После того как объект сконструирован, указатель передают смарту. Смарт автоматически разрушается при вызове деструктора, и если у него будет указатель, то он его разрушит, а если нет, то разрушиться только сам (указатель-то не успели передать).
     
  8. varnie

    varnie New Member

    Публикаций:
    0
    Регистрация:
    2 янв 2005
    Сообщения:
    1.785
    Booster,
    это я знаю. но у меня вот как в д. момент реализовано:

    Код (Text):
    1. class CMyCLass
    2. {
    3.    public:
    4.        ...//
    5.        bool loadFromFile(const char *fPath);
    6.    private:
    7.        vector<char> _data;
    8. };
    9.  
    10. bool CMyClass::loadFromFile(const char *fPath)
    11. {
    12.     char *pData = NULL;
    13.         ...//открыли файл fPath, получили его размер в size
    14.     printf("size = %i\n", _size);
    15.     printf("allocating _pData\n");
    16.     try
    17.     {  
    18.         //Allocate data for the image and load it there.
    19.  
    20.         pData = new char[ sizeof(char) * size ];
    21.  
    22.     }
    23.     catch(std::bad_alloc )
    24.     {  
    25.                 printf("bad_alloc error1!\n");
    26.         fclose( pFile );
    27.         return false;
    28.  
    29.     }    
    30.     catch(...)
    31.     {
    32.         printf("some error\n");
    33.         fclose( pFile );
    34.         return false;
    35.     }
    36.  
    37.     printf("allocated ok\n");
    38.     try    
    39.     {      
    40.         fread( pData, sizeof(char), size, pFile );
    41.  
    42.                 ...//сделали что надо с pData
    43.    
    44.         //скопировали эти данные в мембер класса _data;     
    45.         for ( register long index = 0; index < size; ++index )
    46.             _data.push_back(pData[index]);   
    47.  
    48.                //освободили память выделенную в темповом объекте
    49.                printf("deleting pData\n");
    50.                delete [] pData;
    51.     }
    52.     catch(std::bad_alloc)
    53.     {
    54.         printf("bad_alloc error2!\n");
    55.         fclose(pFile);
    56.         return false;
    57.     }  
    58.  
    59.         return true;
    60. }
    61.  
    62. СMyClass::CMyClass(const char *fPath)
    63. {
    64.     if (! loadFromFile)
    65.        throw MyClassException();
    66. }
    так вот, на определенном шаге при тестинге:
    Код (Text):
    1.       for ( uint i = 1; i < 1000; ++i )
    2.            CMyClass test("test.txt");
    вылетает std::bad_alloc и в консоль выводится:
    что ясно дает понять о том, в какой именно части кода это исключеине произошло. выходит, что при delete [] pData;

    так почему выделить память под pData получилось в этом случае, а удалить - нет, и вылетает это std::bad_alloc ?
     
  9. Booster

    Booster New Member

    Публикаций:
    0
    Регистрация:
    26 ноя 2004
    Сообщения:
    4.860
    А printf("deleting pData\n"); выводиться?
    Исключение точно не в _data.push_back(pData[index]);?
    Вот из msdn:
    IMHO delete исключений не генерит, по крайней мере С++.
     
  10. varnie

    varnie New Member

    Публикаций:
    0
    Регистрация:
    2 янв 2005
    Сообщения:
    1.785
    Booster,
    ага, я тоже уже выяснил, что printf("deleting pData\n"); не выводится --> исключение в _data.push_back(pData[index]); происходит в ходе цикла.
    пытаюсь выяснить - почему.
     
  11. varnie

    varnie New Member

    Публикаций:
    0
    Регистрация:
    2 янв 2005
    Сообщения:
    1.785
    сейчас пришел к двум вариантам. по логике одинаковы, но один из них производит вышеупомянутый баг..

    вариант #1:
    Код (Text):
    1. bool CTexture::loadFromFile(const std::string &filePath)
    2. {    
    3.         if( filePath.empty() )
    4.         {
    5.    
    6.             return false;
    7.         }
    8.  
    9.         if ( filePath.find(".tga") != std::string::npos )
    10.         {
    11.  
    12.             FILE *pFile = NULL;
    13.  
    14.             char tempColor;
    15.  
    16.             unsigned char bitCount;
    17.  
    18.             uint colorMode;
    19.  
    20.             std::size_t tgaSize;
    21.  
    22.             const static unsigned char unCompressHeader[12] = { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    23.  
    24.             unsigned char tgaHeader[12];
    25.  
    26.             unsigned char header[6];
    27.  
    28.  
    29.             // Open the file.
    30.  
    31.             if ( (pFile = fopen(filePath.c_str(), "rb")) == NULL )
    32.             {
    33.                 return false;
    34.             }
    35.  
    36.             // Read the header.
    37.             fread( tgaHeader, 1, sizeof(tgaHeader), pFile );
    38.  
    39.             if ( memcmp(unCompressHeader, tgaHeader, sizeof(unCompressHeader)) != 0 )
    40.             {
    41.                 fclose( pFile );
    42.                 return false;
    43.  
    44.             }
    45.  
    46.             fread(header, 1, sizeof(header), pFile);
    47.  
    48.             _sizeX = header[1] * 256 + header[0];
    49.  
    50.             _sizeY = header[3] * 256 + header[2];
    51.  
    52.             bitCount = header[4];
    53.  
    54.             colorMode = bitCount / 8;
    55.  
    56.             _size = tgaSize = _sizeX * _sizeY * colorMode;
    57.             _transparent =  ( colorMode == 4 ) ? true : false;
    58.  
    59.             char *pData = NULL;
    60.  
    61.             try
    62.             {    
    63.                 pData = new char[ sizeof(char) * tgaSize ];
    64.  
    65.                 if ( fread( pData, sizeof(char), _size, pFile ) != tgaSize)
    66.                             {
    67.                                fclose(pFile);
    68.                                return false;
    69.                             }
    70.  
    71.                 for ( register std::size_t index = 0; index < _size; index += colorMode )
    72.                 {
    73.                 tempColor = pData[index];
    74.                 pData[index] = pData[index + 2];
    75.                 pData[index + 2] = tempColor;
    76.  
    77.                 }
    78.  
    79.                    }
    80.                    catch(std::bad_alloc &)
    81.                    {
    82.                   printf("std::bad_alloc\n");
    83.                   fclose(pFile);
    84.                   return false;
    85.                    }
    86.  
    87.                    try
    88.                    {        
    89.                 for ( register std::size_t index = 0; index < _size; ++index )
    90.                 {
    91.                 //printf("index = %i\n", index);            
    92.                 _data.push_back(pData[index]);    
    93.                 }
    94.                 delete [] pData;
    95.                        }            
    96.                    catch(std::bad_alloc)
    97.                    {
    98.                    delete [] pData;
    99.                    printf("std::bad_alloc2\n");
    100.                    fclose(pFile);
    101.                    return false;
    102.                    }        
    103.            
    104.  
    105.                fclose( pFile );
    106.                printf("return true\n");
    107.  
    108.                return true;  
    109.  
    110.         }
    111.  
    112.                 //not a TGA format
    113.                 return false;
    114. }
    вариант #2:
    Код (Text):
    1.     bool CTexture::loadFromFileBUGGY(const std::string &filePath)
    2.     {
    3.         if (filePath.empty())
    4.             return false;
    5.         if (filePath.find(".tga") != std::string::npos)
    6.         {
    7.             char tempColor;
    8.  
    9.             unsigned char bitCount;
    10.  
    11.             uint colorMode;
    12.  
    13.             static const unsigned char unCompressedHeader[12] = {0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    14.             static std::vector<unsigned char> tgaHeader(12);
    15.  
    16.             static std::vector<unsigned char> header(6);
    17.  
    18.             std::ifstream f(filePath.c_str(), std::ios_base::in | std::ios_base::binary);
    19.  
    20.             assert(f);
    21.            try
    22.           {    
    23.             f.read((char*)&tgaHeader[0], 12);
    24.             if ( !equal(tgaHeader.begin(), tgaHeader.end(), &unCompressedHeader[0] ) )
    25.                 {
    26.                 return false;
    27.             }
    28.  
    29.             f.read( (char*)&header[0], 6 );
    30.  
    31.             _sizeX = header[1] * 256 + header[0];
    32.  
    33.             _sizeY = header[3] * 256 + header[2];
    34.  
    35.             assert(_sizeX && _sizeY);
    36.  
    37.  
    38.             bitCount = header[4];
    39.  
    40.    
    41.  
    42.             colorMode = bitCount / 8;
    43.        
    44.             _size = _sizeX * _sizeY * colorMode;
    45.             _transparent =  ( colorMode == 4 ) ? true : false;                
    46.                    
    47.             assert(_size);
    48.            
    49.             _data.resize( _size);
    50.             f.read((char*)&_data[0], sizeof(char)*_size);
    51.                
    52.                 for ( register std::size_t index = 0; index+2 < _size; index += colorMode )
    53.  
    54.                 {    
    55.  
    56.                 tempColor = _data[index];
    57.  
    58.                 _data[index] = _data[index + 2];
    59.  
    60.                 _data[index + 2] = tempColor;
    61.  
    62.                 }
    63.            }
    64.            catch(std::bad_alloc &)
    65.            {    
    66.               return false;
    67.            }
    68.    
    69.               return true;
    70.          }
    71.    
    72.          //not a TGA format
    73.          return false;
    74.     }
    в конструкторе этого класса вызываем этот метод loadFromFile(loadFromFile2):

    Код (Text):
    1. CTexture::CTexture(const std::string &filePath) throw(CTextureError)
    2. :_sizeX(0),_sizeY(0),_pData(NULL),_transparent(false),_texID(0),_size(0)
    3. {
    4.  
    5.         if ( ! loadFromFileBUGGY(filePath) ) //или же if ( ! loadFromFile(filePath) )
    6.         {    
    7.             printf("unable to load from file\n");
    8.             throw  CTextureError("unable to load from file");        
    9.         }
    10.  
    11.         //далее что надо делаем
    12.  
    13. }
    так вот, если вызывать в конструкторе 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?
    если есть ошибки, ткните носом, буду признателен. спасибо!
     
  12. varnie

    varnie New Member

    Публикаций:
    0
    Регистрация:
    2 янв 2005
    Сообщения:
    1.785
    все же я не успокоился и далее решил поразбираться. у меня есть опасения что этот "free: chunk is already free" происходит в связи с либой SDL, которую я юзаю для работы с графикой.
    #1
    итого, вот такой тест прекрасно пробегается без каких бы то ни было ошибок:

    Код (Text):
    1. #include "CTexture.cpp"
    2.  
    3.   main()
    4.   {
    5.     try{
    6.     for ( uint i = 1; i < 500; ++i )
    7.     {    printf("%i\n", i);
    8.            
    9.         game::CTexture test("./images/big_background.tga");
    10.     }
    11.     }
    12.     catch(const game::CTexture::CTextureError &e)
    13.     {    printf("texture creating error captured1\n");
    14.        
    15.     return -1;
    16.     }
    17.  
    18.     return 0;
    19.   }
    создается и уничтожается именно 499 объектов, что я вижу по выводу значения i в консоль от 1 до 499.

    #2
    если же я тестю вот этот пример, где я сначала инициализирую графику через либу SDL, а потом уже прокручиваю вышеописаный цикл, то опять таки по выводу в консоль вижу, что автоматический объект создался и удалился 499 раз, а затем вылетел еррор my_test in free(): error: junk pointer, too high to make sense:

    Код (Text):
    1. #include <SDL/SDL.h>
    2. #include <GL/gl.h>
    3. #include "CTexture.cpp" //мой класс
    4.  
    5. int main()
    6. {
    7.     if( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_TIMER  ) == - 1 )
    8.     {
    9.         printf( "Init error: %s\n", SDL_GetError() );
    10.           return -1;
    11.     }
    12.  
    13.     atexit( SDL_Quit );
    14.        
    15.     if ( SDL_SetVideoMode(1024,768,32, SDL_OPENGL | SDL_FULLSCREEN ) == NULL )
    16.     {
    17.             printf( "Init error: %s\n", SDL_GetError() );
    18.         return -1;
    19.     }
    20.    
    21.         SDL_WM_SetCaption( "scroller", NULL);
    22.  
    23.     glClearDepth(1.0);                
    24.  
    25.      glDisable(GL_DEPTH_TEST);      
    26.  
    27.       glShadeModel(GL_SMOOTH);    
    28.  
    29.       glEnable(GL_TEXTURE_2D);
    30.     glEnable(GL_POINT_SMOOTH);
    31.     glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
    32.     glEnable(GL_BLEND);
    33.     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    34.  
    35.     try
    36.         {
    37.         for ( uint i = 1; i < 500; ++i )
    38.         {    
    39.             printf("%i\n", i);
    40.             game::CTexture test("./images/big_background.tga");
    41.         }
    42.     }
    43.     catch(const game::CTexture::CTextureError &e)
    44.     {    
    45.         printf("texture creating error captured1\n");
    46.         SDL_Quit();
    47.         return -1;
    48.     }
    49.  
    50.     SDL_Quit();
    51.        
    52.     return 0;
    53. }
    Загрузил под gdb полученный core-файл, и вот там какая инфа:
    никто не знает..?
     
  13. CnCVK

    CnCVK New Member

    Публикаций:
    0
    Регистрация:
    9 авг 2006
    Сообщения:
    108
    Это может быть баг компилятора.
    У тебя нет возможности попробовать другие компиляторы?
    может ошибка в SDL.
    GCC не не умеет отлавливать ошибки так хорошо как например VC++.
    запись за конец массива например ни как не опознается.
    а как то я видел код от чувака:
    Код (Text):
    1. FILE*f = fopen(...
    2.  
    3. fclose(f);
    4. delete f;
    и....работало.