为什么调用 std::map::clear() 后内存仍然可以访问?
我正在观察 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
如果您将指针存储在地图(或列表或类似的东西)上,您负责删除指针,因为地图不知道它们是否是用 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.
std::map 不管理指针值指向的内存 - 这取决于您自己。 如果你不想使用智能指针,你可以编写一个通用的 free & 指针。 像这样清除函数:
并使用它:
;
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:
And use it:
;
这是因为
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 toa
. 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.当您释放一块堆内存时,其内容不会被清零。 它们仅可再次分配。 当然,您应该考虑内存不可访问,因为访问未分配内存的影响是未定义的。
实际上,阻止访问内存页面发生在较低级别,而标准库不会这样做。
当你用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.
任何容器都存储您的对象类型并调用相应的构造函数:每个节点的内部代码可能类似于:
当您分配它时,它是通过基于类型构造 val 然后链接来发生的。 类似于:
当您删除时,它会调用相应的析构函数。
当您存储引用(指针)时,它不会调用任何构造函数,也不会析构。
Any container stores your object Type and call corresponding constructors: internal code each node might look similar to:
When you allocate it happens by constructing the val based on type and then linking. Something similar to:
When you delete it calls corresponding destructors.
When you store references (pointers) it won't call any constructor nor it will destruct.
在花费了过去两个月的饮食、睡眠和呼吸图之后,我有一个建议。 尽可能让地图分配它自己的数据。 它更加干净,正是出于您在这里强调的原因。
还有一些微妙的优点,例如,如果您将数据从文件或套接字复制到映射的数据,则只要节点存在,数据存储就会存在,因为当映射调用 malloc() 分配节点时,它会分配内存对于密钥和数据。 (又名 map[key].first 和 map[key].second)
这允许您使用赋值运算符而不是 memcpy(),并且需要减少 1 次对 malloc() 的调用 - 您所做的调用。
这里有一些需要注意的注意事项值得指出。
考虑一下在哪里“锚定”你的地图。 IE:声明它们的地方。 您不希望它们错误地超出范围。 main{} 始终是安全的。
我的运气非常好,并且很高兴让关键映射分配一个结构作为节点数据,并让该结构“锚定”一个映射。 虽然匿名结构已被 C++ 放弃(这是一个非常非常可怕的决定,必须逆转),但作为第一个结构成员的映射的工作方式就像匿名结构一样。 非常光滑、干净,尺寸效应为零。 在函数调用中传递指向叶拥有的结构的指针或按值传递结构的副本,两者都可以很好地工作。 强烈推荐。
为您自己节省大量的混乱和输入,并为您的映射使用 typedef,就像这样。
使用 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 结合起来,您可以编写一些像这样干净的代码。
使用指针挂在为自己分配所有内容的映射节点上,即:没有用户指针指向用户 malloc()ed 对象,效果很好,可能更高效,并且可以用于改变节点的数据而无需根据我的经验,副作用。
在同一主题上,这样的指针可以非常有效地用于保存节点的状态,如上面的
pThisCDRLeafData
所示。 将其传递给改变/更改特定节点数据的函数比传递对映射的引用以及返回到 pThisCDRLeafData 所指向的节点所需的键更干净。迭代器并不神奇。 它们既昂贵又缓慢,因为您需要在地图上导航以获取值。 对于保存一百万个值的映射,您可以以每秒约 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.
A few caveats to be aware of are worth pointing out here.
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.
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.
save yourself a HUGE amount of confusion and typing, and use typedefs for your maps, like so.
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.
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.
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.