从指针的使用角度看数组与结构体
数组与结构体有什么区别和联系?如果要找到他们的本质联系我们通过什么途径?下面分别通过多维数组和结构体的几个实例来分析两者之间的关系.
(一) 一维数组与结构体
main()
{
int IntValue;//暂时存放从数组中取出来的值
int a[3]={1,2,3};
int *p;
p=a;
IntValue =*(p+2);
对于数组a,系统在内存中开辟了3*4字节的连续空间来存放.
对于一维数组的访问一般通过下标.
如:a[0]=1;a[2]=3
不过为与后面的结构体建立联系我们通过指针来访问,
我们知道,对于一维数组来说,数组名就是这个数组的首部地址,他指向某种数据类型对应大小的内存块.如:int a[3],则地址a指向一个字节数为4的内存块.显然对其进行单位量移动表示每移动一个单位量代表了移动4字节.用指针访问则:
int *p;
p=a;
则:*p=0(等价于a[0]);*(p+2)=3(等价于a[2]);
内存中结构如下:
├──────┤← p
│ a[0] │
├─┄┄┄┄─┤← p+1
│ a[1] │
├─┄┄┄┄─┤← p+2
│a[2] │
├─┄┄┄┄─┤
5: int IntValue;
6: int a[3]={1,2,3};
0040E9C8 mov dword ptr [ebp-10h],1//注【1】
0040E9CF mov dword ptr [ebp-0Ch],2
0040E9D6 mov dword ptr [ebp-8],3
7: int *p;
8: p=a;
0040E9DD lea eax,[ebp-10h]
0040E9E0 mov dword ptr [ebp-14h],eax
9: IntValue =*(p+2);
0040E9E3 mov ecx,dword ptr [ebp-14h] //注【2】
0040E9E6 mov edx,dword ptr [ecx+8]
0040E9E9 mov dword ptr [ebp-4],edx
main()
{
int IntValue;
struct student
{
int num;
char ***;
int score;
};
struct student jesse={2316124029,'M',98};
struct student *p;
p=&jesse;
IntValue=*((int *)p+2);//说明2
这个结构体定义了3个成员变量,字节数分别为4,1,4,和数组一样,他们在内存中也占据一组连续的存储空间,但是要注意的是这里他们一共占据的空间并不是4+1+4=9,这里涉及到”对齐原则”,编译器为提供内存访问速度,要保证结构地址满足4字节的对齐要求,所以系统用了4个字节来存放char类型,也即浪费了3个字节的空间,因此,这里总共占据空间为12字节.
同样,对结构体的访问一般为:
jesse.num=2316124029或jesse.***=M.
同样我们也可以定义个指针来对起成员变量进行访问
struct student *p;
p=&jesse;//将结构体的首部地址赋给指针变量p
则: *((int *)p)=2316124029 (等价于jesse.num); *((int *)p+2)=98(等价于jesse.score)
内存中结构如下:
├──────┤← p
│num │
├─┄┄┄┄─┤← p+1
│*** │
├─┄┄┄┄─┤← p+2
│score │
├─┄┄┄┄─┤
5: int IntValue;
6: struct student
7: {
8:
9: int num;
10: char ***;
11: int score;
12:
13:
14: };
15: struct student jesse={2316124029,'M',98};
0040E9C8 mov dword ptr [ebp-10h],8A0D3F7Dh//注【3】
0040E9CF mov byte ptr [ebp-0Ch],4Dh
0040E9D3 mov dword ptr [ebp-8],62h
16:
17: struct student *p;
18: p=&jesse;
0040E9DA lea eax,[ebp-10h]
0040E9DD mov dword ptr [ebp-14h],eax
19: IntValue=*((int *)p+2);
0040E9E0 mov ecx,dword ptr [ebp-14h] //注【4】
0040E9E3 mov edx,dword ptr [ecx+8]
0040E9E6 mov dword ptr [ebp-4],edx
注【1】和注【3】分别对应系统给数组、结构体分配内存的反汇编代码,可见,除了赋给寄存器具体的值不一样外,两中数据类型在内存中的分配结构是一模一样的.再看看注【2】和注【4】他们分别对应的是访问两种数据结构的反汇编代码,令人惊喜的是,他们也是一样的,由此说明,虽然他们是不同的数据类型,但是在内存中结构是没有任何区别的.
说明:1.事例中我们定义的数组和结构体的大小有一定的特殊性,这是为了便于我们理解两这之间的关联,这并不防碍对其他结构的理解.
说明2. IntValue=*((int *)p+2);是将结构体的首部地址强制转换为int类型,这样就能通过对指针的加减对结构体中的成员进行访问了,具体原理请参见我以前写的<学会用指针—指针的强制转化>.
那么对于多维数组和结构体又有什么联系?
(二)多维数组和结构体数组
main()
{
int IntValue_1,IntValue_2;
int x[4][3]={{1,2,3},{5,6,7},{9,10,11},{13,14,15}};
int (*p)[3];
p=x;
IntValue_1=*((int *)p);
IntValue_2=*((int *)(p+2)+2);
.我们可以这样来理解多维数组:一个为int x[4]的一维数组每个成员分别又内嵌了3个int类型的子成员.但是与一维数组相比较它在内存中的结构没有什么太大的区别,仍然开辟了12*4字节的连续空间来存放.
对于多维数组的访问一般通过下标.
如:a[0][0]=1;a[2][2]=11
我们还是用指针访问成员:
int (*p)[3]; //表示P是一个指向4个int类型的一维数组,这里的p就是这个一维数组的首部地址,见图中兰色部分.
p=x;
则: *((int *)p)=1(等价于a[0][0]=1); *((int *)(p+2)+2)=11(d等价于a[2][2]=11)
内存结构如下:
├──────┤← p
│a[0][0] │
├─┄┄┄┄─┤
│a[0][1] │
├─┄┄┄┄─┤
│a[0][2] │
├──────┤← p+1
│a[1][0] │
├─┄┄┄┄─┤
│a[1][1] │
├─┄┄┄┄─┤
│a[1][2] │
├──────┤← p+2
│a[2][0] │
├─┄┄┄┄─┤
│a[2][1] │
├─┄┄┄┄─┤
│a[2][2] │← *((int *)(p+2)+2)
├──────┤← p+3
│a[3][0] │
├─┄┄┄┄─┤
│a[3][1] │
├─┄┄┄┄─┤
│a[3][2] │
├──────┤
反汇编代码如下:
6: int IntValue_1,IntValue_2;
7: int x[4][3]={{1,2,3},{5,6,7},{9,10,11},{13,14,15}};
0040E9C8 mov dword ptr [ebp-38h],1
0040E9CF mov dword ptr [ebp-34h],2
0040E9D6 mov dword ptr [ebp-30h],3
0040E9DD mov dword ptr [ebp-2Ch],5
0040E9E4 mov dword ptr [ebp-28h],6
0040E9EB mov dword ptr [ebp-24h],7
0040E9F2 mov dword ptr [ebp-20h],9
0040E9F9 mov dword ptr [ebp-1Ch],0Ah
0040EA00 mov dword ptr [ebp-18h],0Bh
0040EA07 mov dword ptr [ebp-14h],0Dh
0040EA0E mov dword ptr [ebp-10h],0Eh
0040EA15 mov dword ptr [ebp-0Ch],0Fh
8: int (*p)[3];
9: p=x;
10: IntValue_1=*((int *)p);
0040EA1C lea eax,[ebp-38h]
0040EA1F mov dword ptr [ebp-3Ch],eax
11: IntValue_2=*((int *)(p+2)+2);
0040EA22 mov ecx,dword ptr [ebp-3Ch]
0040EA25 mov edx,dword ptr [ecx]
0040EA27 mov dword ptr [ebp-4],edx
12:
0040EA2A mov eax,dword ptr [ebp-3Ch]
0040EA2D mov ecx,dword ptr [eax+20h]
0040EA30 mov dword ptr [ebp-8],ecx
2.定义一个结构体数组
main()
{
int IntValue_1,IntValue_2;
struct student
{
int num;
char ***;
int score;
};
struct student stu_1[4]={{00001,'m',506},{00002,'M',125},{00003,'F',405},{00004,'D',758}};
struct student *p;
p=stu_1;
IntValue_1=*((int *)p);
IntValue_2=*((int *)(p+2)+2);
}
内存结构如下:
├──────┤← p
│00001 │
├─┄┄┄┄─┤
│ m │
├─┄┄┄┄─┤
│ 506 │
├──────┤← p+1
│ 00002 │
├─┄┄┄┄─┤
│ M │
├─┄┄┄┄─┤
│ 125 │
├──────┤← p+2
│00003 │
├─┄┄┄┄─┤
│ F │
├─┄┄┄┄─┤
│ 405 │← *((int *)(p+2)+2)
├──────┤← p+3
│00004 │
├─┄┄┄┄─┤
│ D │
├─┄┄┄┄─┤
│ 758 │
├──────┤
5: int IntValue_1,IntValue_2;
6: struct student
7: {
8:
9: int num;
10: char ***;
11: int score;
12: };
13: struct student stu_1[4]={{12345,'m',506},{22222,'M',125},{333333,'F',5063333},{44444,'D',5064444}};
0040E9C8 mov dword ptr [ebp-38h],3039h
0040E9CF mov byte ptr [ebp-34h],6Dh
0040E9D3 mov dword ptr [ebp-30h],1FAh
0040E9DA mov dword ptr [ebp-2Ch],56CEh
0040E9E1 mov byte ptr [ebp-28h],4Dh
0040E9E5 mov dword ptr [ebp-24h],7Dh
0040E9EC mov dword ptr [ebp-20h],51615h
0040E9F3 mov byte ptr [ebp-1Ch],46h
0040E9F7 mov dword ptr [ebp-18h],4D42A5h
0040E9FE mov dword ptr [ebp-14h],0AD9Ch
0040EA05 mov byte ptr [ebp-10h],44h
0040EA09 mov dword ptr [ebp-0Ch],4D46FCh
14: struct student *p;
15: p=stu_1;
0040EA10 lea eax,[ebp-38h]
0040EA13 mov dword ptr [ebp-3Ch],eax
16: IntValue_1=*((int *)p);
0040EA16 mov ecx,dword ptr [ebp-3Ch]
0040EA19 mov edx,dword ptr [ecx]
0040EA1B mov dword ptr [ebp-4],edx
17: IntValue_2=*((int *)(p+2)+2);
0040EA1E mov eax,dword ptr [ebp-3Ch]
0040EA21 mov ecx,dword ptr [eax+20h]
0040EA24 mov dword ptr [ebp-8],ecx
我们可以看到,多维数组与结构体数组在内存中分配结构还是一样的.
结语
首先,通过上面的事例看出数组和结构体其实在内存级根本就没有什么本质的区别,而且访问也可以归结为同一种方式----指针.我想同一种语言给我们提供了不同的数据类型以及一些规则来使用,就象你可以方便的使用数组来对同一中数据结构进行操作,而结构体又满足了你对不同数据操作的要求,但是归根到底,在计算机底层他们的操作方式都是一样,计算机本身只是给我们提供了硬件平台,如内存,至于怎么使用,你要用怎样的规则来使用这就要看高级语言给你提供了怎样的机制了.在这里我们可以大胆设想结构体和类又会是怎样的联系呢?
谈到这我想起了在学校大家最爱谈论的事----“某某语言现在很热,快去学了”说这话的时候,连C语言都还没弄清醒,结果大学4年就这样奔命式的度过了.准确的说是我们在”语言规则”上兜了一个不大不小的圈子.