同事的一段代码里出现的异常,内存访问错误。遂一起排查。使用的是VC6.0,系统是Win2K。
主调函数func1,传进一个数组的首地址。用数组的下标方式访问,下标是一个子函数func2的返回值。异常出现在这里。一开始怀疑传进来的地址有问题,跟踪下来发现是对的。我怀疑func2的返回值,但看到这个函数很简单,就一句返回一个结构的WORD成员,好象不应该有错。
func2有一个参数,是一个结构指针lp,函数体是return lp->val。函数声明返回WORD类型,结构的成员val也是WORD类型。
在出事地前加了个printf("%x/n",func2(p)),打印func2的返回值。结果说明了出错原因,打印值是1520000。很明显,func2返回了一个DWORD值。访问数组越界,所以异常。
为什么会这样呢?为什么函数返回类型是WORD,使用时却得到一个DWORD值呢?
做了一个尝试,如果把返回值赋给一个WORD类型的局部变量,得到的值是对的;如果加上类型转换(WORD)func2(p)也是对的。
分别看一下这三种情况(一错二对)的汇编码,可以发现,正确的情况,在func1中使用func2的返回值前有一句:
0040B7F2 and eax,0FFFFh //清高16位。
因为在调用func2时,结构指针参数的传递用了32位的eax。func2的汇编码中可以看到,需返回的WORD值放入了16位的ax。所以在func1使用前清高16位,使用的仍然是eax,但值是所需的正确值。
而错误的情况,即不加转换的直接使用,没有这么一句清高位。赋值的时候也没有使用movzx零扩展指令,直接用mov到eax。
0040B7E6 mov edx,dword ptr [ebp-4]
0040B7E9 push edx
0040B7EA call @ILT+5(_func) (0040100a)
0040B7EF add esp,4
0040B7F2 and eax,0FFFFh //出错的代码没这句。
0040B7F7 push eax
0040B7F8 push offset string "a = %d/n" (00420f74)
0040B7FD call printf (00401090)
0040B802 add esp,8
怀疑是编译器的bug。于是做一个实验。新建一个project,模拟了这个func1和func2。结果的正确的。即使是不加转换的直接使用汇编码里也有清高位,返回的值不会受干扰。更加疑惑。
继续实验。怀疑实际的那个结构有问题。把实际project中的结构移植到实验project中。却也是正确的。
怀疑project的设置有问题。把实验中的结构和函数移植到实际project中,又是正确的。郁闷。
改变此成员在结构中的定义位置,怀疑没有对齐,虽然这不应该是程序员关心的,但也没有发现区别。改变此成员的类型(DWORD,BYTE),都没有找出问题所在。至此,技穷。
希望能找到问题的根源。