本章剩下的内容主要有:
1.指针与数组 & 地址算术运算
2.字符指针与函数
3.多维数组 & 指针与多维数组
4.指向函数的指针
每个内容举一个例子,第一个例子:
int a[10] = {1,2,3,4,5,6};
int *pa = &a[0];
main(){
pa = &a[0];
pa = a;
int x = *pa;
int i;
for(i=0; i<5; i++){
printf("%d", a[i]);
printf("%d", pa[i]);
printf("%d", *(pa+i));
}
}
指针类型的pa
指向了a[0]
,即pa
的值是a[0]
的地址。数组变量a
的值也是数组的首地址,所以pa
的两种赋值方式完全相同。x
的值是a[0]
,即1。pa[i]
与*(pa+i)
指向的也是同一个元素。对照编译后的汇编代码,如下:
.globl _a
.data
_a:
.long 1
.long 2
.long 3
.long 4
.long 5
.long 6
.space 16
.globl _pa
_pa:
.long _a
.section .rdata,"dr"
LC0:
.ascii "%d\0"
.text
.globl _main
_main:
mov DWORD PTR _pa, OFFSET FLAT:_a
mov DWORD PTR _pa, OFFSET FLAT:_a
mov eax, DWORD PTR _pa
mov eax, DWORD PTR [eax]
mov DWORD PTR [ebp-4], eax
mov DWORD PTR [ebp-8], 0
L2:
cmp DWORD PTR [ebp-8], 4
jg L3
mov eax, DWORD PTR [ebp-8]
mov eax, DWORD PTR _a[0+eax*4]
mov DWORD PTR [esp+4], eax
mov DWORD PTR [esp], OFFSET FLAT:LC0
call _printf
mov eax, DWORD PTR [ebp-8]
lea edx, [0+eax*4]
mov eax, DWORD PTR _pa
mov eax, DWORD PTR [edx+eax]
mov DWORD PTR [esp+4], eax
mov DWORD PTR [esp], OFFSET FLAT:LC0
call _printf
mov eax, DWORD PTR [ebp-8]
lea edx, [0+eax*4]
mov eax, DWORD PTR _pa
mov eax, DWORD PTR [edx+eax]
mov DWORD PTR [esp+4], eax
mov DWORD PTR [esp], OFFSET FLAT:LC0
call _printf
lea eax, [ebp-8]
inc DWORD PTR [eax]
jmp L2
L3:
leave
ret
可以看到_pa
的值就是标号_a
,而后面两个printf
的代码也完全一样。
第二个例子,字符指针与函数:
char amessage[] = "now is the time"; /* 定义一个数组 */
char *pmessage = "now is the time"; /* 定义一个指针 */
char *b[] = {"xxx","yyy"};
main(){
char *a[] = {amessage,pmessage};
}
编译后的汇编代码:
.globl _amessage
.data
_amessage:
.ascii "now is the time\0"
.section .rdata,"dr"
LC0:
.ascii "now is the time\0"
.globl _pmessage
.data
_pmessage:
.long LC0
.section .rdata,"dr"
LC1:
.ascii "xxx\0"
LC2:
.ascii "yyy\0"
.globl _b
.data
_b:
.long LC1
.long LC2
.text
.globl _main
_main:
mov DWORD PTR [ebp-8], OFFSET FLAT:_amessage
mov eax, DWORD PTR _pmessage
mov DWORD PTR [ebp-4], eax
leave
ret
变量a
和b
是指针数组,在汇编代码中与其他变量其实并无不同,在函数内部都是一个内存空间,在函数外部都是一个标号。
第三个例子,指针与多维数组:
static char daytab[2][13] = {
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
int a[10][20];
int *w[10];
main(){
int a = daytab[1][10];
f(daytab);
}
f(char daytab[][13])
{
char b = daytab[1][10];
}
编译后的汇编代码:
.data
_daytab:
.byte 0
.byte 31
.byte 28
.byte 31
.byte 30
.byte 31
.byte 30
.byte 31
.byte 31
.byte 30
.byte 31
.byte 30
.byte 31
.byte 0
.byte 31
.byte 29
.byte 31
.byte 30
.byte 31
.byte 30
.byte 31
.byte 31
.byte 30
.byte 31
.byte 30
.byte 31
.text
.globl _main
_main:
movsx eax, BYTE PTR _daytab+23
mov DWORD PTR [ebp-4], eax
mov DWORD PTR [esp], OFFSET FLAT:_daytab
call _f
leave
ret
.globl _f
_f:
push ebp
mov ebp, esp
sub esp, 4
mov eax, DWORD PTR [ebp+8]
add eax, 23
movzx eax, BYTE PTR [eax]
mov BYTE PTR [ebp-1], al
leave
ret
.comm _a, 800 # 800
.comm _w, 48 # 40
可以发现,汇编代码中并不区分一维还是二维数组,而是调用时根据行和列的值计算出具体位置。
最后一个例子,指向函数的指针:
int f(int i){
return i+1;
}
int w(int i){
return i+2;
}
int useforw(int (*x)(int));
main(){
int (*pf)(int);
pf = f;
int a = 1;
int b = 2;
b = (int (*)(int)) a;
useforw(f);
useforw(w);
useforw((int (*)(int))(1+1==2 ? f : w));
}
int useforw(int (*x)(int)){
int y = (*x)(9);
}
main
函数上面定义了两个函数f
和w
,省去了声明。useforw
函数的参数是一个指向函数的指针,也即函数指针。函数指针其实就是指向了函数代码的首地址,而函数名也是代码的首地址,这和数组很类似,在汇编代码中,数组名和函数名都是一个标号。
main
函数中第一行:
int (*pf)(int);
声明了一个函数指针变量,变量名为pf
,类型为int (*)(int)
。接着把函数f
的地址赋给了pf
。其实不管pf
是什么类型,int
型还是看起来很复杂的指针型,在汇编代码中都只是一个没有姓名的内存空间而已。
后面的代码将变量a
强制转换为int (*)(int)
类型,也就是函数指针类型,这种写法是可以的,但是不能用(int (*)(int)) a;
这种方式来将a
声明为此种类型的变量。
编译后的汇编代码如下:
.text
.globl _f
_f:
push ebp
mov ebp, esp
mov eax, DWORD PTR [ebp+8]
inc eax
pop ebp
ret
.globl _w
_w:
push ebp
mov ebp, esp
mov eax, DWORD PTR [ebp+8]
add eax, 2
pop ebp
ret
.globl _main
_main:
mov DWORD PTR [ebp-4], OFFSET FLAT:_f
mov DWORD PTR [ebp-8], 1
mov DWORD PTR [ebp-12], 2
mov eax, DWORD PTR [ebp-8]
mov DWORD PTR [ebp-12], eax
mov DWORD PTR [esp], OFFSET FLAT:_f
call _useforw
mov DWORD PTR [esp], OFFSET FLAT:_w
call _useforw
mov DWORD PTR [esp], OFFSET FLAT:_f
call _useforw
leave
ret
.globl _useforw
_useforw:
push ebp
mov ebp, esp
sub esp, 8
mov DWORD PTR [esp], 9
mov eax, DWORD PTR [ebp+8]
call eax
mov DWORD PTR [ebp-4], eax
leave
ret
可以看到,函数指针作为参数时,虽然写法看起来复杂,实际只是把函数名/标号的地址传递了过去。
好了,第五章就到这里,下一篇开始学习第六章——结构。
网友评论