数组和指针寻址

本节讲述的是指针是如何汇编的。


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;函数指针呢? (提示函数指针是不是也是指针,所以这个指针存放的肯定是函数的地址了,呵呵,所以调用的时候肯定是个简介调用了)


如果错误,请指正。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言中二维数组指针数组都可以用来存储多个数据。但是它们有着不同的特点和用途。 1. 二维数组 二维数组是一个有着两个维度的数组,每个维度可以存储多个数据。它可以用来存储表格形式的数据,比如矩阵、棋盘等。 二维数组的声明方式为:`type name[row][column]`,其中type表示数据类型,name表示数组名,row和column表示数组的行数和列数。 例如,下面是一个3行4列的二维整型数组的声明: ``` int arr[3][4]; ``` 可以通过下标访问二维数组中的元素,例如: ``` arr[0][0] = 1; arr[1][2] = 3; ``` 二维数组的优点是可以方便地存储二维数据,并且访问速度较快。缺点是在传递到函数中时,需要指定数组的行数和列数,不够灵活。 2. 指针数组 指针数组是一个数组,其中每个元素都是一个指针。它可以用来存储多个字符串或多个指向不同类型数据的指针指针数组的声明方式为:`type *name[length]`,其中type表示指针指向的数据类型,name表示数组名,length表示数组的长度。 例如,下面是一个存储3个字符串的指针数组的声明: ``` char *strArr[3] = {"hello", "world", "!"}; ``` 可以通过下标访问指针数组中的元素,例如: ``` printf("%s\n", strArr[0]); ``` 指针数组的优点是可以存储不同类型的数据,传递到函数中时,只需要传递指针数组的名字,不需要指定数组长度,更加灵活。缺点是访问速度相对较慢,需要多次间接寻址。 总的来说,二维数组适合存储表格形式的数据,而指针数组适合存储不同类型的数据。选择使用哪种方式,需要根据具体需要来决定。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值