发布版本的运行与debugging版本不同的原因是什么?
我有一个Visual Studio 2005 C ++程序在发行模式下的运行方式与在debugging模式下的运行方式不同。 在释放模式下,发生(明显的)间歇性崩溃。 在debugging模式下,它不会崩溃。 发布版本的工作与debugging版本不同的原因是什么?
还值得一提的是我的程序相当复杂,并使用多个第三方库进行XML处理,消息代理等。
提前致谢!
生存发行版本提供了一个很好的概述。
我遇到的事情 – 大部分已经提到
variables初始化迄今为止最为常见。 在Visual Studio中,debugging将显式初始化分配的内存给定值,请参阅例如Memory Values 。 这些值通常很容易被识别出来,当用作索引或索引时被用作索引或访问冲突时会导致越界错误。 但是,一个未初始化的布尔值是真的,并且可能会导致多年未检测到未初始化的内存错误。
在发布版本中,内存没有明确初始化,只是保留了之前的内容。 这导致了“有趣的价值”和“随机”崩溃,但经常发生确定性的崩溃,需要在实际崩溃的命令之前执行明显不相关的命令。 这是由第一个命令“设置”具有特定值的内存位置引起的,而当内存位置被回收时,第二个命令将它们视为初始化。 未初始化的堆栈variables比堆更常见,但后者也发生在我身上。
原始内存初始化在发布版本中也可以不同,无论是从Visual Studio(从附加的debugging器)开始还是从资源pipe理器开始。 这使得“最好”的发布版本在debugging器中永远不会出现。
有效的优化在我的情况下排在第二位。 C ++标准允许进行大量的优化,这可能是令人惊讶的,但是完全有效的,例如,当两个指针别名相同的内存位置,不考虑初始化的顺序,或者多个线程修改相同的内存位置,并且你期望某个顺序在线程B看到由线程A所做的更改。通常,编译器被指责为这些。 不是那么快,年轻的yedi! – 见下文
由于各种原因(优化,提供线程同步点的日志loggingfunction,不执行的断言等debugging代码等), 定时发布版本不仅“运行得更快”,而且操作之间的相对定时也显着变化。 由此发现的最常见的问题是竞态条件,也是基于消息/定时器/事件的代码的死锁和简单的“不同顺序”执行。 即使他们是计时问题,他们也可能在构build和平台上出奇地稳定,复制品“除了PC 23之外总是能够工作”。
警卫字节 。 debugging版本通常在所选实例和分配周围放置(更多)保护字节,以防止索引溢出并且有时下溢。 在极less数情况下,代码依赖于偏移或大小,例如序列化原始结构,它们是不同的。
其他代码差异一些指令 – 例如断言 – 在发布版本中评估为无。 有时他们有不同的副作用。 这是macros观的诡计,如在经典(警告:多个错误)
#ifdef DEBUG #define Log(x) cout << #x << x << "\n"; #else #define Log(x) #endif if (foo) Log(x) if (bar) Run();
其中,在发布版本中,评估为if(foo && bar)这种types的错误对于正常的C / C ++代码和正确编写的macros是非常罕见的。
编译器错误这真的永远不会发生。 那么 – 它确实如此,但是如果事实并非如此,那么你的职业生涯的大部分时间会更好。 在与VC6合作的十年中,我发现一个地方,我仍然相信这是一个不固定的编译器错误,而对数十种模式(甚至可能有数百个)并没有充分理解经文(也就是标准)。
在debugging版本中,通常会启用断言和/或debugging符号。 这可能导致不同的内存布局。 如果指针错误,数组溢出或类似的内存访问在一种情况下访问严重的坏内存(例如函数指针),在其他情况下可能只是一些非关键的内存(例如只是一个docstring被丢弃)
未明确初始化的variables将在Release版本中归零或不归零。
发布版本(希望)会比你的debugging版本运行得更快。 如果您使用多个线程,则可能会看到更多的交织,或者只是一个线程比其他线程运行得更快,这在debugging构build中可能没有注意到。
不久前 ,我遇到了类似的问题 ,最终导致堆栈在发布版本中的处理方式不同。 其他事情可以不同:
- 内存分配的处理方式与VS编译器中的debugging版本不同(即,在清除的内存上写入0xcc等)
- 循环展开和其他编译器优化
- 指针的引导
它取决于编译器供应商和使用DEBUG标志编译的库。 虽然DEBUG代码不应该影响运行代码(应该没有副作用),但有时候这样做。
特别是variables只能在DEBUG模式下初始化,并且在RELEASE模式下保持未初始化状态。 Visual Studio编译器中的STL在DEBUG和RELEASE模式下是不同的。 这个想法是,在DEBUG中完全检查迭代器来检测可能的错误(使用无效的迭代器,例如,如果迭代器被检索之后发生插入,迭代器到vector中是无效的)。
第三方库也是如此,我能想到的第一个就是QT4,如果与创buildgraphics对象的线程不同的线程执行绘制操作,那么将终止程序的断言。
随着所有的改变,你的代码和内存占用将在两种模式下不同。 如果该位置是可读的,则指针(读取一个传递数组尾部的位置)问题可能未被检测到。
断言意味着在DEBUG期间杀死应用程序,并从RELEASE构build中消失,所以我不会认为断言是你的问题。 stream氓指针或访问结束将是我的第一个嫌疑人。
前段时间有一些编译器优化破坏代码的问题,但我最近没有读到投诉。 那里可能会有一个优化问题,但这不会是我的第一个嫌疑犯。
发布版本通常是在编译器启用优化的情况下编译的,而debugging版本通常不是。
在某些语言或使用许多不同的库时,这可能导致间歇性崩溃 – 尤其是当select的优化级别非常高时。
我知道这是用gcc C ++编译器的情况,但我不确定微软的编译器。
http://www.debuginfo.com/tips/userbpntdll.html
由于在debugging版本中添加了保护字节,因此您可以“安全”地访问数组(特别是dynamic数组)的越界内存,但这会在发布版本中造成访问冲突。 这个错误可能会被忽略,导致一个损坏的堆,并可能在与原始错误无关的地方访问冲突。
使用PageHeap(或者,如果您安装了debugging工具,则可以使用gflags)来发现与腐败堆相关的错误。
根据我的经验,最常见的原因似乎是configuration与发布/构build设置有很多不同之处。 例如包含不同的库,或者debugging版本与发布版本有不同的堆栈大小。
为了避免在Visual Studio 2005项目中出现这种情况,我们广泛使用了属性表。 这样释放和debuggingconfiguration可以共享通用设置。
这篇文章连同提供的链接对于修复相关的bug非常有帮助。 增加上面的列表,调用约定的差异也可能导致这种行为 – 它在发布版本中失败,只为我优化。 我声明为__stdcall并定义为__cdecl(默认情况下)。 [奇怪的是,即使在警告级别为4的MSVC中,这个警告也不会被挑选出来]