6

众所周知,Visual C++运行时用特殊的非零标记标记未初始化或刚刚释放的内存块。有没有办法完全禁用此行为而无需手动将所有未初始化的内存设置为零?它对我的有效非空检查造成了严重破坏,因为0xFEEEFEEE != 0.

嗯,也许我应该解释得更好一点。我创建并初始化一个变量(通过new),一切都很好。当我释放它(通过删除)时,它会将指针设置为0xFEEEFEEE而不是NULL. 当我插入适当的检查时NULL,就像所有管理自己内存的好程序都应该做的那样,我会遇到问题,因为0xFEEEFEEE检查NULL没有问题。除了在删除它们时手动设置所有指针之外,还有什么好方法NULL可以检测内存何时已被释放?我宁愿不使用Boost仅仅是因为我不想要开销,尽管它可能很小,因为这是我使用 Boost 的唯一目的。

4

15 回答 15

17

创建指针时,将其显式初始化为NULL. 同样在delete. 取决于未初始化数据的值(除了在少数特定情况下)是自找麻烦。

通过使用智能指针类(例如 ),您可以省去很多麻烦,boost::shared_ptr它会自动处理指针是否已初始化。

于 2008-09-15T19:05:17.623 回答
14

VC++ 的行为不应该对您可以执行的任何有效检查造成严重破坏。如果您看到 0xfeeefeee,那么您还没有写入内存(或已释放它),所以无论如何您都不应该从内存中读取。

于 2008-09-15T19:08:39.270 回答
8

如果您正在读取未初始化的内存,那么您的检查肯定不是“有效的”。内存被释放。它可能已经用于其他用途。您不能对 C/C++ 中未初始化内存的内容做出任何假设。

Java(和 C#,我相信)将保证分配的内存在使用前清零,当然垃圾收集会阻止您看到已释放的内存。但这不是 C 堆的属性,它直接暴露内存。

于 2008-09-15T19:10:06.787 回答
7

delete所有指向对象的指针重置为NULL. 此外,您不应更改 Windows DEBUG 运行时的默认内存填充,并且您应该boost::shared_ptr<>以任何方式使用指针之类的东西。

也就是说,如果你真的想在脚上开枪,你可以。

您可以使用这样的分配器挂钩来更改Windows DEBUG 运行时默认填充。这仅适用于 HEAP 分配的对象!

int main(int argc,char** arv)
{
  // Call first to register hook    
  _CrtSetAllocHook(&zero_fill);
  // Do other stuff
  malloc(100);
}


int zero_fill(int nAllocType, 
              void* pvData, 
              size_t nSize,
              int nBlockUse, 
              long lRequest, 
              const unsigned char *szFileName, 
              int nLine )
{
  /// Very Importaint !! 
  /// infinite recursion if this is removed !!
  /// _CRT_BLOCK must not do any thing but return TRUE
  /// even calling printf in the _CRT_BLOCK will cause
  /// infinite recursion
  if ( nBlockUse == _CRT_BLOCK )
    return( TRUE );
  switch(nAllocType)
  {
  case _HOOK_ALLOC:
  case _HOOK_REALLOC:
    // zero initialize the allocated space.
    memset(pvData,0,nSize);
    break;
  case _HOOK_FREE:
    break;
  }
  return TRUE;
}
于 2008-09-15T19:37:08.797 回答
6

如果您在 Release 模式而不是 Debug 模式下构建,则运行时根本不会填充未初始化的内存,但它仍然不会为零。但是,您不应该依赖这种行为 - 您应该使用 memset()、ZeroMemory() 或 SecureZeroMemory() 自己显式初始化内存,或者在某处设置一个标志,指示内存尚未初始化。读取未初始化的内存将导致未定义的行为。

于 2008-09-15T19:09:06.787 回答
5

你说:

我创建并初始化一个变量(通过new),一切都很好。当我释放它(通过删除)时,它会将指针设置为 0xFEEEFEEE 而不是 NULL。当我插入对 NULL 的正确检查时,就像所有管理自己内存的好程序都应该那样,我遇到了问题,因为 0xFEEEFEEE 通过了 NULL 检查而没有问题。

即使是 MSVC 的调试堆例程也不会改变你要删除的指针的值——你要删除的指针的值不会改变(甚至是 NULL)。听起来您正在访问一个属于您刚刚删除的对象的指针,这是一个简单明了的错误。

我很确定您尝试做的只是掩盖无效的内存访问。您应该发布一段代码,向我们展示真正发生的事情。

于 2008-09-16T15:51:15.960 回答
4

这实际上是 VC++(我相信其他编译器)中的一个非常好的特性,因为它允许您在调试器中查看指针的未分配内存。在禁用该功能之前,我会三思而后行。当您在 C++ 中删除一个对象时,您应该将指针设置为NULL以防万一以后尝试再次删除该对象。此功能可让您找出忘记将指针指向的位置NULL

于 2008-09-16T03:16:12.017 回答
4

@杰夫哈伯德(评论):

这实际上无意中为我提供了我想要的解决方案:我可以在 _HOOK_FREE 上将 pvData 设置为 NULL,并且不会遇到指针地址的 0xFEEEFEEE 问题。

如果这对您有用,那么这意味着您在测试 NULL 指针时正在读取已释放的内存(即,指针本身驻留在您释放的内存中)。

这是一个错误。

您使用的“解决方案”只是隐藏而不是修复错误。当释放的内存被分配给其他东西时,突然间你会使用错误的值作为指向错误东西的指针。

于 2008-09-16T22:07:53.590 回答
4

如果它在释放模式下工作,那是因为剪切运气。

Mike B 认为调试修复隐藏了一个错误是正确的。在释放模式下,正在使用的指针已被释放但未设置为NULL,并且它指向的内存仍然“有效”。在未来的某个时刻,内存分配会发生变化,或者内存映像会发生变化,或者某些事情会导致“有效”内存块变为“无效”。那时,您的发布版本将开始失败。切换到调试模式来查找问题是没有用的,因为调试模式已经“修复”了。

我想我们都同意以下代码不应该工作。

char * p = new char[16];     // 16 bytes of random trash
strcpy(p, "StackOverflow");  // 13 characters, a '\0' terminator, and two bytes of trash
delete [] p;                 // return 16 bytes to the heap, but nothing else changes;

if (p != NULL)               // Why would p be NULL?  It was never set to NULL
    ASSERT(p[0] == 'S');     // In debug, this will crash, because p = 0xfeeefeee and 
                             // dereferencing it will cause an error.
                             // Release mode may or may or may not work, depending on
                             // other memory operations

正如几乎所有其他发帖人所说,指针应该设置为NULLafter 调用delete。无论您是自己做还是使用 boost 或其他一些包装器,甚至这个线程中的宏都取决于您。

于 2008-09-17T03:15:54.270 回答
3

发生的事情是我的代码在调试编译下崩溃,但在发布编译下成功。

发布版本将在客户的机器上崩溃。它总是这样。

我已经在调试器下检查了它,并且在我对它们调用 delete 后,我的指针被设置为 0xFEEEFEEE。

调用 delete 后,指针不会更改。他们指向的内存被设置为 0xfeeefeee, 0xfeeefeee, ..., 0xfeeefeee。

如果你发现你的程序从释放的内存中读取数据(这在 DEBUG 构建中由 0xfeeefeee 模式方便地指示),那么你就有一个错误。

于 2008-12-06T16:41:34.240 回答
1

@[杰夫哈伯德]:

发生的事情是我的代码在调试编译下崩溃,但在发布编译下成功。0xFEEEFEEE我已经在调试器下检查了它,并且在我对它们调用 delete 之后,我的指针被设置为。同样,发布时的相同代码不会崩溃并且按预期运行。

这是一种非常奇怪的行为——我仍然相信_CrtSetAllocHook()解决方法可能隐藏了一个潜在的错误。

OS 堆管理器使用0xFEEEFEEE签名来指示释放的内存(请参阅http://www.nobugs.org/developer/win32/debug_crt_heap.html)。您是否可以发布一些重现代码并准确指出您正在使用的编译器版本?

于 2008-09-17T02:05:37.667 回答
0

我很确定您不能在此处禁用 Visual Studio 默认值,即使您这样做了,该值也将是分配内存之前内存中的任何值。

最好的办法是养成一开始就将它们设置为 0 的习惯,这只是额外的 2 个字符。

int *ptr=0;

您还可以使用定义为 0 的 NULL 宏(但不是默认值,因此在包含 windows.h 之类的内容并自行定义时,请注意多个定义!

于 2008-09-15T19:07:39.723 回答
0

如果您使用的是 malloc,它不会将内存初始化为任何内容。你得到什么。如果您想分配一个块并将其初始化为 0,请使用“calloc”,它与 malloc 类似,仅用于初始化(如果您想模拟 malloc,则将其设置为 1 的元素大小参数)。你应该在使用 calloc 之前阅读它,因为它有一些细微的差别。

http://wiki.answers.com/Q/What_is_the_difference_between_malloc_and_calloc_functions

于 2008-09-15T22:53:19.683 回答
0

为什么不创建自己的#define 并养成使用它的习惯呢?

IE

#define SafeDelete(mem) { delete mem; mem = NULL; }
#define SafeDeleteArray(mem) { delete [] mem; mem = NULL; }

显然,您可以随意命名它。deleteZ,deletesafe,随便你。

于 2008-09-16T03:38:53.927 回答
0

您也可以创建一个内存管理器。然后,您可以覆盖 new 和 delete 以从/放回预分配的内存块。

于 2014-09-23T19:58:59.190 回答