本节讲述的是指针是如何汇编的。
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;函数指针呢? (提示函数指针是不是也是指针,所以这个指针存放的肯定是函数的地址了,呵呵,所以调用的时候肯定是个简介调用了)
如果错误,请指正。