vector.resize函数在大小太大时破坏内存

时间:2009-10-23 19:45:17

标签: c++ stl vector resize

正在发生的事情是我正在读取加密数据包,并且遇到一个损坏的数据包,它会返回一个非常大的随机数。

size_t nLengthRemaining = packet.nLength - (packet.m_pSource->GetPosition() - packet.nDataOffset);

seckey.SecretValues.m_data.resize(nLengthRemaining);

在此代码中,m_data是std::vector<unsigned char>。 nLengthRemaining由于数据包损坏而过大,因此抛出调整大小函数。问题不在于调整大小(我们处理异常),但调整大小已经损坏了内存,这会导致更多异常。

我想要做的是在调用resize之前知道长度是否太长,然后只有调用resize才可以。我已经尝试在调用resize之前输入此代码:

std::vector<unsigned char>::size_type nMaxSize = seckey.SecretValues.m_data.max_size();
if(seckey.SecretValues.m_data.size() + nLengthRemaining >=  nMaxSize) {
    throw IHPGP::PgpException("corrupted packet: length too big.");
}
seckey.SecretValues.m_data.resize(nLengthRemaining);

此代码使用std :: vector max_size成员函数来测试nLengthRemaining是否更大。但这肯定不可靠,因为nLengthRemaining仍然小于nMaxSize,但显然仍然足以导致调整大小出现问题(nMaxSize为4xxxxxxxxx且nLengthRemaining为3xxxxxxxxx)。

另外,我还没有确定调整大小的异常。它不是std :: length_error,它不是std :: bad_alloc。它真正抛出的例外对我来说并不重要,但我很想知道。

顺便说一句,您知道,此代码在正常情况下可以正常工作。这种损坏数据包的情况是它疯狂的唯一地方。请帮忙!感谢。

更新:

@迈克尔。现在,如果数据包大于5 MB,我将忽略该数据包。我将与其他团队成员讨论可能验证数据包的问题(它可能已存在,我只是不知道它)。我开始认为它确实是我们的STL版本中的一个错误,它抛出的异常甚至不是std :: exception,这让我很惊讶。我将尝试从我的主管那里找出我们正在运行的STL版本(我将如何检查?)。

另一个更新: 我只是证明它是我在Visual Studio 6开发机器上使用的STL版本中的一个错误。我写了这个示例应用程序:

// VectorMaxSize.cpp:定义控制台应用程序的入口点。 //

#include "stdafx.h"
#include <vector>
#include <iostream>
#include <math.h>
#include <typeinfo>

typedef std::vector<unsigned char> vector_unsigned_char;

void fill(vector_unsigned_char& v) {
    for (int i=0; i<100; i++) v.push_back(i);
}


void oput(vector_unsigned_char& v) {
    std::cout << "size: " << v.size() << std::endl;
    std::cout << "capacity: " << v.capacity() << std::endl;
    std::cout << "max_size: " << v.max_size() << std::endl << std::endl;
}

void main(int argc, char* argv[]) {
    {
        vector_unsigned_char v;

        fill(v);

        try{
            v.resize(static_cast<size_t>(3555555555));
        }catch(std::bad_alloc&) {
            std::cout << "caught bad alloc exception" << std::endl;
        }catch(const std::exception& x) {
            std::cerr << typeid(x).name() << std::endl;
        }catch(...) {
            std::cerr << "unknown exception" << std::endl;
        }

        oput(v);    
        v.reserve(500);
        oput(v);
        v.resize(500);
        oput(v);
    }

    std::cout << "done" << std::endl;
}

在我的VS6开发机器上它具有与加密项目相同的行为,它会造成各种各样的破坏。当我在我的Visual Studio 2008机器上构建并运行它时,resize将抛出一个std :: bad_alloc异常,并且矢量不会被破坏,就像我们预期的那样!一些EA体育NCAA足球的时间嘿嘿!

3 个答案:

答案 0 :(得分:5)

我认为vector::max_size()几乎总是一个'硬编码'的东西 - 它与系统/库准备动态分配的内存量无关。您的问题似乎是矢量实现中的一个错误,当分配失败时会破坏事物。

'Bug'可能过于强烈。 vector::resize()是根据vector::insert()定义的,标准是关于vector::insert()

  

如果除了T的复制构造函数或赋值运算符之外抛出异常,则没有效果

所以似乎有时候允许resize()操作来破坏一个向量,但是如果操作是异常安全的话它仍然会很好(我认为它不会出错期望图书馆这样做,但也许它比我想象的更难。)

您似乎有几个合理的选择:

  • 更改或更新到没有损坏错误的库(您使用的是哪个编译器/库版本?)
  • 而不是将vector::max_size()设置nMaxSize与您自己合理的最大值进行核对,而是执行上面的操作,而不是使用该阈值。

编辑:

我看到你正在使用VC6 - vector::resize()中肯定存在一个可能与你的问题有关的错误,虽然看着补丁我老实说也看不出来(实际上这是一个错误) vector::insert(),但如上所述,resize()调用insert())。我想有必要访问Dinkumwares' page for bug fixes to VC6并应用修复程序。

问题可能还与该页面上的<xmemory>补丁有关 - 目前还不清楚那里讨论的错误是什么,但vector::insert()确实调用_Destroy()和{{1确定名称vector<>,以便您可能遇到该问题。一件好事 - 您不必担心管理标题的更改,因为Microsoft从未再次触及它们。只需确保修补程序进入版本控制并记录下来。

请注意,“有效STL”中的Scott Meyers建议使用SGI'sSTLPort's库来获得比VC6更好的STL支持。我没有这样做,所以我不确定这些库是如何工作的(但我也没有将VC6与STL一起使用)。当然,如果您可以选择转移到较新版本的VC,请务必执行此操作。


再一次编辑:

感谢您的测试计划......

VC6的默认分配器_Ty实现(在_Allocate()中)使用signed int来指定要分配的元素数,如果传入的大小是负数(显然就是你的那个)重做 - 当然在你的测试程序中)<xmemory>函数强制所请求的分配大小为零并继续。请注意,零大小的分配请求几乎总是成功(不是_Allocate()检查失败),因此vector函数会愉快地尝试将其内容移动到新块中,这不是'至少可以说是相当大的。所以堆被破坏了,它可能会打到一个无效的内存页面,无论如何 - 你的程序被冲洗了。

所以底线是不要求VC6一次分配超过vector::resize()个对象。在大多数情况下(VC6或其他情况)可能不是一个好主意。

此外,您应该记住,当分配失败而不是抛出INT_MAX时,VC6使用从new返回0的预标准惯用语。

答案 1 :(得分:5)

我强烈建议您在调用库函数之前检查数据是否有错误!

在数据包上使用某种哈希码或校验和算法。 您不能依赖图书馆来帮助您,因为它无法做到: 可能是你给它一个损坏但仍然有效(从库的角度来看)大小是真正的大,所以它分配例如768MB的RAM。如果系统中有足够的可用内存,但如果运行的其他程序在1024MB计算机上占用过多内存,则可能会失败。

如上所述:先检查一下!

答案 2 :(得分:4)

当你说“调整大小已损坏内存”时,我不知道你的意思。你是如何确定的?

FWIW,我不同意Michael's answer。如果std::vector<>::resize()引发向量扩展,我会看到两种可能性:

  1. 用于填充新空间(或复制元素)的其中一个构造函数抛出或
  2. 用于增长矢量的分配器
  3. 或之前确定的向量,请求的大小太多并且抛出。
  4. 使用std::vector<unsigned char>,我们可以安全地解除#1,因此留下#2。如果您不使用任何特殊分配器,则应使用std::allocator和AFAIK,它将调用new来分配内存。并且new会抛出std::bad_alloc。但是,你说你不能抓住这个,所以我不知道会发生什么。

    无论是什么,都应该来自std::exception,所以你可以这样做来找出:

    try {
      my_vec.resize( static_cast<std::size_t>(-1) );
    } catch(const std::exception& x) {
      std::cerr << typeid(x).name() << '\n';
    }
    

    结果是什么?

    无论如何,不​​管它是什么,我都相当不应该破坏记忆。这可能是你的std lib实现中的一个错误(不太可能,如果你问我,除非你使用一个非常旧的)或你在其他地方做错了。


    编辑现在你说你正在使用VS6 ......

    你应该早点说出来。 VC已经在十多年前被释放了,因为他们没有在会议上出现太长时间,因此他们已经失去了他们在std委员会的投票权。他们发布的std lib实现来自Dinkumware(很好),但是由于法律问题,它是VC5的一个(非常糟糕),它有很多越来越小的bug,甚至没有成员模板的支持,即使VC6编译器支持它。老实说,你对这么老的产品有什么期望?

    如果你不能切换到一个不错的VC版本(我建议至少VC7.1又称VS.NET 2003,因为这是实现标准一致性的重大飞跃),至少看看{{3}仍然出售他们优秀图书馆的VC6t版本。 (实际上,我会感到惊讶,但他们曾经有过一个你永远都不知道......)

    至于例外:在早期的VC版本中(这包括VC6并且不包括VC8又名VS.NET 2005,但我不确定VC7.1),默认情况下,访问冲突可能被{{1 }}。因此,如果这样的catch块捕获了某些东西,你就不会知道这是否是一个C ++异常。我的建议是只使用catch(...)catch(...)一起使用才能让该异常传递。如果你这样做,你会在AV上遇到真正的崩溃,并能够在调试器中对它们进行堆栈跟踪。如果你不这样做,AV将被吞下,然后你就会被一个在你甚至不知道的情况下疯狂的应用程序所困扰。但做任何事情,除了用AV'ed应用程序中止是没有意义的。 AV是未定义行为的一个结果,之后,所有投注均已关闭。