编译选项简析及相关编码规范
作者:童磊(magictong)
环境:VS2005
一、基础
Debug和Release本身并没有什么本质的界限,他们只是一组编译选项的集合,编译器只是按照预定的选项工作而已。因此,我们可以修改这些选项,从而得到优化过的调试版本或是带跟踪语句的发布版本。
Debug 版本:
/MDd /MLd 或 /MTd |
调试版本的运行时库 |
/Od |
关闭优化 |
/D "_DEBUG" |
#define _DEBUG,打开编译调试开关 |
/ZI |
创建Edit and continue(编辑继续)数据库,这样在调试过程中如果修改了源代码不需重新编译 |
/GZ |
捕获内存错误(VS2005已经废去,用RTC代替) |
/Gm |
最小化重编译开关,减少编译时间 |
Release 版本:
/MD /ML 或 /MT |
发布版本的运行时刻函数库 |
/O1 /O2或 /Ox |
优化开关,使程序最小,最快或者全面优化 |
/D "NDEBUG" |
关闭编译调试开关 |
/GF |
常量字符串池开关,合并重复的字符串 |
1、ASSERT在Release版本中是不会被编译的,这种语句要转化成检测语句才有效果,这个地方曾经发生过crash,原因就是pos越界了。
ASSERT宏大概是这样定义的:
#ifdef _DEBUG
#define ASSERT(x) if( (x) == 0) report_assert_failure()
#else
#define ASSERT(x)
#endif
而assert本身在assert.h里面的定义是这样的:
#ifdef NDEBUG
#define assert(_Expression) ((void)0)
#else
……
2、DEBUG下会进行变量特别填充(开启/RTC1),这个可以在debug下直接调试查看内存。在调试内存错误的时候特别有用(是不是真的有用,谁用谁知道)。
0xCC:填充未初始化的栈变量。(烫烫烫烫烫烫烫烫烫烫)有木有!!!
0xCD:填充从堆中申请的内存。(经常见到的“屯屯屯屯屯屯屯屯屯”)
0xDD:(VC6)已经释放的内存。(“葺葺葺葺葺葺葺葺葺葺”)
0xFEEE:已经释放的内存。(“铪铪铪铪铪铪铪铪铪铪铪”)
0xFD:填充应用程序申请内存的前后的内存(前面4个字节,后面4个字节)。
3、尽量建立UNICODE工程。国际化,尤其是从VC6转换过来的工程最好是第一时间转换成UNICODE工程。
二、高级
/EH /EH{s|a}[c][-] |
异常处理模型 |
/GR |
启用运行时类型信息 |
/Zc |
一致性 |
/RTC /RTCc /RTCu /RTCs |
运行时检测 |
/Ob /Ob{0|1|2} |
内联函数展开 |
/Oy |
传说中的FPO,栈帧指针省略 |
1、 下面的代码如果关闭了GR选项,会有运行时错误,编译时会报4541警告:
warning C4541: 'typeid' used on polymorphic type 'Base' with /GR-; unpredictable behavior may result
class Base
{
public:
virtual void func() {}
};
class DClass : public Base
{
};
……
DClass* pd = new DClass;
Base* pb = pd;
cout << typeid( pb ).name() << endl;
cout << typeid( *pb ).name() << endl;
cout << typeid( pd ).name() << endl;
cout << typeid( *pd ).name() << endl;
……
2、/Zc
/Zc:forScope 强制for 循环变量的作用域
for (int i = 0; i < 10; ++i)
{
cout << i << endl;
}
i = 100;
/Zc:wchar_t 设置wchar_t为内置类型
3、/Ob 内联函数展开,后面分别跟0 1 2三个数字表示三种意思。
/Ob0 不进行内联;
/Ob1 只内联使用关键字inline标识的函数或者类声明中定义的函数;
/Ob2 包含Ob1选择的函数并智能选择一些可以内联的函数进行内联;
注意:对于这个编译选项,编译器将其和关键字inline视为建议的。不保证函数将被内联。而且不能强制编译器去内联特定的函数。(譬如虚函数就不可能内联)
4、/Oy FPO栈帧指针优化,默认是关闭的
5、/EH /EH{s|a} 异常处理模型
/EHs 同步异常处理模型
/EHa 异步异常处理模型
详细说明可以见:
http://blog.csdn.net/magictong/archive/2011/03/17/6256685.aspx
关于异常处理一个有意思的问题,看下面的一段代码(这是一个真实的例子):
vector<int> vecData;
try
{
vecData[100] = 12;
}
catch(...)
{
printf("Catched a exception/n");
}
在正常情况,这个异常是不会被catch的,即使设置了异步异常处理,也不会被catch。看起来似乎很奇怪。
跟踪一下代码流程,也许就不奇怪了……
VS2005里面微软对CRT的一些与安全相关的代码做了些改动,新的CRT版本在遇到参数异常时,把异常抛给了默认的调试器(默认是Dr.Watson),而不再通知应用程序设置的异常捕获函数。我们可以看一下代码的流程。
0040129D cmp dword ptr [ebp-1Ch],0
004012A1 jne main+2AFh (4012AFh)
004012A3 mov dword ptr [ebp-104h],0
004012AD jmp main+2BEh (4012BEh)
004012AF mov edx,dword ptr [ebp-18h]
004012B2 sub edx,dword ptr [ebp-1Ch]
004012B5 sar edx,2
004012B8 mov dword ptr [ebp-104h],edx
004012BE cmp dword ptr [ebp-104h],64h
004012C5 ja main+2CCh (4012CCh)
004012C7 call _invalid_parameter_noinfo (407384h)
004012CC mov eax,64h
004012D1 shl eax,2
004012D4 mov ecx,dword ptr [ebp-1Ch]
004012D7 mov dword ptr [eax+ecx],0Ch
_CRTIMP void __cdecl _invalid_parameter_noinfo(void)
{
_invalid_parameter(NULL, NULL, NULL, 0, 0);
}
_CRTIMP void __cdecl _invalid_parameter(
const wchar_t *pszExpression,
const wchar_t *pszFunction,
const wchar_t *pszFile,
unsigned int nLine,
uintptr_t pReserved
)
{
_invalid_parameter_handler pHandler = __pInvalidArgHandler;
pszExpression;
pszFunction;
pszFile;
pHandler = (_invalid_parameter_handler) _decode_pointer(pHandler);
if (pHandler != NULL)
{
pHandler(pszExpression, pszFunction, pszFile, nLine, pReserved);
return;
}
// No user handler is defined. Notify the debugger if attached.
_CRT_DEBUGGER_HOOK(_CRT_DEBUGGER_INVALIDPARAMETER);
_invoke_watson(pszExpression, pszFunction, pszFile, nLine, pReserved);
}
_CRTIMP void __cdecl _invoke_watson(
const wchar_t *pszExpression,
const wchar_t *pszFunction,
const wchar_t *pszFile,
unsigned int nLine,
uintptr_t pReserved
)
{
......
/* Make sure any filter already in place is deleted. */
SetUnhandledExceptionFilter(NULL);
ret = UnhandledExceptionFilter(&ExceptionPointers);
// if no handler found and no debugger previously attached
// the execution must stop into the debugger hook.
if (ret == EXCEPTION_CONTINUE_SEARCH && !wasDebuggerPresent) {
_CRT_DEBUGGER_HOOK(_CRT_DEBUGGER_INVALIDPARAMETER);
}
TerminateProcess(GetCurrentProcess(), STATUS_INVALID_PARAMETER);
}
/* Make sure any filter already in place is deleted. */
SetUnhandledExceptionFilter(NULL);
通过这一句干掉了我们设置的异常处理程序,然后就直接结束程序了……太暴力了,但是我们也是可以干预这个过程的,在函数_invalid_parameter里面有句:
pHandler = (_invalid_parameter_handler) _decode_pointer(pHandler);
if (pHandler != NULL)
{
pHandler(pszExpression, pszFunction, pszFile, nLine, pReserved);
return;
}
这里有个Handler貌似可以设置,确实是可以设置的:
void invalid_parameter_handler(
const wchar_t * expression,
const wchar_t * function,
const wchar_t * file,
unsigned int line,
uintptr_t pReserved)
{
}
_set_invalid_parameter_handler(invalid_parameter_handler);
调用之后就直接返回,你可以在invalid_parameter_handler函数里面做一些事情,然后退出程序,或者啥都不做,因为_invalid_parameter里面如果是设置了invalid_parameter_handler的情况,也调用了之后,就直接return了,因此异常还会存在,会被SEH捕获。
附录:
http://msdn.microsoft.com/zh-cn/library/y0zzbyt4(v=vs.80).aspx
链接器选项
参考文档:
1、 MSDN
2、 互联网