一、返回值反汇编
-
char类型的返回值存储到al中,即返回值数据类型宽度为8位时,用al存储结果
-
short类型的返回值存储到ax中
-
int类型的返回值存储到eax中
-
宽度为64位的返回值数据类型怎么存储?使用两个32位寄存器来存储。见作业中的第一题
-
反汇编如下:
char Func(){ return 4; //对逆向来说,不同的数据类型中存的都是二进制数,所以只用在意宽度即可 } short Func2(){ return 4; } int Func3(){ return 6; } void main(int argc,char* argv[]){ int m = Func(); //用一个变量去接返回值 short n = Func2(); int i = Func3(); }
如果返回值类型为char则在函数中使用al存储返回值,在函数外将al的值再赋给局部变量等操作,如果变量数据类型宽度比返回值类型数据宽度大,则先进行有符号扩展,再传值
返回值类型为short则在函数中使用ax存储,在函数外再将ax中存储的返回值存入内存(局部变量)
int类型返回值同理
二、参数传递反汇编
1.本机尺寸
-
本机尺寸的概念:如果本机是32位的,那么对32位的数据支持最好,如果是64位的,那么对64位的支持。即计算机是32位那么如果一次读取或存入的数据也是32位的,计算机的效率是最高的!64位PC读取64位数据亦是如此
-
编译器是知道本机尺寸的,且编译器遵守了这个规则:所以你即使传参时用的16位或者8位数据类型,如果PC是32位的,那么都会用32位的寄存器或者内存来存储参数。char类型或者short类型的参数不但没有节省空间,反而浪费了多余的操作
void Func(char x,short y,int z){ } void main(int argc,char* argv[]){ Func(1,2,3); }
任何数据类型参数都用32位内存(堆栈)来存取,因为全是push操作。至于用32位内存还是32位寄存器存储看函数使用的调用约定
-
但是注意:虽然传任何类型参数都用32位容器存,但是用的时候不会把32位容器的32位全部使用,而是根据数据类型的宽度来选择指定宽度的数据使用
void Func(char x,short y,int z){ int m; m = x + y + z; } void main(int argc,char* argv[]){ Func(1,2,3); }
-
结论:整数类型的参数,一律使用int类型(效率最高)
2.参数传递本质
-
将上层函数的变量,或者表达式的值“复制一份”,传递给下层函数
void Plus(int x){ x = x + 1; //在调用Plus函数前先将main函数中x的值复制一份,而不是直接使用那块地址(不是指针),所以这里做运算的变量不是main中x变量本身,接着做完运算将结果存储在此时的Plus函数堆栈的[ebp+8] } int main(int argc, char* argv[]){ int x = 1; //这里定义的mian函数局部变量x的值存储在此时main堆栈的[ebp-4] Plus(x); //综上所以这里的值依然是1 printf("%d\n",x); return 0; }
-
先把局部变量值复制一份,存到eax中再入栈
-
在函数中运算的是复制过来的值,而不是main函数中局部变量x本身的值
-
三、局部变量反汇编
-
数据类型小于等于32位的局部变量,空间在分配时,按32位分配;但使用时按实际的宽度使用
比如一个short a变量,也会分配32位内存表示这个局部局部变量;比如一个char arr[3],虽然3个char类型数组元素只有24位,但是还是会分配32位来存储arr中的三个元素,只不过有8位是空出来的,浪费了;再比如char arr[5],会分配8字节内存
-
不要定义char/short类型的局部变量(32位计算机用int效率最高,原理还是按照本机尺寸规则)
-
函数的参数与局部变量没有本质区别,都是局部变量,都在栈中分配,只不过一个在[ebp+],一个在[ebp-]。所以完全可以把参数当初局部变量使用
-
局部变量会影响编译器在函数调用初始化时的缓冲区空间:VC6中的编译器默认提升堆栈,开辟的缓冲区(内存)大小为0x40字节–64字节,即16块内存单元(一块内存单元32位,即4字节),如果函数中每定义了一个任意类型局部变量,开辟的缓冲区大小就增加4字节(32位)。比如现在函数中定义了两个任意类型局部变量,那么开辟的缓冲区大小为0x48
void Func(){ char x = 1; int y = 1; } void main(int argc,char* argv[]){ Func(); }
注意不同的编译器开辟的大小规则是不一样的!
-
而且虽然局部变量中不同类型类型占的宽度不一样,但是小于等于32位的变量都是用4字节内存单位来存储的,只是占用4字节内存中的宽度不同,比如char类型就占分配给它的32位中的8位;short类型就占分配给它的32位中的16位;int类型就占分配给它的32位中的32位
四、作业
1.返回值超过32位时,存在哪里?用long long(__int64)
类型做实验
-
long long也是一种数据类型,在VC6中对应的表示形式是
__int64
,因为long long在VC6中没法直接使用,long long类型变量宽度是64bit,即8字节__int64 Func(){ __int64 x = 0x1234567890; return x; //返回值占8字节 } void main(int argc,char* argv[]){ __int64 i = x; }
-
分析反汇编:
-
可以发现如果局部变量定义的数据类型是64位宽度,即8字节,那么提升堆栈时要开辟的缓冲区大小应该为0x40 + 0x8 = 0x48字节大小
-
而且将一个8字节的数存到内存中要占用两个内存块,因为一个内存块的地址表示4字节的内存,那么此时就要将0x1234567890拆成两半,低4字节部分存到[ebp-8]中,高4字节部分存到[ebp-4]中
-
此时会使用两个32位寄存器来存储long long类型的返回值
-
如果main函数中的long long类型的局部变量i要使用Func函数的64位返回值,那么同样也是将eax和edx两个寄存器中存储的返回值存储到main函数为变量i开辟的两个内存块的空间中,且eax赋值给低位内存,edx赋值给高位内存
-