登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

秒大刀 博客

好好学习 天天向上

 
 
 

日志

 
 
 
 

MACRO max & MACRO min  

2008-11-15 18:13:22|  分类: C/C++ |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

 MACRO max & MACRO min

早闻宏max和宏min的副作用非常严重。《Effective C++》简体中文第三版“条款03:尽可能使用const”Page16页有详细的例证:

//以a和b的较大值调用f

#define CALL_WITH_MAX(a, b) f((a) > (b)) ? (a) : b())

这般长相的宏有着太多的缺点,光是想到它们就让人痛苦不堪。

无论何时当你写出这种宏,你必须记住为宏中的所有实参加上小括号,否则某些人在表达式中调用这个宏时可能会遭遇麻烦。但纵使你为所有的实参加上小括号,看看下面不可思议的事情:

int a = 5, b = 0;

CALL_WITH_MAX(++a, b); //a被累加二次

CALL_WITH_MAX(++a, b+10); //a被累加一次

在这里,调用f之前,a的递增次数竟然取决于“它被拿来和谁比较”!

好了,现在看看VS2008自带的头文件中关于max和min宏定义:

Windef.h line 187

#ifndef NOMINMAX

#ifndef max

#define max(a,b) (((a) > (b)) ? (a) : (b))

#endif

#ifndef min

#define min(a,b) (((a) < (b)) ? (a) : (b))

#endif

#endif /* NOMINMAX */

这些宏简直要和《Effective C++》所鄙视的做法一模一样了!如果用到了max和min宏,切记切记不要在传参的时候做任何可能带来副作用事情。我的建议是,如果用到max和min宏,仅仅传给他们简单的数字,包括+1这样的运算都最好在传入之前计算完毕。

下面再看看max和min宏对性能产生的不利影响。不利影响?宏不是因为避免了函数调用而提高了性能吗?答案是否。宏就是宏,简单的文字游戏,性能是需要程序员去控制的,而不是宏。测试代码如下(为了避免max、min宏和std::max、std::min名字冲突,我把它们添加了前缀“macro_”):

#ifndef macro_max

#define macro_max(a,b) (((a) > (b)) ? (a) : (b))

#endif

#ifndef macro_min

#define macro_min(a,b) (((a) < (b)) ? (a) : (b))

#endif

#include <iostream>

void main()

{

const char* str = "012345";

int n = 0;

n = macro_min(strlen(str), 100);

std::cout << n << std::endl;

n = std::min(strlen(str), 100U);

std::cout << n << std::endl;

}

下面是VS2008 RELEASE下的汇编代码(下面代码的批注我以“@”开头了):

#ifndef macro_max

#define macro_max(a,b) (((a) > (b)) ? (a) : (b))

#endif

#ifndef macro_min

#define macro_min(a,b) (((a) < (b)) ? (a) : (b))

#endif

#include <iostream>

void main()

{

const char* str = "012345";

int n = 0;

n = macro_min(strlen(str), 100);  @先看看宏的表现如何

00401000 mov eax,offset string "012345" (402114h)  @给strlen压入参数,开始strlen的调用。经过VS强大的优化之后,strlen被完全内联了,所以你看不到call了

00401005 sub esp,8

00401008 lea edx,[eax+1]

0040100B jmp main+10h (401010h)

0040100D lea ecx,[ecx]

00401010 mov cl,byte ptr [eax]

00401012 inc eax

00401013 test cl,cl

00401015 jne main+10h (401010h)

00401017 sub eax,edx    @第一次的strlen调用到这里结束

 

00401019 cmp eax,64h  @这里是宏中那个三目运算符“?”的执行,64h就是源码中的100

0040101C jae main+31h (401031h)

 

0040101E mov eax,offset string "012345" (402114h)  @重新给strlen压入参数,并开始strlen的调用

00401023 lea edx,[eax+1]

00401026 mov cl,byte ptr [eax]

00401028 inc eax

00401029 test cl,cl

0040102B jne main+26h (401026h)

0040102D sub eax,edx  @到这里第二次的strlen调用结束

 

0040102F jmp main+36h (401036h)

00401031 mov eax,64h

std::cout << n << std::endl;  @这句必须有,否则前面后面的代码都会被编译器优化给裁掉的,这句cout告诉编译器“刀下留人”。不信的话你可以自己试一下。

00401036 mov ecx,dword ptr [__imp_std::endl (402040h)]

0040103C push ecx

0040103D mov ecx,dword ptr [__imp_std::cout (402044h)]

00401043 push eax

00401044 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (40203Ch)]

0040104A mov ecx,eax

0040104C call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (402038h)]

 

n = std::min(strlen(str), 100U);    @下面该STL std::min模板算法表演了

00401052 mov eax,offset string "012345" (402114h)  @同上,给strlen压入参数

00401057 mov dword ptr [esp],64h

0040105E lea edx,[eax+1]

00401061 mov cl,byte ptr [eax]

00401063 inc eax

00401064 test cl,cl

00401066 jne main+61h (401061h)

00401068 sub eax,edx  @strlen到此执行完毕

 

0040106A mov dword ptr [esp+4],eax  @随后比较比较结果就出来了

0040106E cmp eax,64h

00401071 lea eax,[esp]

00401074 ja main+7Ah (40107Ah)

00401076 lea eax,[esp+4]

 

std::cout << n << std::endl; @同上,此处的cout不可省略啊

0040107A mov edx,dword ptr [__imp_std::endl (402040h)]

00401080 mov eax,dword ptr [eax]

00401082 mov ecx,dword ptr [__imp_std::cout (402044h)]

00401088 push edx

00401089 push eax

0040108A call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (40203Ch)]

00401090 mov ecx,eax

00401092 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (402038h)]

}

00401098 xor eax,eax  @这显然时main的返回代码了

0040109A add esp,8

0040109D ret

仅以最直观的方式就可以发现,即使编译器进行了大量的优化,macro_min还是对strlen(str)这个函数进行了两次调用,而这是和程序员本应想表达的意思相违背的。既然已经违背了程序员的本意,那啥事还不能发生呢?如果是strlen还好说,至多浪费一次时间复杂度为O(n)的函数调用,如果macro_min(f1, f2)中的f1、f2可以修改程序的状态,那多执行一次会带来什么后果恐怕只有崩溃后去猜了。

鉴于以上血淋淋的事实。Windows下编程,请不要再使用max和min宏了。在你的预编译中加入NOMINMAX定义、或者将NOMINMAX加入到预编译头文件、或者显式的采用#undef max #undef min直接将二者干掉。转而#include<algorithm>,但别using namespace std;很嚣张的写上std::max or std::min,明确的告诉编译器和你自己,你调用的是STL的算法。

  评论这张
 
阅读(2048)| 评论(0)

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018