C++ 为什么在调用 std::map::clear() 后仍然可以访问内存?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/569110/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why is memory still accessible after std::map::clear() is called?
提问by sneg
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().
我正在观察 std::map::clear() 的奇怪行为。这个方法应该在调用时调用元素的析构函数,但是在调用 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
?
问题是,为什么变量a
在 map::clear() 调用其析构函数后仍然可以访问?我需要delete a;
在调用后写入my_map.clear()
还是覆盖 的内容是否安全a
?
Thanks in advance for your help, sneg
预先感谢您的帮助,sneg
回答by Marc
If you store pointers on a map (or a list, or anything like that) YOUare 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.
如果您将指针存储在地图(或列表或类似的东西)上,则您有责任删除这些指针,因为地图不知道它们是否是用 new 创建的。如果不使用指针, clear 函数只会调用析构函数。
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.
哦,还有一件事:调用析构函数(甚至调用 delete)并不意味着不能再访问内存。这仅意味着如果您这样做,您将访问垃圾。
回答by Marc
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:
std::map 不管理指针值指向的内存 - 由您自己决定。如果您不想使用智能指针,您可以编写一个通用的 free & clear 函数,如下所示:
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 )
;
;
回答by jpalecek
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.
那是因为map.clear()
调用映射中包含的数据的析构函数,在您的情况下,是指向a
. 而这没有任何作用。
You might want to put some kind of smart pointerin the map for the memory occupied by a
to be automatically reclaimed.
您可能希望在地图中放置某种智能指针,a
以便自动回收所占用的内存。
BTW, why do you put the template arguments in the call to make_pair
? The template argument deduction should do pretty well here.
顺便说一句,你为什么把模板参数放在调用中make_pair
?模板参数推导在这里应该做得很好。
回答by John Smith
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.
用new分配内存时,需要自己删除,除非使用智能指针。
回答by FL4SOF
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:
当您通过基于类型构造 val 然后链接来分配它时。类似于:
_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.
当您存储引用(指针)时,它不会调用任何构造函数,也不会破坏。
回答by user2548100
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.
在过去的 2 个月里,吃、睡和呼吸地图,我有一个建议。尽可能让地图分配它自己的数据。正是出于您在此处强调的那种原因,它更干净了。
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)
还有一些微妙的优势,比如如果你将数据从文件或套接字复制到映射的数据,只要节点存在,数据存储就存在,因为当映射调用 malloc() 来分配节点时,它会分配内存对于密钥和数据。(又名 map[key].first 和 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.
这允许您使用赋值运算符而不是 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
A few caveats to be aware of are worth pointing out here.
需要注意的一些注意事项值得在此指出。
- 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().
- 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.
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;
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.
- 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)
- 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)
- 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
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;
pass references to maps using the typedef and & operator as in
ULNG DestroyCustomer_callMap(CUST_SUM Summary, CDR_MAP& cdrMap, KCI_MAP& kciMap)
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) {
define some manifest constants to make the return from .erase and .empty() more meaningfull.
if(ERASE_SUCCESSFUL == cdrMap.erase (itr->second->iGUID)) {
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; }
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.
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 nodepThisCDRLeafData
is pointing to.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.
- 不要在添加树节点的代码行中调用 malloc() 或 new,因为您对 malloc() 的调用将在映射对 malloc() 的调用分配了一个位置来保存 malloc() 的返回值之前返回一个指针.
- 在调试模式下,在尝试 free() 内存时可能会遇到类似的问题。这两个对我来说似乎都是编译器问题,但至少在 MSVC 2012 中,它们存在并且是一个严重的问题。
考虑在哪里“锚定”您的地图。IE:它们被声明的地方。您不希望它们错误地超出范围。main{} 始终是安全的。
INT _tmain(INT argc, char* argv[]) { IC_CDR CDR, *pThisCDRLeafData=NULL; CDR_MAP cdrMap; CUST_MAP custMap; KCI_MAP kciMap;
我运气很好,很高兴有一个关键映射分配一个结构作为节点数据,并让该结构“锚定”一个映射。虽然匿名结构已被 C++ 放弃(一个可怕的、可怕的决定,必须扭转),作为第一个结构成员的映射就像匿名结构一样工作。非常光滑和干净,零尺寸效应。在函数调用中传递指向叶子拥有的结构的指针,或传递结构的副本,这两种方法都非常有效。强烈推荐。
- 您可以捕获 .insert 的返回值以确定它是否在该键上找到了现有节点,或者创建了一个新节点。(有关代码,请参阅#12)使用下标符号不允许这样做。最好选择 .insert 并坚持使用它,特别是因为 [] 表示法不适用于多重映射。(这样做没有意义,因为在多映射中没有“a”键,而是一系列具有相同值的键)
- 你可以而且应该为 .erase 和 .empty() 捕获返回值(是的,这些东西中的一些是函数,并且需要 () 和一些,比如 .erase,不需要),这很烦人)
- 您可以使用 .first 和 .second 获取任何地图节点的键值和数据值,按照惯例,所有地图都使用它们分别返回键和数据
为自己节省大量的混乱和打字,并为您的地图使用 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;
使用 typedef 和 & 运算符传递对映射的引用,如
ULNG DestroyCustomer_callMap(CUST_SUM Summary, CDR_MAP& cdrMap, KCI_MAP& kciMap)
对迭代器使用“auto”变量类型。编译器将根据 for() 循环体其余部分中指定的类型确定要使用哪种类型的映射 typedef。它是如此干净,几乎是魔术!
for(auto itr = Summary.callMap.begin(); itr!= Summary.callMap.end(); ++itr) {
定义一些清单常量以使 .erase 和 .empty() 的返回更有意义。
if(ERASE_SUCCESSFUL == cdrMap.erase (itr->second->iGUID)) {
鉴于“智能指针”实际上只是保持引用计数,请记住您始终可以保持自己的引用计数,这可能是一种更清晰、更明显的方式。将此与上面的#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; }
使用指针挂在为自己分配所有内容的映射节点上,IE:没有指向用户 malloc()ed 对象的用户指针,运行良好,可能更高效,并且用于改变节点的数据而没有副作用在我的经验中。
在同一主题上,可以非常有效地使用这样的指针来保存节点的状态,如上
pThisCDRLeafData
所示。将其传递给改变/更改特定节点数据的函数比传递对映射的引用和返回节点所需的键所pThisCDRLeafData
指向的引用更清晰。迭代器不是魔术。它们既昂贵又缓慢,因为您正在导航地图以获取值。对于包含一百万个值的地图,您可以每秒大约 2000 万次读取基于键的节点。使用迭代器可能会慢 1000 倍。
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.
我认为现在已经涵盖了它。如果有任何变化或有其他见解要分享,我们会更新。我特别喜欢在 C 代码中使用 STL。IE:在任何地方都看不到一个类。它们只是在我工作的上下文中没有意义,这不是问题。祝你好运。