函数
- 通过栈传递参数
- 平衡栈(函数调用约定)
- cdecl
- 参数从右至左入栈
- 调用者清栈
- stdcall
- 参数从右至左入栈
- 自身清栈
- fastcall:
- ecx和edx传前两个参数, 剩下的参数从右至左入栈
- 自身清栈
- cdecl
- 提升栈使用EBP寻址, 函数内使用提升后的"新"栈
- 提升: 将原EBP地址压入栈, 使用EBP存储原ESP地址, 提升ESP
- 下降: 将ESP复原, 取出EBP并复原
- 返回值使用EAX传递
变量
- 变量类型确定内存宽度
- 变量名是内存地址的别名
全局变量
- 每个全局变量有一个固定的独一无二的内存地址
- 全局变量在编译的时候就已经确定了内存地址和宽度
- 如果不重新编译, 全局变量的内存地址不变
- 全局变量中的值任何程序都可以修改, 是公用的
- 全局变量有默认值
局部变量
- 局部变量是函数内部申请的, 如果函数没有执行, 那么局部变量没有内存空间
- 局部变量的内存是在栈中分配的, 程序执行时才分配(我们无法预知函数何时执行, 这意味着无法确定局部变量的内存地址)
- 因为局部变量的内存是不确定的, 所以只能在函数内部使用, 其他函数不能使用
- 局部变量定义后必须初始化值
整数类型
- 十转二: 除二取余(整数位继续除直到0, 小数位0与非0为二进制), 能保证精度
- 使用补码存储
- 如果数据溢出(超出范围), 将舍弃高位(截断)
浮点类型
- 十转二: 乘二取整(小数位继续乘直到0, 整数位为二进制), 不能保证精度
- 存储方式:
- float(32bit):
- 31-30(1bit): 符号位
- 30-22(8bit): 指数部分(第一位是科学计数法方向位, 小数点向左移是1, 向右移是0)
- 22-0(23bit): 尾数部分
- double(64bit):
- 63-62(1bit)
- 62-51(11bit)
- 51-0(52bit):
- float(32bit):
- 编码步骤:
- 转为二进制小数
- 使用科学计数法表示
- 按编码规则填入
- 栗子:
- 8.25->1000.01->1.00001*2^3->0 10000010 00001000000000000000000
- 16进制: 41040000
类型转换
- 小转大: MOVSX(带符号)/MOVZX(不带符号)
- 大转小: 使用小寄存器EAX/AX/AL
运算
分支语句
- switch的效率比if-else高
- switch在大于3个分支时, 将跳转地址存入内存, 使用jmp计算跳转
- switch生成一张连续跳转表, 如果不连续则填充default地址, 将x减去分支中的最小值, 大于分支数则跳default, 否则映射到跳转表jmp [计算值*4+跳转表地址]
- if-else是遍历条件执行
数组
数组越界问题
- 数组下标+2(从栈顶开始)可以用下标找到[ebp+4]
多维数组
- 内存连续的一维数组
- 第1个下标表示第几个数组, 第2个下标表示每个数组中的第几个元素
- 第1个下标每加1相当于内存中加上每个数组的长度
- 二维数组与一维数组的映射: 从0-N所在的一维数组之间的数组个数乘一维数组的长度加上N
转换
指针
- 指针类型的变量宽度永远是4字节(32位)
- 指针类型的变量加减运算的实际宽度是它基础类型的宽度(寻址宽度)(去掉一个*)
- char* a = (char*) 1; a++; //a实际加了1
- int* b= (int*) 1; b++; //b实际加了4
- char** c (char**) 1; c++; //c实际加了4
- 取地址(&):
- 局部变量: lea eax, dword ptr ss:[ebp-4]
- 全局变量: mov eax, 00426d9c
- 取值(*):
- 读: mov eax, dword ptr ds:[eax]
- 写: mov dword ptr ds:[eax], 1
- 取值后的类型是指针类型去掉一个*的类型, 栗如int (*)[2]类型取值的类型是int [2]
字符串
- char a[] = {'A', 'B', '\0'}
- 直接赋值数组(没有常量区字符串, 数组可修改)
- char b[] = "AB"
- 将字符串放到常量区
- 将字符串复制到数组(有常量区字符串, 数组可修改)
- char* c = "AB"
- 将字符串放到常量区
- 将地址给c(有常量区字符串, 字符串不可修改)
指针与指针指向的类型没有关系
struct st {
int a;
int b;
}
int arr[8] = {1, 2, 3, 4, 5, 6, 7, 8};
struct st* sx = (struct st*) arr;
for (int i = 0; i < 4; i++) {
printf("%d %d\n", sx->a, sx->b);
sx++;
}
数组与数组指针类型
- 数组是数组类型(type [length]), 不是数组指针类型(type (*)[length]), 只是数组类型的值是地址
- 数组类型(数组名)增减是数组内数据类型的宽度
- 数组指针类型(&数组名)增减是数组宽度(数组长度*数据类型宽度)
栗子: 二维数组只是表象
- 数组指针的维度与数组的维度没有关系
指针偏移取一维数组类型
int a1[6] = {5, 6, 7, 4, 3, 2};
int (*p)[3] = (int (*)[3]) &a1;
- p的宽度是3(对于int数组来说), p[1]等于*(p + 1), 位置+3到"4"的位置
- *(p + 1)取值得到int数组类型, 再偏移取值p[1][1]或((p + 1) + 1)
二维数组类型
int a1[6] = {5, 6, 7, 4, 3, 2};
int (*p)[2][3] = (int (*)[2][3]) &a1;
- *p得到二维数组类型, 操作二维数组类型取值(*p)[1][1]
指针偏移取二维数组类型
int a1[8] = {5, 6, 7, 4, 3, 2, 1, 8};
int (*p)[2][2] = (int (*)[2][3]) &a1;
- p的宽度是4(对于int数组来说), *(p + 1)使位置到"3"的位置
- *(p + 1)取值得到int二维数组类型, 对后四个数操作, (*(p + 1))[1][1]
结构体
- 对结构体的操作并不是转为指针操作(数组), 而是结构体类型
- 结构体作为函数参数将进行传值(所有成员)而不是传地址
- 传一个四个成员的结构体与传四个参数没有区别
- 结构体的相互赋值将拷贝所有结构体成员
- 结构体作为函数参数将进行传值(所有成员)而不是传地址
- 问题: 大量的内存复制
- 使用指针传递结构体