VBS中&H前缀十六进制数的陷阱

标签: , , , ,

16进制在编程中是很普遍的,在VBScript中,以“&H”前缀来表示十六进制数。

例如:

'Author: Demon
'Website: https://demon.tw
'Date: 2012/5/7

Const cr = ChrW(&HD) 'Carriage return
Const lf = ChrW(&HA) 'Line feed

Const HKCR = &H80000000 'HKEY_CLASSES_ROOT
Const HKCU = &H80000001 'HKEY_CURRENT_USER
Const HKLM = &H80000002 'HKEY_LOCAL_MACHINE
Const HKU  = &H80000003 'HKEY_USERS
Const HKCC = &H80000005 'HKEY_CURRENT_CONFIG

Const Normal     = &H000 'Normal file
Const ReadOnly   = &H001 'Read-only file
Const Hidden     = &H002 'Hidden file
Const System     = &H004 'System file
Const Volume     = &H008 'Disk drive volume label
Const Directory  = &H010 'Folder or directory
Const Archive    = &H020 'File has changed since last backup
Const Alias      = &H400 'Link or shortcut
Const Compressed = &H800 'Compressed file

当这些以&H为前缀的十六进制值比较小或者仅用于位运算时,一切都很正常。但是当它们的值比较大时并且参与运算时,噩梦就开始了:

问题代码一:

'Author: Demon
'Website: https://demon.tw
'Date: 2012/5/7

For i = 0 To &HFFFF
    WScript.Echo ChrW(i)
Next

这段代码的本意是输出0到65535之间的Unicode字符,但是运行后却没有任何输出。

问题代码二:

'Author: Demon
'Website: https://demon.tw
'Date: 2012/5/7

uni = 55401
'   &HD800 = 55296      &HDBFF = 56319
If (uni >= &HD800) And (uni <= &HDBFF) Then
    WScript.Echo uni
    'do something
End If

这是一个条件判断语句,看似符合条件,结果还是没有任何输出。

问题代码三:

'Author: Demon
'Website: https://demon.tw
'Date: 2012/5/7

WScript.Echo AscW("魔")
WScript.Echo MyAscW("魔")

Function MyAscW(s)
    Dim n
    n = AscW(s)
    If n < 0 Then
        n = n + &HFFFF
    End If
    MyAscW = n
End Function

如果字符的代码点(code point)大于32,767,那么AscW函数会返回负数(详见《AscW函数返回负数的问题》)。自定义函数MyAscW的本意是AscW的改进版,但是依然返回了负值。

例子就举那么多,不知道你看出问题所在了吗?没看出来也不要紧,让我们一起来终结这个噩梦。

首先复习一下基础知识,VBS中的整数类型分为Integer和Long,Integer的范围是-32,768到32,767,Long的范围是-2,147,483,648到2,147,483,647。可以知道,Integer是16位的,Long是32位的,并且两者都是有符号数。

当&H前缀十六进制数比较小时,也就是&H0000到&H7FFF时,正好处于Integer正数的范围之内。但是&H7FFF的下一个数&H8000(十进制32768)超出了Integer所能表示的正数,这时候就会变成-32768,为什么呢?

十六进制的8000转成二进制是1000000000000000,16位中除了最高位是1以外,其他位都是0。最高位是1,你想到了什么?没错,因为Integer是有符号数,所以最高位是符号位,0表示正数,1表示负数,所以得到的值当然是负数。于是从&H8000到&HFFFF之间的十六进制数都是负数,依次为-32768到-1。

数字继续增大,&HFFFF的下一个数&H10000已经不能用16位表示了,此时就用32位的Long表示,原理与Integer一样。所以从&H10000到&H7FFFFFFF都是正数,对应32,768到2,147,483,647;&H80000000到&HFFFFFFFF都是负数,对应-2,147,483,648到-1。

现在32位都已经用完了,VBS没有64位的整数,所以&H100000000及其之后的数会报错:Microsoft VBScript compilation error: Syntax error。

OK,原理讲完了,现在看上面的代码应该知道错在哪里了。那么怎么解决呢,难道所有用&H十六进制数参与运算的地方都要改成十六进制不成?

这的确是解决方法之一,只不过如果数值比较大的话代码会很长很难看而已,而且十进制看起来没有十六进制那么直观。

这里有一个我无意中搜索到的undocumented的技巧,那就是在&H十六进制数后面再加上一个&,强制转换成Long类型,这应该是从VB那里继承下来的吧。

'Author: Demon
'Website: https://demon.tw
'Date: 2012/5/7

x = &HFFFF
y = &hFFFF&
WScript.Echo x, TypeName(x)
WScript.Echo y, TypeName(y)

顺便提醒一句,这个技巧对于&H80000000到&HFFFFFFFF是没有用的,因为它们本身就已经是Long了。

最后的最后,说一个很奇怪的地方,在&H0000到&HFFFF范围内,除了&H8000(-32768)之外,其他数的类型(用TypeName函数)都是Integer,而&H8000的类型却是Long:

'Author: Demon
'Website: https://demon.tw
'Date: 2012/5/7

x = &H8000
WScript.Echo x, TypeName(x)

可如果它是Long的话,应该是32768才对啊,为什么还会是负值呢?百思不得其解,如果哪位高人知道,请留言指点一下。

参考链接:http://social.technet.microsoft.com/Forums/fi/ITCG/thread/673d7646-2fbb-45f2-be67-91717589a6f5

赞赏

微信赞赏支付宝赞赏

随机文章:

  1. 用JavaScript实现PHP的basename函数
  2. PHP base_convert函数的一个有趣现象
  3. 用VBS下载文件
  4. VBS实现Unicode(UTF-16)转UTF-8
  5. cURL 7.22.0 with zlib SSL and IPv6 support

7 条评论 发表在“VBS中&H前缀十六进制数的陷阱”上

  1. wuye说道:

    很有钻研精神,不知道有没有找到实习单位?

  2. PopEye说道:

    问题代码三中的n = n + &HFFFF貌似少加了1

  3. Sasha说道:

    我居然奇迹般地看懂了这篇文章!你的问题我也在纳闷。。
    另外,VBS不是解释执行的吗?怎么会有compilation error呢?

  4. […] 答案见《VBS中&H前缀十六进制数的陷阱》。 […]

  5. tary说道:

    ‘Author: Demon
    ‘Website: http://demon.tw
    ‘Date: 2012/5/7

    x = &H8000
    WScript.Echo x, TypeName(x)

    &H8000的integer值是-32768,转换成long时要符号扩展的,还是-32768,即&HFFFF8000

  6. 773827986说道:

    Set FSO = CreateObject(“Scripting.FileSystemObject”)
    Set FileObj = FSO.CreateTextFile(“E:\123.EXE”, True)
    FileObj.Write Chr(CLng(“&H90”))
    显示的是问号3F,请问怎么写入是实实在在的16进制数据90呢

  7. abc说道:

    &H8000的integer值是-32768,确实应该是是符号扩展的原因,符号扩展,让符号高位全为1,仍然是负数。C++里面用“unsigned”去除符号位,不知道VBS里有没有类似语法?

留下回复