用Vc6编写的程序有时候Debug版本好好的,Release版本运行就出错,很多人都遇到过这种问题。前几天又有人问起,于是找了点时间写下这篇东西。总结了一部分Debug版本和Release版本的区别,都是些个人理解,欢迎交流。
Debug版本和Release版本之所以有区别,就是在于各自的编译项不同。不同的编译项组合产生不同的代码,Debug版本的编译项屏蔽了编译器的优 化,增加了调试信息的输出。也就是说,可以通过修改编译项的组合,使得优化过的版本也包含调试信息。于是调试Release版本成为了可能。那就先说说调 试方面Debug做的工作。
提起Vc6的调试,不能不提的就是它的PDB(Program Database)。编译项加上 /Zi 或者 /ZI 都能生成PDB文件,其中Debug版本默认编译项使用的是 /ZI 选项,因为 /ZI 选项支持Edit And Continue特性。PDB文件的内容非常丰富,包括公共函数,公共变量的名字信息和位置信息;静态函数、静态变量、局部变量、函数参数等非公共的信息 的名字和位置;类型信息自然是不能少的;还有源文件和代码行的信息等。文件信息是绝对路径,所以把程序和整个DEBUG目录一块儿复制到另一个地方时,需 要重新编译才能调试,因为路径变了~ 呵呵~ Release版本也可以生成PDB文件,加上编译项 /Zi 就可以了。PDB文件可以帮我们简单而快捷的定位内存访问异常导致程序退出的错误。
接下来是内存管理部分。这一部分的区别有些经验的朋友应该都比较清楚。Debug模式下,我们看到栈(stack)上分配的内存都被初始化成了0xcc, 而堆(heap)上分配的内存都被初始化成了0xcd(微软称之为_bCleanLandFill),并且在分配的内存的两头各有四个字节的0xFD作为 边界(微软称之为_bNoMansLandFill),方便检测内存越界。这个边界是用来方便程序员查错的,但是同时也导致了和Release版本下的行 为有可能出现差异。
还有个小差异就是volatile类型的处理。Debug版本下,所有的变量都是volatile类型。而Release版本下由于优化的关系,如果多线 程环境下该声明为volatile的不声明,那是会出问题滴。本来想写段代码来测试这种差异,但是失败了。
#include <stdio.h>
#include <windows.h>
BOOL g_bExit = FALSE; //不加volatile关键字
DWORD WINAPI Worker(LPVOID)
{
while(!g_bExit)
{
Sleep(1000);
}
return 0;
}
int main()
{
HANDLE hWorker = CreateThread(NULL, 0, Worker, NULL, 0, NULL);
Sleep(1000);
g_bExit = TRUE;
Sleep(2000);
if (WAIT_TIMEOUT == WaitForSingleObject(hWorker, -1))
{
//希望编译器对Worker线程中的g_bExit优化到寄存器中
printf("test ok! ");
}
else
{
//结果没有
printf("test failed ");
}
return 0;
}
后来google了一下,发现有很多转载了都是DDJ上面的一篇老文:volatile - Multithreaded Programmer's Best Friend(发现件很可耻的事情,编程世界网站(www.ibiancheng.cn)去年12月发的文,居然写自己是原创,强烈BS之)。不过这篇文章上的代码测试了一下,也没有出现问题,也许是编译器智能些了?不明白。不管怎样,多线程开发中,还是要合理利用volatile关键字,避免出现不必要的麻烦。
关于异常处理,VC6中区分同步异常处理和异步异常处理两种,默认的是同步异常处理。C++认为异常仅在显示throw或者调用了函数时才会发生,也就是 同步异常处理,所以在Release版本中对同步的异常处理作了优化,把这部分认为没用的处理去掉了。对于异步异常处理,可以认为每个语句都可能发生异 常,加上/EHa编译项就可以了。