前言

前言

坑源,在于其具有二义性
打开网易新闻 查看精彩图片
坑源,在于其具有二义性

各位熟悉VB/VBA的童鞋们,对Boolean肯定是再熟悉不过了,因为它是匹配计算机二进制世界的逻辑门,任何算法逻辑的推演都离不开Boolean的支持。但是,对于VB/VBA中的Boolean坑点,大家都有踩到过吗?

Boolean导致的Bug,在对Boolean缺乏深入的认识前,是极难发现的。笔者就曾吃过不少亏,本文就将这些吃亏的经历,以及如何从根本上避免,给大家做个分享。期待能对大家有所帮助,少走弯路。

一、一起来踩坑

一、一起来踩坑

Boolean类型,看上去很简单,要讲起来似乎也凑不出个123来,渐渐地便理所当然起来。比如在If语句中,要么为了简洁省事,要么为了效率,就有如下变化:“if Var<>0 then”逐步写成 “if Var then”,这的确可以提高效率。于是“if not (Var<>0) then”是不是顺手写成了“if not Var then”?

整数,才是计算机的真相
打开网易新闻 查看精彩图片
整数,才是计算机的真相

在《VB的整数,你真的了解?》中,已经提醒大家要谨慎了。若不信,可以将Var随便赋值一个非0值,来测试测试吧。怎么样?原以为可以提高效率,没想到掉到坑里了吧。

再来看VB/VBA中使用API时的坑吧。在《VB变量的重构,横看成岭侧成峰》中给大家讲到,VB/VBA可将1、2、4字节类型传递给Long类型。而API的参数几乎都是Long类型的,因为指针可用该类型表示,1、2、4字节的数值类型更可以使用该类型表示(具体可参考《VB中Byte、Boolean和Integer与Long的开销及性能相同吗?》)。这使得在VB/VBA中声明API函数时,有极大的灵活性,对参数类型可以自作主张地进行本地化改造。比如,可以将字符串缓冲区指针,声明为String类型,从而避免使用VB中不方便的指针。

很多API函数会使用Bool类型,通过非0整数表示True,用0表示False。那在VB/VBA中声明使用这些API函数时,为了简单明了,自然可以声明为Boolean类型,相信大家经常这么干。通常情况下,不会遇到什么麻烦。但是,在极少数情况下,这里面的坑就会让人很难受。

比如判断指定进程是32位还是64位,通常会使用IsWow64Process函数。其第二个参数(Wow64Process),通常当进程运行在64位系统的Wow64环境中时,会返回True。若在VB/VBA中将该参数声明为Boolean类型,而恰巧又对于该参数的返回值,使用了Not 运算符来表示与64位的匹配性。如"Not Wow64Process",期望Wow64Process=True时表示非64位,Wow64Process=False时表示64位。

如果读者,在任务管理器中选择标识为32位的进程,跟着去做个验证,很快就会发现结果并不正确。利用VB/VBA强大的逐句调试功能,很快就会发现,"Not Wow64Process"中,Wow64Process明明为True,但这个"Not True"的结果仍然为True。这让人百思不得其解,甚至怀疑Not运算符是不是有什么讲究,或者VB/VBA本来就很那个才有如此漏洞。

如果,不深入了解VB/VBA中的Boolean,即便找到了Bug所在,也只会怀疑人生,甚至对VB/VBA失去好感。接下来,让我们看看,究竟是怎么一回事呢?

二、重新认识VB/VBA中的Boolean

二、重新认识VB/VBA中的Boolean

一提到Boolean,大家自然会想到True和False。没错,通常我们将Boolean与True/False进行等同,并混淆使用,这也是错误发生的根源所在。True和False是VB/VBA中定义的两个常量,其中True为-1(0xFFFF),False为0(0x0000)。如有怀疑,可以使用Hex函数进行验证。在以前的《VB的整数,你真的了解?》中也对此进行了分析,False的定义几乎是通用的,唯独要谨慎True的定义。

根据VB/VBA对True的定义,Not True的确为False。或许当年的设计者们,就是为了获得这种理解上的统一吧。但是,Boolean类型却是和系统定义一致的,也即非0为True,0为False。为了更清楚地说明问题所在,可以定义1个Boolean类型的变量uBool,uBool=1之后,发现uBool的结果为True,但Not uBool却仍然为True,显然与前面提到的Bug如出一辙。

我们知道VB/VBA中的Boolean类型,其实是个2字节有符号整数(详见《VB的整数,你真的了解?》)。前述uBool尽管结果为True,但其内存里装的却是1,Not uBool其实就是Not 1,这结果怎么可能为0呢!同样,对于前面提到的Bug,若用Hex函数查看Wow64Process参数的返回值,发现这个True,其实就是1。

这样一来,VB中的True就容易产生“二义性”,这就是关于Boolean有关坑爹的源头。总之一句话,Boolean类型变量的True并不能等同于True常量。

三、如何避免踩到Boolean的坑

三、如何避免踩到Boolean的坑

既然知道VB/VBA中的True具有“二义性”,那就得弄清里面的原因,否则又该怪罪VB/VBA了。

对于Boolean类型的变量而言,VB/VBA的封装机制,让其将非0都解释为True,但这个True是个解释用的临时值,它并不会更改变量内存值为True这个常量值。当我们使用监视功能时,显示的就是这个临时值。这个临时值,是不会参与针对变量内存的相关运算的。

我们外面不一样,里面一样
打开网易新闻 查看精彩图片
我们外面不一样,里面一样

至此,其实已经很清楚了,无论是常量还是变量,都要从内存结构上去理解。尤其是VB/VBA为了提高易用性,在封装层面进行了大量的拟人化,这很容易让使用者弄不清里面的原委。其实,这个“二义性”正是常量和变量的混淆导致的,如果理解清楚了,也就没有所谓的“二义性”了。

如此,再回头来看,就清楚为什么“if uBool then ”与“if uBool<>0 then”等效且更高效了。uBool和uBool<>0均为条件表达式,条件表达式是需要计算出结果的,不存在临时结果一说。前者的计算结果就是解释值True,而后者不仅需要解释uBool,而且解释值还要参与比较运算。那“if not uBool then”为什么会存在问题呢?因为Not是按位取反,会涉及uBool变量的内存,使用的自然是uBool的内存值,而非解释值呀。

怎么样,看到这里有没有眼前一亮?欢迎关注BtOfficer,阅读更多有意思的知识点。各位读者在使用VB/VBA(其他语言,需等承诺的相关工具放出后,再分享)过程中,若有任何困惑可留言哦,将视情况进行专题解答分享哦。