向量重新分配导致 malloc 错误
Vector reallocation causes malloc error
我正在开发一个从 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,大小和容量增加一 - 0/0、1/1、2/2 等。当大小达到 29 时,向量的容量增加到 42,即增加 50%增加原有容量。
虽然错误总是在第28/29个元素附近,但并不完全一致。一个文件将向量容量增加到 42 并立即崩溃并出现 "triggered a breakpoint" 异常,另一个文件在遇到第 28 个元素时立即触发崩溃。
我已经在代码中尝试了 existingMetadataExif.reserve(42),但没有任何区别。
虽然大小重新分配似乎是触发点,但我也想知道
values = new BYTE[noValues]
结构内的行。这是必需的,因为每个元数据都可以包含不同数量的值,包括 none 但在应用程序结束之前,我不会在需要的任何地方直接删除数组。
我在 Visual Studio 2013 年 Windows 8.1 上开发,但没有使用任何 MS 特定代码,因为此应用程序最终将移植到 iOS。
编辑
澄清一下 - existingMetadataExif 是向量,在代码的其他地方声明,错误发生在
existingMetadataExif.push_back(m);
线条
if (existingMetadataExif.size() >27){
bool debug = true;
}
无关紧要,可以忽略,我只是把它们拿出来帮助我自己进行调试。
糟糕,您将一个包含新分配的 char 数组和只有一个构造函数的对象推送到 std 容器。搬起石头砸自己的脚...
可以用,但一定要谨慎:
- 您必须在构造函数中分配 char 数组:很好
- 你必须从析构函数中释放它
- 你必须实现一个复制构造函数,在其中分配一个新数组并复制内容
NathanOliver 的来电者 The Rule of Three 在其评论中就是这么说的
如果你想使用 C++ 11 移动语义,你可以添加一个移动构造函数来复制原始对象数组的地址并将指针(在原始对象中)设置为 0 或 nullptr。这样,您就可以保存数组的分配和副本。
任何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
:(同样,从link 以上)
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) );
我正在开发一个从 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,大小和容量增加一 - 0/0、1/1、2/2 等。当大小达到 29 时,向量的容量增加到 42,即增加 50%增加原有容量。
虽然错误总是在第28/29个元素附近,但并不完全一致。一个文件将向量容量增加到 42 并立即崩溃并出现 "triggered a breakpoint" 异常,另一个文件在遇到第 28 个元素时立即触发崩溃。
我已经在代码中尝试了 existingMetadataExif.reserve(42),但没有任何区别。
虽然大小重新分配似乎是触发点,但我也想知道
values = new BYTE[noValues]
结构内的行。这是必需的,因为每个元数据都可以包含不同数量的值,包括 none 但在应用程序结束之前,我不会在需要的任何地方直接删除数组。
我在 Visual Studio 2013 年 Windows 8.1 上开发,但没有使用任何 MS 特定代码,因为此应用程序最终将移植到 iOS。
编辑
澄清一下 - existingMetadataExif 是向量,在代码的其他地方声明,错误发生在
existingMetadataExif.push_back(m);
线条
if (existingMetadataExif.size() >27){
bool debug = true;
}
无关紧要,可以忽略,我只是把它们拿出来帮助我自己进行调试。
糟糕,您将一个包含新分配的 char 数组和只有一个构造函数的对象推送到 std 容器。搬起石头砸自己的脚...
可以用,但一定要谨慎:
- 您必须在构造函数中分配 char 数组:很好
- 你必须从析构函数中释放它
- 你必须实现一个复制构造函数,在其中分配一个新数组并复制内容
NathanOliver 的来电者 The Rule of Three 在其评论中就是这么说的
如果你想使用 C++ 11 移动语义,你可以添加一个移动构造函数来复制原始对象数组的地址并将指针(在原始对象中)设置为 0 或 nullptr。这样,您就可以保存数组的分配和副本。
任何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
:(同样,从link 以上)
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) );