繁体   English   中英

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

[英]Program crashes only in Release mode outside debugger

我有相当大的程序(> 10k行的C ++代码)。 从Visual Studio中启动时,它在调试模式或发布模式下都能正常工作,但是从命令行手动启动时,释放模式二进制文件通常会崩溃(并不总是!!!)。

删除行会导致崩溃:

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做了一些事情,它会在删除时崩溃。

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

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

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

感谢帮助。

你有没有检查其他地方的内存泄漏?

通常奇怪的删除行为是由于堆在某一点上被破坏引起的,然后在很久之后,由于另一个堆的使用而变得明显。

调试和发布之间的区别可能是由Windows在每个上下文中分配堆的方式引起的。 例如,在调试中,堆可能非常稀疏,并且损坏不会立即影响任何内容。

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

是微软为此功能提供的少数几个提及之一,在这里您可以找到一些关于它的链接。

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

您可能在某处有内存覆盖,而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()并再次运行。 它应该显示源文件和堆损坏的行以及最后一次它可以的位置。

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

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

我观察到这种症状时遇到的一种问题是,当我在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;

这两个是它们功能中的前两行。

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

也许你应该展示这两个功能。

您是否曾尝试使用相同的构建文件将其隔离,但仅根据您上面的内容编写代码? 就像是:

int main(int argc, char* argv[] )
{
    const int len( 16 );
    char* buf = new char[len + 1]; 

    delete [] buf;
}

您给出的代码绝对正常,并且在它自己的代码中,应该在调试或优化时运行没有任何问题。 因此,如果问题不在于您的代码的具体细节,那么它必须归结为项目的细节(即编译/链接)

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

听起来你在代码中的某个地方有一个整体变量。

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

在释放模式下,除非您明确地执行某些操作,否则不会初始化内存。

使用最高级别设置的警告运行编译器。
然后确保代码编译没有警告。

我遇到了同样的问题,我发现当我去删除字符串长度为1的[]字符串指针时,我的程序才崩溃。

void DeleteCharArray(char* array){
 if(strlen(array)>1){delete [] array;}
 else{delete array;}
}

这解决了这个问题,但它仍然容易出错,但可以修改为其他方式。 无论如何,我怀疑这是因为C ++ char * str = new char [1]char * str = new char; 是同一个东西,这意味着当你试图删除一个只有数组的delete []时 ,结果是意外的,而且往往是致命的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM