std::map内存分配错误

5
我遇到了一个与std::map相关的问题。由于未知原因,有时向映射中插入元素会导致“bad allocation”异常。
下面是我用于向映射中插入元素的函数。
BOOL Add2WaitList(Object<LPVOID> *newObj)
{
    try
    {
        _set_se_translator( trans_func );
        m_syncWQ.Lock();
        if (m_waitingQueue.count(newObj->uid)>0)
        {
            m_syncWQ.Unlock();
            return FALSE;
        }
        m_waitingQueue[newObj->uid] = *newObj; <-- failing here
        m_syncWQ.Unlock();
        return TRUE;
    }
    catch(std::exception &ex){
        ...
    }
    catch(SE_Exception &e){
        ...
    }
    catch(...){
        ...
    }
}

请问有人能告诉我如何解决这个问题吗?

注意:我无法确定重现此问题的步骤。

提前感谢您!

关于对象和映射的详细信息:

template <typename T>
struct Object{
public:
    void Kill()
    {
        if (response!=NULL)
            delete response;
        if (object!=NULL)
            delete object;
    }

    enum objType;
    std::string uid;
    enum status;
    double p;
    enum execType;
    T object;
    LPVOID response;
};

std::map<std::string,Object<LPVOID>> m_waitingQueue;

这是一个错别字修正。 - SairuS
使用浅拷贝默认拷贝构造函数和赋值运算符,尽管它包含指针。非常可疑。 - James Kanze
指针被用于添加不同类型的内容,这会对我的问题产生什么影响? - SairuS
指针通常意味着在某个地方进行动态分配。如果没有用户定义的复制构造函数(或者它执行错误操作),这可能会导致诸如双重删除之类的问题。像双重删除这样的问题可能会破坏自由空间区域,或导致其他代码覆盖std::map内部表示的部分。 - James Kanze
那不是你的“Object”类型的实际代码,因为你提供的代码无法编译。 - James Kanze
显示剩余5条评论
4个回答

2

异常std::bad_alloc意味着"operator new失败"。因此,要么newObj上的operator*调用operator new(我们对此一无所知),要么是映射的插入运算符调用它(这极有可能发生)。

具体而言,当您使用某个参数k调用映射的operator[]时:

如果k与容器中任何元素的键不匹配,则该函数将插入一个具有该键的新元素,并返回对其映射值的引用。请注意,即使未将映射值分配给元素(使用其默认构造函数构造元素),这始终会将容器大小增加一。

(如此处所述)。

Map::operator[]在失败时提供强烈的保证:

Strong guarantee: if an exception is thrown, there are no changes in the container.

但不保证不会抛出异常(即它不提供无异常保证)。

operator new 抛出异常的原因可能有不同的性质。然而,所有这些都归结为:

throws bad_alloc if it fails to allocate storage.

话虽如此,正如JamesKanze在评论中所提到的:

std::bad_alloc出现另一个可能的原因是未定义的行为。例如,如果他已经破坏了自由空间区域。实际上,如果他真的没有足够的内存,那么失败的分配将会有所不同。如果它始终发生在这里,我会怀疑Object的复制构造函数是否存在问题,而不是其他方面。

这意味着operator new无法分配存储空间,因为程序的其他部分存在某些错误。您可以通过在调用operator[]之前分配(非常)大的数据块来针对他的零假设进行调试(就像统计学家所说的)。如果虚拟分配不会失败,则可以有很好的信心说存在复制构造函数中的错误。


1
std::bad_alloc 的另一个可能原因是未定义的行为,例如如果他破坏了空闲空间区域。而实际上,如果他真的没有足够的内存,那么失败的分配将会有所不同。如果它在此处系统性地出现,我会怀疑 Object<LPVOID> 的复制构造函数中存在问题,这比其他任何事情都更可能。 - James Kanze
我在思考这个问题,但是我无法确定什么原因导致了错误分配内存? 我已经添加了对象的描述,希望能有所帮助。 - SairuS
@JamesKanze 感谢您的建议,您的评论总是非常恰当。希望您愿意继续审查我的答案! :) - Stefano Falasca

2
很明显,std::map操作引起了问题。
m_waitingQueue[newObj->uid] = *newObj;

这实际上是一次插入地图操作,可能会在后台分配内存:STL map是如何分配的?堆栈还是堆?。一个可能的原因是分配内存导致 Bad allocation 异常:C++中的 Bad allocation 异常
但这段代码本身并没有解释背后发生了什么。我认为需要更多与“m_waitingQueue”相关的信息,因为该变量是全局的,因此在该函数之外可能会对其进行任何操作。

地图被用作等待队列,并由3个不同函数访问的线程。插入只在此处完成,其余操作使用相同锁进行读取。 - SairuS
@SairuS 很好。我认为问题现在更完整了。这是一个我必须点赞和收藏的问题。我正在等待有人提出具体的答案,或者如果你自己解决了问题,请让我知道。 - lulyon

0

operator new()函数无法找到所请求的内存。此函数可以从new表达式或直接在std::map的分配器中调用。

您没有提供有关上下文的任何信息。真正的问题是:它总是在这特定的点失败吗?如果您真的由于内存泄漏而耗尽了内存,那么预计它也会影响其他分配。其他可能性是,在调用此函数之前,您正在破坏空闲空间区域,或者Object<LPVOID>的复制构造函数存在问题,导致它请求无限制的内存,或损坏空闲空间区域,因此下一个分配将失败。您将此对象复制到其他位置吗?您传递指针表明可能不是,如果是这样,那么这将是您看到问题的地方。

编辑:

既然您已经发布了Object的代码:您使用的Object来自哪里?指针通常指向什么,如果它是动态分配的,那么删除是如何管理的?

由于Object没有用户定义的构造函数,这意味着指针可能包含随机垃圾数据。删除随机指针是破坏自由空间区域的好方法。

另外:我注意到您有类似同步原语(Lock()Unlock())。m_waitingQueue在哪里还被使用?如果m_waitingQueue可以从不同的线程访问,则必须使用相同的同步对象(m_syncWQ)同步所有对m_waitingQueue的访问。在此处修改m_waitingQueue时,在另一个线程中尝试修改m_waitingQueue也可能导致未定义的行为(队列对象写入不应该写入的位置)。


将源对象声明为非指针类型。通常我会用指针来调用函数,但有些情况下我需要以非指针(即副本)的方式发送对象。 - SairuS
在我看来,问题与内存泄漏无关,因为有时会出现在第一个操作/添加到映射中。如果我使用不同的插入方式,是否有帮助? - SairuS
@SairuS 这大概是我猜测的问题。你的内存管理有问题:可能是双重删除,或者在已删除的内存中写入,或者超出已分配缓冲区的末尾进行覆盖。如果你正在使用Linux,请在valgrind下运行代码。(Windows也可能有类似的工具,但我不知道除了Purify之外还有什么。Purify非常昂贵,而且在Intel上非常慢。) - James Kanze
@James Kanze - 这显然是Windows平台,例如有免费的Dr.Memory内存调试器。 - SChepurin

0

也许 Add2WaitList(Object<LPVOID>) 被调用了数百万次,直到内存耗尽。

在这种情况下,原因可能在代码的其他地方 - 例如无限循环或回归。另一个可能的原因是,如果您的 Object 不小心获得了不同的 uid,那么这种情况可能会发生。例如,当 uid 源自未初始化的数字时,就可能会发生这种情况。


谢谢您的回答,但所有情况都不适用于我的情况。我在“James Kanze”的评论中提到,这有时会发生在第一次插入时。 - SairuS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接