滴水-C语言
1. C语言的汇编表示
1.1 基本信息
- 入口程序:程序开始执行的地方
- return:执行结束
1.2 什么是函数?
函数是一些列指令的集合,用以完成某个会被重复使用的特定功能
- C语言中函数格式:
返回类型 函数名(参数列表){}
<1>返回类型、函数名不能省略
<2>参数列表可以省略
[!info] 函数名、参数名的命名规则
- 只能以字母、数字、下划线组成,且首字母不为数字
- 区分大小写
- 不能使用关键字
1.3 基本操作
- 创建exe文件(F7)
- 运行(F5)
- 断点(F9)
- 步过(F10)
- 步入(F11)
1.4 函数的调用
- 汇编中的函数调用:
CALL/JMP指令
- C语言中的函数调用:
- 函数名(参数1,参数2);
实际上:函数名就是编译器起的内存地址的别名
2. 参数传递与返回值
2.1 函数定义
1 | 返回类型 函数名(参数列表) |
数据类型:
int – 4 Byte
short – 2 Byte
char – 1 Byte
进制标识:
- 十六进制 – 通常
h结尾,例如40h - 二进制 – 通常
b结尾,例如1010b - 八进制 – 通常
o或q结尾,例如77o - 十进制 – 默认无后缀,或有时用
d
2.2 堆栈图理解
画堆栈图可以方便我们从汇编角度理解C语言命令是怎么运行的
- 堆栈缓冲区会填入CC
- 堆栈使用后,需要平栈
- 堆栈使用后,原先使用的堆栈并没有被清除,会被利用
2.3 参数传递
C语言:堆栈传参 从右向左
2.4 返回值存储
C语言:返回值存储在EAX中
3. 变量
3.1 声明变量
数据类型的宽度:
- int – 4 Byte
- short – 2 Byte
- char – 1 Byte
[!info] 函数名、参数名的命名规则
- 只能以字母、数字、下划线组成,且首字母不为数字
- 区分大小写
- 不能使用关键字
3.2 全局变量
- 编译的时候就已经确定了内存地址和宽度,变量名就是内存地址的别名
- 如果不重写编译,全局变量的内存地址保持不变。游戏外挂中的找“基址”,就是找全局变量
- 全局变量中的值任何程序都可以修改,是公用的
如:CE搜索基址
3.3 局部变量
- 局部变量是函数内部申请的,如果函数没有执行,那么局部变量没有内存空间。
- 局部变量的内存是在堆栈中分配的,程序执行时才分配。我们无法预知程序何时执行,所以我们无法确定局部变量的内存地址。
- 因为局部变量地址内存不确定,所以,局部变量只能在函数内部使用,其他函数不能使用
3.4 变量初始值
- 全局变量可以没有初始值而直接使用,系统默认初始值为0
- 局部变量在使用前必须要赋值
4. 变量与参数的内存空间
缓冲区:用于存储局部变量
| EBP-4 | 局部变量 |
|---|---|
| EBP | 原EBP值 |
| EBP+4 | 返回地址 |
| EBP+8 | 参数区 |
5. 函数嵌套调用的内存布局
画堆栈图理解
函数与嵌套函数:在堆栈上是相邻完整的函数堆栈
6. 整数类型
6.1 C语言变量类型
- 基本类型
- 整数类型
- 浮点类型
- 构造类型
- 数组类型
- 结构体类型
- 共用体(联合)类型
- 指针类型
- 空类型(void)
6.2 整数类型的宽度
char、short、int、long
char – 8 BIT – 1 Byte – 00xFF0xFFFF
short – 16 BIT –2 Byte – 0
int – 32 BIT – 4 Byte – 00xFFFFFFFF0xFFFFFFFF
long – 32 BIT – 4 Byte – 0
特别说明:int在16位(32位以上)计算机中与short(long)宽度一致
6.3 数据溢出
- char x=0xFF; //1111 1111
- char y=0x100; //0001 0000 0000
数据溢出时舍弃高位
6.4 存储格式
有符号数补码存储
char x=1; //0000 0001(0x01)
char x=-1; //1111 1111(0xFF)
6.5 有符号与无符号数
signed、unsigned
- 数据存储时都为文本直接存储,使用时再判断类型
- 数据宽度变大时,存储在低位,有符号数补1/无符号数补0
7. 浮点类型
7.1 浮点类型的种类
float – 4 Byte
double – 8 Byte
long double – 8 Byte(某些平台的编译器可能是16字节)
建议:
float x=1.23F;
double d=2.34;
long double d=2.34L;
7.2 浮点类型的存储格式
float和double在存储方式上都遵从IEEE编码规范
float的存储方式:符号位(1) – 指数部分(8) – 尾数部分(23)
double的存储方式:符号位(1) – 指数部分(11) – 尾数部分(52)
7.2.1 十进制整数转二进制
整数部分转换二进制:
如:8d -> 1000b
8/2=4 ··· 0
4/2=2 ··· 0
2/2=1 ··· 0
1/2=0 ··· 1
————————————
1000b
存储方式:取上一次计算结果/2得到的余数部分,直到结果为0停止,从下往上读
9/2=4 ··· 1
4/2=2 ··· 0
2/2=1 ··· 0
1/2=0 ··· 1
————————————
1001b
总结:所有的整数一定可以完整转换成二进制
7.2.2 十进制小数转二进制
小数部分转换二进制:
如:0.25
0.25*2=0.5 – 0
0.5*2=1.0 – 1
01
存储方式:取上一次计算结果的小数部分*2得到的整数部分,直到小数部分为0停止,从上往下读
0.4*2=0.8 – 0
0.8*2=1.6 – 1
0.6*2=1.2 – 1
0.2*2=0.4 – 0
···
总结:二进制描述小数,不可能做到完全精准
7.2.3 科学计数法
[!info]
二进制数*2,左移一位
二进制数/2,右移一位(可移至小数点后)
存储方式:
- 符号位:+为0,-为1
- 指数部分:
- 科学计数法时小数点左(右)移,最高位为1(0)
- 指数-1得到的数转换为二进制,从最低位填入指数部分
- 尾数部分:小数部分填入尾数部分的最高位
- 其余空处补0
- 最后将32位的二进制数转为16进制存储
[!example]
8.25 -> 1000.01 -> 1.00001*2^3
1.00001*^3
0 10000010 00001000000000000000000
0100 0001 0000 0100 0000 0000 0000 0000
41040000h
7.3 浮点类型的精度
float和double的精度是由尾数的位数决定的
float:2^23=8388608,共7位,故最多有7位有效数字
double:2^52=4503599627370496,共16位,故最多有16位有效数字
8. 字符与字符串
8.1 字符的使用
1 | int x='A'; int y='B'; |
ASCII表
8.2 字符类型
ASCII表最后一位为127位(7F),因此一个字节即可存储
于是同常使用char x='A'来存储字符
实际上char是一个只能存储1字节的整型,而不是字符类型
8.3 转义字符
转义字符:\
1 | char i='n'; //输出n |
8.4 printf函数的使用
字符串:一堆字符的ASCII拼在一起
1 | printf("Hello_World!\n"); //打印字符串 |
9. 中文字符
- 拓展ASCII码表后,将两个大于127的字符连在一起,表示一个汉字,这种编码规则就是GB2312或GB2312-80
- 在这些编码里,连在ASCII里本来就有的数字、标点、字母都重新编了两个字节长的编码,这就是常说的全角字符,而原来的127号之前的就叫做半角字符
弊端:
<1>两种编码可能使用相同的数字代表两个不同的符号
因此,出现了Unicode编码
10. 运算符与表达式
10.1 运算符与表达式
表达式的结果:
char –> short –> int –> float –> double
10.2 算数运算符
+ - * / % ++ –
加 减 乘 除 取余 自增 自减
++x; //
x++; //
1050
10.3 关系运算符
< <= > >= == !=
- 关系运算符的值只能是0和1
- 关系运算符的值为真(假)时,结果值都为1(0)
10.4 逻辑运算符
! && ||
逻辑非 逻辑与(且) 逻辑或(或)


