在各种代码中,我看到过在调试版本中使用NULL
进行内存分配...
memset(ptr,NULL,size);
或者使用0xDEADBEEF
...
memset(ptr,0xDEADBEEF,size);
- 每种方法的优点是什么,通常在C/C++中实现这个问题有什么更好的方式吗?
- 如果一个指针被赋予了
0xDEADBEEF
的值,它是否仍然可以解引用有效数据?
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++ 标准 - C++11 - 允许将 NULL
定义为 nullptr
。 nullptr
值不能隐式转换为类型 int
,这意味着以上代码在 C++11 及更高版本中无法保证编译通过。
在 C 语言中(您的问题也标记为 C),宏 NULL
可以扩展为 (void *) 0
。即使在 C 中,(void *) 0
也不能隐式转换为类型 int
,这意味着在一般情况下,memset(ptr, NULL, size)
简单地是无效的代码。
其次,尽管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
时,您只能使用一个字节的值,如0xAB
或0xEF
。如果这对您的目的足够好,请使用memset
。如果您想要一个更具表现力和独特的“垃圾”值,例如0xDEDABEEF
或0xBAADFOOD
,则无法使用memset
。您需要编写一个专用函数来填充内存区域的4字节模式。
C和C++中的指针不能被分配任意整数值(除了空指针常量,即零)。只能通过显式转换将整数值强制转换为指针来实现此类分配。从正式意义上讲,这样的强制转换的结果是实现定义的。结果值当然可以指向有效数据。
memset(ptr, 0xDEADBEEF, size)
就意味着将ptr指向的内存块填充为0xDEADBEEF。此外,在某些平台上存在对齐要求,这样解引用0xDEADBEEF
可能会导致失败(而且实现可能不会生成非对齐指针)。 - skyking0xDEADBEEF
不能指向所需或编译器选择(所需)驻留在对齐地址上的数据。这也取决于“有效数据”的含义——如果你只需要能访问数据而不出现分段错误,那么在x86平台上就可以通过,但如果你需要确保数据实际上是有效的,则情况就比较少见,因为它需要是 bool
或 char
类型。 - skyking写入0xDEADBEEF
或其他非零位模式是一个好主意,可以捕获写后删除和读后删除的使用情况。
通过写入特定模式,您可以检查已经被释放的块是否由有缺陷的代码后续写入;在我们的调试内存管理器中,我们使用一个块的空闲列表,并在回收内存块之前检查自定义模式是否仍然覆盖整个块。当然,当我们发现问题时,它有点“晚”,但仍然比不进行检查要早得多。
此外,我们有一个特殊的函数,定期调用它,也可以按需调用它,只需遍历所有已释放的内存块的列表并检查它们的一致性,因此在追踪错误时我们可以经常调用此函数。使用0x00000000
作为值不会像使用0xDEADBEEF
那样有效,因为零可能恰好是有缺陷的代码想要写入已释放的块中的值,例如将字段清零或将指针设置为NULL(而有缺陷的代码想要写入0xDEADBEEF
的可能性则更小)。
保留已释放块的内容或甚至仅写入零会增加某人读取死内存块内容时仍会发现值合理且符合不变量(例如,在许多架构上,NULL指针只是二进制零、整数0、ASCII NUL
字符或双精度值0.0)的可能性。
相反,通过编写“奇怪”的模式,如0xDEADBEEF
,大多数以只读模式访问这些字节的代码可能会发现异常不合理的值(例如,整数-559038737或双精度值-1.1885959257070704e + 148),希望触发其他自我一致性检查断言。
当然,0xDEADBEEF
位模式并非特定于任何内容,实际上我们对已释放的块、块前后区域使用不同的模式,同时内存管理器在将任何内存块分配给应用程序之前,还会向其内容部分写入另一个(依赖于地址)特定的位模式,以帮助发现未初始化内存的使用。
将缓冲区清空或设置为特殊值的一个原因是您可以在调试器中轻松判断缓冲区内容是否有效。
解引用数值为“0xDEADBEEF
”的指针几乎总是危险的(可能会导致程序/系统崩溃),因为在大多数情况下,您不知道存储在那里的内容是什么。
memset
不能用于使用4字节模式重写内存区域。 - AnT stands with Russia我会选择NULL
,因为它比后期遍历并将所有指针设置为0xDEADBEEF更容易批量清零内存。此外,在x86上,没有任何东西可以阻止0xDEADBEEF成为有效的内存地址-尽管这可能不寻常,但远非不可能。 NULL
更可靠。
最终看起来- NULL
是语言约定。 0xDEADBEEF只是看起来漂亮而已。你得不到任何好处。库将检查NULL
指针,它们不会检查0xDEADBEEF指针。在C ++中,零指针的概念甚至与零值无关,仅用文字零表示,在C ++ 0x中有一个nullptr
和一个nullptr_t
。
NULL
指针的事实就是为什么你可能不应该这样做的原因,除非你100%确定行为将继续到发布版本。如果你忘记初始化一些数据,周围的代码将高兴地忽略它,因为你可能本来就打算将它初始化为零。然后,你切换到发布版本,它回归到实际上看起来像未初始化的数据,你所有的if(!p)
检查变得毫无价值。只要确保选择一个系统保证无效的值;0通常不是唯一的选择。 - Dennis Zickefoose如果这篇文章对于StackOverflow来说太主观了,请投票让我下线。但是我认为整个讨论的问题是我们用来制作软件的工具链中存在一个明显的漏洞的症状。
通过使用“垃圾值”初始化内存来检测未初始化变量只能检测某些数据中的某些错误。
在调试版本中检测未初始化的变量,但在发布版本中不检测,就像只在测试飞机时遵循安全程序,并告诉乘客“好吧,测试结果还可以”一样。
我们需要硬件支持来检测未初始化的变量。就像每个可寻址的内存实体(大多数机器上的字节)都伴随着一个“无效”位一样,由操作系统在VirtualAlloc()(等同于其他操作系统上的等效函数)分配给应用程序的每个字节中设置,并且在写入字节时自动清除,但如果首先读取则会引发异常。
现在的内存足够便宜,处理器足够快。这种方法不仅可以消除对“有趣”模式的依赖,而且可以使我们保持诚实。
memset
中的第二个参数应该是一个字节,也就是说它会被隐式转换为 char
或类似类型。对于大多数平台,0xDEADBEEF
会转换为 0xEF
(对于一些奇怪的平台可能会有其他结果)。int
,而 NULL
不是。如果内存用于存储指针,那么清除内存确实意味着会得到一个NULL
指针,并且通常对其进行间接引用将导致分段错误(这将被观察为故障)。然而,如果您以另一种方式使用它,例如作为算术类型,那么您将获得0
,对于许多应用程序来说,这不是奇怪的数字。
如果您改用0xDEADBEEF
,则将获得相当大的整数,即使在将数据解释为浮点数时,它也将是相当大的数字(如我所知)。如果将其解释为文本,则它将非常长,并且包含非ASCII字符,如果使用UTF-8编码,则可能无效。现在,如果在某些平台上将其用作指针,则会因某些类型的对齐要求而失败-在某些平台上,该内存区域可能已映射出(请注意,在x86_64上,指针的值将为0xDEADBEEFDEADBEEF
,这超出了地址范围)。
请注意,虽然填充0xEF
将具有相似的属性,但如果要用0xDEADBEEF
填充内存,则需要使用自定义函数,因为memset
无法做到这一点。
CHAR_BIT
是8,那么memset(ptr, 0xDEADBEEF, size);
和memset(ptr, 0xEF, size);
的效果完全相同。 - pmgmemset()
函数的描述(在标准文档中7.21.6.1节),该函数原型为void *memset(void *s, int c, size_t n);
,并指出:“函数memset()将c(转换为unsigned char)复制到指向s对象的前n个字节中。” 此外,我已经测试过它,并验证了我的实现与标准描述相同。 - pmg