您的位置:首页 > 其它

数组和指针寻址

2012-04-26 23:02 676 查看
本节讲述的是指针是如何汇编的。

1:数组和字符数组是如何保存的

先来看看C语言源码

#include<stdio.h>

int main()
{
int int_a[5]={1,2,3,4,5};

char char_a[] = "hello world";
char * char_pa = "hello world";
}


以下是汇编后的代码

; File d:\ѧϰ\c++ anti asm\capter08\main.c
CONST	SEGMENT
??_C@_0M@BNI@hello?5world?$AA@ DB 'hello world', 00H	; `string'   常量存储区
CONST	ENDS
;	COMDAT _main
_TEXT	SEGMENT
_int_a$ = -20	;标识偏移
_char_a$ = -32
_char_pa$ = -36
_main	PROC NEAR					; COMDAT

; 4    : {

push	ebp
mov	ebp, esp
sub	esp, 100				; 00000064H
push	ebx
push	esi
push	edi
lea	edi, DWORD PTR [ebp-100]
mov	ecx, 25					; 00000019H
mov	eax, -858993460				; ccccccccH
rep stosd

; 5    : 	int int_a[5]={1,2,3,4,5};

mov	DWORD PTR _int_a$[ebp], 1       ; _int_a 的偏移为 -20  依次将5个数拷贝到int型数组的数据区

mov	DWORD PTR _int_a$[ebp+4], 2
mov	DWORD PTR _int_a$[ebp+8], 3
mov	DWORD PTR _int_a$[ebp+12], 4
mov	DWORD PTR _int_a$[ebp+16], 5

; 6    :
; 7    : 	char char_a[] = "hello world";	  ; char_a 的偏移为  -32  依次将字符串常量拷贝的到这个数据区

mov	eax, DWORD PTR ??_C@_0M@BNI@hello?5world?$AA@
mov	DWORD PTR _char_a$[ebp], eax
mov	ecx, DWORD PTR ??_C@_0M@BNI@hello?5world?$AA@+4
mov	DWORD PTR _char_a$[ebp+4], ecx
mov	edx, DWORD PTR ??_C@_0M@BNI@hello?5world?$AA@+8
mov	DWORD PTR _char_a$[ebp+8], edx

; 8    : 	char * char_pa = "hello world";	   ; char_a 的偏移为  -36   将字符串常量的地址拷贝到这个数据区

mov	DWORD PTR _char_pa$[ebp], OFFSET FLAT:??_C@_0M@BNI@hello?5world?$AA@ ; `string'

; 9    : }

pop	edi
pop	esi
pop	ebx
mov	esp, ebp
pop	ebp
ret	0
_main	ENDP
_TEXT	ENDS
END


由上对应可知:

int int_a[5]={1,2,3,4,5};
int型数组在栈中的分布是以 int_a 变量的地址依次放置的。位置为 ebp-20 到 ebp-4

char char_a[] = "hello world";
char型数组在栈中的分布同 int型数组类似也是在栈中依次放置的,开辟的空间为 ebp-32 到 ebp-24

char * char_pa = "hello world";
指针赋值的时候仅预留4字节的指针地址空间,初始化的时候仅仅将常量的地址拷贝到该地址

2:数组是如何作为参数和返回值的

请先看源码

#include<stdio.h>
#include<string.h>
char * show( char szBuff[])
{
strcpy(szBuff,"Hello");
return szBuff;
}

int main()
{
char char_a[] = "hello world";

printf("%s\n", show(char_a));
return 0;
}


汇编以后,这部分是 show函数的代码

PUBLIC	_show
PUBLIC	??_C@_05DPEH@Hello?$AA@				; `string'
EXTRN	_strcpy:NEAR
EXTRN	__chkesp:NEAR
;	COMDAT ??_C@_05DPEH@Hello?$AA@
; File D:\ѧϰ\C++ anti asm\Capter08\main.c
CONST	SEGMENT
??_C@_05DPEH@Hello?$AA@ DB 'Hello', 00H			; `string'   ;常量
CONST	ENDS
;	COMDAT _show
_TEXT	SEGMENT
_szBuff$ = 8	偏移
_show	PROC NEAR					; COMDAT

; 4    : {

push	ebp
mov	ebp, esp
sub	esp, 64					; 00000040H
push	ebx
push	esi
push	edi
lea	edi, DWORD PTR [ebp-64]
mov	ecx, 16					; 00000010H
mov	eax, -858993460				; ccccccccH
rep stosd

; 5    : 	strcpy(szBuff,"Hello");

push	OFFSET FLAT:??_C@_05DPEH@Hello?$AA@	; `string'  取常量首地址入栈作为 strcpy 的第一个参数
mov	eax, DWORD PTR _szBuff$[ebp]     ;获取 show 函数实参 szBuff的地址,这个地址是main函数中调用show函数中时入栈的,具体的函数调用栈请看我此类型文章的第一篇函数的工作原理

push	eax
call	_strcpy
add	esp, 8

; 6    : 	return szBuff;

mov	eax, DWORD PTR _szBuff$[ebp]   ;返回时,将szBuff的地址放入了寄存器 eax

; 7    : }

pop	edi
pop	esi
pop	ebx
add	esp, 64					; 00000040H
cmp	ebp, esp
call	__chkesp
mov	esp, ebp
pop	ebp
ret	0
_show	ENDP
_TEXT	ENDS


现在来看一下main函数中调用的过程

; 13   : 	printf("%s\n", show(char_a));

lea	eax, DWORD PTR _char_a$[ebp]   ;这里获得 char_a 数组的首地址

push	eax
call	_show
add	esp, 4
push	eax
push	OFFSET FLAT:??_C@_03HHKO@?$CFs?6?$AA@	; `string'
call	_printf
add	esp, 8


现在总结一下:在使用数组作为参数时,数组退化为指针,传入被调用函数的是一个指针,如果大家测试 sizeof(szBuff)会发现输出的为4,所以大家在写代码时要避免如下情况

void test(char szBuff[])
{
int nLen = 0;
nLen = sizeof(szBuff);  //错误
nLen = strlen(szBuff); //正确
}


3:如何通过下标和指针寻址

通过上面实例测试我们已经发现 数组名其实就是数组的首地址;

比如 int a[10]; 那么 a[4]实际的地址 即 &a + 4*sizeof(int);

那么 指针是如何寻址的呢其实很简单,因为 一个指针里保存的是数组首地址,所以在汇编代码中,先通过指针获得数组的首地址,然后过的过程就和通过下标寻址一致了,所以指针寻址比通过数组的下标寻址多执行一条指令

4:经过了前面的积累,让我们来看一下相对复杂的多维数组

#include<stdio.h>
#include<string.h>
int main()
{
int nTwoA[2][2] = {1,2,3,4};
int i,j;
printf("%d\n",nTwoA[1][1]);

return 0;
}


; 5    : 	int nTwoA[2][2] = {1,2,3,4};

mov	DWORD PTR _nTwoA$[ebp], 1
mov	DWORD PTR _nTwoA$[ebp+4], 2
mov	DWORD PTR _nTwoA$[ebp+8], 3
mov	DWORD PTR _nTwoA$[ebp+12], 4


可知在二维数组的初始化时和普通的一维数组是一致的,那二维数组的元素是如何访问的呢

; 8    : 	printf("%d\n",nTwoA[i][j]);

mov	edx, DWORD PTR _i$[ebp]    ;获取 i的值
lea	eax, DWORD PTR _nTwoA$[ebp+edx*8]  ;
mov	ecx, DWORD PTR _j$[ebp]
mov	edx, DWORD PTR [eax+ecx*4]
push	edx
push	OFFSET FLAT:??_C@_03HMFC@?$CFd?6?$AA@	; `string'
call	_printf
add	esp, 8
由以上代码可知,元素访问的过程是先 获取i 的值, 然后获取 第一维的偏移量, edx*8 ,因为第二维有2个数,所以应该乘以8,

然后获取j 的值,然后在第一次偏移的基础上获取第二维的偏移量 ecx*4

说了这么多,现在给大家提出几个问题:

1:指向字符串的指针数组是怎么存放的呢 如 char *p[10]; (提示,这说白了还是指针,所以存放的只有地址)

2: char a[2][2]; char *pp = a; char b = pp[1][1]; 访问
pp[1][1] 的过程需要几次寻址操作 (提示3次)

3;函数指针呢? (提示函数指针是不是也是指针,所以这个指针存放的肯定是函数的地址了,呵呵,所以调用的时候肯定是个简介调用了)

如果错误,请指正。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: