版权声明:可以任意转载,转载时请务必以超链接形式标明如下文章原始出处和作者信息及本声明
作者:xixi
出处:http://blog.csdn.net/slowgrace/archive/2009/09/14/4550293.aspx
本文来自此帖的冗长讨论,感谢Tiger_Zhao的全程指点和陈辉、阿勇、马云剑等很多朋友的热心参与。本文其他部分在:(一)、(二)、(四)。
再来看这段代码:
CopyMemory pString1,ByVal VarPtr(String1),4 CopyMemory String2,pString1,14
可以说和老魏的代码有异曲同工之妙。看第2个CopyMemory,又是从一个Long型变量地址拷14字节,我打眼一望,判断结果是乱码,可是没成想结果居然是浮肿型的非乱码。不废话,比葫芦画瓢,看下面的代码注释吧:
而这个不会。这个变量次序无论怎么折腾,从pString1拷到的14字节中的头4字节总是String1的字符串缓冲区指针,也就是说_tmp2总是能得到这个指针,虽然后面的10字节就不定是啥了。这个代码貌似在拷字符串缓冲区,可实质上却是通过拷贝字符串缓冲区指针得到了基本正确的结果。这个乱啊……
3.5 我在34楼的代码——中英文混杂的字符串如何算字节数
看下面这段代码。
这段代码的输出结果是我有点S*,没能完全地把“我有点Slow”这个字符串拷出来。这是为什么呢?其实用上面各例的原理完全可以理解这个结果。
只要记住
无论是
Unicode
还是
ANSI
编码,中文字符始终是维持双字节一个字符。而英文字符则是在
UA
转换时,将2个字节
缩减为
1
个字节;
AU
转换时,将1个字节扩张为2个字节。所以,上面的CopyMemory的实际执行过程是:
(1)首先String1C“我有点Slow”被转换为ANSI字符串_tmp1共10字节,所以拷14字节的话,最后4字节是不确定的,不定是啥。
(2)其次String2_7被转换为ANSI字符串_tmp2共7字节。拷14字节到_tmp2,后7字节会溢出(这7字节中的最后3字节内容还不确定)。因此_tmp里只有前7字节,就是"我有点S"。
(3)最后VB把_tmp2转到String2,这是AU转换,只最后填个0,其他长度不变化。因此最后,LenB(String2)=8
(2)其次String2_7被转换为ANSI字符串_tmp2共7字节。拷14字节到_tmp2,后7字节会溢出(这7字节中的最后3字节内容还不确定)。因此_tmp里只有前7字节,就是"我有点S"。
(3)最后VB把_tmp2转到String2,这是AU转换,只最后填个0,其他长度不变化。因此最后,LenB(String2)=8
3.5.1 暴强练习
其次对以下
每一对语句用如下语句在立即窗口观察结果:
Debug.Print String2_7 & "*",Len(String2_7),LenB(String2_7) Debug.Print String2_14 & "*",Len(String2_14),LenB(String2_14)
来看如下语句和注释你是否都能看懂吧:
注:输出结果放在每个语句后的注释中。我在每个字符串的末尾加了个“*”输出结果,以便于数字符串末尾的空格。另外,上面赵老虎的解释中给出的'ANSI编码,实际上指的是_tmp2接受拷贝后得到的值;'Unicode编码则是指VB在结束API调用后String2最后得到的值。
3.5.2 长度不确定?
看上面第2小节赵老虎的详细注释,会发现有好几行的注释说String2的长度不确定。比如例5。这很有点奇怪,String2_14我们已经初始化过长度了啊?而且输出的结果是11和22,也是对的(因为14个字节里有3个中文字符,所以要减3)。那是不是赵老虎弄错了呢?来看赵老虎的解释吧。
Unicode-Ansi 转换时,确定 _Tmp 长度为 14 字节。Ansi-Unicode 转换时,只对这 14 字节进行转换,而 String2_14 根据转换的长度,会重新分配一个字符串,并不是说将结果直接写回旧的字符串内存中。这可以通过调用前后 StrPtr(String2_14) 的变化来确认。所以 String2_14 的长度受到最后 4 个不确定字节的影响。看起来长度确定只不过你的代码中最后 4 字节正好全 0 而已。
下面的例子说明 4 个不确定字节的影响。为了不溢出,只复制 14 字节。为了指定后面的字节,String1C 的 Unicode-Ansi 转换就自己模拟了。
补充提问:
Q:_tmp在AU转换后的长度是谁确定的?
A:那当然是VB确定的。CopyMemory根本不知道转换这回事。
Q:_tmp在AU转换后的长度是如何确定的?是根据chr(0)字符么?
A:BSTR标准中每个字符串的字符串缓冲区之前的4个字节记录了它的长度啊。
3.5.3 如何查看字符串的编码
细心的朋友也许看到上节的注释会问,这些ANSI编码和Unicode编码是如何得到的啊?这个简单,有两种方法。一种是用ASC函数(
这篇博文的最后有详细介绍),另一种是把字符串赋值给Byte数组,然后逐个Hex查看。看以下的代码:
在立即窗口中键入GetUACode("我有点Slow"),可得到如下结果:
第一行和第二行分别是用Byte数组和Asc函数得到的Unicode编码;第一行和第二行分别是用Byte数组和Asc函数得到的ANSI编码;第五行和第六行分别是用我们得到的Unicode编码和ANSI编码复原得到字符串。
从第二行的输出可以看出,汉字“我”的Unicode编码是6211,这从第五行的输出也可以验证。而从第一行的输出可以看出这个编码在内存里的存储方式是1162。想想我们前面提到的小端序,低位在前,这个结果就可以理解了。
可是的话,看ANSI编码又会费解了。比如观察第3行、第4行、第6行的输出,可以发现汉字“我”的ANSI编码CED2在内存里的存储方式是CED2,高位在前,怎么回事?不是说Intel架构的都是小端序么?
呵呵,这其实不是真正的大端序,而是因为多字符集编码的特殊规定导致的。因为对于汉字的ANSI编码而言,无所谓MSB,LSB。它就是把第一个字节理解为Leading Byte,第二个字节理解为另外的编码,所以它们在内存里的存放次序不能倒过来,否则就编码就不能得到正确解释了。(
这个帖子里有相关讨论,感谢AisaC)
3.5.4 插播:Debug.Print String1是不是蕴含了UA转换?
Q:Debug.Print String1是不是蕴含了UA转换?你看,String1在内存里显示的英文都是2字节的,每个英文之后都有个空格,可是打印出来却没有了。
3.6 替换指针法——最有效率的方法?!
直接把String2的字符串缓冲区指针指向String2的,这样只需要拷4个字节,不是很妙么?
(1)最直接的想法是像下面这样,不过它涉及隐含的UA/AU转换
CopyMemory String2,String1,4 'UA/AU转换'
(2)如果不想要这样隐含的UA/AU转换,那就不要传字符串参数,像下面这样:
CopyMemory ByVal VarPtr(String2),4 '无需UA/AU转换'
也可以像下面这样
CopyMemory ByVal VarPtr(String2),StrPtr(String1),4 '无需UA/AU转换'
但是不能像下面这样,自己想为什么吧,呵呵。
CopyMemory StrPtr(String2),4 '目标地址不对
附上完整的代码:
这看起来是最有效率的字符串复制的方法,只需要拷贝4个字节。但是,这样做是
不对的,不仅是不对的,而且是
危险的。因为,在VB中字符串和字符串缓冲区是一对一的,如果把两个字符串变量的缓冲区指向同一个地方,就会导致重复释放同一块内存,而这会引起不可预期的错误。而同时,由于String2原先的缓冲区不再被VB记住,这又会导致内存泄漏。不过,据
赵老虎说,CopyMemory String2,4这种写法可以在参数声明为String时正常使用,待考证。
补充提问
:
Q:那如果我们把String2 = String$(7,"0")这一句拿掉,不给它显式初始化,还会导致重复释放内存和内存泄露么?
A:内存泄露是不会了。但是重复释放内存还是会的。因为在程序接受后,VB并不需要检查字符串变量是否被初始化,只要看有指针值,就要释放字符串缓冲区的空间。
WEI WAN DAI XU