我正在大学里学习C++中的指针.我编写了一个程序,它是一个二进制对象树,指向一个链接的子对象列表.如果我甚至是正确的措辞.无论如何,我的程序似乎工作正常,但我无法绕过如何测试指针删除.
例如,我对二叉树的单个对象的删除函数是:
void EmployeeRecord::destroyCustomerList()
{
if(m_oCustomerList != NULL)
{
delete m_oCustomerList;
m_oCustomerList = NULL;
}
}
Run Code Online (Sandbox Code Playgroud)
打印我的树时,所有内容都会填充并正确取下(意味着每次删除节点时树都保持不变)...但是如何确认解除分配的内存会发生什么?我知道,因为我将指针*m_oCustomerList设置为NULL,我可以测试先前填充的对象上的NULL值,但实际内存会发生什么?
我正在使用Visual Studio/C++并且已经读过调试器将使用从0xCC开始的代码来释放内存......但我似乎无法弄清楚如何使用该信息.
请注意您的代码
void EmployeeRecord::destroyCustomerList()
{
if(m_oCustomerList != NULL)
{
delete m_oCustomerList;
m_oCustomerList = NULL;
}
}
Run Code Online (Sandbox Code Playgroud)
简化为:
void EmployeeRecord::destroyCustomerList()
{
delete m_oCustomerList;
m_oCustomerList = NULL;
}
Run Code Online (Sandbox Code Playgroud)
delete在C++中对空指针调用操作符是安全的.它什么都不做.换句话说,检查null已经"内置".
一旦删除了一个对象,它就不再存在,并且指向该对象的指针变为不确定值(因此将该指针的所有副本都清零并不是一个坏主意).
什么真的发生了内存在实际的C++实现,而不是抽象的意义上,是继续在同一地址存在,但被标记为免费的,所以它可以被分配用于其他用途.来自程序(可能是完全不相关的模块)或可能来自系统中的另一个程序的分配请求可以获得该存储器供其自己使用.
指向不再存在的对象的指针的任何使用都是"未定义的行为".用于安全验证这种指针的函数确实存在,但它们非常特定于平台并且很少完美.
问题是,虽然实现确认指针是坏的并不是特别困难,但是不可能确认指针是好的.我们可以遍历内存分配器的内部存储器数据结构,以确定某些指针指的是空闲存储.但是如果随后分配了存储怎么办?然后指针不再指免费存储.但它也没有引用分配的原始对象!这被称为"ABA歧义":因为一些A变成了B,但后来变成了A,与原来的A无法区分.
存在解决ABA模糊性的方法(如果不完全至少部分地).例如,指针变为"胖",因此除了地址位之外,它们还有一个额外的字段.该字段可以包含序列号,用于标记从分配器返回的指针.现在,当删除并重新分配对象时,指向同一位置的新指针具有不同的序列号:我们有ABA'.指针A变坏了,使它成为B,但是当它复活时,它又变回A'.如果我们要求系统验证A,它将正确地确定A是坏的,因为它没有预期的序列号.指向对象的正确有效指针是A',它与A不匹配.
但是,序列号字段只有很多位宽,它们最终会回绕.所以ABA问题还没有真正解决.良好指针和坏指针的验证只能更加可靠.为了绝对处理ABA问题,系统必须总是发出新指针,这些指针不同于任何仍然可以使用的指针.这意味着永远不会释放任何东西(从而耗尽内存)或实现垃圾收集.(意思是delete实际上什么都不做:已删除的对象被破坏,但是在内存中一直存在,直到它们被垃圾收集,这在程序不再记住指针的任何副本时发生.此时,程序不再记住A,并且所以A可以重新引入,并且没有ABA问题.)
要使所有指针"变胖",您必须更改整个工具链和运行时:编译器,库等等.还有一些困难,因为大型程序往往有多个内存分配器.如果你问错误的分配器"这个指针是否有效",它可以说是"这个指针不是来自我的竞技场".您可以做的另一种方法是创建自己的指针,并在C++中将它们实现为智能指针.您的指针可以支持is_valid尽可能可靠的方法(以某种方式处理ABA问题:部分地使用某些序列号等,或者通过实现您自己的垃圾收集方案.)