assert宏实现中的注意点

本文深入探讨assert宏的不同实现版本,从最初简单的版本到最终完善的版本,并解析每一步改进的原因。文章还介绍了cast-to-void和sizeof操作符在assert宏中的应用,以及它们如何帮助消除警告并避免不必要的函数调用。

  断言assert是由宏来实现的,debug下生效,release下不生效。当我们用这个宏的时候不会在意,其实细看他的实现还是蛮有意思的。这个实现里面涉及好多小技巧以及要避开的陷阱,cast-to-void,sizeof,大括号,do……while。本文第一部分先介绍了,assert实现之路,一版比一版完善。第二部分,第三部分则详细解释了为什么要这么实现的原因,中间列举了例子以及涉及到了编译器的一些设置选项。

 

目录

一:assert的宏实现版本进化(只关注#else 分支的时候,即asset不生效的时候)

版本1.会出现警告版:#define POW2_ASSERT(x)   

版本2.消除警告版,但会有函数执行的副作用

版本3.最终版,无警告无副作用

二:对变量进行cast to void ,用来忽略变量

三:对函数调用进行 cast to void以及sizeof的作用

1.当C++优化设置为禁用:会对函数进行调用

2.当C++优化设置为O2:不会对函数进行调用

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/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ivy_0709

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值