什么时候和为什么编译器会在malloc/free/new/delete时将内存初始化为0xCD、0xDD等值?

151

我知道编译器有时会使用特定的模式来初始化内存,例如0xCD0xDD。我想知道的是这种情况发生的时间原因

时间

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

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

这与平台有关吗?

是否会在其他操作系统上发生,例如LinuxVxWorks

原因

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

您能否给出任何实际例子说明这种初始化的有用性?

我记得在某个地方(也许是《代码大全2》中)阅读过,当分配内存时,将内存初始化为已知模式很好,并且某些模式将在Win32中触发中断,从而导致异常显示在调试器中。

这种方法的可移植性如何?

9个回答

217
Microsoft的编译器在调试模式下使用以下方法处理未拥有/未初始化内存的各个部分(支持情况可能因编译器版本而异):
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中)。我不确定它是什么时候引入的,但至少在VS 2005(VC++8)的调试运行时中存在。
最初,用于填充这些缓冲区的值是0xFD,与无人区使用的相同值。然而,在VS 2008(VC++9)中,该值被更改为0xFE。我认为这是因为可能存在填充操作将超出缓冲区末尾的情况,例如,如果调用者传递了一个对于fread()来说过大的缓冲区大小。在这种情况下,值0xFD可能不会触发检测到这个溢出,因为如果缓冲区大小只大了1个字节,那么填充值将与用于初始化校验码的无人区值相同。无人区的变化意味着溢出不会被注意到。
因此,在VS 2008中更改了填充值,以便这种情况会更改无人区的校验码,并由运行时检测到问题。
正如其他人所指出的,这些值的关键属性之一是,如果解引用带有其中一个值的指针变量,它将导致访问冲突,因为在标准的32位Windows配置中,用户模式地址不会高于0x7fffffff。

2
哦,是的 - 其中一些源代码来自于 DbgHeap.c 中的 CRT 源代码。 - Michael Burr
3
@seane 提供的链接似乎已经失效。新的链接(文本已经得到改进)在此处可用:http://msdn.microsoft.com/en-us/library/974tc9t1.aspx。 - Simon Mourier
这些块的名称是什么?是内存屏障,membar,内存栅栏还是栅栏指令(https://en.wikipedia.org/wiki/Memory_barrier)? - Miro
1
这是一个很棒的总结!这里有另一个更新 - /GZ标志已被弃用,以下是替代方案的最新文档 - /RTC https://learn.microsoft.com/en-us/cpp/build/reference/rtc-run-time-error-checks?view=vs-2019 - PhysicalEd
1
@PhysicalEd 非常感谢您提供RTC文档的链接 - 在我无法在命令行中找到/GZ时,我一直在费尽心思地寻找这些信息! - AJM
显示剩余2条评论

41

填充值0xCCCCCCCC的一个好处是,在x86汇编中,操作码0xCC是int3操作码,它是软件断点中断。因此,如果您尝试执行使用该填充值填充的未初始化内存中的代码,您将立即触发断点,操作系统将允许您附加调试器(或者杀死该进程)。


9
0xCD 是 int 指令,因此执行 0xCD 0xCD 将生成一个 int CD 中断,同样会触发陷阱。 - Tad Marshall
2
在今天的世界中,数据执行预防甚至不允许CPU从堆中获取指令。这个答案已经过时,自XP SP2以来。 - MSalters
2
@MSalters:是的,新分配的内存默认情况下是不可执行的,但是有人可以轻松使用VirtualProtect()mprotect()使内存可执行。 - Adam Rosenfield
2
对于有足够声望进行1个字符编辑的任何人 - 此帖子中现在有一个https版本的URL。 - AJM

10

我的猜测是它用于检查您是否正确终止了字符串(因为那些0xCD或0xDD被打印出来)。 - strager
0xCC = 未初始化的本地变量(堆栈) 0xCD = 未初始化的类变量(堆?) 0xDD = 已删除的变量 - FryGuy
@FryGuy 这些值(部分)存在实际原因,正如我在这里所解释的那样。 - Glenn Slayden

4
这是否与使用的编译器有关?实际上,这几乎总是运行时库的一个特性(如 C 运行时库)。运行时通常与编译器强相关,但是有一些组合可以交换。我相信在 Windows 上,调试堆(HeapAlloc 等)也使用与在调试 C 运行时库中提供的 malloc 和 free 实现不同的特殊填充模式。因此,它可能也是一个操作系统特性,但大多数情况下,它只是语言运行时库。Malloc/new 和 free/delete 在这方面的工作方式是否相同?new 和 delete 的内存管理部分通常是使用 malloc 和 free 实现的,因此使用 new 和 delete 分配的内存通常具有相同的特性。这是否是平台特定的?
细节是特定于运行时的。实际使用的值通常被选择为不仅在查看十六进制转储时看起来不寻常和明显,而且设计成具有处理器的某些特性。例如,经常使用奇数值,因为它们可能会导致对齐故障。使用大值(而不是0),因为如果您循环到未初始化的计数器,则会导致令人惊讶的延迟。在x86上,0xCC是一个指令,因此如果执行未初始化的内存,它将陷阱。
“它是否会发生在其他操作系统上,例如Linux或VxWorks?”
这主要取决于您使用的运行时库。
“您能否举出任何实际例子说明这种初始化有用吗?”
我上面列出了一些。通常选择这些值以增加在使用无效内存部分时发生异常情况的机会:长时间延迟、陷阱、对齐故障等。堆管理器有时还使用特殊的填充值来填补分配之间的空白。如果这些模式发生变化,它就知道某个位置有坏写入(如缓冲区溢出)。
我记得读过一些关于在分配内存时将内存初始化为已知模式的好处的内容(也许在《代码大全2》中),某些模式会在Win32中触发中断,从而导致调试器显示异常。这个方法的可移植性如何?
《编写高质量代码》(也许包括《代码大全》)谈到了选择填充模式时需要考虑的事项。我在这里提到了其中的一些,并且维基百科上关于魔数(编程)的文章也总结了它们。一些技巧取决于您使用的处理器的具体情况(例如它是否需要对齐读写以及映射到会陷阱的指令的值)。其他技巧,如使用大值和在内存转储中突出显示的不寻常值,则更具可移植性。

4

这与操作系统无关,而是与编译器有关。您也可以修改行为-请参见本帖底部。

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

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

如果Visual Studio没有将变量预先初始化为已知值,那么这个bug可能很难找到。有了预初始化的变量(或者说是预初始化的堆栈内存),问题就可以在每次运行时重现。
然而,有一个小问题。Visual Studio使用的值是TRUE - 除了0以外的任何值都是TRUE。实际上,在Release模式下运行代码时,未初始化的变量可能会被分配到一个恰好包含0的堆栈内存,这意味着您可能会遇到仅在Release模式下显示的未初始化变量bug。
这让我很烦,所以我 编写了一个脚本 来直接编辑二进制文件以修改预填充值,从而找到仅在堆栈包含零时才会出现的未初始化变量问题。这个脚本只修改了堆栈预填充;我从未尝试过堆预填充,虽然这应该是可能的。可能涉及编辑运行时DLL,也可能不涉及。

1
Visual Studio是否像GCC一样,在使用未初始化的值之前发出警告? - strager
3
是的,但并非总是如此,因为它取决于静态分析。因此很容易将其与指针算术混淆。 - Airsource Ltd
3
不是操作系统的问题,是编译器的问题。实际上,也不完全是编译器的问题,而是运行时库的问题。 - Adrian McCarthy
在调试时,Visual Studio 调试器会显示一个布尔值的值,如果不是 0 或 1,则会显示类似于 true (204) 的内容。因此,如果您跟踪代码,就相对容易发现这种错误。 - Phil1970

2
这篇文章介绍了一些不寻常的内存位模式以及在遇到这些值时可以使用的各种技术。请看:

不寻常的内存位模式


2

很容易看到内存已经从初始值改变,通常在调试过程中,有时也会在发布代码中发生,因为您可以在运行过程中附加调试器。

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


2
明显的原因是,假设你有一个像这样的类:
class Foo
{
public:
    void SomeFunction()
    {
        cout << _obj->value << endl;
    }

private:
    SomeObject *_obj;
}

当你实例化一个 Foo 并调用 SomeFunction 时,它会出现访问冲突并尝试读取 0xCDCDCDCD。这意味着你忘记了初始化某些内容。这就是“为什么”的部分。如果不是这样,那么指针可能已经与其他内存对齐,这将更加难以调试。它只是让你知道你遇到访问违规的原因。请注意,这种情况非常简单,但在一个更大的类中很容易犯这个错误。
据我所知,这仅适用于Visual Studio编译器在调试模式下(而不是发布模式)。

1
你的解释不正确,因为尝试读取 0x00000000 也会导致访问冲突,这并没有什么用处(或者更糟糕,因为地址错误)。正如我在本页的另一条评论中指出的那样,0xCD(和0xCC)的真正原因是它们是可解释的x86操作码,可以触发软件中断,从而允许在仅有一种特定且罕见的错误类型中优雅地恢复到调试器中,即当CPU错误地尝试执行非代码区域中的字节时。除了这个功能性用途外,填充值只是提示建议,正如你所指出的那样。 - Glenn Slayden

1
IBM XLC编译器有一个“initauto”选项,可以将自动变量赋值为您指定的值。我在我的调试构建中使用了以下内容:
-Wc,'initauto(deadbeef,word)'
如果我查看未初始化变量的存储,它将被设置为0xdeadbeef。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接