在VB6中用CopyMemory拷贝字符串的种种猫腻二 vb copymemory 字符串
出处:http://blog.csdn.net/slowgrace/archive/2009/09/14/4550116.aspx
第三节 经典错误代码集锦
好的,现在我们可以来看看VB妈妈好心没做好事的几个例子了。先说明一下,以下所有这些例子都来自这个帖子热心朋友的回复,它们都共享第一节里声明的模块级变量和常数。
3.1 我在0楼的代码——结果为何变短
Subtest5()
String1=STR_E
String2=String$(7,0)
CopyMemorypString1,ByValVarPtr(String1),4
CopyMemoryByValString2,ByValpString1,14
Debug.PrintString2'得到的不是PowerVB,而是“Powe”?
EndSub
这个例子的运行过程如下:
(1)第1个CopyMemory得到的是String1的地址,并把这个地址作为源地址传给第2个CopyMemory
(2)第2个CopyMemory从String1的地址拷贝14个字节。由于VB中字符串的内部表示是Unicode,所以这时得到的14个字节的内容是“P- -o- -w- -e- -r- -V- -B- -”(注意,其中的“-”是我加入用来分割字符的,并不真的包括在字符串内存中)。
(3)由于CopyMemory的第一个参数是ByVal String2,是一个字符串,而VB会自动对API函数中的字符串参数做UA转换。所以,系统会把14个字节的Unicode空字符串String2转为7个字节的ANSI空字符串,并存在一个临时变量中,假设叫_tmp。
(4)然后系统把拷来的14字节数据“P- -o- -w- -e- -r- -V- -B- -”向_tmp拷。注意_tmp只有7字节,所以这里有溢出的危险。
(5)由于_tmp只有7字节,所以_tmp实际只得到头7个字节的数据,就是“P- -o- -w- -e-”
(6)最后VB要把ANSI字串再转回Unicode字串,并把转回的结果赋给String2。AU转换就是是将英文的 1 个字节扩张为 2个字节,这样String2最终的内容是“P- - - -o- - - -w- - - -e- -”,用Debug打印出来,可不就是“P o w e”么?
下面这个表总结了上述过程:
另外,我还做了下图来说明上面的过程:
哎,这个图画得太杰出了。一目了然啊!你看,第②步的Unicode字符串硬是被当做ANSI字串在第③步被强行AU了,所以结果“浮肿”了很多(被插入好些空格)。
这个例子如何改对呢?中间两句不改,前后各改/增加一句,如下:
String2 = String$(LenB(String1), 0) '先确保_tmp2长度足够
CopyMemory pString1, ByVal VarPtr(String1), 4
CopyMemory ByVal String2, ByVal pString1, LenB(String1)
String2 = StrConv(String2, vbFromUnicode) '再做UA转换以抵消VB多做的一次AU转换
3.2 阿勇在11楼的代码——结果为何变胖
'阿勇11楼
Subtest8_Yong()
DimpString1AsLong
String1=STR_E
String2=String$(14,0)
CopyMemorypString1,VarPtr(String1),4
CopyMemoryString2,ByValpString1,4
Debug.PrintString2,StrConv(String2,vbFromUnicode)
EndSub
运行上面的程序,你会发现string2的结果也是浮肿的。
(0)第1个CopyMemory获得String1变量指针,并存在pString1里;
(1)第2个CopyMemory首先从pString1里把String1缓冲区地址拷出来;然后把这个地址拷到临时字符串变量_tmp2里,也就是让_tmp2的字符串缓冲区指针指向String1的字符串缓冲区地址;
(2)之后VB把_tmp2的内容做AU转换,并把AU转换的内容拷到一个新分配的字符串缓冲区中,并把String2的字符串缓冲区指针指向这个新分配的地址。
看下面的图,第②步之后_tmp2字符串缓冲区里是来自String1的Unicode字符串,可是它还是在第③步被VB妈妈当初ANSI字符串强行AU,然后再倒手给String2,String2里得到的字符串可不就是浮肿的么:)
这个例子如何改对呢?最后加个String2 = StrConv(String2, vbFromUnicode)把VB多做的那一次AU转换抵消就可以了。
3.2.1 插播:字符串内存的初始化
Q:String2 = String$(14, 0)这一句可以不要么?
A:这一句是必要的,相当于给它初始申请内存。千万别用没分配内存的指针,否则很容易崩溃的。另外,String$(7,0)相当于加入7个Chr(0)字符(vbNullChar),Space$(7)则相当于加入7个Chr(20)字符(空格)。由于VB字符串允许含Null字符,所以这两种初始化方法都可以的。
3.3 Modest在16楼的方法——错误的代码正确的结果
这个方法Modest自己起初的评语是“通俗易懂、还不出错”,打眼一望,我也很赞同这个评语。之后有些细节感觉想不通,请求继续解释。赵老虎分析完之后说“根本就是瞎猫碰上死老鼠”,“实际上是在胡乱操作内存”。刚看到这个评语,我觉得Tiger_Zhao这厮也太那个了,好歹老魏也是VB版一大虾啊。可是看完他的解释之后,我却发现这个评语还真中肯。这里要赞一下Modest,当真好气度,换了别人这么说我,甭管对错,我得先一蹦老高。闲话少说,咱们来看老魏的代码吧。核心的代码如下:
CopyMemory pString1, String1, 4CopyMemory pString2, String2, 4CopyMemory pString2, pString1, LenB (String1)
初看起来,这个代码貌似是分别得到两个字符串缓冲区的指针,然后把String1的字符串缓冲区拷给String2,结果也是正确的。当真“通俗易懂、还不出错”。但是细想想,你会发现这一切都不大靠谱:
(1)首先对于头2个CopyMemory而言,既然VB妈妈会做UA转换,那么pString1和pString2得到的应该分别是_tmp1和_tmp2的地址,而这两个临时变量在CopyMemory调用之后会被释放掉,也就是说pString1和pString2得到的其实是无效的地址(见下图)。
所以这两个语句应该改成这样;
CopyMemory pString1, VarPtr(String1), 4CopyMemory pString2, VarPtr(String2), 4
(2)其次,就算我们把头两个语句改成上面这样,第3个CopyMemory真的在拷贝字符串缓冲区么?看出来了么?要拷贝字符串缓冲区,第3个语句应该加上ByVal,像这样:
CopyMemory ByVal pString2, ByVal pString1, LenB (String1)
(3)可是,诡异的是,就这么一段漏洞百出的代码,它的运行结果明明是正确的啊?这是为什么呢?看下面Tiger_Zhao的解释。
Subtest9_Modest()
DimString1AsString
DimString2AsString
DimpString1AsLong
DimpString2AsLong
'这4个变量每个长4字节,在栈上按地址从低到高为:
'|pString2|pString1|String2|String1|
String1="PowerVB"
String2=Space$(Len(String1))
'String1、String2分别指向两个字符串
CopyMemorypString1,String1,4
CopyMemorypString2,String2,4
'pString1、pString2值为已被释放的临时变量的地址,无所谓
CopyMemorypString2,pString1,LenB(String1)
'由于不加ByVal,其实是从变量pString1的地址向变量pString2的地址复制14个字节
'等于直接操作栈内存,让变量向左复制(看后面插播的“覆盖模式”),于是
'|pString2:原pString1的值
'|pString1:原String2的字符串指针
'|String2:原String1的字符串指针
'|String1:不确定|
Debug.PrintString2
EndSub
3.3.1 插播1:关于栈
(1)变量都是存放在栈中的。这个内存由编译器自动分配释放。
(2)对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。所以先声明的变量的内存地址会比后声明的高。比如上面的示例里那样。
3.3.2 插播2:CopyMemory自动处理覆盖
A: CopyMemory是Copy还是Cut?原来地址中内存的内容还在么?
Q: 复制。源和目标内存不交叉,源不变;如果交叉,还会自动处理覆盖情况。
A: “自动处理覆盖情况”是什么意思?是说:万一交叉了,还会把被覆盖的复原,而且把目的地址挪开点么?
Q: 看下面的例子:
'如果有一个数组a():00-01-02
'a)
CopyMemorya(1),a(0),2
'结果是00-00-01,会避免:先用a(0)覆盖a(1),然后再用a(1)覆盖a(2),最终变成00-00-00
'b)
CopyMemorya(0),a(1),2
'结果是01-02-02,会避免:先用a(2)覆盖a(1),然后再用a(1)覆盖a(0),最终变成02-02-
3.3.3 插播3:VB挂掉的话会有什么后果?
Modest这段代码如果把变量的次序换一下,VB就可能会挂掉。大家可以试一试:P
Q: 这种崩溃有可能波及到整个操作系统么?还是甭管我怎么瞎折腾,把VB关掉就万事大吉?
A: 不可能波及整个系统。因为VB之所以崩溃,就是因为系统搞的鬼。操作系统为了保护自己,会强行关闭它自认为潜在的“威胁”,所以会把VB搞掉。而一旦保护不了,而且出错,就会出现所谓的“蓝屏”。常规情况的话,重开VB就可以了。
Q:在调试状态下用错指针,会导致VB崩溃。那如果是编译成可执行程序,里面有错误的指针操作的话,可能会蓝屏么?
A:通常有进程保护,不会蓝屏。除非你的程序操作了系统级资源。
更多阅读
在Win7系统中添加打印机的方法 xp系统添加网络打印机
在Win7系统中添加打印机的方法——简介Windows7操作系统和之前用户群体庞大的WindowsXP操作系统在操作上大同小异,但是还是有些区别。对于Win7如何添加打印机,很多用户还不是很了解。这里我们做一个简单的教程,教会大家如何在Win7操作
怎么做艺术字体,在word中插入艺术字的方法技巧 word2010艺术字形状
怎么做艺术字体,在word中插入艺术字的方法技巧——简介word中插入艺术字功能还是比较简单易用的,但是很多时候如果不熟悉这系列office办公软件的话,可能一时找不到这个功能所在,下面一步步教大家在word下插入漂亮的艺术字,这在求职简历中
听!是谁在唱歌,还是你心里的盼望 听是谁在唱歌
听!是谁在唱歌,还是你心里的盼望制作人:sea6340(海)当忙碌“沙尘暴”将我们刮得东倒西歪、焦头烂额时,总有一种体贴,让我们不至失去希望;当寒冷的棱角将我们刺得苍白虚弱、遍体鳞伤时,总有一抹温暖,不离不弃将我们拥抱……音乐的作用,无可估
海底捞在美国开店所犯的跨文化管理错误值得引起大家注意 跨文化管理 陈晓萍
去年9月,网上刊登一篇具有意思的反映国内著名火锅企业海底捞在美国开店遇到水土不服的问题的案例文章,文章说海底捞到美国开店,虽然做了一些适合美国本土的改良,但是,犯了几点跨文化管理的常识性错误,被作者称为“水土不服”。我认为可能
净土法门内的种种困惑是什么?为何本是万修万去的法门而往生率却 往生净土神咒念诵
为何素食使皮肤娇嫩有光泽和弹性,而肉食使相貌粗糙丑陋?(从科学角度的分析,不信佛者愿意接受)佛经云:吸烟为魔咒使然!善穴关闭,地狱穴打开,死后堕吸烟地狱!在佛教道场抽烟果报极重(力荐)净土法门内的种种困惑是什么?为何本是万修万去的法门而往生