我有一个指向给定类的指针。可以说,例如,指针是:
0x24083094
指针指向:
0x03ac9184
这是我班级的虚拟功能表。这对我来说很有意义。在windbg中,一切看起来都是正确的。
我删除了指针。现在0x24083094
是:
0x604751f8
但它不是一些随机的垃圾,每次都会把地址放在那里,它始终是0x604751f8
!这么多,以便我可以实际使用该地址来确定是否删除了我的应用程序执行之间的指针!
但为什么?它如何确定应该在那里写0x604751f8
?
为了记录,我正在使用Windows,在visual studio 2003下构建。
我知道我不能依赖于设定的值,即使它确实看起来是一致的,但我可以依赖它不同吗?即,如果指针被删除,0x03ac9184
将不在0x24083094
,对吗?什么放在那里?它可能是任何东西,但0x03ac9184
肯定不会存在(或者我仍然可以调用方法,因为那是虚函数表)。我是对的吗?
我觉得我有答案。删除后不能依赖任何东西。也许一些背景会帮助人们看到我来自哪里。从本质上讲,我正在尝试修复一个错误,指针从我身下被删除。这是一个很长的故事,我不会详细介绍。
基本上,我试图检测到我处于这种情况,所以我可以优雅地退出我的功能。我想最简单和最好的方法是弄清楚谁实际拥有这个指针,然后问他是否有任何改变。所以我要实现这样的修复。它避免了我正在讨论的任何这个C ++删除黑客。
然而,有趣的是,在我们的代码中,我们有一个名为'BogusObject'的类,它基本上充当一个托盘,捕捉那些意外取消引用释放对象的人。基本上,我们挂钩我们自己的删除函数,并将BogusObject类bash到任何释放类的vtable中。
然后,如果有人打电话给他们,他们会收到一条好消息说“嘿,有些事情是错的。”。这种情况发生在我的情况下。即,0x604751f8+(someoffset)
位于BogusObject类中。 但是我们不再使用BogusObject了!它实际上并没有在任何地方设置(如果我完全删除了BogusObject类,甚至链接都正确),但我仍然得到一条好消息说错了!但我现在认为这是巧合。
由于某种原因,运行时在删除它时会在该指针中放置0x604751f8
值,而这恰好与一个有类似这样的情况的类相对应!
答案 0 :(得分:16)
标准中没有任何内容可以确定写入的内容。 Visual Studio(至少在调试模式下)通常会在整个地方编写sentinal值,以帮助尽早捕获bug。
这个值不是你可以依赖的东西,但是如果你发现这个值神秘地出现在你的程序中,你可以假设你正在引用被删除的内存。有关一个编译器下的值列表,请参阅this answer。
它也完全有可能是一个自由列表指针,指向下一段可用内存。大多数内存分配器使用他们跟踪的可用内存将各自的内存保存在各种链接列表中,以存储跟踪数据。
在任何情况下,你都不能将指针值用于你想继续工作的任何东西,除非你调用microsoft并获得一些文档说明为什么它的价值是什么,并让它们保证它不会改变。即便如此,要知道您的代码现在与一个编译器的行为联系在一起。在C ++中,访问未分配的内存是不确定的和邪恶的。
编辑: 删除后,您甚至无法依赖该值更改。没有任何说明编译器需要修改删除数据。
答案 1 :(得分:4)
您删除对象时,正在使用的内存将被放回到免费存储(堆)中。当然,免费商店将拥有自己的数据结构(可能还有调试数据),它将应用于该内存。
您正在寻找的具体价值意味着什么?几乎可以是任何事情。
答案 2 :(得分:2)
只是为指针调用delete运算符并不意味着将清除“已删除”的内存。它只会调用已删除对象的析构函数,并将已分配的堆内存标记为已释放。 (这是删除操作符的默认行为)。
如果您需要在删除时清除内存内容,则需要覆盖删除操作符。
答案 3 :(得分:2)
正如Josh所说,可以插入许多值,以便使调试版本的调试器更轻松。这些是特定于编译器的,绝不可依赖。在发布版本中,我相信大多数C ++编译器的默认行为是对释放的内存不做任何事情,因此,在再次分配该地址空间之前,内容基本上就是之前的内容,当然,你不应该依赖于此。
答案 4 :(得分:2)
每次删除对象时,几乎可以肯定有一个特定的内部含义。
但是,它可能会随着下一版本的Visual C ++而改变,并且在其他供应商的编译器中肯定会有所不同。
每次删除对象时它看起来都是相同的这一事实并不代表任何有用的。它甚至也不具备潜在的用途。假设你找到了一些方法来利用它,这将是一个令人震惊的黑客攻击,你最终会后悔。
尽量忘记它!
答案 5 :(得分:2)
正如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!
让我们知道您的发现!
答案 6 :(得分:2)
这个指针值很可能是基类的vtable。当派生类的析构函数运行时,在完成其正常体之后,它将内存“重新”作为基类型(基本上,将基类vtable指针写入对象),然后调用基类析构函数。
请注意,此行为是编译器的运行时C ++支持的内部实现细节,因此其他编译器(或同一编译器的未来版本)可能会执行完全不同的操作。但是这个'将vtable转换为基类并调用基类析构函数'是相当常见的,并且可以追溯到C ++的原始cfront实现
答案 7 :(得分:2)
该计划试图告诉你一些事情。日期,电话号码,谁知道?
现在认真,这是未指定,完全依赖于实现,当然在delete
之后尝试取消引用该指针会导致未定义的行为。那么,简而言之,谁在乎呢?
答案 8 :(得分:2)
不,你不能依赖它被设置。你甚至不能依赖它与众不同。
MS-DOS堆管理器经常允许在下次调用malloc之前使用释放的内存。那个时代的新的和删除叫做malloc和free。
现在,大多数堆管理器都很合理地将内存返回给操作系统,这意味着你甚至不能依赖它的可读性!即使是一个仍然允许它的人(glibc有一个允许它的bwd-compat模式)你也会遇到线程竞争条件。
此外,如果指针是左值,则允许删除将指针更改为NULL。
一旦调用delete,就不要考虑取消引用指针。
答案 9 :(得分:0)
您可以在Visual Studio中访问CRT源代码。你可以看看。我曾经做过一次,以便更好地理解我遇到的错误。