为什么在调用std :: map :: clear()之后仍然可以访问内存?

时间:2009-02-20 10:51:36

标签: c++ stdmap

我正在观察std :: map :: clear()的奇怪行为。这个方法应该在调用时调用元素的析构函数,但是在调用clear()之后仍然可以访问内存。

例如:

struct A
{
  ~A() { x = 0; }
  int x;
};

int main( void )
{
  std::map< int, A * > my_map;
  A *a = new A();
  a->x = 5;
  my_map.insert( std::make_pair< int, *A >( 0, a ) );

  // addresses will be the same, will print 5
  std::cout << a << " " << my_map[0] << " " << my_map[0]->x << std::endl;

  my_map.clear();

  // will be 0
  std::cout << a->x << std::endl;

  return 0;
}

问题是,为什么变量a在map :: clear()调用析构函数后仍然可以访问?在致电delete a;后我是否需要写my_map.clear()或覆盖a的内容是否安全?

先谢谢你的帮助, SNEG

6 个答案:

答案 0 :(得分:22)

如果您将指针存储在地图(或列表或类似内容)上, YOU 负责删除指针,因为地图不知道它们是否已使用new创建, 或不。如果你不使用指针,clear函数只调用析构函数。

哦,还有一件事:调用析构函数(甚至调用delete)并不意味着无法再访问内存。它只意味着你将访问垃圾。

答案 1 :(得分:20)

std :: map不管理指针值指向的内存 - 由你自己完成。如果你不想使用智能指针,你可以写一个通用的免费&amp;明确这样的功能:

template <typename M> void FreeClear( M & amap ) 
    for ( typename M::iterator it = amap.begin(); it != amap.end(); ++it ) {
        delete it->second;
    }
    amap.clear();
}

并使用它:

std::map< int, A * > my_map;
// populate
FreeClear( my_map )

答案 2 :(得分:4)

这是因为map.clear()调用地图中包含的数据的析构函数,在您的情况下,指向a的指针。这没有任何作用。

您可能希望在地图中放置某种smart pointer,以便自动回收a占用的内存。

BTW,为什么要将模板参数放入make_pair的调用中?模板参数推导在这里应该做得很好。

答案 3 :(得分:1)

当你释放一块堆内存时,它的内容不会被归零。它们仅可再次分配。当然你应该考虑内存不可访问,因为访问未分配内存的影响是未定义的。

实际上阻止访问内存页面发生在较低级别,而std库不会这样做。

使用new分配内存时,除非使用智能指针,否则需要自行删除内存。

答案 4 :(得分:0)

任何容器都存储您的对象Type并调用相应的构造函数:每个节点的内部代码可能类似于:

__NodePtr
{
    *next;
    __Ty    Val;
}

当你通过构造基于类型的val然后链接来分配它时。类似于:

_Ty _Val = _Ty();
_Myhead = _Buynode();
_Construct_n(_Count, _Val);

当你删除它时,调用相应的析构函数。

当你存储引用(指针)时,它不会调用任何构造函数,也不会破坏它。

答案 5 :(得分:0)

我花了最近两个月吃饭,睡觉和呼吸地图,我有一个建议。让地图尽可能地分配它自己的数据。由于你在这里强调的原因,它更清洁了。

还有一些微妙的优点,例如,如果您将数据从文件或套接字复制到地图的数据,则一旦节点存在,数据存储就会存在,因为当地图调用malloc()来分配节点时,它为密钥和数据分配内存。 (AKA map [key] .first and map [key] .second)

这允许你使用赋值运算符而不是memcpy(),并且需要少量调用malloc() - 你所做的那个。

IC_CDR CDR, *pThisCDRLeafData;  // a large struct{}

    while(1 == fread(CDR, sizeof(CDR), 1, fp))  {
    if(feof(fp)) {
        printf("\nfread() failure in %s at line %i", __FILE__, __LINE__);
    }
    cdrMap[CDR.iGUID] = CDR; // no need for a malloc() and memcpy() here    
    pThisCDRLeafData  = &cdrMap[CDR.iGUID]; // pointer to tree node's data

需要注意的一些注意事项值得指出。

  1. 不要在添加树节点的代码行中调用malloc()或new,因为对malloc()的调用会在地图调用malloc()之前返回一个指针,以便保存来自你的返回的malloc()。
  2. 在调试模式下,在尝试释放()内存时会遇到类似的问题。这两个看起来像编译器问题,但至少在MSVC 2012中,它们存在并且是一个严重的问题。
  3. 考虑一下“锚定”地图的位置。 IE:声明它们的位置。你不希望它们错误地超出范围。 main {}总是安全的。

    INT _tmain(INT argc, char* argv[])    {
    IC_CDR      CDR, *pThisCDRLeafData=NULL;
    CDR_MAP     cdrMap;
    CUST_MAP    custMap;
    KCI_MAP     kciMap;
    
  4. 我非常幸运,我很高兴有一个关键地图分配一个结构作为它的节点数据,并让该结构“锚定”一个地图。虽然C ++已经抛弃了匿名结构(一个可怕的,可怕的决定必须颠倒),但是作为第一结构成员的地图就像匿名结构一样。非常光滑,干净,零尺寸效果。在函数调用中通过值传递指向叶拥有的结构或结构的副本,两者都可以很好地工作。强烈推荐。

  5. 您可以捕获.insert的返回值,以确定它是否在该键上找到了现有节点,或者创建了一个新节点。 (代码见#12)使用下标符号不允许这样做。最好选择.insert并坚持使用它,特别是因为[]表示法不适用于多图。 (这样做没有任何意义,因为没有“a”键,而是多图中具有相同值的一系列键)
  6. 你可以而且也应该为.erase和.empty()捕获返回(是的,令人讨厌的是,其中一些是函数,需要()和一些,如.erase,不要)< / LI>
  7. 您可以使用.first和.second获取任何地图节点的键值和数据值,按惯例,所有地图都用于分别返回键和数据
  8. 为您节省大量的混淆和打字,并为您的地图使用typedef,就像这样。

    typedef map<ULLNG, IC_CDR>      CDR_MAP;    
    typedef map<ULLNG, pIC_CDR>     CALL_MAP;   
    typedef struct {
        CALL_MAP    callMap;
        ULNG        Knt;         
        DBL         BurnRateSec; 
        DBL         DeciCents;   
        ULLNG       tThen;       
        DBL         OldKCIKey;   
    } CUST_SUM, *pCUST_SUM;
    typedef map<ULNG,CUST_SUM>  CUST_MAP, CUST_MAP;  
    typedef map<DBL,pCUST_SUM>  KCI_MAP;
    
  9. 使用typedef和&amp;传递对地图的引用运算符在

    ULNG DestroyCustomer_callMap(CUST_SUM Summary, CDR_MAP& cdrMap, KCI_MAP& kciMap)

  10. 对迭代器使用“auto”变量类型。编译器将从for()循环体的其余部分中指定的类型中找出要使用的map typedef类型。它太干净了,几乎是神奇的!

    for(auto itr = Summary.callMap.begin(); itr!= Summary.callMap.end(); ++itr) {

  11. 定义一些清单常量,使得.erase和.empty()的返回更有意义。

    if(ERASE_SUCCESSFUL == cdrMap.erase (itr->second->iGUID)) {

  12. 鉴于“智能指针”实际上只是保持引用计数,请记住,您可以始终保持自己的引用计数,可能是更清晰,更明显的方式。将它与上面的#5和#10相结合,你可以编写一些很好的干净代码。

    #define Pear(x,y) std::make_pair(x,y) //  some macro magic
    
    auto res = pSumStruct->callMap.insert(Pear(pCDR->iGUID,pCDR));
    if ( ! res.second ) {
        pCDR->RefKnt=2;
    } else {
        pCDR->RefKnt=1;
        pSumStruct->Knt += 1;
    }
    
  13. 使用指针挂起到为自己分配所有东西的地图节点,IE:没有指向用户malloc()ed对象的用户指针,效果很好,可能更有效,并且可以用来变异根据我的经验,节点的数据没有副作用。

  14. 在同一主题上,这样的指针可以非常有效地用于保存节点的状态,如上面的pThisCDRLeafData所示。将此传递给一个函数,该函数改变/更改特定节点的数据比传递对映射的引用更清晰,并且返回节点pThisCDRLeafData所需的密钥指向。

  15. 迭代器不是魔术。它们既昂贵又缓慢,因为您在导航地图以获取值。对于包含百万个值的地图,您可以基于密钥以每秒约2000万个读取节点。使用迭代器,它可能慢约1000倍。

  16. 我认为现在就涵盖它。如果有任何变化或者有其他分享见解,将会更新。我特别喜欢使用带有C代码的STL。 IE:不是任何地方的视野。它们只是在我工作的环境中没有意义,而且这不是问题。祝好运。