查看原文
其他

Debug模式和Release模式有什么区别?

程序喵大人 程序喵大人 2022-08-22


春节将至,留守过年的朋友赶紧添加程序喵微信进群和大厂大佬们一起讨论计算机知识春节放送独家原创学习资料

程序喵大人微信


张爱玲曾说过:“开发过程就是个玄学。”

有时候,程序在Debug模式下运行的好好的,一Releasecrash了。

有时候,程序在Debug模式下崩溃了,Release模式下居然能正常运行。

可能很多人都遇到过以上的情况。

用C/C++的朋友都知道编译器编译有各种优化级别,编译器优化级别大体如下:

O0(默认选项):不开启优化,方便功能调试

Og:方便调试的优化选项(比O1更保守)

O1:保守的优化选项,打开了四十多个优化选项

O2:常用的发布优化选项,在O1的基础上额外打开了四十多个优化选项,包括自动内联等规则

Os:产生较小代码体积的优化选项(比O2更保守)

O3:较为激进的优化选项(对错误编码容忍度最低),在O2的基础上额外打开了十多个优化选项

Ofast:打开可导致不符合IEEE浮点数等标准的性能优化选项。


具体介绍如下:

O0:编译器默认就是O0,该选项下不会开启优化,方便开发者调试。

O1:致力于在不需要过多的编译时间情况下,尽量减少代码大小和尽量提高程序运行速度,它开启了下面的优化标志:

 -fauto-inc-dec                                                

 -fbranch-count-reg

 -fcombine-stack-adjustments

 -fcompare-elim

 -fcprop-registers 

 -fdce -fdefer-pop 

 -fdelayed-branch

 -fdse

 -fforward-propagate 

 -fguess-branch-probability 

 -fif-conversion

 -fif-conversion2

 -finline-functions-called-once

 -fipa-modref

 -fipa-profile

 -fipa-pure-const

 -fipa-reference

 -fipa-reference-addressable

 -fmerge-constants

 -fmove-loop-invariants

 -fomit-frame-pointer

 -freorder-blocks

 -fshrink-wrap

 -fshrink-wrap-separate

 -fsplit-wide-types

 -fssa-backprop

 -fssa-phiopt 

 -ftree-bit-ccp

 -ftree-ccp

 -ftree-ch

 -ftree-coalesce-vars

 -ftree-copy-prop

 -ftree-dce

-ftree-dominator-opts

 -ftree-dse

 -ftree-forwprop 

 -ftree-fre

 -ftree-phiprop

 -ftree-pta

 -ftree-scev-cprop

 -ftree-sink 

 -ftree-slsr

 -ftree-sra 

 -ftree-ter

 -funit-at-a-time


Og:如果是为了调试,该选项是比O0更好的选择,它会打开O1大部分优化标志,但是不会启用那些影响调试的标志:

 -fbranch-count-reg                                        

 -fdelayed-branch

 -fdse

 -fif-conversion

 -fif-conversion2

 -finline-functions-called-once

 -fmove-loop-invariants

 -fssa-phiopt

 -ftree-bit-ccp 

 -ftree-dse

 -ftree-pta 

 -ftree-sra



O2:常见的Release级别,该选项下几乎执行了所有支持的优化选项,它增加了编译时间,提高了程序的运行速度,又额外打开了以下优化标志:

 -falign-functions                                           

 -falign-jumps

 -falign-labels

 -falign-loops

 -fcaller-saves

 -fcode-hoisting

 -fcrossjumping

-fcse-follow-jumps

  -fcse-skip-blocks

 -fdelete-null-pointer-checks

 -fdevirtualize

  -fdevirtualize-speculatively

-fexpensive-optimizations

 -ffinite-loops

 -fgcse

 -fgcse-lm

 -fhoist-adjacent-loads

 -finline-functions

 -finline-small-functions

 -findirect-inlining

 -fipa-bit-cp

  -fipa-cp

  -fipa-icf

 -fipa-ra

  -fipa-sra

  -fipa-vrp

 -fisolate-erroneous-paths-dereference

 -flra-remat

 -foptimize-sibling-calls

 -foptimize-strlen

 -fpartial-inlining

 -fpeephole2

 -freorder-blocks-algorithm=stc

 -freorder-blocks-and-partition

  -freorder-functions

 -frerun-cse-after-loop

   -fschedule-insns

  -fschedule-insns2 -fsched-interblock

  -fsched-spec

 -fstore-merging

 -fstrict-aliasing

 -fthread-jumps

 -ftree-builtin-call-dce

 -ftree-pre

 -ftree-switch-conversion

  -ftree-tail-merge

 -ftree-vrp


Os:打开了几乎所有的O2优化标志,除了那些经常会增加代码大小的优化标志:

  -falign-functions                                                

  -falign-jumps

  -falign-labels

  -falign-loops

  -fprefetch-loop-arrays

  -freorder-blocks-algorithm=stc


它还启用了-finline-functions优化标志,使编译器根据代码大小而不是程序运行速度进行优化,为了减少代码大小。


O3:在O2的基础上又打开了以下优化标志

 -fgcse-after-reload                                         

 -fipa-cp-clone

 -floop-interchange

 -floop-unroll-and-jam

 -fpeel-loops

 -fpredictive-commoning

 -fsplit-loops

 -fsplit-paths

 -ftree-loop-distribution

 -ftree-loop-vectorize

 -ftree-partial-pre

 -ftree-slp-vectorize

 -funswitch-loops

 -fvect-cost-model

 -fvect-cost-model=dynamic

 -fversion-loops-for-strides


Ofast更加激进的编译选项,它不会严格遵循标准,在O3的优化基础上,它又开启了一些可能导致不符合IEEE浮点数等标准的性能优化选项,如- fast-math, -fallow-store-data-races等。


tips:上述优化选项如果想要了解具体含义可以看https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html 官方文档。


编译器有这么多优化级别,Debug版本和Release版本其实就是优化级别的区别,Debug称为调试版本,编译的结果通常包含有调试信息,没有做任何优化,方便开发人员进行调试,Release称为发布版本,不会携带调试信息,同时编译器对代码进行了很多优化,使代码更小,速度更快,发布给用户使用,给用户使用以更好的体验。但Release模式编译比Debug模式花的时间也会更多。


Debug模式下在内存分配上有所区别,在我们申请内存时,Debug模式会多申请一部分空间,分布在内存块的前后,用于存放调试信息。


对于未初始化的变量,Debug模式下会默认对其进行初始化,而Release模式则不会,所以就有个常见的问题,局部变量未初始化时,Debug模式和Release模式表现有所不同。


bool func() { bool found; for (int i = 0; i < vec.size(); ++i) { if (vec[i] == 3) { found = true; } } return found; }


Debug模式下可能运行正常,但Release模式下可能会返回错误结果,因为found局部变量在Release模式下没有初始化。


Debug模式以32字节为单位分配内存,例如当申请24字节内存时,Release模式下是正常的分配24字节,Debug模式会分配32字节,多了8字节,所以有些数组越界问题在Debug模式下可以安全运行,Release模式下就会出问题。


Debug模式下可以使用assert,运行过程中有异常现象会及时crash,Release模式下模式下不会编译assert,遇到不期望的情况不会及时crash,稀里糊涂继续运行,到后期可能会产生奇奇怪怪的错误,不易调试,殊不知其实在很早之前就出现了问题。编译器在Debug模式下定义_DEBUG宏,Release模式下定义NDEBUG宏,预处理器就是根据对应宏来判断是否开启assert的。


数据溢出问题,在一个函数中,存在某些从未被使用的变量,且函数内存在数据溢出问题,在Debug模式下可能不会产生问题,因为不会对该变量进行优化,它在栈空间中还是占有几个字节,但是Release模式下可能会出问题,Release模式下可能会优化掉此变量,栈空间相应变小,数据溢出就会导致栈内存损坏,有可能会产生奇奇怪怪的错误。


例如:

void func() { char buffer[10]; int counter; lstrcpy(buffer, "abcdefghik"); // 需要拷贝11字节}


tips:不要将Debug库和Release库混合在一起,可能会出问题,至于为啥会出问题,知道的可在下方评论区留言,一般都是有两个目录,Debug目录和Release目录。


回到开篇提到的那两个问题:


程序在Debug模式下运行的好好的,Release模式下就crash了,怎么办?

可以尝试着找一找代码中的assert,看一下是否是assert导致的两种模式下的差异,从这个思路入手。



程序在Debug模式下会崩溃,Release模式下却正常运行,怎么办?

看一下代码中是否有未初始化的变量,是否有数组越界问题,从这个思路入手。


参考资料

http://www.flounder.com/debug_release.htm

https://vinayakgarg.wordpress.com/2012/03/31/difference-between-debug-build-and-release-build/

https://www.xspdf.com/resolution/312312.html

https://docs.oracle.com/cd/E19205-01/820-1214/bkacs/index.html

https://www.cnblogs.com/huaping-audio/archive/2008/08/23/1274833.html

https://www.zhihu.com/question/27090458

https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html



文中图片来源网络,如有侵权,请联系删除!



往期推荐


1、少写点
if-else吧,它的效率有多低你知道吗?
2、年度原创好文汇总
3、深度好文|面试官:进程和线程,我只问这19个问题
4
他来了,他来了,C+
+17新特性精华都在这了
5、一文让你搞懂设计模式

6、C++11新特性,所有知识点都在这了!





如果喜欢这篇文章,请点赞、在看,支持一下哦~谢谢!


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存