0xDEADBEEF vs. NULL

0xDEADBEEF vs. NULL

本文关键字:NULL vs 0xDEADBEEF      更新时间:2023-10-16

在各种代码中,我看到了NULL调试构建中的内存分配…

memset(ptr,NULL,size);

或与0xDEADBEEF

memset(ptr,0xDEADBEEF,size);
  1. 使用它们的优点是什么?在C/c++中实现这一目标的一般首选方法是什么?
  2. 如果指针被赋值为0xDEADBEEF,它不能仍然服从有效数据吗?
  1. 使用memset(ptr, NULL, size)memset(ptr, 0xDEADBEEF, size)是一个明确的事实表明,作者不明白他们在做什么。

    首先,在C和c++中,如果NULL被定义为整零,memset(ptr, NULL, size)确实会将内存块归零。

    然而,在这种情况下使用NULL来表示零值是不可接受的做法。NULL是一个专门为指针上下文引入的宏。memset的第二个参数是整数,不是指针。清除内存块的正确方法是memset(ptr, 0, size)。注意:0不是NULL。我想说,即使memset(ptr, '', size)看起来也比memset(ptr, NULL, size)好。

    此外,最新的(目前的)c++标准——c++ 11——允许将NULL定义为nullptrnullptr值不能隐式转换为int类型,这意味着上面的代码不能保证在c++ 11或更高版本中编译。

    在C语言中(你的问题也标记为C)宏NULL可以扩展到(void *) 0。即使在C语言中,(void *) 0也不能隐式转换为int类型,这意味着在一般情况下,memset(ptr, NULL, size)在C语言中只是无效代码。

    其次,尽管memset的第二个参数的类型是int,但函数将其解释为unsigned char值。这意味着只有一个低字节的值被用来填充目标内存块。由于这个原因,memset(ptr, 0xDEADBEEF, size)可以编译,但不会像代码作者天真地希望的那样,用0xDEADBEEF的值填充目标内存区域。memset(ptr, 0xDEADBEEF, size)等同于memset(ptr, 0xEF, size)(假设是8位字符)。虽然这可能足够好,可以用故意的"垃圾"填充一些内存区域,但像memset(ptr, NULL, size)memset(ptr, 0xDEADBEEF, size)这样的东西仍然暴露了作者主要缺乏专业性。

    同样,正如其他答案已经指出的那样,这里的想法是用"垃圾"值填充未使用的内存。在这种情况下,零当然不是一个好主意,因为它不够"垃圾"。当使用memset时,您被限制为单字节值,如0xAB0xEF。如果这对您的目的足够好,请使用memset。如果您想要一个更具表现力和独特的垃圾值,如0xDEDABEEF0xBAADFOOD,则不能将memset与它一起使用。你必须写一个专门的函数,可以用4字节的模式填充内存区域。

  2. C和c++中的指针不能被赋值为任意整数值(除了空指针常量,即零)。这种赋值只能通过显式强制转换将整型值转换到指针中来实现。正式地说,这种类型转换的结果是实现定义的。结果值肯定指向有效的数据。

写入0xDEADBEEF或其他非零位模式是一个好主意,可以同时捕获删除后写入和删除后读取的使用。

1)删除后写入

通过编写一个特定的模式,你可以检查一个已经被释放的块是否被错误的代码重写;在我们的调试内存管理器中,我们使用一个空闲的块列表,在回收内存块之前,我们检查我们的自定义模式是否仍然写在整个块上。当然,当我们发现问题时有点"晚了",但仍然比不做检查时发现问题要早得多。我们还有一个特殊的函数,可以定期调用,也可以按需调用,它可以遍历所有已释放内存块的列表,并检查它们的一致性,所以我们可以在追踪bug时经常调用这个函数。使用0x00000000作为值不会那么有效,因为零可能正是错误代码想要在已经释放的块中写入的值,例如将字段归零或将指针设置为NULL(相反,错误代码更不可能想要写入0xDEADBEEF)。

2)删除后读取

保留未分配块的内容,甚至只写零,将增加某人读取死内存块内容的可能性,仍然会发现值合理且与不变量兼容(例如NULL指针,因为在许多体系结构上NULL只是二进制零,或整数0,ASCII NUL字符或双精度值0.0)。通过编写像0xDEADBEEF这样的"奇怪"模式,大多数将以读模式访问这些字节的代码可能会发现奇怪的不合理的值(例如整数-559038737或值-1.1885959257070704e+148的双精度值),希望触发一些其他的自一致性检查断言。

当然,没有什么是真正特定于0xDEADBEEF的位模式,实际上我们使用不同的模式来释放块,块前区域,块后区域,并且我们的内存管理器在将内存块提供给应用程序之前,将另一个(依赖于地址的)特定位模式写入任何内存块的内容部分(这是为了帮助查找未初始化内存的用途)。

我绝对推荐0xDEADBEEF。它清楚地识别未初始化的变量,并访问未初始化的指针。

很奇怪,当加载一个字时,解引用0xdeadbeef指针肯定会在PowerPC体系结构上崩溃,并且很可能在其他体系结构上崩溃,因为内存可能在进程的地址空间之外。

内存归零是一种方便,因为许多结构体/类都有使用0作为初始值的成员变量,但我非常建议在构造函数中初始化每个成员,而不是使用默认的内存填充。您确实希望掌握是否正确初始化了变量。

http://en.wikipedia.org/wiki/Hexspeak

这些"神奇"的数字是一个调试辅助,以识别坏的指针,未初始化的内存等。您需要一个在正常执行期间不太可能出现的值,并且在进行内存转储或检查变量时可以看到这个值。在这方面,初始化为0就不那么有用了。我猜当你看到人们初始化为0的时候那是因为他们需要在0处有那个值。值为0xDEADBEEF的指针可能指向有效的内存位置,因此将其用作NULL的替代方法是一个坏主意。

您将缓冲区设置为空或将其设置为特殊值的一个原因是您可以轻松地在调试器中判断缓冲区内容是否有效

解引用值为"0xDEADBEEF"的指针几乎总是危险的(可能会使程序/系统崩溃),因为在大多数情况下,您不知道那里存储了什么。

DEADBEEF是HexSpeek的一个例子。使用它,作为程序员,您有意地传达一个错误条件。

我个人建议使用NULL(或0x0),因为它代表预期的NULL,在比较时很方便。想象一下,由于某种原因(不知道为什么),您在DEADBEEF上使用char *和两者之间的字符,那么至少您的调试器会非常方便地告诉您它是0x0。

我会选择NULL,因为它比稍后遍寻并将所有指针设置为0xDEADBEEF要容易得多。此外,没有什么可以阻止0xDEADBEEF成为x86上的有效内存地址——诚然,这是不寻常的,但远非不可能。NULL更可靠。

最终,look- NULL是语言约定。deadbeef只是看起来很漂亮,仅此而已。你什么也得不到。库将检查NULL指针,而不检查0xDEADBEEF指针。在c++中,零指针的概念甚至不与零值联系在一起,只是用字面上的零表示,在c++ 0x中有nullptrnullptr_t .

如果这对StackOverflow来说太过观点,请投我反对票,但我认为这整个讨论是我们用来制作软件的工具链中一个明显漏洞的症状。

通过使用"garbage -y"值初始化内存来检测未初始化的变量,只检测某些类型数据中的某些类型的错误。

在调试版本中检测未初始化的变量,而不是在发布版本中检测,就像只有在测试飞机时才遵循安全程序,并告诉飞行公众对"嗯,它测试得很好"感到满意。

我们需要硬件支持检测未初始化的变量。就像一个"无效"位,伴随着内存的每个可寻址实体(在我们的大多数机器上=字节),它是由操作系统在每个字节中设置的,VirtualAlloc()(等,或其他操作系统上的等效)交给应用程序,当字节被写入时自动清除,但如果先读,会导致异常。

内存足够便宜,处理器也足够快。这结束了对"有趣"模式的依赖,让我们都诚实起来。

请注意,memset中的第二个参数应该是一个字节,也就是说它被隐式地转换为char或类似的类型。0xDEADBEEF会在大多数平台上转换为0xEF(在一些奇怪的平台上也会转换为其他东西)。

还要注意,第二个参数应该是正式的int,而NULL不是。

初始化的好处是。首先,当然,行为将更有可能是确定性的(即使我们最终在未定义的行为,行为在实践中是一致的)。

具有确定性的行为将意味着调试变得更容易,当你发现一个错误时,你"只"需要提供相同的输入,错误就会显示出来。

现在,当您选择要使用的值时,您应该选择一个最有可能导致不良行为的值——这意味着使用未初始化的数据更有可能导致观察到的错误。这意味着您必须使用有关平台的一些知识(尽管它们中的许多行为非常相似)。

如果内存被用来保存指针,那么确实已经清除了内存将意味着你得到一个NULL指针,通常解引用将导致分段错误(这将被观察为错误)。但是,如果您以另一种方式使用它,例如作为算术类型,那么您将得到0,并且对于许多应用程序来说,它不是那个奇数。

如果你使用0xDEADBEEF,你会得到一个相当大的整数,当解释数据为浮点数时,它也会是一个相当大的数字(IIRC)。如果将其解释为文本,则它将非常长并且包含非ascii字符,并且如果使用UTF-8编码,则可能无效。现在,如果在某些平台上用作指针,它将无法满足某些类型的对齐要求-同样在某些平台上,该内存区域可能会被映射出来(注意,在x86_64上,指针的值将是0xDEADBEEFDEADBEEF,这超出了地址的范围)。

请注意,虽然用0xEF填充将具有非常相似的属性,但如果您想用0xDEADBEEF填充内存,则需要使用自定义函数,因为memset无法做到这一点。