断言assert是由宏来实现的,debug下生效,release下不生效。当我们用这个宏的时候不会在意,其实细看他的实现还是蛮有意思的。这个实现里面涉及好多小技巧以及要避开的陷阱,cast-to-void,sizeof,大括号,do……while。本文第一部分先介绍了,assert实现之路,一版比一版完善。第二部分,第三部分则详细解释了为什么要这么实现的原因,中间列举了例子以及涉及到了编译器的一些设置选项。
目录
一:assert的宏实现版本进化(只关注#else 分支的时候,即asset不生效的时候)
版本1.会出现警告版:#define POW2_ASSERT(x)
三:对函数调用进行 cast to void以及sizeof的作用
3.当IsTrue中有其他操作,那么优化设置为O2也是会对函数进行调用
4.如果加上sizeof,不论C++优化设置如何以及函数内容如何:都不会对函数进行调用
一:assert的宏实现版本进化(只关注#else 分支的时候,即asset不生效的时候)
3个版本,后一个都是在前一个的基础上进行完善。
版本1.会出现警告版:#define POW2_ASSERT(x)
原因见 第二节。
#ifdef POW2_ASSERTS_ENABLED
#define POW2_ASSERT(x) \
do { if (!(x)) { pow2::Assert::Fail(#x, __FILE__, __LINE__); } } while(0)
#else
#define POW2_ASSERT(x)
#endif
ps:宏函数加do{…}while(0)其实并不是额外加了什么功能,只是为了保持宏函数实现的时候语意和我们所写的代码一致,防止大括号以及分号的干扰。
版本2.消除警告版,但会有函数执行的副作用
结果如何取决于编译器优化设置以及x的内容,详见第三节内容。
如果x的内容是函数调用,且函数内容不可被优化,那么本不想被调用执行的函数会被调用执行。
#ifdef POW2_ASSERTS_ENABLED
#define POW2_ASSERT(x) \
do { if (!(x)) { pow2::Assert::Fail(#x, __FILE__, __LINE__); } } while(0)
#else
#define POW2_ASSERT(x) \
do { (void)(x); } while(0)
#endif
版本3.最终版,无警告无副作用
详见第三节内容。
#ifdef POW2_ASSERTS_ENABLED
#define POW2_ASSERT(x) \
do { if (!(x)) { pow2::Assert::Fail(#x, __FILE__, __LINE__); } } while(0)
#else
#define POW2_ASSERT(x) \
do { (void)sizeof(x); } while(0)
#endif
二:对变量进行cast to void ,用来忽略变量
这一部分是用来解释版本2中 为什么要用cast-to-void的技巧。
首先准备工作:
1.设置vs的警告等级 为 level4(项目 右键-->属性-->C++-->常规-->警告等级)
2.设置C++优化 为 禁用(项目 右键-->属性-->C++-->优化-->优化=禁用Od)
然后编译以下代码
int main() {
int x = 0;
return 0;
}
会提示warning,x初始化了没有使用。
1>------ 已启动生成: 项目: zigzag, 配置: Debug Win32 ------
1> main.cpp
1>h:\vs_project\zigzag\zigzag\zigzag\main.cpp(9): warning C4189: “x”: 局部变量已初始化但不引用
========== 生成: 成功 1 个,失败 0 个,最新 0 个,跳过 0 个 ==========
加上一句把x强制转换成void类型:
int main() {
int x = 0;
(void)x;
return 0;
}
就没有提示warning了(通过看反汇编可以看到直接 忽略了(void)x 这句话)
1>------ 已启动生成: 项目: zigzag, 配置: Debug Win32 ------
1> main.cpp
========== 生成: 成功 1 个,失败 0 个,最新 0 个,跳过 0 个 ==========
所以将变量强制转换为void是一种在C和C ++中明确忽略变量的惯用方式。
那可以cast to 其他类型吗?
在vs2015中 也是没有warning。(反汇编 忽略了(short)x)
int main() {
int x = 0;
(short)x;
return 0;
}
1>------ 已启动生成: 项目: zigzag, 配置: Debug Win32 ------
1> main.cpp
========== 生成: 成功 1 个,失败 0 个,最新 0 个,跳过 0 个 ==========
在这篇文章说 在gcc中这么写,会出现另外一种警告。而转化了void 类型就不会出现警告。
ps: sizeof(void*) : 指针所占的字节数。即看编译器目标平台类型,x86通常4 bytes,x64通常8bytes。
三:对函数调用进行 cast to void以及sizeof的作用
这一部分是用来解释 对函数调用 cast-to-void 的作用和 加上 sizeof的作用 。
采用的是对比的方法,看出其作用:
1和2 对比了不同的设置优化等级
2和3 对比了函数内容的不同
3和4 对比了加上sizeof的区别
1.当C++优化设置为禁用:会对函数进行调用
(项目 右键-->属性-->C++-->优化-->优化=禁用Od)
bool isTrue()
{
return false;
}
int main() {
(void)(isTrue()); // 汇编语言 会进行 调用 isTrue 函数
(int)(isTrue()); // 汇编语言 会进行 调用 isTrue 函数
return 0;
}
int main() {
01022930 push ebp
01022931 mov ebp,esp
01022933 sub esp,0C0h
01022939 push ebx
0102293A push esi
0102293B push edi
0102293C lea edi,[ebp-0C0h]
01022942 mov ecx,30h
01022947 mov eax,0CCCCCCCCh
0102294C rep stos dword ptr es:[edi]
(void)(isTrue()); // 汇编语言 会进行 call isTrue
0102294E call isTrue (0102133Eh)
(int)(isTrue()); // 汇编语言 会进行 call isTrue
01022953 call isTrue (0102133Eh)
return 0;
01022958 xor eax,eax
}
2.当C++优化设置为O2:不会对函数进行调用
(项目 右键-->属性-->C++-->优化-->优化=速度最大O2)
同时设置基本运行时检查为默认(项目 右键-->属性-->C++-->代码生成-->基本运行时检查=默认值)
反汇编:忽略这这两句话。
--- h:\vs_project\zigzag\zigzag\zigzag\main.cpp --------------------------------
(void)(isTrue());
(int)(isTrue());
return 0;
002916E0 xor eax,eax
}
3.当IsTrue中有其他操作,那么优化设置为O2也是会对函数进行调用
(项目 右键-->属性-->C++-->优化-->优化=速度最大O2)
源代码:(注意上一个函数IsTrue中 只有return false,可以被优化,而现在有输出操作)
bool isTrue()
{
std::cout << "aaa" << std::endl;
return false;
}
int main() {
(void)(isTrue());
(int)(isTrue());
return 0;
}
反汇编:执行了isTrue函数
--- h:\vs_project\zigzag\zigzag\zigzag\main.cpp --------------------------------
(void)(isTrue());
00B91B90 push offset std::endl<char,std::char_traits<char> > (0B9105Fh)
00B91B95 push offset string "aaa" (0B96B30h)
00B91B9A push dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0B99070h)]
00B91BA0 call std::operator<<<std::char_traits<char> > (0B9129Eh)
00B91BA5 add esp,8
00B91BA8 mov ecx,eax
00B91BAA call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B9906Ch)]
(int)(isTrue());
00B91BB0 push offset std::endl<char,std::char_traits<char> > (0B9105Fh)
00B91BB5 push offset string "aaa" (0B96B30h)
00B91BBA push dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0B99070h)]
00B91BC0 call std::operator<<<std::char_traits<char> > (0B9129Eh)
00B91BC5 add esp,8
00B91BC8 mov ecx,eax
00B91BCA call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B9906Ch)]
return 0;
00B91BD0 xor eax,eax
}
4.如果加上sizeof,不论C++优化设置如何以及函数内容如何:都不会对函数进行调用
为什么是sizeof操作符:
if only there were some C++ keyword that could syntactically accept almost anything and be guaranteed not to emit any code.
The operand is either an expression, which is not evaluated, or a parenthesized type-id.
源代码:
bool isTrue()
{
std::cout << "aaa" << std::endl;
return false;
}
int main() {
(void)sizeof((isTrue()));
(int)sizeof((isTrue()));
return 0;
}
反汇编:没有对函数进行调用,直接忽略了这两句。
--- h:\vs_project\zigzag\zigzag\zigzag\main.cpp --------------------------------
(void)sizeof((isTrue()));
(int)sizeof((isTrue()));
return 0;
00861B90 xor eax,eax
}
参考资料
http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/