2020-08-11汇编学习00
请注意,本文编写于 840 天前,最后修改于 204 天前,其中某些信息可能已经过时。

目录


一、返回值是如何传递的?

1、char类型的返回值
char 类型的返回值经过运算后保存在al

2、short 类型的返回值
short类型的返回值经过运算后保存在ax

3、int 类型的返回值
int类型的返回值经过运算后保存在eax

二、参数传递的本质

参数传递的本质:将上层函数的变量,或者表达式的值“复制一份”,传递给下层函数.

如何把一个参数传到程序里呢?
:堆栈、寄存器

调用约定:
cdecl、fastcall、stdcall

1、8位参数传递

void Function(char x,char y,char z)
{

    //实际上char类型并没有节省空间,
    //在内存中,是保存成32位的
    //编译器遵循本机尺寸,char类型依旧按照char类型来传递
}
//尽管定义的是char类型,压栈的却是eax而并不是al。
//在32位的系统中,系统默认最合适的数据类型,就是32个bit,即4字节。同理,64位的系统就是8字节。
//也就是说,如果使用小于4个字节的局部变量来进行参数传递,vc编译器仍然会按照4个字节来进行传递,但是多余的部分并不会使用。

2、16位参数传递


void Function(short x,short y,short z)
{
    //实际上char类型并没有节省空间,
    //在内存中,是保存成32位的
    //编译器遵循本机尺寸,char类型依旧按照char类型来传递
}

3、32位参数传递

void Function(int x,int y,int z)
{


}

总结

  1. 本机尺寸:如果本机是32位的,那么对32位的数据支持最好,如果是64位的,那么对64位的支持
  2. 编译器遵守了这个规则:
    char类型或者short类型的参数不但没有节省空间,反而浪费了多余的操作.
    结论:整数类型的参数,一律使用int类型

参数传递的本质:将上层函数的变量,或者表达式的值“复制一份”,传递给下层函数.

三、局部变量的内存分配

局部变量与参数,全部都是在堆栈中分配的空间,一起区别就是参数实在函数调用的时候分配的值,局部变量是函数执行的时候分配的值。

示例

void Function()
{
	char a = 1;
	char b = 2;

	short c = 3;
	short d = 4;

}

void Function(int x,int y)
{
	char a = x;
	char b = y;

	x = 3;
	y = 4;
}

总结

  1. 小于32位的局部变量,空间在分配时,按32位分配.

  2. 使用时按实际的宽度使用.

  3. 不要定义char/short类型的局部变量.

  4. 参数与局部变量没有本质区别,都是局部变量,都在栈中分配.

  5. 完全可以把参数当初局部变量使用

四、赋值语句的本质

将某个值存储到变量中的过程就是赋值.

示例:

int x = 1;


int x = 1;
int y = 2;
int z = 3;

int r = 1*2+3;
int r = 1*(2+3);

思考题:

  1. int r = x + y;是赋值语句吗?
    是赋值语句,将表达式计算结果赋给r

  2. int r = Add(x,y)是赋值语句吗?
    是赋值语句,函数运行完后会return一个值,会赋给r

五、数组的本质

数组在汇编中的特征 一般是,连续并且等宽的。

示例1:定义10个变量,观察反汇编

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;
}

01

void Function()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
}

02

由此可见:数组就是,一组相同类型的变量,为了方便读写,采用另外一种表示形式.,它与一一定义变量,赋值在内存中是一样的。

示例2:

void Function()
{
	int x = 10;
	int arr[x] = {1,2,3,4,5,6,7,8,9,10};
}

声明数组的时候是不可以这样写的,因为x是变量,如果需要可以使用 define 定义一个常量

# define Max 10;

void Function()
{
	//int x = 10;
	int arr[Max] = {1,2,3,4,5,6,7,8,9,10};
}

六、数组的使用

示例:


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];    //从编译的角度讲,写多少都可以。但会越界
}

总结

  1. 数组在使用时,可以通过变量来定位数据.

  2. 数组定位时,可以超过数组的长度,编译不会有错,但读取的数据是错的.

作业

1、返回值超过32位时,存在哪里?用long long(__int64)类型做实验

long long类型在VC6中对应的是__int64

__int64 Function()
{
__int64 x = 0x1234567890;
return x;
}

低位 - 后八个存在eax里,高位 - 前八个存在edx里

2、char arr[3] = {1,2,3};与 char arr[4] = {1,2,3,4}

哪个更节省空间,从反汇编的角度来说明你的观点

char arr[3] = {1,2,3};

char arr[4] = {1,2,3,4}

从汇编可以看出,两者占用的空间是一样的都是4个字节,VC分配的空间是一样的,所以char arr[3]会有一定的浪费

3、找出下面赋值过程的反汇编代码

代码

汇编

0040D828   mov         dword ptr [ebp-4],1
0040D82F   mov         dword ptr [ebp-8],2
0040D836   mov         dword ptr [ebp-34h],1
0040D83D   mov         dword ptr [ebp-30h],2
0040D844   mov         dword ptr [ebp-2Ch],3
0040D84B   mov         dword ptr [ebp-28h],4
0040D852   mov         dword ptr [ebp-24h],5
0040D859   mov         dword ptr [ebp-20h],6
0040D860   mov         dword ptr [ebp-1Ch],7
0040D867   mov         dword ptr [ebp-18h],8
0040D86E   mov         dword ptr [ebp-14h],9
0040D875   mov         dword ptr [ebp-10h],0Ah
0040D87C   mov         eax,dword ptr [ebp-30h]
0040D87F   mov         dword ptr [ebp-0Ch],eax


//r = arr[x];

0040D882   mov         ecx,dword ptr [ebp-4]            //ecx = 1
0040D885   mov         edx,dword ptr [ebp+ecx*4-34h]
0040D889   mov         dword ptr [ebp-0Ch],edx
//ebp+1*4-34h, 1乘以4依然是4,所以就变成了ebp+4-34h
//再继续,4-34h = -30h, 所以就变成了ebp-30h = 2

//r = arr[x+y];

0040D88C   mov         eax,dword ptr [ebp-4]           //eax = 1
0040D88F   add         eax,dword ptr [ebp-8]           //eax = 1 + 2 = 3
0040D892   mov         ecx,dword ptr [ebp+eax*4-34h]   //ebp + 3*4-34h = ebp + C - 34h = ebp -28h = 4
0040D896   mov         dword ptr [ebp-0Ch],ecx         //ebp-0Ch = r = 4


0040D899   mov         edx,dword ptr [ebp-4]           //edx = 1
0040D89C   mov         eax,dword ptr [ebp-8]           //eax = 2
0040D89F   lea         ecx,[eax+edx*2]                 //ecx = 2 + 1 * 2 = 4
0040D8A2   mov         edx,dword ptr [ebp+ecx*4-34h]   //ebp + 4*4-34h = ebp + 10 - 34h = ebp - 24h = 5
0040D8A6   mov         dword ptr [ebp-0Ch],edx         // ebp - 0Ch = r = 5

[ebp+eax*4-34h] 及 [ebp+ecx*4-34h] 中的 *4 是因为 int 是4个字节,所以*4
像 short 就是*2,如:[ebp+ecx*2-34h]
char 就不用 乘 [ebp+ecx-18h]

3、桶排序

2 4 6 5 3 1 2 7

c
// home05.cpp : Defines the entry point for the console application.
//

# include "stdafx.h"

int arr1[8] = {2,4,6,5,3,1,2,7};

//创建一个下标为 arr1中最大值+1 也就是7+1=8的长度
int arr2[8] = {0};	//桶数组初始化为0


void Function()
{

	for(int i=0;i<8;i++)
	{
		arr2[arr1[i]]++;	//将arr1的值放入arr2的下标,如下标arr1[0]的值是2,那么下标arr2[2]的值就会加1,如有重复的那就继续加1
	}

	printf("桶数组:\n");
	for(int j=0;j<8 ;j++)
	{
		printf("%d ",arr2[j]);	//遍历桶数组arr2的值,查看是否准确
	}

	printf("\n");	//换行


	//进行打印
	printf("桶排序后: \r");

	for(int k=0;k<8;k++)
	{
		/*出现了几次就打印几次
		 0 1 2 1 1 1 1 1
		*/
		for(int t=1;t <=arr2[k];t++)
		{
			printf("%d ",k);
		}

	}


}
int main(int argc, char* argv[])
{
	Function();

	return 0;
}

//使用while循环

// home05.cpp : Defines the entry point for the console application.
//

# include "stdafx.h"

int arr1[8] = {2,4,6,5,3,1,2,7};

//创建一个下标为 arr1中最大值+1 也就是7+1=8的长度
int arr2[8] = {0};	//桶数组初始化为0

int t;


void Function()
{

	for(int i=0;i<8;i++)
	{
		arr2[arr1[i]]++;	//将arr1的值放入arr2的下标,如下标arr1[0]的值是2,那么下标arr2[2]的值就会加1,如有重复的那就继续加1
	}

	printf("桶数组:\n");
	for(int j=0;j<8 ;j++)
	{
		printf("%d ",arr2[j]);	//遍历桶数组arr2的值,查看是否准确
	}

	printf("\n");	//换行


	//进行打印
	printf("桶排序后: \r");

	for(int k=0;k<8;k++)
	{
		if(arr[k] > 0)
		{
		    t = arr[k];
		    while(t >0)
		    {
		        printf("%d",k);
		        t--;
		    }
		}
	}


}
int main(int argc, char* argv[])
{
	Function();

	return 0;
}

笔记内容来自滴水三期

本文作者:Na1r

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!