我有一个指向给定类的指针。 比方说,例如,指针是:
0x24083094
该指针指向:
0x03ac9184
这是我class的虚拟function表。 这对我有意义。 在windbg,一切看起来是正确的。
我删除了所说的指针。 现在在0x24083094
是:
0x604751f8
但是不是一些随意的垃圾,那个地址每次都放在那里,一直是0x604751f8
! 所以我可以真正使用这个地址来确定这个指针是否被删除,在我的应用程序执行之间!
但为什么? 如何确定0x604751f8
应该写在那里?
为了logging,我正在使用windows,在visual studio 2003下编译。
我知道我不能依赖这个价值,即使它看起来是一致的,但我可以依靠它是不同的吗? 即,如果指针被删除, 0x03ac9184
将不会在0x24083094
,对吧? 放什么? 它可能是任何东西,但0x03ac9184
肯定不会在那里(否则我仍然可以调用方法,因为这是虚函数表)。 我对吗?
我觉得我有答案。 删除后不能依赖任何东西。 也许有些背景会帮助人们看到我来自哪里。 本质上,我试图修复一个指针从我下面被删除的错误。 这是一个很长的故事,我不会详细介绍。
基本上,我试图检测到我处于这种情况,所以我可以从我的function优雅地退出。 我想最简单也是最好的方法就是找出谁拥有这个指针,并问他是否有任何改变。 所以我要实现这样的修复。 它避免了我正在讨论的任何这种C ++删除黑客。
然而 ,有趣的是,在我们的代码中,我们有一个名为“BogusObject”的类,它本质上就像一个抓取释放对象的托盘。 基本上,我们挂钩我们自己的删除函数,并将BogusObject类打到任何已经释放的类的vtable中。
那么如果有人打电话给他们,他们会得到一个很好的信息,说“嘿,有什么不对的。” 这发生在我的情况。 即, 0x604751f8+(someoffset)
在BogusObject类中。 但是我们不再使用BogusObject! 它的字面意思是没有设置任何地方(甚至链接正确,如果我完全删除BogusObject类),但我仍然最终得到好消息说有什么不对劲! 但我现在认为这是巧合。
由于某种原因,运行时在被删除时将这个0x604751f8
值放在这个指针中,而恰好碰巧与这个类有一个类似的情况。
标准中没有什么决定写在那里的东西。 Visual Studio(至少在调试模式下)通常会在整个地方编写寄件值,以帮助早日捕捉错误。
这个值不是你可以依赖的,但是如果你发现在你的程序中神秘地弹出了这个值,你可以假定你正在引用被删除的内存。 在一个编译器下看到这个答案的值的列表。
这也是完全可能的,它是一个空闲的列表指针,指向下一块空闲的内存。 大多数内存分配器使用正在跟踪的空闲内存来存储跟踪数据,从而将它们的空闲内存保存在链接列表中。
在任何情况下,除非你调用microsoft并获得一些文档说明为什么这个值是什么,并且保证它不会改变,否则你不能使用那个指针的值来继续工作。 即使如此,知道你的代码现在被绑定到一个编译器的行为。 在C ++中,访问未分配的内存是不确定的,也是邪恶的。
编辑:你甚至不能依靠删除后改变的值。 没有什么说编译器需要修改删除的数据。
一个你删除一个对象,它使用的内存被放回到免费商店(堆)。 当然,免费商店将拥有自己的数据结构(可能还有调试数据),它将应用于该内存。
你所看到的特殊价值是什么意思? 几乎可以做任何事情。
只要调用指针的删除操作符并不意味着“已删除”的内存将被清除。 它只会调用已删除对象的析构函数,并将已分配的堆内存标记为已释放。 (这是删除操作符的默认行为)。
如果在删除时需要清除内存内容,则需要覆盖删除操作符。
正如Josh所说,可以插入一些值,使调试器的调试生成变得更容易。 这些是编译器特有的,决不能依赖。 在发布版本中,我相信大多数C ++编译器的默认行为是对被释放的内存不做任何事情,所以,直到再次分配地址空间时,内容将基本上成为前所未有的内容,当然,你永远不要依靠这个。
几乎可以肯定,每次删除对象时都会出现一个特定的内部含义。
但是,它可能会随着Visual C ++的下一个版本而改变,并且在其他供应商编译器上肯定会有所不同。
每次删除一个对象似乎都是一样的事实并不意味着任何有用的东西 。 它甚至不可能有用。 假如你找到了一些方法来利用它,这将是一个令人震惊的黑客,你最后会后悔的。
试着把它从你的脑海里抹去!
正如Michael Burr早些时候所说,记忆可以追溯到免费的商店。 一些免费的商店被实现为链接列表, – >下一个指针放置在空闲缓冲区的开始。 有可能你看到的幻数(0x604751f8)是'列表的尾数'后卫。 您可以通过执行以下实验来检查:
Foo* f = new Foo(); Bar* b = new Bar(); // make a note of the values of f and b _pointers_ delete b; // check that b points now to 0x604751f8 delete f; // check that f points now to 0x604751f8 // now check that does b point to; it might point to f!
让我们知道你发现了什么!
这个指针值最可能是基类的vtable。 当一个派生类的析构函数运行完后,它完成了它的正常体,它将“重写”内存作为基本类型(基本上,将基类的vtable指针写入对象),然后调用基类的析构函数。
请注意,此行为是编译器运行时C ++支持的内部实现细节,所以其他编译器(或同一编译器的未来版本)可能会做一些完全不同的事情。 但是,“将vtable转换为基类并调用基类析构函数”是相当常见的,可以追溯到C ++的原始cfront实现
不,你不能依靠它被设置。 你甚至不能依靠它是不同的。
MS-DOS堆管理器经常允许使用释放的内存,直到下一次调用malloc。 在那个时代新增和删除了malloc和free。
现在,大多数堆管理员都是合理的回到操作系统,这意味着你甚至不能依赖它的可读性! 即使有一个仍然允许它(glibc有一个bwd-compat模式,允许它),你受到线程竞争的条件。
另外,如果它是一个左值,则允许删除将指针改为NULL。
一旦你调用delete,甚至不要考虑解引用指针。
该方案正试图告诉你的东西。 一个日期,一个电话号码,谁知道?
现在认真,这是没有指定 ,完全实现依赖 ,当然试图取消引用delete
后会导致未定义的行为。 所以,总之, 谁在乎呢?
您可以访问Visual Studio中的CRT源代码。 你可以看看。 我曾经有一次更好地了解我有一个错误。