Перераспределение вектора вызывает ошибку malloc

Я разрабатываю приложение, которое читает данные Exif из файла JPEG. Данные хранятся в структуре, как показано ниже:

struct Metadata{
    int tagID = 0;
    std::string tagIDHex;
    int ifdNo = 0; // 0=IDF0, 1= Exif, 2=GPS, 3=Interop, 4 = IFD1
    BYTE* values;
    int noValues = 0;
    long valuesStart = 0; 
    int bytesPerValue = 1;
    long dataSize = 0;  // Generally bytesPerValue x noValues
    bool usesOffset = false; 
    /*If no bytes used by values is 4 or less, last 4 bytes in field hold actual values, 
    otherwise they point to location elsewhere in file */
    BYTE fieldData[12]; // Holds IFD field

    Metadata(BYTE* data, int ifd){
        ifdNo = ifd;
        tagID = data[0] * 256 + data[1];
        tagIDHex = intToHex(tagID);
        for (int b = 0; b < 12; b++){
            fieldData[b] = data[b];
        }
        noValues = (int)((fieldData[4] * std::pow(256, 3) + fieldData[5] * std::pow(256, 2) + fieldData[6] * std::pow(256, 1)
            + fieldData[7] * std::pow(256, 0)));
        // Look up datatype size based on TIFF spec where 1= BYTE, etc. 
        bytesPerValue = getBytesPerValue(fieldData[3]);
        dataSize = noValues*bytesPerValue; 
        usesOffset = (dataSize>4);
        if (usesOffset){
            values = new BYTE[noValues]; // will get populated later
        }
    }
};

Следующий фрагмент кода проходит по полям, содержащимся в EXIF ​​IFD, и добавляет каждое из них в вектор с именем istingMetadataExif.

for (int f = 0; f < exifFields; f++){
    long tagAddress = exifStart + 2 + f * 12;
    Metadata m = Metadata(&file[tagAddress], 1);
    if (m.usesOffset){
        m.valuesStart = (int)(tiffStart + (m.fieldData[8] * std::pow(256, 3) + m.fieldData[9] * std::pow(256, 2) + m.fieldData[10] * std::pow(256, 1) + m.fieldData[11] * std::pow(256, 0)));
        for (int d = 0; d < (m.noValues*m.bytesPerValue); d++){
            m.values[d] = file[m.valuesStart + d];
        }
    }
    if (existingMetadataExif.size() >27){
        bool debug = true;
    }
    existingMetadataExif.push_back(m);
}

Код отлично работает для некоторых файлов, но у меня проблема с памятью других. Проблема, похоже, связана с перераспределением вектора. Все файлы прекрасно работают до 28 элементов. Кажется, это зарезервированная емкость по умолчанию для вектора. Поскольку каждый элемент добавляется до 28, размер и емкость увеличиваются на единицу - 0/0, 1/1, 2/2 и т. Д. Когда размер достигает 29, емкость вектора увеличивается до 42, т.е. на 50%. увеличение первоначальной емкости.

Хотя ошибка всегда находится вокруг 28-го /29-го элемента, она не является полностью непротиворечивой. один файл увеличивает емкость вектора до 42 и сразу падает с исключением "триггерная точка останова", другой файл вызывает сбой, как только достигает 28-го элемента.

Я пробовал существующий MetadataExif.reserve(42) внутри кода, но это не имеет значения.

Хотя кажется, что перераспределение размера является триггерной точкой, я также задаюсь вопросом о

 values = new BYTE[noValues] 

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

Я занимаюсь разработкой в ​​Visual Studio 2013 для Windows 8.1, но не использую какой-либо специальный код для MS, так как это приложение в конечном итоге будет перенесено на iOS.

РЕДАКТИРОВАТЬ

Просто чтобы уточнить - существующий MetadataExif является вектором, объявленным в другом месте кода, и ошибка возникает в

 existingMetadataExif.push_back(m);

Линии

    if (existingMetadataExif.size() >27){
        bool debug = true;
    }

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

2 ответа

Решение

Любой new рискованно, даже если вы (думаете) у вас есть соответствующие delete, В современном C++ вам почти никогда не нужно new,

((Собственно, почему бы просто не избавиться от указателей и использовать vector<BYTE> вместо?))

    if (usesOffset){
        values = new BYTE[noValues]; // will get populated later
    }

Вы должны рассмотреть возможность использования общего указателя здесь:

 #include<memory>
 ...
 std::shared_ptr<BYTE> values;
 ....
    if (usesOffset){
        values = std::shared_ptr<BYTE> ( new BYTE[noValues], std::default_delete<BYTE[]>() );
    }

Обратите внимание на специальный удалитель, используемый здесь, потому что это массив. ( Подробнее об этом).

Предполагая, что компилируется, и вы заинтересованы в том, чтобы каждый Metadata владеет собственной копией valuesтогда вы должны использовать unique_ptr вместо этого: (опять же, по этой ссылке выше)

std::unique_ptr<BYTE> values;
...
    values = std::unique_ptr<BYTE[]> ( new BYTE[noValues] ); // this
                                     // will correctly call delete[]

Хорошей новостью является то, что использование unique_ptr, вероятно, приведет к сбою вашего кода. Я говорю хорошо, потому что это заставляет вас заниматься копированием. Либо вы реализуете конструктор полной копии и оператор копирования-назначения, либо вы используете move Вот:

    existingMetadataExif.push_back( std::move(m) );

Упс, вы помещаете в контейнер std объект, который содержит новый выделенный массив символов и только один конструктор. Это как выстрелить себе в ногу...

Это может сработать, но вы должны быть осторожны:

  • Вы должны выделить массив char в конструкторе: fine
  • вы должны освободить его от деструктора
  • Вы должны реализовать конструктор копирования, выделить в нем новый массив и скопировать содержимое

Вот что вызвал Натан Оливер : Правило трех в своем комментарии

Если вы хотите использовать семантику перемещения C++ 11, вы можете добавить конструктор перемещения, который копирует адрес массива исходного объекта и устанавливает указатель (в исходном объекте) на 0 или nullptr. Таким образом, вы сохраняете выделение и копию массива.

Другие вопросы по тегам