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/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
《仙剑奇侠传》之父姚壮宪热情推荐,技术作家孟岩高度评价! 云风也是我在国最佩服的游戏开发者。看了云风的研发历程,我觉得就是一部国的游戏程序史,从最早的 Z80 , 6502 , PC8088 , 286 , 386…DOS ,保护模式, Assembler 到 C++ 的整个发展轨迹。这本书可以说横跨了游戏程序的过去、现在和未来。 书传达的不仅是一些实用的技术经验,更是传达一种理念——虽然研发的环境随着时代而变,但研发的精神是不变的,那就是“在实践积累”。 ——《仙剑奇侠传》之父 姚壮宪 之前我经常奇怪,云风还非常年轻,他程序思想的那种老练的智慧是从何处得来的呢?读完这本书之后,我终于明白,还是那句话:“无他,唯手熟耳”。 面对这沉甸甸的作品,我确实感到,这是云风用心写的书。用心写的书,当然出色。 ——技术作家 孟岩 我现在是国并不成熟的游戏制作行业的一员,游戏给了我太多,我告诉自己需要做一事情。分享知识和经验是我的义务,别无它。 ——云风 内容简介 本书忠实地记录了作者十余年来对游戏编程的所思、所感、所悟。全书按照作者本人学习和实践的过程,带着读者从基础的计算机知识到高级的编程技术,从非常专业的汇编优化到非常实际的项目管理进行了一次游戏开发的全景探索。 本书不仅适合游戏开发者阅读,也会给所有的开发者和程序爱好者带来启示。 作者简介 云风,时年二十七岁。自幼学习编程,十数年从未间断,对程序设计有所领悟。大学时代开发的游戏图像引擎“风魂”曾用于多家游戏公司的游戏项目。参与过《大话西游》系列、《梦幻西游》、《网易泡泡游戏》的开发。现从事新一代网络游戏引擎的研究与开发,并在游戏模式上做一些新的尝试。 性格开朗,兴趣广泛,好交友,绝非沉浸在计算机世界的书呆子。国学、历史书籍常备案头,以先贤之教诲修其心;休息时常作户外运动,尤其喜爱攀岩。 目录 第1章 计算机,游戏,我 1 1.1 计算机 2 1.2 计算机游戏 3 1.3 计算机与我 7 1.3.1 启蒙 7 1.3.2 编程 9 第2章 算法,程序的灵魂 13 2.1 程序=算法+数据结构 14 2.1.1 算法 15 2.1.2 数据结构 17 2.2 搜索算法 23 2.2.1 地图寻路问题 23 2.2.2 博弈问题 27 2.2.3 更为广泛的运用 28 2.3 智能算法 29 2.3.1 遗传算法(Genetic Algorithm) 29 2.3.2 模拟退火算法(Simulated Annealing) 31 2.3.3 禁忌搜索(Tabu Search) 33 2.3.4 人工神经网络 (Artificial Neural Network) 34 2.4 优化 36 2.4.1 质数问题 36 1.4.2 俄罗斯方块竞赛 37 2.5 Apple II上的编程之路 39 第3章 编程语言 45 3.1 C 语言 46 3.2 BASIC 50 3.3 C++ 51 3.4 汇编语言 54 3.4.1 概述 55 3.4.2 程序的本质 57 3.4.3 寄存器 58 3.4.4 寻址方式 60 3.4.5 汇编指令 61 3.4.6 C/C++ 语言和汇编 62 3.4.7 小结 63 3.5 其他语言 63 3.5.1 Forth 63 3.5.2 Lisp 64 3.5.3 Java 64 3.5.4 Python、Lua、更多 65 第4章 前Windows 时代 67 4.1 386保护模式 68 4.2 VGA 到VESA 70 4.2.1 超越 BGI 70 4.2.2 VGA 72 4.2.3 VESA 标准 72 4.2.4 花絮 74 4.3 保护模式下的开发工具 75 4.4 闲话 Allegro 81 4.4.1 用C与汇编写成的程序库 81 4.4.2 BITMAP 82 4.4.3 Sprite 85 4.4.4 几何图形和 3D 89 4.4.5 数据文件 91 4.4.6 声音 92 4.4.7 其他的部分 93 4.4.8 小结 94 4.5 cfido 国惠多网 94 第5章 Windows 编程 101 5.1 Windows编程入门 104 5.1.1 Windows版本综述 105 5.1.2 操作系统的核心 107 5.1.3 Windows API和DLL 110 5.1.4 COM 111 5.1.5 Windows的窗口和消息处理与传递 114 5.1.6 Windows GDI 125 5.2 控制游戏的速度 130 5.3 浅谈MFC 132 5.4 小结 132 第6章 汇编优化 135 6.1 浅谈代码优化 138 6.2 并不仅仅是汇

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值