0xDEADBEEF与NULL

在各种代码中,我看到调试版本中的内存分配为NULL

 memset(ptr,NULL,size); 

或者使用0xDEADBEEF ……

 memset(ptr,0xDEADBEEF,size); 
  1. 使用每个优势有什么好处,在C / C ++中实现这一目标的首选方法是什么?
  2. 如果指针被赋值为0xDEADBEEF ,那么它仍然不能满足有效数据吗?

  1. 使用memset(ptr, NULL, size)memset(ptr, 0xDEADBEEF, size)清楚地表明作者不明白他们在做什么。

    首先,如果将NULL定义为整数零,则memset(ptr, NULL, size)确实会将C和C ++中的内存块清零。

    但是,在此上下文中使用NULL表示零值不是一种可接受的做法。 NULL是专门为指针上下文引入的宏。 memset的第二个参数是整数,而不是指针。 将存储器块清零的正确方法是memset(ptr, 0, size) 。 注意: 0不是NULL 。 我要说即使memset(ptr, '\0', size)看起来也比memset(ptr, NULL, size)

    此外,最新的(目前)C ++标准nullptr ++ 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 ++中的指针不能被赋予任意整数值(除了空指针常量,即零)。 只能通过使用显式强制转换将积分值强制转换为指针来实现此类赋值。 从forms上讲,这种演员表的结果是实现定义的。 结果值当然可以指向有效数据。

写入0xDEADBEEF或其他非零位模式是一个好主意,能够捕获写后删除和读后删除使用。

1)删除后写

通过编写特定模式,您可以检查已经解除分配的块是否稍后由错误代码写入; 在我们的调试内存管理器中,我们使用一个空闲的块列表,在回收内存块之前,我们检查我们的自定义模式是否仍在整个块中写入。 当然,当我们发现问题时,它有点“迟到”,但仍然比发现不进行检查时更早。 此外,我们还有一个特殊的函数,它被定期调用,也可以按需调用,只需通过所有释放的内存块列表并检查它们的一致性,因此我们可以在追逐错误时经常调用此函数。 使用0x00000000作为值将不会那么有效,因为零可能正好是错误代码想要在已经解除分配的块中写入的值,例如将字段归零或将指针设置为NULL(相反,错误的代码想要更不可能写0xDEADBEEF )。

2)删除后读取

将解除分配的块的内容保持不变甚至只写零都会增加读取死存储块内容的人仍然会发现合理的值并与不变量兼容的可能性(例如,许多架构上的NULL指针NULL只是二进制零,或整数0,ASCII NUL char或双NUL值0.0)。 通过编写像“ 0xDEADBEEF ”这样的“奇怪”模式大多数将在读取模式下访问的代码,这些字节可能会找到奇怪的不合理值(例如整数-559038737或值为-1.1885959257070704e + 148的double),希望触发其他一些自我一致性检查断言。

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

我肯定会推荐0xDEADBEEF。 它清楚地标识未初始化的变量,并访问未初始化的指针。

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

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

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

这些“魔术”数字是用于识别错误指针,未初始化内存等的调试辅助工具。您需要在正常执行期间不太可能发生的值以及在执行内存转储或检查变量时可见的值。 在这方面,初始化为零不太有用。 我猜想当你看到人们初始化为零时,这是因为他们需要将该值设置为零。 值为0xDEADBEEF的指针可能指向有效的内存位置,因此将其用作NULL的替代方案是一个坏主意。

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

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

DEADBEEF是HexSpeek的一个例子。 有了它,作为程序员,你故意传达一个错误条件。

我个人建议使用NULL(或0x0),因为它表示预期的NULL,并在比较时派上用场。 想象一下你正在使用char *和DEADBEEF之间出于某种原因(不知道为什么),那么至少你的调试器会非常方便地告诉你它的0x0。

我会选择NULL因为将内存质量调零比后来更容易并将所有指针设置为0xDEADBEEF要容易得多。 另外,没有任何东西可以阻止0xDEADBEEF成为x86上的有效内存地址 – 诚然,这是不寻常的,但远非不可能。 NULL更可靠。

最终,看起来NULL是语言惯例。 0xDEADBEEF看起来很漂亮,就是这样。 你没有得到任何东西。 库将检查NULL指针,它们不检查0xDEADBEEF指针。 在C ++中,零指针的概念甚至没有绑定到零值,只用字面零表示,而在C ++ 0x中有一个nullptr和一个nullptr_t

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

通过使用“garabage-y”值初始化内存来检测未初始化的变量仅检测某些类型的数据中的某些类型的错误。

并且在调试版本中检测未初始化的变量而不是发布版本就像是在测试飞机并告诉飞行公众满意“好,它测试正常”时遵循安全程序。

我们需要硬件支持来检测未初始化的变量。 就像在存储器的每个可寻址实体(=大多数机器上的字节)附带的“无效”位一样,并且由OS在每个字节中设置VirtualAlloc()(等等,或其他OS的等价物)手转到应用程序,并在写入字节时自动清除,但如果先读取则会导致exception。

内存足够便宜,处理器速度足够快。 这种依赖于“有趣”模式的结束,让我们都诚实地开机。

请注意, memset中的第二个参数应该是一个字节,即它被隐含地强制转换为char或类似char 。 对于大多数平台, 0xDEADBEEF会转换为0xEF (对于某些奇数平台,还有其他东西)。

另请注意,第二个参数应该正式为NULL ,而NULL不是。

现在为了做这些初始化的优势。 当然,首先行为更可能是确定性的(即使我们最终以未定义的行为结束,行为在实践中也是一致的)。

具有确定性行为将意味着调试变得更容易,当您发现错误时,您“只”必须提供相同的输入并且故障将表现出来。

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

如果内存用于保存指针,那么确实已清除内存将意味着您获得一个NULL指针并且通常解除引用将导致分段错误(将被视为故障)。 但是,如果您以其他方式使用它,例如作为算术类型,那么您将获得0并且对于许多不是奇数的应用程序。

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

请注意,虽然填充0xEF将具有非常相似的属性,但是如果要使用0xDEADBEEF填充内存,则需要使用自定义函数,因为memset不起作用。