数组和指针寻址
2012-04-26 23:02
676 查看
本节讲述的是指针是如何汇编的。
1:数组和字符数组是如何保存的
先来看看C语言源码
以下是汇编后的代码
由上对应可知:
2:数组是如何作为参数和返回值的
请先看源码
汇编以后,这部分是 show函数的代码
现在来看一下main函数中调用的过程
现在总结一下:在使用数组作为参数时,数组退化为指针,传入被调用函数的是一个指针,如果大家测试 sizeof(szBuff)会发现输出的为4,所以大家在写代码时要避免如下情况
3:如何通过下标和指针寻址
通过上面实例测试我们已经发现 数组名其实就是数组的首地址;
比如 int a[10]; 那么 a[4]实际的地址 即 &a + 4*sizeof(int);
那么 指针是如何寻址的呢其实很简单,因为 一个指针里保存的是数组首地址,所以在汇编代码中,先通过指针获得数组的首地址,然后过的过程就和通过下标寻址一致了,所以指针寻址比通过数组的下标寻址多执行一条指令
4:经过了前面的积累,让我们来看一下相对复杂的多维数组
可知在二维数组的初始化时和普通的一维数组是一致的,那二维数组的元素是如何访问的呢
然后获取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;函数指针呢? (提示函数指针是不是也是指针,所以这个指针存放的肯定是函数的地址了,呵呵,所以调用的时候肯定是个简介调用了)
如果错误,请指正。
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;函数指针呢? (提示函数指针是不是也是指针,所以这个指针存放的肯定是函数的地址了,呵呵,所以调用的时候肯定是个简介调用了)
如果错误,请指正。
相关文章推荐
- 基于arm的C++反汇编 数组和指针的寻址
- 数组 指针比较 直接寻址和间接寻址 数组和指针在编译的时候的区别
- C++反汇编学习笔记7——数组和指针以及他们的寻址
- C++逆向第十课-----数组与指针的寻址
- 【C++沉思录】数组和指针的寻址
- 关于用二维数组调用函数的寻址(多维数组和指针)
- 数组 指针比较 直接寻址和间接寻址 数组和指针在编译的时候的区别。。。
- (转载)函数指针及函数指针数组的妙用
- C语言编程(练习8:数组与指针)
- 指针与数组的关系
- C语言学习-----指针篇(1)-----指向多维数组的指针和指针变量
- 黑马程序员--IOS入学学习--4-数组、字符串及指针
- 数组指针和数组头指针
- 数组指针和指针数组
- 函数指针、指针函数、指针的指针、指向数组的指针
- C语言 第 7 节 如何将字符串指针指向的内容赋值给数组
- C中数组与指针及多维数组
- C/C++数组名与指针区别深层探索
- C#获取C++中修改过的float数组(指针),dll
- 数组指针和指针数组详解