在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: 看下面的例子:

在VB6中用CopyMemory拷贝字符串的种种猫腻(二) vb copymemory 字符串
'如果有一个数组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:通常有进程保护,不会蓝屏。除非你的程序操作了系统级资源。

  

爱华网本文地址 » http://www.413yy.cn/a/25101011/84173.html

更多阅读

在Win7系统中添加打印机的方法 xp系统添加网络打印机

在Win7系统中添加打印机的方法——简介Windows7操作系统和之前用户群体庞大的WindowsXP操作系统在操作上大同小异,但是还是有些区别。对于Win7如何添加打印机,很多用户还不是很了解。这里我们做一个简单的教程,教会大家如何在Win7操作

听!是谁在唱歌,还是你心里的盼望 听是谁在唱歌

听!是谁在唱歌,还是你心里的盼望制作人:sea6340(海)当忙碌“沙尘暴”将我们刮得东倒西歪、焦头烂额时,总有一种体贴,让我们不至失去希望;当寒冷的棱角将我们刺得苍白虚弱、遍体鳞伤时,总有一抹温暖,不离不弃将我们拥抱……音乐的作用,无可估

声明:《在VB6中用CopyMemory拷贝字符串的种种猫腻二 vb copymemory 字符串》为网友刺青刺心分享!如侵犯到您的合法权益请联系我们删除