我正在开发一个从JPEG文件中读取Exif数据的应用程序。数据存储在结构中,如下所示:
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中保存的字段,并将每个字段添加到名为existingMetadataExif的向量中。
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时,大小和容量增加1 - 0 / 0,1 / 1,2 / 2等。当大小达到29时,向量的容量增加到42即50%增加原始容量。
虽然错误总是在第28个/第29个元素附近,但它并不完全一致。一个文件将向量容量增加到42并立即崩溃,并触发断点&#34;异常,另一个文件一碰到第28个元素就会触发崩溃。
我在代码中尝试过existingMetadataExif.reserve(42),但没有区别。
虽然看起来尺寸重新分配是触发点,但我也想知道
values = new BYTE[noValues]
结构内部的行。这是必需的,因为每个元数据可以包含不同数量的值,包括无,但我不会在应用程序结束之前在任何地方直接删除数组。
我在Windows 8.1上的Visual Studio 2013中进行开发,但没有使用任何MS特定代码,因为此应用程序最终将移植到iOS。
只是澄清一下 - existingMetadataExif是向量,在代码中的其他地方声明,错误发生在
existingMetadataExif.push_back(m);
行
if (existingMetadataExif.size() >27){
bool debug = true;
}
是无关紧要的,可以忽略,我只是帮助我自己的调试尝试。
答案 0 :(得分:2)
Oups,你将一个包含新分配的char数组和一个构造函数的对象推送到std容器。就像在脚下射击自己......
它可以工作,但你必须谨慎:
这就是NathanOliver在评论中的来电者The Rule of Three
如果要使用C ++ 11移动语义,可以添加移动构造函数,复制原始对象数组的地址,并将指针(在原始对象中)设置为0或nullptr。这样,您可以保存数组的分配和副本。
答案 1 :(得分: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[]>() );
}
请注意这里使用的特殊删除器,因为它是一个数组。 (For more on this)。
假设编译,并且您希望确保每个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) );