向量重新分配导致 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) );