版权声明:可以任意转载,转载时请务必以超链接形式标明如下文章原始出处和作者信息及本声明
作者:xixi
出处:http://blog.csdn.net/slowgrace/archive/2009/04/26/4124347.aspx
本文主要来自此帖的讨论,感谢unsigned、Soyokaze、Chen8013、Tiger_Zhao、m60a1、myjian、PctGL等朋友的指点,本文很多文字直接来自他们的发言。
在VB中指针的算法非常诡异,你必须计算元素的大小,因为VB不会帮你完成这项工作。
1、引言:VC指针和VB指针(来自unsigned)
指针只是一个形象的说法,实际在内存当中就是一个地址。VB没有C语言的指针类型,从而带来的问题是,在VB中,对你声明的长整形指针变量自加自减只是机械地加了“1”而已,并没有像C编译器那样,会根据指针指向的数据类型自动地加减这个数据类型的字节数。比如:
int *p = a;
p ++;
a的每个元素是两个字节,p自加后,指向a[1]。因此,如果是在VB里,想指向a(1),需要这样:
a( 0) = 99
a( 1) = 88
Dim p As Long
p = VarPtr( a( 0))
p = p + 2
再如C/C++等当中,当定义好一个结构指针类型之后,进行指移偏移运算的时候,会以结构大小进行偏移移位:
unsigned char x;
unsigned char y;
short reserved;
int z;
} MyStruct , * LPMyStruct;
LPMyStruct P;
P = 0;
P ++; //这里是P = (LPMyStruct)((unsigned long)P + sizeof(MyStruct)),而不是P = (LPMyStruct)((unsigned long)P + 1);
而在VB当中则完全需要自己对指针偏移进行正确计算,这种计算可以借助于API,以及VarPtr、StrPtr以及address of等等操作符做到。不过在这之前,我们最好还是熟悉一下VB使用内存的方式。
2、VB使用内存的方式
2.1 按字长对齐(来自unsigned)
主流编译器为了内存访问的优化,会按机器字长进行内存对齐,从而导致某些结构例如:
unsigned char x;
unsigned char y;
int z;
} MyStruct;
看上去只有 sizeof(unsigned char) + sizeof(unsigned char) + sizeof(int) = 1 + 1 + 4。但是在Win32平台下,在没有强制做存储压缩的情况下,编译器却实际上让它占用了8字节的空间。x和y各占一个字节,所以x和y被分到同一个机器字长的空间当中;由于z是int,刚好是一个机器字长,为了访问z的时候能够一次性读入cache,而不是先从第一个机器字长的空间当中读取半个z,然后再读取第二个机器字长的空间才读取到后半个z,编译器就把z直接放到了下一个机器字长的空间当中,从而就导致了y和z中间隐藏了2个字节的空间。
而在有的编译器当中,数据是压缩存储的,也就是说
x as byte
y as byte
z as long
End Type
这样一个结构,在有的编译器当中就是6个字节。从而如果你在调用一个API的时候,刚好碰到上面的C结构,那么你就很有可能会把一个在C当中占了8字节的结构定义成了只有6个字节。如此你在调用API的时候,就会导致内存访问溢出,后果是非常严重的。
2.2 堆和栈
通常,局部变量是放在栈中的,动态分配的内存则是放在堆中的。如果你声明一个含动态数组成员的结构,那么结构中指向数组注册地址的指针是在栈中的,4字节。而数组的具体内容则是在堆中的,一般是连续存放的。详见堆与栈的区别(1)和堆和栈的区别(2)。栈里的内存的对齐方式,通常由编译器决定;堆里内存的对齐方式,通常由操作系统决定。
注意1:数组元素的存放应该是连续的,只是首址按双字对齐。也就是,数组的第一个元素的地址是双字对齐的,后续元素则不再按双字对齐,而是依次连续不间断地排放,中间没有填充空闲字节。既使是结构数组也是这样(未经xixi验证)。
注意2:VB6中动态数组和字符串是动态分配内存的。也就是变长字符串运算和redim动态数组是要重新分配内存的,重新Redim时,各元素地址会发生变化。
2.3 有关无符号长整型
此外,在VB中运用指针,我们还必须处理缺乏无符号长整型数据类型的问题,详见这篇文章。
下面让我们通过几个例子来体会VB中指针位移的计算:
3、一个关于局部变量的内存分配的例子(来自Chen8013)