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

发布于 2024-07-14 02:53:24 字数 753 浏览 8 评论 0原文

我正在观察 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()调用其析构函数后仍然可以访问? 调用 my_map.clear() 后是否需要编写 delete a; 或者覆盖 a 的内容是否安全?

在此先感谢您的帮助, 斯内格

I am observing strange behaviour of std::map::clear(). This method is supposed to call element's destructor when called, however memory is still accessible after call to clear().

For example:

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;
}

The question is, why is variable a still accessible after its destructor was called by map::clear()? Do I need to write delete a; after calling my_map.clear() or is it safe to overwrite the contents of a?

Thanks in advance for your help,
sneg

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

帅的被狗咬 2024-07-21 02:53:24

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

哦,还有一件事:调用析构函数(甚至调用删除)并不意味着内存不能再被访问。 这仅意味着如果您这样做,您将访问垃圾。

If you store pointers on a map (or a list, or anything like that) YOU are the responsible for deleting the pointers, since the map doesn't know if they have been created with new, or not. The clear function only invokes destructors if you don't use pointers.

Oh, and one more thing: invoking a destructor (or even calling delete) doesn't mean the memory can't be accessed anymore. It only means that you will be accessing garbage if you do.

请叫√我孤独 2024-07-21 02:53:24

std::map 不管理指针值指向的内存 - 这取决于您自己。 如果你不想使用智能指针,你可以编写一个通用的 free & 指针。 像这样清除函数:

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 )

;

std::map does not manage the memory pointed to by the pointer values - it's up to you to do it yourself. If you don't want to use smart pointers, you can write a general purpose free & clear function like this:

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

And use it:

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

;

時窥 2024-07-21 02:53:24

这是因为 map.clear() 调用映射中包含的数据的析构函数,在您的例子中,是指向 a 的指针。 这没有任何作用。

您可能想要放置某种智能指针在map中让a占用的内存自动回收。

顺便说一句,为什么要将模板参数放在对 make_pair 的调用中? 模板参数推导在这里应该做得很好。

That's because map.clear() calls destructors of the data contained in the map, in your case, of the pointer to a. And this does nothing.

You might want to put some kind of smart pointer in the map for the memory occupied by a to be automatically reclaimed.

BTW, why do you put the template arguments in the call to make_pair? The template argument deduction should do pretty well here.

淑女气质 2024-07-21 02:53:24

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

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

当你用new分配内存时,你需要自己删除它,除非你使用智能指针。

When you free a piece of heap memory, its contents don't get zeroed. They are merely available for allocation again. Of course you should consider the memory non accessible, because the effects of accessing unallocated memory are undefined.

Actually preventing access to a memory page happens on a lower level, and std libraries don't do that.

When you allocate memory with new, you need to delete it yourself, unless you use a smart pointer.

耶耶耶 2024-07-21 02:53:24

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

__NodePtr
{
    *next;
    __Ty    Val;
}

当您分配它时,它是通过基于类型构造 val 然后链接来发生的。 类似于:

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

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

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

Any container stores your object Type and call corresponding constructors: internal code each node might look similar to:

__NodePtr
{
    *next;
    __Ty    Val;
}

When you allocate it happens by constructing the val based on type and then linking. Something similar to:

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

When you delete it calls corresponding destructors.

When you store references (pointers) it won't call any constructor nor it will destruct.

霊感 2024-07-21 02:53:24

在花费了过去两个月的饮食、睡眠和呼吸图之后,我有一个建议。 尽可能让地图分配它自己的数据。 它更加干净,正是出于您在这里强调的原因。

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

这允许您使用赋值运算符而不是 memcpy(),并且需要减少 1 次对 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. 在调试模式下,尝试 free() 内存时预计会遇到类似的问题。 这两个对我来说似乎都是编译器问题,但至少在 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,则不需要)
  7. 您可以使用 .first 和 .second 获取任何地图节点的键值和数据值,按照惯例,所有映射都使用它分别返回键和数据
  8. 为您自己节省大量的混乱和输入,并为您的映射使用 typedef,就像这样。

    typedef map   CDR_MAP;     
      typedef map   呼叫地图;    
      类型定义结构{ 
          CALL_MAP 呼叫映射; 
          ULNG结;          
          DBL BurnRateSec;  
          DBL 十美分;    
          ULLNG t然后;        
          DBL 旧 KCI 密钥;    
      } CUST_SUM, *pCUST_SUM; 
      typedef 映射   CUST_MAP,CUST_MAP;   
      typedef 映射   KCI_MAP; 
      
  9. 使用 typedef 和 & 传递对映射的引用; 运算符如

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

  10. 对迭代器使用“auto”变量类型。 编译器将从 for() 循环体的其余部分中指定的类型找出要使用哪种类型的映射 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) // 一些宏魔法 
    
      auto res = pSumStruct->callMap.insert(Pear(pCDR->iGUID,pCDR)); 
      if ( !res.second ) { 
          pCDR->RefKnt=2; 
      } 别的 { 
          pCDR->RefKnt=1; 
          pSumStruct->Knt += 1; 
      } 
      
  13. 使用指针挂在为自己分配所有内容的映射节点上,即:没有用户指针指向用户 malloc()ed 对象,效果很好,可能更高效,并且可以用于改变节点的数据而无需根据我的经验,副作用。

  14. 在同一主题上,这样的指针可以非常有效地用于保存节点的状态,如上面的 pThisCDRLeafData 所示。 将其传递给改变/更改特定节点数据的函数比传递对映射的引用以及返回到 pThisCDRLeafData 所指向的节点所需的键更干净。

  15. 迭代器并不神奇。 它们既昂贵又缓慢,因为您需要在地图上导航以获取值。 对于保存一百万个值的映射,您可以以每秒约 2000 万次的速度读取基于键的节点。 如果使用迭代器,速度可能会慢 1000 倍。

我认为现在已经涵盖了它。 如果有任何变化或有其他见解可以分享,我们将进行更新。 我特别喜欢使用 STL 和 C 代码。 IE:在任何地方都看不到一个类。 它们只是在我工作的环境中没有意义,但这不是问题。 祝你好运。

Having spent the last 2 months eating, sleeping, and breathing maps, I have a recommendation. Let the map allocate it's own data whenever possible. It's a lot cleaner, for exactly the kind of reasons you're highlighting here.

There are also some subtle advantages, like if you're copying data from a file or socket to the map's data, the data storage exists as soon as the node exists because when the map calls malloc() to allocate the node, it allocates memory for both the key and the data. (AKA map[key].first and map[key].second)

This allows you to use the assignment operator instead of memcpy(), and requires 1 less call to malloc() - the one you make.

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

A few caveats to be aware of are worth pointing out here.

  1. do NOT call malloc() or new in the line of code that adds the tree node as your call to malloc() will return a pointer BEFORE the map's call to malloc() has allocated a place to hold the return from your malloc().
  2. in Debug mode, expect to have similar problems when trying to free() your memory. Both of these seem like compiler problems to me, but at least in MSVC 2012, they exist and are a serious problem.
  3. give some thought as to where to "anchor" your maps. IE: where they are declared. You don't want them going out of scope by mistake. main{} is always safe.

    INT _tmain(INT argc, char* argv[])    {
    IC_CDR      CDR, *pThisCDRLeafData=NULL;
    CDR_MAP     cdrMap;
    CUST_MAP    custMap;
    KCI_MAP     kciMap;
    
  4. I've had very good luck, and am very happy having a critical map allocate a structure as it's node data, and having that struct "anchor" a map. While anonymous structs have been abandoned by C++ (a horrible, horrible decision that MUST be reversed), maps that are the 1st struct member work just like anonymous structs. Very slick and clean with zero size-effects. Passing a pointer to the leaf-owned struct, or a copy of the struct by value in a function call, both work very nicely. Highly recommended.

  5. you can trap the return values for .insert to determine if it found an existing node on that key, or created a new one. (see #12 for code) Using the subscript notation doesn't allow this. It might be better to settle on .insert and stick with it, especially because the [] notation doesn't work with multimaps. (it would make no sense to do so, as there isn't "a" key, but a series of keys with the same values in a multimap)
  6. you can, and should, also trap returns for .erase and .empty() (YES, it's annoying that some of these things are functions, and need the () and some, like .erase, don't)
  7. you can get both the key value and the data value for any map node using .first and .second, which all maps, by convention, use to return the key and data respectively
  8. save yourself a HUGE amount of confusion and typing, and use typedefs for your maps, like so.

    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. pass references to maps using the typedef and & operator as in

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

  10. use the "auto" variable type for iterators. The compiler will figure out from the type specified in the rest of the for() loop body what kind of map typedef to use. It's so clean it's almost magic!

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

  11. define some manifest constants to make the return from .erase and .empty() more meaningfull.

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

  12. given that "smart pointers" are really just keeping a reference count, remember you can always keep your own reference count, an probably in a cleaner, and more obvious way. Combining this with #5 and #10 above, you can write some nice clean code like this.

    #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. using a pointer to hang onto a map node which allocates everything for itself, IE: no user pointers pointing to user malloc()ed objects, works well, is potentially more efficient, and and be used to mutate a node's data without side-effects in my experience.

  14. on the same theme, such a pointer can be used very effectively to preserve the state of a node, as in pThisCDRLeafData above. Passing this to a function that mutates/changes that particular node's data is cleaner than passing a reference to the map and the key needed to get back to the node pThisCDRLeafData is pointing to.

  15. iterators are not magic. They are expensive and slow, as you are navigating the map to get values. For a map holding a million values, you can read a node based on a key at about 20 million per second. With iterators it's probably ~ 1000 times as slow.

I think that about covers it for now. Will update if any of this changes or there's additional insights to share. I am especially enjoying using the STL with C code. IE: not a class in sight anywhere. They just don't make sense in the context I'm working in, and it's not an issue. Good luck.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文