Перераспределение вектора вызывает ошибку 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. Таким образом, вы сохраняете выделение и копию массива.