【问题标题】:new not called, yet memory allocatednew 未调用,但已分配内存
【发布时间】:2012-12-22 17:53:51
【问题描述】:

我写了一个简单的Trie 实现。以下是源代码:

#include <string>
#include <map>

typedef unsigned int uint;

class Trie {
public:
    class Node {
    public:
            Node(const char & _value);
            ~Node();
            char get_value() const;
            void set_marker(const uint & _marker);
            uint get_marker() const;
            bool add_child(Node * _child);
            Node * get_child(const char & _value) const;
            void clear();
    private:
            char m_value;
            uint m_marker;
            std::map<char, Node *> m_children;
    };

    Trie();
    ~Trie();
    bool insert(const std::string & _str);
    bool find(const std::string & _str) const;
private:
    Node * m_root;
};
// - implementation (in a different file)
using namespace std;

Trie::Node::Node(const char & _value) :
            m_value(_value), m_marker(0), m_children() {
}

Trie::Node::~Node() {
    clear();
}

void Trie::Node::clear() {
    map<char, Node*>::const_iterator it;
    for (it = m_children.begin(); it != m_children.end(); ++it) {
            delete it->second;
    }
}

void Trie::Node::set_marker(const uint & _marker) {
    m_marker = _marker;
}

uint Trie::Node::get_marker() const {
    return m_marker;
}

char Trie::Node::get_value() const {
    return m_value;
}

Trie::Node * Trie::Node::get_child(const char & _value) const {
    map<char, Node*>::const_iterator it;
    bool found = false;
    for (it = m_children.begin(); it != m_children.end(); ++it) {
            if (it->first == _value) {
                    found = true;
                    break;
            }
    }
    if (found) {
            return it->second;
    }
    return NULL;
}

bool Trie::Node::add_child(Node * _child) {
    if (_child == NULL) {
            return false;
    }
    if (get_child(_child->get_value()) != NULL) {
            return false;
    }
    m_children.insert(pair<char, Node *>(_child->get_value(), _child));
    return true;
}

Trie::Trie() :
            m_root(new Node('\0')) {
}

Trie::~Trie() {
    delete m_root;
}

bool Trie::insert(const string & _str) {
    Node * current = m_root;
    bool inserted = false;
    for (uint i = 0; i < _str.size(); ++i) {
            Node * child = current->get_child(_str[i]);
            if (child == NULL) {
                    child = new Node(_str[i]);
                    current->add_child(child);
                    inserted = true;
            }
            current = child;
    }
    if (current->get_marker() != _str.size()) {
            current->set_marker(_str.size());
            inserted = true;
    }
    return inserted;
}

bool Trie::find(const std::string & _str) const {
    Node * current = m_root;
    bool found = false;
    for (uint i = 0; i < _str.size(); ++i) {
            Node * child = current->get_child(_str[i]);
            if (child == NULL) {
                    break;
            } else {
                    current = child;
            }
    }
    if (current->get_marker() == _str.size()) {
            found = true;
    }
    return found;
}

这是我的测试程序:

#include <iostream>
#include <sstream>
#include "Trie.h"

int main() {
    Trie t;
    for (unsigned int i = 0; i < 10000; ++i) {
            t.insert("hello");
    }
    return 0;
}

我的问题是,即使在第二次尝试插入时已经插入了“hello”,因此不再调用 new,但仍有大量内存被分配和释放。这个数量随着 I 增加 max i 的值而增加。例如,在上述情况下,valgrind 给出以下输出:

==10322== HEAP SUMMARY:
==10322==     in use at exit: 0 bytes in 0 blocks
==10322==   total heap usage: 10,011 allocs, 10,011 frees, 300,576 bytes allocated

我已经确认调用 Node() 构造函数的次数是恒定的。那么为什么以及如何分配和释放所有内存呢?

【问题讨论】:

  • 您正在创建大量地图。他们可能会在内部分配内存。

标签: c++ trie


【解决方案1】:

每次调用 insert 时,都会向它传递 const char[6],但它需要 const std::string&amp;,因此每次迭代都会创建一个临时的 std::string,然后将其传递给函数,并且然后在下一次迭代之前销毁。这澄清了 10000 个分配和解除分配,只剩下 11 个,这大概是您对节点的分配,以及 std::map 在内部所做的任何事情,以及我忽略的其他一些地方(例如字符串的副本或地图)

即使容器不包含任何元素,它也可以分配内存,但我认为它应该以其他方式设计,如果容器的任何主要实现都做了这样的事情,我会感到惊讶。 (虽然双端队列可能是个例外)

【讨论】:

    【解决方案2】:

    std::map 将动态分配自己的内存,每次调用get_child() 时都会创建一个新内存。使用默认构造函数时它分配了多少内存我不能说,但它可能是某事。仅仅因为你不调用new 并不意味着你的类创建的其他类型不调用。

    另外,std::map 不会为每个插入的元素分配一个全新的堆存储。那将是非常低效的。它有一些内部算法来在需要时增加其后备存储,并且它肯定会分配比适合该新元素所需的更多。

    【讨论】:

    • 您能否更彻底地确认一下?我只是通过迭代器遍历存储的std::map
    • @anupamsr 每当您调用Trie::Node::get_child() 时,您都会在堆栈上创建一个std::mapmap&lt;char, Node*&gt; children;
    • @bames53:但是分配是在堆上报告的。那是我的困惑。大量的 i 可以感觉到程序的缓慢。即使在删除该行之后,我仍然会收到相同数量的分配报告。
    • @anupamsr 在堆栈上创建std::map 可能会导致std::map 在堆上分配内存。
    • 如果std::map 在不包含任何元素的情况下分配了任何内容,我会感到惊讶。我敢打赌,这些分配中的每一个都是std::string
    猜你喜欢
    • 2011-12-27
    • 1970-01-01
    • 2010-10-10
    • 2015-05-01
    • 2015-10-11
    • 2020-10-05
    • 1970-01-01
    • 2020-02-13
    • 2013-05-09
    相关资源
    最近更新 更多