编译器何时以及为什么要在 malloc/free/new/delete 上将内存初始化为0xCD、0xDD 等?

我知道编译器有时会使用某些模式(如 0xCD0xDD)初始化内存。我想知道的是 什么时候为什么会发生这种情况。

什么时候

这是特定于所使用的编译器吗?

malloc/newfree/delete在这方面的工作方式是否相同?

是特定平台的吗?

它是否会出现在其他操作系统上,例如 LinuxVxWorks

为什么

我的理解是,这只发生在 Win32调试配置中,它用于检测内存溢出并帮助编译器捕获异常。

您能给出一些实际的例子来说明这种初始化是如何有用的吗?

我记得读过一些东西(可能在代码完成2中) ,说当分配内存时,最好将内存初始化为一个已知的模式,并且某些模式将触发 Win32中的中断,这将导致异常显示在调试器中。

这个有多便携?

35818 次浏览

它是特定于编译器和操作系统的,Visual Studio 将不同类型的内存设置为不同的值,因此在调试器中,你可以很容易地看到是否进入了 malloced 内存、固定数组或未初始化的对象。

Https://learn.microsoft.com/en-gb/visualstudio/debugger/crt-debug-heap-details?view=vs-2022

“为什么”的显而易见的原因是,假设你有这样一个类:

class Foo
{
public:
void SomeFunction()
{
cout << _obj->value << endl;
}


private:
SomeObject *_obj;
}

然后你实例化一个 Foo并调用 SomeFunction,它会给一个试图读取 0xCDCDCDCD的访问冲突。这意味着您忘记初始化某些内容。这就是“为什么”。如果没有,那么指针可能已经与其他内存对齐,而且调试将更加困难。它只是让你知道你违反访问权限的原因。注意,这种情况非常简单,但是在更大的类中很容易出现这种错误。

AFAIK,这只有在调试模式下(与发布相反)才能在 VisualStudio 编译器上工作

不是操作系统的问题,而是编译器的问题。你也可以修改这种行为——看看这篇文章的底部。

MicrosoftVisualStudio 生成(在调试模式下)一个用0xCC 预先填充堆栈内存的二进制文件。它还在每个堆栈帧之间插入一个空格,以检测缓冲区溢出。这里有一个非常简单的例子(在实践中,Visual Studio 会发现这个问题并发出警告) :

...
bool error; // uninitialised value
if(something)
{
error = true;
}
return error;

如果 VisualStudio 没有将变量预先初始化为已知值,那么可能很难找到这个 bug。对于预先初始化的变量(或者更确切地说,预先初始化的堆栈内存) ,问题在每次运行时都是可重现的。

不过,还有一个小问题。VisualStudio 使用的值为 TRUE-除0之外的任何值都为。实际上,当你在发布模式下运行代码的时候,很有可能统一化的变量会被分配到一块刚好包含0的堆栈内存中,这意味着你可能有一个统一化的变量 bug,它只会在发布模式下显示出来。

这让我很恼火,所以我 写了个剧本通过直接编辑二进制文件来修改预填充值,允许我查找未初始化的变量问题,这些问题只有在堆栈包含零时才会出现。这个脚本只修改堆栈预填充; 我从未试验堆预填充,尽管它应该是可能的。可能涉及编辑运行时 DLL,也可能不涉及。

可以很容易地看到内存已经从初始值改变,通常是在调试期间,但有时对于发布代码也是如此,因为您可以在进程运行时将调试器附加到进程。

不仅仅是内存,许多调试器在进程启动时都会将寄存器内容设置为一个哨兵值(一些版本的 AIX 会将一些寄存器设置为 0xdeadbeef,这有点搞笑)。

关于填充值0xCCCCCC 的一个很好的属性是,在 x86汇编中,操作码0xCC 是 第三章操作码,它是软件断点中断。因此,如果您尝试在未初始化的内存中执行填充了该填充值的代码,您将立即遇到一个断点,并且操作系统将允许您附加一个调试器(或终止进程)。

微软的编译器在调试模式下编译时对各种未知/未初始化内存位的使用(支持可能因编译器版本而异) :

Value     Name           Description
------   --------        -------------------------
0xCD     Clean Memory    Allocated memory via malloc or new but never
written by the application.


0xDD     Dead Memory     Memory that has been released with delete or free.
It is used to detect writing through dangling pointers.


0xED or  Aligned Fence   'No man's land' for aligned allocations. Using a
0xBD                     different value here than 0xFD allows the runtime
to detect not only writing outside the allocation,
but to also identify mixing alignment-specific
allocation/deallocation routines with the regular
ones.


0xFD     Fence Memory    Also known as "no mans land." This is used to wrap
the allocated memory (surrounding it with a fence)
and is used to detect indexing arrays out of
bounds or other accesses (especially writes) past
the end (or start) of an allocated block.


0xFD or  Buffer slack    Used to fill slack space in some memory buffers
0xFE                     (unused parts of `std::string` or the user buffer
passed to `fread()`). 0xFD is used in VS 2005 (maybe
some prior versions, too), 0xFE is used in VS 2008
and later.


0xCC                     When the code is compiled with the /GZ option,
uninitialized variables are automatically assigned
to this value (at byte level).




// the following magic values are done by the OS, not the C runtime:


0xAB  (Allocated Block?) Memory allocated by LocalAlloc().


0xBAADF00D Bad Food      Memory allocated by LocalAlloc() with LMEM_FIXED,but
not yet written to.


0xFEEEFEEE               OS fill heap memory, which was marked for usage,
but wasn't allocated by HeapAlloc() or LocalAlloc().
Or that memory just has been freed by HeapFree().

免责声明: 这张表格来自我放在周围的一些笔记——它们可能不是100% 正确(或连贯)。

其中许多值是在 vc/crt/src/dbgheap.c 中定义的:

/*
* The following values are non-zero, constant, odd, large, and atypical
*      Non-zero values help find bugs assuming zero filled data.
*      Constant values are good, so that memory filling is deterministic
*          (to help make bugs reproducible).  Of course, it is bad if
*          the constant filling of weird values masks a bug.
*      Mathematically odd numbers are good for finding bugs assuming a cleared
*          lower bit.
*      Large numbers (byte values at least) are less typical and are good
*          at finding bad addresses.
*      Atypical values (i.e. not too often) are good since they typically
*          cause early detection in code.
*      For the case of no man's land and free blocks, if you store to any
*          of these locations, the memory integrity checker will detect it.
*
*      _bAlignLandFill has been changed from 0xBD to 0xED, to ensure that
*      4 bytes of that (0xEDEDEDED) would give an inaccessible address under 3gb.
*/


static unsigned char _bNoMansLandFill = 0xFD;   /* fill no-man's land with this */
static unsigned char _bAlignLandFill  = 0xED;   /* fill no-man's land for aligned routines */
static unsigned char _bDeadLandFill   = 0xDD;   /* fill free objects with this */
static unsigned char _bCleanLandFill  = 0xCD;   /* fill new objects with this */

还有一些时候,调试运行时将用一个已知的值填充缓冲区(或缓冲区的一部分) ,例如,std::string分配中的“松弛”空间或传递给 fread()的缓冲区。这些情况使用一个给定名称 _SECURECRT_FILL_BUFFER_PATTERN的值(在 crtdefs.h中定义)。我不确定它是什么时候引入的,但它至少是在 VS2005(VC + + 8)的调试运行时引入的。

最初,用于填充这些缓冲区的值是 0xFD——与用于无人区的值相同。但是,在 VS2008(VC + + 9)中,值被改为 0xFE。我假设这是因为在某些情况下,填充操作可能会超过缓冲区的末尾,例如,如果调用方传递的缓冲区大小对于 fread()来说太大。在这种情况下,值 0xFD可能不会触发检测这种溢出,因为如果缓冲区大小太大,只有一个,填充值将与用于初始化金丝雀的 no man 的土地值相同。无人区的土地没有变化,就意味着不会有人注意到。

因此,在 VS 2008中改变了填充值,这样一来就会改变无人地带金丝雀,从而导致运行时检测到问题。

正如其他人所指出的,这些值的一个关键属性是,如果一个指针变量与这些值之一被取消引用,它将导致访问冲突,因为在标准的32位 Windows 配置中,用户模式地址不会高于0x7ffffff。

IBMXLC 编译器有一个“ initauto”选项,它将为自动变量分配一个您指定的值。我在调试版本中使用了以下代码:

-Wc,'initauto(deadbeef,word)'

如果我查看一个未初始化的变量的存储,它将被设置为0x 死牛

本文介绍了 不寻常的记忆位模式以及在遇到这些值时可以使用的各种技术。

这是特定于所使用的编译器吗?

实际上,它几乎总是运行时库的一个特性(就像 C 运行时库一样)。运行库通常与编译器密切相关,但也有一些可以交换的组合。

我相信在 Windows 上,调试堆(HeapAlloc 等)也使用特殊的填充模式,这些模式与来自调试 C 运行时库中的 malloc 和 free 实现的模式不同。所以它也可能是一个操作系统特性,但大多数时候,它只是语言运行时库。

在这方面,malloc/new 和 free/delete 的工作方式是否相同?

New 和 delete 的内存管理部分通常使用 malloc 和 free 实现,因此用 new 和 delete 分配的内存具有相同的特性。

是特定平台的吗?

细节是特定于运行时的。所使用的实际值通常不仅在查看十六进制转储时看起来不同寻常和显而易见,而且被设计为具有某些可以利用处理器特性的属性。例如,经常使用奇数值,因为它们可能导致对齐错误。使用较大的值(而不是0) ,因为如果循环到未初始化的计数器,它们会导致令人惊讶的延迟。在 x86上,0xCC 是一个 int 3指令,因此如果执行未初始化的内存,它将捕获。

它会出现在其他操作系统上吗,比如 Linux 或 VxWorks?

它主要取决于您使用的运行时库。

你能给出一些实际的例子来说明这个初始化是如何有用的吗?

我在上面列出了一些。选择这些值通常是为了增加使用无效内存部分(长时间延迟、陷阱、对齐错误等)进行操作时发生异常情况的可能性。堆管理器有时还使用特殊的填充值来填充分配之间的空白。如果这些模式发生变化,它就会知道在某个地方有一个错误的写操作(比如缓冲区溢出)。

我记得读过一些东西(可能在代码完成2中) ,当分配内存时,最好将内存初始化为一个已知的模式,并且某些模式会触发 Win32中的中断,这将导致异常显示在调试器中。

这个有多便携?

编写 Solid Code (可能还有 代码完成)讨论了在选择填充模式时需要考虑的事情。我在这里提到了其中的一些,关于 神奇数字(编程)的 Wikipedia 文章也对它们进行了总结。其中一些技巧取决于您所使用的处理器的具体情况(比如它是否需要对齐的读和写,以及哪些值映射到将捕获的指令)。其他一些技巧,比如使用在内存转储中突出的大值和异常值,则更具可移植性。