查看: 865|回复: 8
|
深入理解数组(Array)、指针(point)===C++
[复制链接]
|
|
深入理解数组(Array)、指针(point)
本文将尝试从编译器的角度来彻底了解数组(Array)、指针(point)、构造体(struct)。要知道:一种语言的特性取决于其编译器或其解释器,也就是说它的编译器的功能,它有何特性,完全影响由它编译的源程序代码。
因此,只有深入了解编译器是如何编译代码,才能真正透彻地理解数组,指针、构造体等。本文中用vc6.0 对比gcc3,观察编译器行为。
一、数组(Array)
以下代码:
main()
{
int a = 100;
int array1[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int i = 0;
printf(“the array1 is: \n”);
for ( ; i < 10; i++)
printf(“%d ”, array1[i]);
}
1、 数组定义及初始化。
需要说明的是,C语言的数组由0开始计数。
定义并初始化int array1[] = { 1,2,3,4,5,6,7,8,9,10 }
定义未初始化 int array1[10];这时一定要确定数组大小。
看看gcc3编译器生成的汇编代码片段:
movl $0, -72(%ebp)
movl $1, -68(%ebp)
movl $2, -64(%ebp)
movl $3, -60(%ebp)
movl $4, -56(%ebp)
movl $5, -52(%ebp)
movl $6, -48(%ebp)
movl $7, -44(%ebp)
movl $8, -40(%ebp)
movl $9, -36(%ebp)
movl $0, -76(%ebp)
我们知道:静态变量(static)是分配在程序的初始化数据段,一般变量均在堆栈上分配,而动态分配的变量一般在内存池(堆)中分配。
Ebp
Ebx
Array1[9] = 9
Array1[8] = 8
Array1[7] = 7
Array1[6] = 6
Array1[5] = 5
Array1[4] = 4
Array1[3] = 3
Array1[2] = 2
Array1[1] = 1
Array1[0] = 0
以上代码,int变量a、i 以及数组变量array1在程序的栈(stack)上分配,而堆栈区域是向下生长的。故可见数组array1[0]分配在 [ebp-72]的地方,而array1[1]分配在[ebp-68],array1[2]分配在[ebp-64],依此类推,array1[9]将分配在[ebp-36] |
|
|
|
|
|
|
|
楼主 |
发表于 21-3-2006 03:16 PM
|
显示全部楼层
代码中,定义一个数组并为它初始化。编译器将数值直接保存在变量里。
这是VC6.0生成的代码(片段):
0040102E: C7 45 D8 00 00 00 mov dword ptr [ebp-28h],0
00401035: C7 45 DC 01 00 00 mov dword ptr [ebp-24h],1
0040103C: C7 45 E0 02 00 00 mov dword ptr [ebp-20h],2
00401043: C7 45 E4 03 00 00 mov dword ptr [ebp-1Ch],3
0040104A: C7 45 E8 04 00 00 mov dword ptr [ebp-18h],4
00401051: C7 45 EC 05 00 00 mov dword ptr [ebp-14h],5
00401058: C7 45 F0 06 00 00 mov dword ptr [ebp-10h],6
0040105F: C7 45 F4 07 00 00 mov dword ptr [ebp-0Ch],7
00401066: C7 45 F8 08 00 00 mov dword ptr [ebp-8],8
0040106D: C7 45 FC 09 00 00 mov dword ptr [ebp-4],9
00401074: C7 45 AC 00 00 00 mov dword ptr [ebp-54h],0
效果同gcc 是一样的,所不同的只是位置,初始化的数值直接写进代码中。
2、将以上代码稍为修改为:
main() {
int array1[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int array2[10];
printf(“the array1 is: \n”);
for (int i = 0; i < 10; i++)
printf(“%d”, array1[i]);
for (int i = 0; i < 10; i++)
array2[i] = array2[i];
}
定义了两个数组,一个被初始化,一个未进行初始化。编译器这时为array1分配空间,并保存数值,而array2只是保留了足够的空间以装下10个int类型,共计(4×10)40个字节空间,不会进行赋值操作。
3、访问数组。
(1)用下标进行访问。
以上的代码为例:for (i = 0; i < 10; i++) printf(“%d”, array1[i]); 遍历数组。
为数组变量,指定一个坐标,进行访问。
我们来看看gcc编译器是如何处理的?
.L2:
cmpl $9, -76(%ebp)
jle .L5
jmp .L3
.L5:
movl -76(%ebp), %eax
movl -72(%ebp,%eax,4), %eax
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call printf
leal -76(%ebp), %eax
incl (%eax)
jmp .L2
.L3:
movl $0, -76(%ebp)
其中,-76(%ebp),等于 [ebp–76], 也就是定义的变量i, 用来控制循环次数。通过不地比较[ebp-76]是否大于9,进行循环。在这里关键的:
movl –72(%ebp,%eax,4), %eax 这是 unix 的汇编语言格式。等价于以下:
move eax, [ebp – 72 + eax + 4]。由前面得知,[ebp-72]就是array1[0]的地址
也就是array数组变量的基地址。通过不断改变 eax 就可以容易是访问每一个数组元素。
当然,eax的取值范围是0-8,为什么是0-8,是因为,0可以访问到array1[1],8可以访问到array1[9]。[ebp-72+eax+4]加4是由于,内存中以字节为单位存放的。每个int变量为4个字节。
总结编译器处理数组的行为:设array1数组首地址为基地址,加上偏移量,通过改变偏移量从而达到访问数组元素。 |
|
|
|
|
|
|
|
楼主 |
发表于 21-3-2006 03:16 PM
|
显示全部楼层
(2)用指针进行访问。
int *p = array1;
int i = 0;
for ( ; i < 10; i++)
printf("%d ", *p++);
这里,我们定义一个指针p并将数组首地址赋给它,通过改变指针来访问数组。观察编译器是如何处理?
00401005: C7 44 24 08 00 00 mov dword ptr [esp+8],0
0040100D: C7 44 24 0C 01 00 mov dword ptr [esp+0Ch],1
00401015: C7 44 24 10 02 00 mov dword ptr [esp+10h],2
0040101D: C7 44 24 14 03 00 mov dword ptr [esp+14h],3
00401025: C7 44 24 18 04 00 mov dword ptr [esp+18h],4
0040102D: C7 44 24 1C 05 00 mov dword ptr [esp+1Ch],5
00401035: C7 44 24 20 06 00 mov dword ptr [esp+20h],6
0040103D: C7 44 24 24 07 00 mov dword ptr [esp+24h],7
00401045: C7 44 24 28 08 00 mov dword ptr [esp+28h],8
0040104D: C7 44 24 2C 09 00 mov dword ptr [esp+2Ch],9
00401055: 8D 74 24 08 lea esi,[esp+8]
00401059: BF 0A 00 00 00 mov edi,0Ah
0040105E: 8B 06 mov eax,dword ptr [esi]
00401060: 83 C6 04 add esi,4
观察上述结果:array1[0]的地址是:[ebp+8],而array1[0]的地址,也就是整个数组array1的地址。
代码:int *p = array1; 编译器作如下处理:
lea esi, [esp+8]
然后,通过 add esi, 4 移动指针,通过 mov eax, dword ptr [esi] 访问元素
对比gcc 产生的代码:
4、数组运算
array1[3] = array1[0] + array1[1];
以上代码,编译器如何处理?
0040107A: 8B 4D D8 mov ecx,dword ptr [ebp-28h]
0040107D: 03 4D DC add ecx,dword ptr [ebp-24h]
00401080: 89 4D E4 mov dword ptr [ebp-1Ch],ecx
在这里,编译器对号入座,将[ebp-28h]与[ebp-24h]相加,结果送到[ebp-1Ch]。编译器作了最简单的运算。 |
|
|
|
|
|
|
|
楼主 |
发表于 21-3-2006 03:17 PM
|
显示全部楼层
5、二维数组
定义(1):int array2D[][2] = { {1, 1}, {2, 2} };
或者(2):int array2D[2][2] = { {1, 1}, {2, 2} };
第一种方法省略了第一个框,但第二个框必须要!它的一维个数由编译器自动计算得来。
第二种方法是完完全全的正规写法。
int array2D[][2] = { {1, 1}, {2, 2} };
int i,j;
for (i = 0; i < 2; i++) {
printf("\n");
for (j = 0; j < 2; j++)
printf("%d", array2D[i][j]);
}
看看编译器的处理方法:
00401028: C7 45 F0 01 00 00 mov dword ptr [ebp-10h],1
0040102F: C7 45 F4 01 00 00 mov dword ptr [ebp-0Ch],1
00401036: C7 45 F8 02 00 00 mov dword ptr [ebp-8],2
0040103D: C7 45 FC 02 00 00 mov dword ptr [ebp-4],2
看起来,在编译器内部,二维数组和平常的变量处理方法没什么不同。
那,到底与一维数组有什么不同呢,答案是在下:
00401044: C7 45 EC 00 00 00 mov dword ptr [ebp-14h],0
0040104B: EB 09 jmp 00401056
0040104D: 8B 45 EC mov eax,dword ptr [ebp-14h]
00401050: 83 C0 01 add eax,1
00401053: 89 45 EC mov dword ptr [ebp-14h],eax
00401056: 83 7D EC 02 cmp dword ptr [ebp-14h],2
0040105A: 7D 44 jge 004010A0
0040105C: 68 5C 22 42 00 push offset ??_C@_01BJG@?6?$AA@
00401061: E8 2A 01 00 00 call _printf
00401066: 83 C4 04 add esp,4
00401069: C7 45 E8 00 00 00 mov dword ptr [ebp-18h],0
00401070: EB 09 jmp 0040107B
00401072: 8B 4D E8 mov ecx,dword ptr [ebp-18h]
00401075: 83 C1 01 add ecx,1
00401078: 89 4D E8 mov dword ptr [ebp-18h],ecx
0040107B: 83 7D E8 02 cmp dword ptr [ebp-18h],2
0040107F: 7D 1D jge 0040109E
00401081: 8B 55 EC mov edx,dword ptr [ebp-14h]
00401084: 8D 44 D5 F0 lea eax,[ebp+edx*8-10h]
00401088: 8B 4D E8 mov ecx,dword ptr [ebp-18h]
0040108B: 8B 14 88 mov edx,dword ptr [eax+ecx*4]
0040108E: 52 push edx
0040108F: 68 1C 20 42 00 push offset ??_C@_02MECO@?$CFd?$AA@
00401094: E8 F7 00 00 00 call _printf
当我们遍历二维数组时,要用到二个循环体(当然,也可以有其它方法),定义了i,j两个变量,用于控制循环次数。
[ebp-14h]就是变量i, [ebp-18h]就是变量j。现在我们看关键的访问二维数组元素处理手法:
00401081: 8B 55 EC mov edx,dword ptr [ebp-14h]
00401084: 8D 44 D5 F0 lea eax,[ebp+edx*8-10h]
00401088: 8B 4D E8 mov ecx,dword ptr [ebp-18h]
0040108B: 8B 14 88 mov edx,dword ptr [eax+ecx*4]
步骤一:先取得它的一维值。
步骤二:通过一维值,再计算二维值偏移量,从而进一步访问最终结果。
假设此时:i = 0, j = 0;
edx 的值保存变量i,通过[ebp+dex*8-10h]的计算,取得一维值,即,array2D[0][]的值,保存在eax中。然后通过变量j的值计算出,要访问的二维值,即array2D[0][0]的值。
最终结果保存在edx中,将它送printf()函数打印!
二维数组的使用比一维数组稍复杂,那么三维数组,或多维数组更复杂了,实际使用中,常见的有一维数组,二维数组。
6、二维数组运算
a = array2D[0][0] + array2D[1][0];
00401044: 8B 45 F0 mov eax,dword ptr [ebp-10h]
00401047: 03 45 F8 add eax,dword ptr [ebp-8]
0040104A: 89 45 E4 mov dword ptr [ebp-1Ch],eax
这里,完全看不出,它是一个二维数组变量,与平常的变量无分别,可见,在编译器内部,有标记,记着数组元素。
7、用指针访问二维数组。
方法一:
int *p = array2D;
for (i = 0; i < 2; i++) {
printf("\n");
for (j = 0; j < 2; j++)
printf("%d", *(p + i * 2 + j));
}
将二维数组的首地址直接赋给指针p,通过改变p的值来读取二维数组元素,大家可以看到以上代码的处理手法,其实它就等于编译器在编译代码时处理二维数组的手法是一样的。只不过这里是用c语言的思维,编译器用的是汇编语言的思维罢了!其结果,完全一样!
方法二:
int *p = array2D[0];
for (i = 0 ; i < 2; i++)
printf("%d ", *p++);
p = array2D[1];
for (j = 0 ; j < 2; j++)
printf("%d ", *p++);
将二维数组中的第一维地址赋给指针p,再改变指针p,从而读取二维数组值,这样做法可以减少一重循环。由此可见,可以二维数组简化为一维数组。秘决就是:将二维数组的第一维看作是数组变量!!!同理,甚至将多维数组简化为一维数组!
8、数组作为函数参数
void print_array(int a[], int len) {
int i;
for (i = 0; i < len; i++)
printf("%d ", a[i]);
}
main() {
int array[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
print_array(array, 10);
}
数组用作参数时,不需指出数组大小。我们看看函数print_array()的汇编代码
00401038: C7 45 FC 00 00 00 mov dword ptr [ebp-4],0
0040103F: EB 09 jmp 0040104A
00401041: 8B 45 FC mov eax,dword ptr [ebp-4]
00401044: 83 C0 01 add eax,1
00401047: 89 45 FC mov dword ptr [ebp-4],eax
0040104A: 8B 4D FC mov ecx,dword ptr [ebp-4]
0040104D: 3B 4D 0C cmp ecx,dword ptr [ebp+0Ch]
00401050: 7D 19 jge 0040106B
00401052: 8B 55 FC mov edx,dword ptr [ebp-4]
00401055: 8B 45 08 mov eax,dword ptr [ebp+8]
00401058: 8B 0C 90 mov ecx,dword ptr [eax+edx*4]
0040105B: 51 push ecx
0040105C: 68 1C 20 42 00 push offset ??_C@_03CPCE@?$CFd?5?$AA@
00401061: E8 DA 00 00 00 call _printf
看看一个图表
print_array()函数的堆栈
参数len
数组参数 a
返回地址
ebp
变量 i
[ebp+0c] ->
[ebp+8]->
[ebp+4] ->
[ebp] ->
[ebp-4]->
这里说明一下:C语言的参数传递是从右到左边的,故,先压len入栈,再压a入栈。再加上函数本身定义的变量i 。为什么是 [ebp+8],因为在调用print_array()前,就先将参数压入栈中。
9、几个疑问。
(1)既然,数组本身就是地址,能不能在数组上施以指针操作呢?
答案是不能的。
int i = 0;
for (; i<10; i++)
printf("%d " , *(array++));
将产生编译错误,原因是array不是指针变量。不能自加!
(2)可以对数组施以加减运算。
将以下代码,改一改,如:
for (; i<10; i++)
printf("%d " , *(array + i));
这样是没问题的。原因是,只是用地址值加一个值,并没有对array本身改变! |
|
|
|
|
|
|
|
楼主 |
发表于 21-3-2006 03:18 PM
|
显示全部楼层
二、指针(Point)
1、 定义
int *p; 定义一个int型指针。
char *p; 定义一个 char 型指针。
void *p; 定义一个无型类型指针,这是一个特例,定义时不表明任何类型,但是,可以指向任何类型的数据,在赋值的时候,须要作强制性转换。在C的标准库函数有很多这种指针。例如:malloc()就是其中一个。返回一个值,须要强制性转换。
p = (char *)malloc(. . .);
2、 初始化
为什么一定要初始化,很多教材资料都有说明。需要初始化,要重要的原因是:为了避免指针的不确定性。
int *p = & 在定义时表明它初始指向a变量。
int *p = 0; (或 NULL);如果在初始化时,真的没什么好指向的,你应该这样,将指向置0。 NULL只是0的宏。这样的话,指针p将不会乱指。
记住:在使用指针前,必须确保指针的指向确定性。
在编译器里,定义一个指针,和定义一个普通变量,没什么区别。都内存的一个区域而已。
int a = 0;
int *p = &
编译为:
00401028: C7 45 FC 00 00 00 mov dword ptr [ebp-4],0
0040102F: 8D 45 FC lea eax,[ebp-4]
00401032: 89 45 F8 mov dword ptr [ebp-8],eax
而以上[epb-8]就是指针p了。
3、 字符数组与字符串。
char ch[] = { 's', 't', 'u', 'd', 'e', 'n', 't' };
char *p = "teacher";
前面一个是字符数组,后一个是字符串,其效果看似一样,其实相差很远。
Ch[]占有内存空间是7个字节,指针p指向的字符串占有内存空间是8个,包括一个’\0’字符。是一个结尾符。
0040D418: C6 45 F8 73 mov byte ptr [ebp-8],73h
0040D41C: C6 45 F9 74 mov byte ptr [ebp-7],74h
0040D420: C6 45 FA 75 mov byte ptr [ebp-6],75h
0040D424: C6 45 FB 64 mov byte ptr [ebp-5],64h
0040D428: C6 45 FC 65 mov byte ptr [ebp-4],65h
0040D42C: C6 45 FD 6E mov byte ptr [ebp-3],6Eh
0040D430: C6 45 FE 74 mov byte ptr [ebp-2],74h
0040D434: mov dword ptr [ebp-0Ch],offset @teacher
其次,ch[]的值直接包括在代码中,p指向的字符串存放在数据段中。
Ch[]的访问方式和数组变量的访问方式是一样的,而*p的访问方式很灵活,通过一系列的字符串函数存取,也可以用数组方式访问。 |
|
|
|
|
|
|
|
楼主 |
发表于 21-3-2006 03:18 PM
|
显示全部楼层
4、 几种特殊的指针。
int a, b, c;
int * const p1 = & // 指针常量
const int * p2 = & // 常量指针
const int * const p3 = & // 既是针指常量,又是常量指针
第一种指针:p1只能指向a将不能改变它,指向别的变量
第二种指针:p2指向的内容将不能改变。
第三种指针:p3只能指向c而c的值不能改变。
以下尝试都将失败:
p1 = & //失败,p1是常量
*p2 = 10; //失败,*p2是常量
p3 = &b 或 *p3 = & //都失败。
这些指针在某些场合下很有用处,例如:在参数方面。能防止不正当使用指针。应好好利用。
举个例子,以下是一个函数的定义:
int string_cat(char * const d, const char * const s, int len) { . . . }
函数的目的是:将字符串d与字符串s相连,形成新的字符串回传到d上。 指针d是不能指针其它临时的字符串,而指针s更严格,什么也不能改变。
我们加上了严格的限制,那么程序出错的机会将大大减少!
比较一下这个定义:
int string_cat(char *d, char *s, int len) { . . . }
自然就会明白了。
注意:这些常量指针,在编译器生成代码时,是没什么区别的,只是在编译源程序时,编译程序已经在检查了。
5、 指针的访问与存取
要访问一个指针,可以这样:
a = *p; // 读取p里的内容。
int *p1 = p; // 将指针p的值赋给p1,那么p1将与p一样,指向同一个地方。
要存取一个指针,可以这样:
int *p = & // 将变量a的地址赋给p。使得p 指向a
*p = 10; // 将10 存放在p所指向的地方,也就是a
a = 10; // 等价于 *p = 10;
6、 指针的运算
可以将指针看作一个地址值,那么当然可以将这个地址值加减一个正数,或两个地址相减得出一个偏移量。
int a[] = { 1, 2, 3, 4, 5 };
int *p = a;
int c = *(p + 2); // 读取 a[2]的值。等价于 c = a[0 + 2];
*p++; // 向下移动指针,p自身改变。
*p--; // 后退指针。
int *p1 = p + 2;
int n = p1 – p; // 那么n 的值等于2。两个地址值相减,得出偏移量。
两个地址相乘或相除,是没有意义的。
7、 指针与数组的互运算。
int a[] = { 1, 2, 3, 4, 5 };
int *p = a;
*(p + 1) = 10; 等价于 a[1] = 10;
a[0] = 3; 等价于 *p = 3;
*(a + 1) = 10; 等价于 *(p + 1) = 10;
但是: *(a++) = 10; // 错误。
*(p++) = 10; // 正确。
可见,基本上,指针与数组上是可以互运算的,当要改变变量本身时,就不同了!
观察以下两段代码的,编译器行为:
(1) for (I = 0; I < 10; I++)
s = s + a[i];
0040D460: 8B 55 E4 mov edx,dword ptr [ebp-1Ch]
0040D463: 8B 45 E0 mov eax,dword ptr [ebp-20h]
0040D466: 03 44 95 EC add eax,dword ptr [ebp+edx*4-14h]
(2) for (I = 0; I < 10; I++)
s = s + *p++;
0040D487: 8B 55 E8 mov edx,dword ptr [ebp-18h]
0040D48A: 8B 45 E0 mov eax,dword ptr [ebp-20h]
0040D48D: 03 02 add eax,dword ptr [edx]
0040D48F: 89 45 E0 mov dword ptr [ebp-20h],eax
0040D492: 8B 4D E8 mov ecx,dword ptr [ebp-18h]
0040D495: 83 C1 04 add ecx,4
使用指针,某些场合,效率比数组稍为高一些。 |
|
|
|
|
|
|
|
楼主 |
发表于 21-3-2006 03:19 PM
|
显示全部楼层
8、指针与空间。
当指针与字符串在一起使用时,就特别容易出问题。
char ch[9] = { 's', 't', 'u', 'd', 'e', 'n', 't' , 'e', 'r' };
char *p = "teacher";
int i;
for (i = 0; i < 9; i++)
*p++ = ch[i];
puts(p);
数组ch有9个这符,而p的存放空间只有7个,怎能不出错???
反过来就没问题了:
int i = 0;
while (*p != ‘\0’)
ch[i++] = *p++;
数组越界,是一个非常危险的行为。而指针越界比数组越界更更危险!许多软件有BUG或漏洞就是因为指针越界,造成指针越界是因为,无空间或空间不足。在C库函数中有很多函数就在这个问题。典型的函数如: copy();
char *p;
copy(p, str); //无空间存放,如何能不出错呢?
在赋值之前应该,先为p安排存放空间。
p = (char *)malloc(len(str) + 1);
Copy(p, str);
9、 指针数组
char *p[] = { “student”, “teacher”, “doctor”, “deriver” };
int a, b, c;
int *pi[] = { &a, &b, &c };
p是一个字符串指针数组,pi是一个整型指针数组。
*pi[0] = 1;
*pi[1] = 2;
*pi[2] = 3;
for (i = 0; i < 3; i++)
printf("%d ", *pi[i]);
编译器作如下处理:
0040D438: mov dword ptr [ebp-0Ch],offset ??_C@_07ONEA@student?$AA@
0040D43F: mov dword ptr [ebp-8],offset ??_C@_07GIFE@teacher?$AA@
0040D446: mov dword ptr [ebp-4],offset ??_C@_06HJFF@doctor?$AA@
0040D44D: 8D 45 F0 lea eax,[ebp-10h]
0040D450: 89 45 DC mov dword ptr [ebp-24h],eax
0040D453: 8D 4D EC lea ecx,[ebp-14h]
0040D456: 89 4D E0 mov dword ptr [ebp-20h],ecx
0040D459: 8D 55 E8 lea edx,[ebp-18h]
0040D45C: 89 55 E4 mov dword ptr [ebp-1Ch],edx
0040D45F: 8B 45 DC mov eax,dword ptr [ebp-24h]
0040D462: C7 00 01 00 00 00 mov dword ptr [eax],1
0040D468: 8B 4D E0 mov ecx,dword ptr [ebp-20h]
0040D46B: C7 01 02 00 00 00 mov dword ptr [ecx],2
0040D471: 8B 55 E4 mov edx,dword ptr [ebp-1Ch]
0040D474: C7 02 03 00 00 00 mov dword ptr [edx],3
0040D47A: C7 45 D8 00 00 00 mov dword ptr [ebp-28h],0
前面部分是,字符串指针数组定义,后部分是整型指针数组定义。编译器通过计算总的变量空间,并分配空间,简单地赋地址值给变量。
字符串指针数组,既可以像整型指针数组一样操作,也可以用一系列的字符串函数处理。
10、 函数指针。
void foo(void) {
printf("foo");
}
main() {
void (*pf)(void) = foo;
foo();
(*pf)();
}
代码中,定义了一个函数foo(),定义一个指针pf用来指向函数foo(),定义函数指针有一定的格式:
返回类型 (*指针名)(函数参数类型);
我们可以这样定义并初始化: int (*pf)(int) = foo;
使用函数指针:
(*pf)(); 等价于 pf(); 等价于foo(); 等价于 *pf;
结果是一样的。
看看编译器生成的代码:
00401088: C7 45 FC 05 10 40 mov dword ptr [ebp-4],offset @ILT+0(_foo)
0040108F: E8 71 FF FF FF call @ILT+0(_foo)
00401094: 8B F4 mov esi,esp
00401096: FF 55 FC call dword ptr [ebp-4]
将函数值赋给[ebp-4]也就是指针pf,调用时直接call [ebp - 4] |
|
|
|
|
|
|
|
楼主 |
发表于 21-3-2006 03:21 PM
|
显示全部楼层
11、 函数指针数组。
void foo(void) { printf("foo "); }
void hi(void) { printf("HI "); }
void world(void) { printf("world "); }
main() {
void (*pf[3])(void) = { foo, hi, world };
int i;
for (i = 0; i < 3; i++)
pf[i]();
}
代码中,定义三个函数作为例子,然后定义一个函数指针数组。
函数指针数组格式如:
(返回类型)(*变量名[数组大小])(参数类型);
0040D7B8: C7 45 F4 05 10 40 mov dword ptr [ebp-0Ch],offset @ILT+0(_foo)
0040D7BF: C7 45 F8 14 10 40 mov dword ptr [ebp-8],offset @ILT+15(_hi)
0040D7C6: C7 45 FC 0F 10 40 mov dword ptr [ebp-4],offset @ILT+10(_world)
0040D7CD: C7 45 F0 00 00 00 mov dword ptr [ebp-10h],0
0040D7D4: EB 09 jmp 0040D7DF
0040D7D6: 8B 45 F0 mov eax,dword ptr [ebp-10h]
0040D7D9: 83 C0 01 add eax,1
0040D7DC: 89 45 F0 mov dword ptr [ebp-10h],eax
0040D7DF: 83 7D F0 03 cmp dword ptr [ebp-10h],3
0040D7E3: 7D 12 jge 0040D7F7
0040D7E5: 8B 4D F0 mov ecx,dword ptr [ebp-10h]
0040D7E8: 8B F4 mov esi,esp
0040D7EA: FF 54 8D F4 call dword ptr [ebp+ecx*4-0Ch]
可见,访问函数指针数组,与普通数组方法是一样的。看似复杂,其实并不复杂。
函数指针数组,在内核编写作用非常大,比如说:中断向量表,任务表等等。
12、 指针作为函数参数
比较一下这两个经典的例子:
(1)void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
}
00401086: 8B 45 F8 mov eax,dword ptr [ebp-8]
00401089: 50 push eax
0040108A: 8B 4D FC mov ecx,dword ptr [ebp-4]
0040108D: 51 push ecx
0040108E: E8 77 FF FF FF call @ILT+5(_swap)
(2)void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
00401086: 8D 45 F8 lea eax,[ebp-8]
00401089: 50 push eax
0040108A: 8D 4D FC lea ecx,[ebp-4]
0040108D: 51 push ecx
0040108E: E8 77 FF FF FF call @ILT+5(_swap)
函数的目的是交换a,b的值。
我们观察它们编译产生的代码,示例1使用了值传递,只是在函数范围内交换了值,外界并不知道a、b已经交换了。
示例2,将变量a、b的地址压入栈中,传递给函数,使用了指针,将a、b指针指向的内存区域值交换了,从而真正起了交换的作用!
使用指针作为函数参数有几个目的:
(1)、函数为了与外界交换信息。
(2)、有时为了更好的效率,使用指针,比使用值传递效率更高。
(3)、某些场合必须使用指针。
13、 指针作函数返回值
使用指针作返回值,典型的如C语言的字符串系列的库函数。如:
void * malloc(size_t size);
char * strcat(char * dest, const char * src);
等等。
使用指针返回值,目的也是回传信息给外界。
14、 指向指针的指针。
听起来很拗口,但从字面上很容易理解,指针的指针,相当于指针数组,是可以互换的。
格式为:(类型) * *(变量名);
char **p = 0;
以上定义相当于:char *p[2];
但使用指针的指针更为方便,在定义指针数组时,必须让编译器知道数组的大小,要么提供界限,要么初始化,以供编译器计算界限。而指针的指针就没有这个界限了。
示例:
char **pp = 0;
char *p1 = “student”;
char *p2 = “teacher”;
char *pch[] = { p1, p2 };
pp = &p1;
printf(“%s”, *pp);
pp = &p2;
printf(“%s”, *pp);
printf(“%s”, pch[0]);
printf(“%s”, pch[1]);
15、 总结
(1)、定义一个指针,使用 * 符号, 如:int *p;
(2)、确保指针得到初始化,如:int *p = 0; 空指针。
(3)、泛型指针:void *p = 0;
(4)、在给指针指向的区域赋值时,确保有足够的空间容纳。以防指针越界。
(5)、读取指针所指向的内容,在变量前使用 * 符号,如 a = p; 或,*p = 10;
(6)、对于指针的运算,可以施以,加,减,指针互减。
(7)、指针与数组,大多数情况下可以互用:int ch[] = {1,1}; int *p =ch;
(8)、几个特殊指针:const char *p; char * const p; const char * const p;
(9) 函数指针: 如:void (*pf)(void) = 0;
(10)指针数组,char *p[] = { “student”, “teacher” };
(11)指针的指针:char **p = 0; |
|
|
|
|
|
|
|
楼主 |
发表于 28-3-2006 03:19 PM
|
显示全部楼层
希望可以帮到大家 |
|
|
|
|
|
|
| |
本周最热论坛帖子
|