OneNote防丢失。
一、函数返回值如何传递
1.1 char类型返回
- 代码:
c
char fun(){
//代码
return 12;
}
int main(int argc, char* argv[]){
char i = fun();
return 0;
}
- 反汇编:
-
函数返回值,即return 12代码部分
在fun函数中,将返回值"12"存放在eax中(教程中32位的vc6++是存放在al位置)。 -
函数返回值赋值给main中的变量
将一个字节长度的12赋值给[rbp-0x1]的位置,在教程中32位的vc6++是将1字节长度存放在[ebp-4]的位置,即move byte ptr [ebp-4],al(ebp-4是main函数的缓冲区),同样是仅存储一个字节的长度,编译器的不同会导致程序在空间的利用程度上存在差异
1.2 short类型返回
- 代码:
c
short fun(){
//代码
return 12;
}
int main(int argc, char* argv[]){
short i = fun();
return 0;
}
- 反汇编:
-
函数返回值
short为2字节长度,所以函数中将返回值12存储在eax中,但32位的vc6++中存储在ax中
-
函数返回值赋值给main中的变量
将2字节长度的12存储在[rbp-0x2]的位置,在教程中32位的vc6++是将2字节长度存放在[ebp-4]的位置,即move word ptr [ebp-4],ax
1.3 int类型返回
- 代码:
c
int fun(){
//代码
return 12;
}
int main(int argc, char* argv[]){
int i = fun();
return 0;
}
}
- 反汇编:
- 函数返回值
int为4字节长度,所以函数中将返回值12存储在eax中,32位的vc6++中也存储在eax中
- 函数返回值赋值给main中的变量
将4字节长度的12存储在[rbp-0x4]的位置,在教程中32位的vc6++是将4字节长度存放在[ebp-4]的位置,即move dword ptr [ebp-4],ea
1.4 作业-long long类型(64位)返回值
- 代码
c
__int64 fun(){
//代码
return 0x1234567890;
}
int main(int argc, char* argv[]){
__int64 i = fun();
return 0;
}
- 反汇编:
- 函数返回值
在64位的环境中,__int64类型的数据存放在rax寄存器中,在32位的环境中将结果放在eax和edx中
注:在 64 位代码中,movabs可用于对mov具有 64 位位移或立即数操作数的指令进行编码。 - 函数返回值赋值给main中的变量
64位环境中,将rax中的函数返回值存储在[rbp-0x8]的位置,使用的是qword,在32位环境中将eax(低位)存入[ebp-8],将edx(高位)存入[ebp-4]
1.5 作业-char arr[3]={1,2,3}与char arr[4]={1,2,3,4}哪个更省空间,从反汇编角度说明,用char和short分别创建10个元素的数组,观察如何分配
1.5.1 char arr[3]={1,2,3}与char arr[4]={1,2,3,4}哪个更省空间,从反汇编角度说明
- char arr[3]={1,2,3}
- 代码
c
void Function(){
char arr[3] = {1,2,3};
}
int main(int argc, char* argv[]){
Function();
return 0;
}
- 反汇编(64位的VScode)
以字节大小存储,分配了0x10大小的缓冲区;
- char arr[4]={1,2,3,4}
- 代码
c
void Function(){
char arr[4] = {1,2,3,4};
}
int main(int argc, char* argv[]){
Function();
return 0;
}
- 反汇编(64位的VScode)
- 总结
同样以字节大小存储,分配了0x10大小的缓冲区;
VScode在空函数时不分配缓冲区,即nop之后直接ret返回,当arr数组长度为5时,依然分配0x10大小的缓冲区(而不是海哥视频里的0x40+4n大小的缓冲区),可见现在是优化过的,并不会浪费大量内存去提升效率。
1.5.2 用char和short分别创建10个元素的数组,观察如何分配
- 代码
c
int main(int argc, char* argv[]){
char arr0[10] = {1,2,3,4,5,6,7,8,9,10};
short arr1[10] = {1,2,3,4,5,6,7,8,9,10};
return 0;
}
- 反汇编
char使用字节大小存储,short使用字大小存储(可见新版本还是优化了的,该是多大就是多大)。
1.6 作业-找出下面赋值过程的反汇编代码
1.6.1 作业部分
- 代码:
c
void Function(){
int x = 1;
int y = 2;
int r;
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
r = arr[1];
r = arr[x];
r = arr[x+y];
r = arr[x*2+y];
}
- 反汇编
数组寻址:值=取值(rbp/ebp + rax/eax * 数值宽度 - 数组首地址),其中rax/eax中存的是arr[n]中的n。
1.7 作业-选做桶排序
之前还在卷开发的时候都直接跳过的玩意,略过略过。
二、参数传递的本质
2.1 8位参数传递
- 代码:
- 汇编:
使用堆栈传递参数,并且使用4字节大小内存存储参数,从右往左入栈,并且外平栈,可见使用的调用约定是__cdecl。
2.2 16位参数传递
- 代码:
- 汇编:
2.3 32位参数传递
-
代码:
-
汇编:
2.4 总结:
-
在32位的系统中,参数传递始终按照4字节的大小入栈,所以定义char或者short类型的参数并不会节约空间,反而在char a = 1;Fun(1);传递参数时会产生额外的操作去进行al或者ax的转换。
-
如果本机是32位的,那么及其对于32位的数据支持度最好,反之32位时,对于32位的支持度最好。所以整数类型的参数,全部使用int传递参数。
-
参数传递的本质是将上层函数的变量或者表达式的值复制一份再传递给下层函数。
三、局部变量的内存分配
-
(32位环境中)小于32位的局部变量,空间在分配时,按32位分配;
-
使用时按实际的宽度使用;
-
不要定义char/short类型的局部变量;
-
参数与局部变量没有本质区别,都是局部变量,都在栈中分配(参数是函数调用前分配的值,局部变量是函数执行时分配的值);
-
完全可以把参数当初局部变量使用。
四、赋值语句的本质
将某个值存储到变量中的过程就是赋值。
思考题:
int r = x + y;是赋值语句吗?那么 int r = Add(x,y)是赋值语句吗?
是,无本质区别(此处的区别是在于逆向人的眼中)。
五、数组的本质
1、总结:
1.1 一组相同类型的变量,为了方便读写,采用另外一种表示形式.
1.2 数组在声明的时候,必须用常量来指明长度,不能使用变量.
示例:定义10个变量,观察反汇编:
- 分别赋值
在缓冲区将数组中的数据一一填入
- 数组赋值
可见在底层是一样的。
六、数组的使用
偏向开发的知识点
结尾依然是,海哥牛逼。