【问题标题】:When and why will a compiler initialise memory to 0xCD, 0xDD, etc. on malloc/free/new/delete?编译器何时以及为什么会在 malloc/free/new/delete 上将内存初始化为 0xCD、0xDD 等?
【发布时间】:2010-09-27 01:53:22
【问题描述】:

我知道编译器有时会使用0xCD0xDD 等特定模式初始化内存。我想知道的是何时以及为什么会发生这种情况。

什么时候

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

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

它是特定于平台的吗?

会不会出现在其他操作系统上,比如LinuxVxWorks

为什么

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

你能举出任何实际例子来说明这种初始化是如何有用的吗?

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

这有多便携?

【问题讨论】:

    标签: c++ c memory memory-management


    【解决方案1】:

    它是特定于编译器和操作系统的,Visual Studio 将不同类型的内存设置为不同的值,以便在调试器中您可以轻松查看是否过度进入malloced 内存、固定数组或未初始化的对象。

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

    【讨论】:

    • 我的猜测是它用于检查您是否也忘记正确终止您的字符串(因为打印了那些 0xCD 或 0xDD)。
    • 0xCC = 未初始化的本地(堆栈)变量 0xCD = 未初始化的类(堆?)变量 0xDD = 已删除的变量
    • @FryGuy 有一个实际原因决定了这些值(其中一些),正如我所解释的here
    【解决方案2】:

    “为什么”的明显原因是假设您有这样的课程:

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

    然后你实例化一个Foo 并调用SomeFunction,它会在尝试读取0xCDCDCDCD 时出现访问冲突。这意味着您忘记初始化某些内容。这就是“为什么”。如果不是,那么指针可能与其他一些内存对齐,并且调试起来会更加困难。它只是让您知道您获得访问冲突的原因。请注意,这个案例非常简单,但是在更大的类中很容易犯这个错误。

    AFAIK,这仅适用于调试模式下的 Visual Studio 编译器(而不是发布)

    【讨论】:

    • 您的解释不符合,因为您在尝试读取0x00000000 时也会遇到访问冲突,这同样有用(或更多,作为错误地址)。正如我在本页的另一条评论中指出的那样,0xCD(和0xCC)的真正原因是它们是可解释的 x86 操作码,会触发软件中断,这样就可以在一次调试器中正常恢复特定且罕见的错误类型,即当 CPU 错误地尝试在非代码区域中执行字节时。正如您所注意到的,除了此功能用途之外,填充值只是建议性提示。
    【解决方案3】:

    这不是操作系统,而是编译器。您也可以修改行为 - 请参阅本文底部。

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

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

    如果 Visual Studio 未将变量预初始化为已知值,则可能很难找到此错误。使用预初始化变量(或者更确切地说,预初始化堆栈内存),问题在每次运行时都可以重现。

    但是,有一个小问题。 Visual Studio 使用的值是 TRUE - 除了 0 之外的任何值都是。实际上很可能当您在发布模式下运行代码时,单元化变量可能会分配给恰好包含 0 的堆栈内存,这意味着您可能会有一个单元化变量错误,该错误仅在发布模式下表现出来。

    这让我很恼火,所以我wrote a script 通过直接编辑二进制文件来修改预填充值,这样我就可以找到只有在堆栈包含零时才会出现的未初始化变量问题。此脚本仅修改堆栈预填充;我从未尝试过堆预填充,尽管它应该是可能的。可能涉及编辑运行时 DLL,可能不会。

    【讨论】:

    • 在初始化之前使用值时,VS不会发出警告,如GCC?
    • 是的,但并非总是如此,因为它依赖于静态分析。因此很容易将其与指针算法混淆。
    • "这不是操作系统,而是编译器。"实际上,它不是编译器,而是运行时库。
    • 调试时,如果不是 0 或 1,Visual Studio 调试器将显示布尔值,例如 true (204)。因此,如果您跟踪代码,则相对容易看到这种错误。
    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

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

      【讨论】:

      • 而0xCD是int指令,所以执行0xCD 0xCD会产生int CD,也会陷入陷阱。
      • 在当今世界,数据执行保护甚至不允许 CPU 从堆中获取指令。自 XP SP2 以来,此答案已过时。
      • @MSalters:是的,默认情况下,新分配的内存是不可执行的,但有人可以很容易地使用VirtualProtect()mprotect() 使内存可执行。
      • 致任何有足够代表可以进行 1 个字符编辑的人 - 这篇文章中现在有一个 https 版本的 URL。
      【解决方案6】:

      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。

      【讨论】:

      【解决方案7】:

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

      -Wc,'initauto(deadbeef,word)'

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

      【讨论】:

        【解决方案8】:

        本文介绍了unusual memory bit patterns 以及遇到这些值时可以使用的各种技术。

        【讨论】:

          【解决方案9】:

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

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

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

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

          new和delete的内存管理部分通常用malloc和free实现,所以new和delete分配的内存通常具有相同的功能。

          它是特定于平台的吗?

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

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

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

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

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

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

          这有多便携?

          Writing Solid Code(也许是Code Complete)讨论了在选择填充模式时要考虑的事项。我已经在这里提到了其中的一些,Magic Number (programming) 上的维基百科文章也对它们进行了总结。一些技巧取决于您正在使用的处理器的具体情况(例如它是否需要对齐的读取和写入以及哪些值映射到将捕获的指令)。其他技巧,例如使用在内存转储中突出的大值和不寻常的值,更便于移植。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-02-02
            • 2012-04-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-10-16
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多