程序仅在调试器外的发布模式下崩溃

Posted

技术标签:

【中文标题】程序仅在调试器外的发布模式下崩溃【英文标题】:Program crashes only in Release mode outside debugger 【发布时间】:2011-01-21 19:09:34 【问题描述】:

我有相当庞大的程序(>10k 行 C++ 代码)。从 Visual Studio 中启动时,它在调试模式或发布模式下完美运行,但发布模式二进制文件通常在从命令行手动启动时崩溃(并非总是!!!)。

带有delete的行导致崩溃:

bool Save(const short* data, unsigned int width, unsigned int height, 
          const wstring* implicit_path, const wstring* name = NULL, 
          bool enable_overlay = false)

    char* buf = new char[17];
    delete [] buf;

编辑:根据要求扩展示例。

在我的测试用例中,“len”的长度为 16。没关系,如果我对 buf 做了什么,它会在删除时崩溃。

编辑:应用程序在没有 delete [] 行的情况下工作正常,但我想它会泄漏内存(因为该块永远不会未分配)。在删除行之后从未使用过的 buf。它似乎也不会因 char 以外的任何其他类型而崩溃。现在我真的很困惑。

崩溃消息非常不明确(典型的 Windows“xyz.exe 已停止工作”)。当我点击“调试程序”选项时,它进入VS,其中错误指定为“访问冲突写入位置xxxxxxxx”。虽然“没有为任何堆栈帧加载任何符号”,但它无法找到错误的位置。

我想这是一些非常严重的堆损坏情况,但是如何调试呢?我应该寻找什么?

感谢您的帮助。

【问题讨论】:

检查您是否使用了正确的运行时库,使用依赖库的发布版本等。很难说出确切的原因。检查指针 but 是否未在其他上下文中释放(导致双重释放),或者您是否在到达 delete [] 调用之前在某处调用了 UB(索引超出范围)。 即使我没有触摸 buf 指针,它也会崩溃。我只是分配空间并立即删除它并崩溃。 buf 删除后不被触碰。 注释掉这两行代码会不会崩溃? 没有。如果我注释掉第二行,它也不会崩溃。 你在程序中使用线程吗? 【参考方案1】:

您是否检查过其他地方的内存泄漏?

通常奇怪的删除行为是由堆在某一时刻损坏引起的,然后很久以后,由于另一个堆的使用而变得明显。

调试和发布之间的差异可能是由窗口在每个上下文中分配堆的方式引起的。例如在调试中,堆可能非常稀疏,损坏不会立即影响任何事情。

【讨论】:

最后,正是这种情况。我在一个地方错过了 ONE 的数组边界,程序在 5000 行代码之后崩溃了。 那你最后是怎么找到的? @rogerdpack 二进制搜索。删除/禁用代码段并逐渐回到有罪的路线上。【参考方案2】:

在调试器中启动和自行启动的最大区别在于,当应用程序从调试器启动时,Windows 提供了一个“调试堆”,其中填充了 0xBAADF00D 模式;请注意,这不是 CRT 提供的调试堆,而是用 0xCD 模式 (IIRC) 填充。

Here 是微软为数不多的提及此功能的其中之一,here 您可以找到一些相关链接。

该链接中还提到“启动程序并使用调试器附加到它不会导致它使用要使用的“特殊调试堆”。”

【讨论】:

现在它在调试器内部也崩溃了,但它仍然无法“为任何堆栈帧加载符号”,所以我无法有效地调试它。谢谢,至少有一些进展。 奇怪,通常它会正确加载符号。试试这个:在不从 Visual Studio 调试的情况下启动它,然后使用“附加到进程”命令将 VS 调试器连接到应用程序的进程。这样,VS 应该正确加载应用程序的符号。如果崩溃发生在 API 调用中,请使用调用堆栈窗口将其追溯到您的代码;在这种情况下,您可能会获得一些有关安装 Windows 调试符号的操作系统内部发生的情况的其他信息。 我猜问题是它是使用 /MT 的发布版本,它不会因 /MTd 而崩溃 多线程 debug CRT (/MTd) 掩盖了这个问题,因为就像 Windows 对调试器产生的进程所做的那样,它为您的程序提供了一个调试堆,初始化为 0xCD 模式。可能在某处您使用堆中一些未初始化的内存区域作为指针并取消引用它;使用两个调试堆,由于某种原因(可能是因为在地址 0xbaadf00d 和 0xcdcdcdcd 处有有效分配的内存),但使用“正常”堆(通常初始化为 0),您会遇到访问冲突,因为您取消引用 NULL 指针。【参考方案3】:

您可能在某处有内存覆盖,而 delete[] 只是它第一次引起问题。但是覆盖本身可以位于程序的完全不同的部分。困难在于找到覆盖。

添加如下函数

#include <malloc.h>

#define CHKHEAP()  (check_heap(__FILE__, __LINE__))

void check_heap(char *file, int line)

    static char *lastOkFile = "here";
    static int lastOkLine = 0;
    static int heapOK = 1;

    if (!heapOK) return;

    if (_heapchk() == _HEAPOK)
    
        lastOkFile = file;
        lastOkLine = line;
       return;
    

    heapOK = 0;
    printf("Heap corruption detected at %s (%d)\n", file, line);
    printf("Last OK at %s (%d)\n", lastOkFile, lastOkLine);

现在在整个程序中频繁调用 CHKHEAP() 并再次运行。它应该向您显示堆损坏的源文件和行以及最后一次正常的位置。

【讨论】:

当在崩溃行之前调用时返回 OK,所以看起来堆是 OK 的。 这应该是公认的答案。这个功能真是个好主意!谢谢 Leo,你保存了我的代码!【参考方案4】:

崩溃的可能原因有很多。总是很难找到它们,尤其是当它们从调试模式到发布模式不同时。

另一方面,由于您使用的是C++,因此您可以通过使用std::string 而不是手动分配的缓冲区来摆脱困境>>RAII 存在是有原因的;)

【讨论】:

我在所有可能的地方都使用 std wstring,但是在这个地方我需要将非 unicode char 数组传递给一个第三方函数。 您确定第三方功能在某些情况下没有delete 吗?此外,std::string 有一个 data() 成员函数,它返回一个 char*【参考方案5】:

听起来您在代码的某处有一个统一变量。

在调试模式下,所有内存都被初始化为某种标准,因此您将获得一致的行为。

在释放模式下,除非你明确地做某事,否则内存不会被初始化。

使用设置在***别的警告运行编译器。 然后确保您的代码在没有警告的情况下编译。

【讨论】:

【参考方案6】:

这两行是它们函数的前两行。

如果你的意思是我解释的方式,那么第一行是在一个函数中声明一个局部变量 buf,但 delete 是删除在第二个函数之外声明的一些不同的 buf。

也许你应该展示这两个函数。

【讨论】:

【参考方案7】:

您是否尝试过使用相同的构建文件简单地隔离它,但代码仅基于您在上面放置的内容?比如:

int main(int argc, char* argv[] )

    const int len( 16 );
    char* buf = new char[len + 1]; 

    delete [] buf;

您提供的代码绝对没问题,并且就其本身而言,无论是在调试还是优化时,都应该可以毫无问题地运行。因此,如果问题不在于您的代码细节,那么它必须归结为项目的细节(即编译/链接)

您是否尝试过创建一个全新的项目并将 10K+ 行的 C++ 代码放入其中?可能不需要太长时间来证明这一点。特别是如果现有项目已经导入或进行了重大更改。

【讨论】:

只是一个想法,但您是否尝试过在删除前后放置一些调试输出?从您所说的看来,您已将删除确定为问题的根源,但错误似乎不清楚错误实际发生的位置。可能是删除本身没问题,但是在删除之后某些东西会尝试访问该内存。通常在删除 buf 后将其设置为 0 也是一种很好的做法,以防止出现双重删除问题并便于测试指针是否有效。【参考方案8】:

我遇到了同样的问题,我发现我的程序只有在我去 delete[] 字符串长度为 1 的字符指针时才会崩溃。

void DeleteCharArray(char* array)
 if(strlen(array)>1)delete [] array;
 elsedelete array;

这解决了问题,但仍然容易出错,但可以修改为其他。 无论如何,我怀疑发生这种情况的原因是 C++ char* str=new char[1]char* str=new char; 是同一件事,这意味着当您尝试使用 delete[] 删除仅用于数组的指针时,结果是意外的,而且通常是致命的。

【讨论】:

我想你检查了删除之前执行的所有代码是否越界写入(写入数组末尾)?这些错误通常是由此造成的。删除长度为 1 的 char 数组就好了。【参考方案9】:

当我观察到这种症状时,我遇到的一种问题是,我在 shell 中运行时遇到了多进程程序崩溃,但在从 valgrindgdb 调用时运行完美。我发现(让我很尴尬),我有一些相同程序的杂散进程仍在系统中运行,导致mq_send() 调用返回错误。问题是这些杂散进程也被内核/系统分配了消息队列句柄,因此我新生成的进程中的mq_send() 失败了,但不确定(根据内核调度情况)。

就像我说的,微不足道,但在你发现之前,你会把头发扯掉!

我从这个惨痛的教训中吸取了教训,这些天我的Makefile 拥有所有适当的命令来创建新的构建和清理旧环境(包括拆除旧的消息队列和共享内存和信号量等)。这样,我就不会忘记做某事,并且不得不为一个看似困难(但显然可以轻松解决)的问题而感到心痛。这是我最新项目的剪切和粘贴:

[Makefile]
all:
      ...
...

obj:
      ...
clean:
      ...
prep:
  @echo "\n!! ATTENTION !!!\n\n"
  @echo "First: Create and mount mqueues onto /dev/mqueue (Change for non ubuntu)"
  rm -rf /run/shm/*Pool /run/shm/sem.*;
  rm -rf /dev/mqueue/Test;
  rm -rf /dev/mqueue/*Task;
  killall multiProcessProject || true;

【讨论】:

以上是关于程序仅在调试器外的发布模式下崩溃的主要内容,如果未能解决你的问题,请参考以下文章

iPhone 应用程序仅在 3G 上的发布模式下崩溃

如何调试仅在发布版本中发生的崩溃[关闭]

为啥我的应用程序在发布模式下崩溃但在调试模式下不崩溃?

仅在 Flutter 的调试模式下运行的应用程序

Firebase 消息传递仅在发布版本中使应用程序崩溃

OpenGL 程序仅在 Visual Studio 2013 中的调试模式下工作