前言
这节课主要还是回顾了一下大一学过的数组,但是现在从汇编的角度看它,真的会有了不一样的感悟
课堂
数组的定义
一组相同类型的变量,为了方便读写,采用另外一种表示形式
/*void Function(){
int v_0 = 1;
int v_1 = 2;
int v_2 = 3;
int v_3 = 4;
int v_4 = 5;
int v_5 = 6;
int v_6 = 7;
int v_7 = 8;
int v_8 = 9;
int v_9 = 10;
}*/
//使用数组表示
void Function(){
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
}
注意:在数组声明时,必须用常量来指明长度,不能使用变量
void Func(){
int x = 10;
int arr[x] = {1,2,3,4,5,6,7,8,9,10}; //错误的!在数组声明时,必须用常量来指明长度(根据编译器版本决定是否可以,这里学的是不可以的)
}
这是为什么呢,咱们从反汇编的角度去分析一下
在函数提升堆栈的那一块,编译器提升堆栈时开辟的缓冲区大小默认为 0x40 字节,每有一个局部变量就增加 4 字节,我们定义的数组大小为 10 个元素,即等于 10 个局部变量,任意类型的局部变量都用 32 位容器存储,上一章提过,所以这里要开辟 40h + 28h 大小的缓冲区,但如果现在定义成 int arr[x]的话,数组的长度就无法确定,那么编译器就无法提前给数组分配好内存,所以会报错
数组的使用
-
数组在使用时,可以通过常量、变量来定位数据
-
数组定位时,可以超过数组的长度,编译不会有错,但读取的数据是错的
void Function()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int x = 1;
int y = 2;
int r ;
r = arr[1];
r = arr[x];
r = arr[x+y];
r = arr[x*2+y];
r = arr[arr[1]+arr[2]];
r = arr[Add(1,2)];
int a5 = arr[100]; //不会报错
}
数组的反汇编
-
编译器会根据数组声明时指定的长度来开辟指定大小的空间,无论当中元素有没有赋初始值
-
数组元素存入缓冲区是正着存的,但是在栈中存的位置是从低地址向高地址存的,比如 int arr[3] = {1,2,3};,先把 1 存入[ebp-0xC],再将 2 存入[ebp-8],最后把 3 存入[ebp-4]
作业
2.数组是 3 个的时候 esp 也是提升了 4 个,数组是 4 个的时候也是提升了 4 个,只不过传 3 个,剩下有个 int3,也就是 CC;参数在传递的时候和 32 位架构保持一致,都是一次传 4 个字节,但是函数里面要使用参数的话,用的是自身的字节,比如 char 就还是只使用一个字节,局部变量和参数也是如此,这俩这方面也是一致的;以后定义参数以及局部变量的时候,就不要整什么 char 和 short,因为在计算机中都是统一分配 4 个字节
计算机的步子就是 32 位,尤其堆栈这块都是 4 个 4 个字节来的,计算机只是在追求速度和追求空间中选择了追求速度
3.
- 因为 Func 函数中定义了 13 个局部变量,小于等于 32 位的任何类型的局部变量都会被分配 32 位内存来存储,所以 VC6 的编译器在提升堆栈时会开辟 0x40 + 0x34h = 0x74 字节的缓冲区
- 接着分析定义局部变量和赋值的反汇编:还是正着存局部变量,现存 x = 1 到[ebp - 4],再试 y = 2 到[ebp - 8],接着是 int r,此时 r 会被分配内存空间[ebp - 0xC],但是没有赋值。接着就是存数组从 1 存到 10,但是从低地址往高地址存,所以从[ebp - 0x34]每隔四个字节一直存到[ebp - 0x10]
接着就开始分析如何根据下标找内存中数组元素:
- 如果是
arr[1]
直接可以通过[ebp - 30h]来找到,因为从低地址[ebp - 34h]到高地址[ebp - 10h]分别是 arr[0]到 arr[9]
- 如果是 r = arr[x];,x 也是一个局部变量,所以先把 x 的值从[ebp-4]中取出来赋给一个寄存器 ecx,然后因为 arr[0]所在地址为[ebp - 0x34],如果下标为 1,则加一个 0x4;如果下标为 2,则加两个 0x4。所以现在下标为寄存器 ecx 中存的值,那么就是[ebp - 34h + ecx * 4 ]
- 如果是 r = arr[x+y];,x 和 y 都是局部变量,所以先把 x 的值从[ebp - 4]中取出来赋到一个寄存器 eax 中,同理也把 y 的值从[ebp - 8]中取出来与 eax 中的值相加后结果赋到寄存器 eax 中,那么 arr[x + y]表示的地址即为[ebp - 0x34 + eax * 4],所以将[ebp - 0x34 + eax * 4]内存中的值取出来赋到一个寄存器 ecx 中,最后将 ecx 中的值赋给局部变量 r 所在内存地址[ebp - 0xC]中即可
- 如果是 r = arr[x*2+y];,这个要注意的是乘法编译器是如何翻译成汇编指令的?先将 x,y 的值从内存中以此取出存到 edx 和 eax 寄存器中,然后可以使用 lea 寄存器,[立即数]的方式,直接将表达式 edx * 2 + eax 放到[]中当做一个地址立即数,那么 lea 是直接将这个计算出来的立即数存入 ecx 中,而不会去找这个立即数做表示的内存地址编号中的存的值。所以 ecx 中存的就是 x * 2 + y 的结果,再根据[ebp - 0x34 + ecx * 4]找到这个下标所在的内存空间,最后将当中的值存到 edx 中,再存到 r 表示的内存地址[ebp - 0xC]中,这么看编译器还是有点聪明的哈哈哈哈哈
4.桶排序
void sort_arry_tong(){
int arry[10] = {3,4,6,2,1,6,6,7,0,4};
int arry_none[10] = {0};
int i = 0;
for(i;i < 10 ;i++){
int temp = 0;
temp = arry[i];
arry_none[temp]++;
}
int j = 0;
for(j;j < 10;j++){
int j_2 = 0;
for(j_2;j_2 < arry_none[j];j_2++){
printf("%d",j);
}
}
}
OKK 结束睡觉