吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 963|回复: 10
收起左侧

[学习记录] C语言笔记 | C与汇编

[复制链接]
shinanciya 发表于 2023-9-10 13:00

C语言笔记 | C与汇编

函数

  • 通过栈传递参数
  • 平衡栈(函数调用约定)
    1. cdecl
      • 参数从右至左入栈
      • 调用者清栈
    2. stdcall
      • 参数从右至左入栈
      • 自身清栈
    3. fastcall:
      • ecx和edx传前两个参数, 剩下的参数从右至左入栈
      • 自身清栈
  • 提升栈使用EBP寻址, 函数内使用提升后的"新"栈
    • 提升: 将原EBP地址压入栈, 使用EBP存储原ESP地址, 提升ESP
    • 下降: 将ESP复原, 取出EBP并复原
  • 返回值使用EAX传递

变量

  • 变量类型确定内存宽度
  • 变量名是内存地址的别名

全局变量

  • 每个全局变量有一个固定的独一无二的内存地址
  • 全局变量在编译的时候就已经确定了内存地址和宽度
  • 如果不重新编译, 全局变量的内存地址不变
  • 全局变量中的值任何程序都可以修改, 是公用的
  • 全局变量有默认值

局部变量

  • 局部变量是函数内部申请的, 如果函数没有执行, 那么局部变量没有内存空间
  • 局部变量的内存是在栈中分配的, 程序执行时才分配(我们无法预知函数何时执行, 这意味着无法确定局部变量的内存地址)
  • 因为局部变量的内存是不确定的, 所以只能在函数内部使用, 其他函数不能使用
  • 局部变量定义后必须初始化值

整数类型

  • 十转二: 除二取余(整数位继续除直到0, 小数位0与非0为二进制), 能保证精度
  • 使用补码存储
  • 如果数据溢出(超出范围), 将舍弃高位(截断)

浮点类型

  • 十转二: 乘二取整(小数位继续乘直到0, 整数位为二进制), 不能保证精度
  • 存储方式:
    1. float(32bit):
      • 31-30(1bit): 符号位
      • 30-22(8bit): 指数部分(第一位是科学计数法方向位, 小数点向左移是1, 向右移是0)
      • 22-0(23bit): 尾数部分
    2. double(64bit):
      • 63-62(1bit)
      • 62-51(11bit)
      • 51-0(52bit):
  • 编码步骤:
    1. 转为二进制小数
    2. 使用科学计数法表示
    3. 按编码规则填入
  • 栗子:
    • 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
转换

二位数组坐标转一维数组坐标

指针

  1. 指针类型的变量宽度永远是4字节(32位)
  2. 指针类型的变量加减运算的实际宽度是它基础类型的宽度(寻址宽度)(去掉一个*)
    1. char a = (char) 1; a++; //a实际加了1
    2. int b= (int) 1; b++; //b实际加了4
    3. char c (char) 1; c++; //c实际加了4
  3. 取地址(&):
    1. 局部变量: lea eax, dword ptr ss:[ebp-4]
    2. 全局变量: mov eax, 00426d9c
  4. 取值(*):
    1. 读: mov eax, dword ptr ds:[eax]
    2. 写: mov dword ptr ds:[eax], 1
      • 取值后的类型是指针类型去掉一个*的类型, 栗如int (*)[2]类型取值的类型是int [2]

字符串

  1. char a[] = {'A', 'B', '\0'}
    • 直接赋值数组(没有常量区字符串, 数组可修改)
  2. char b[] = "AB"
    • 将字符串放到常量区
    • 将字符串复制到数组(有常量区字符串, 数组可修改)
  3. 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]

结构体

  • 对结构体的操作并不是转为指针操作(数组), 而是结构体类型
    1. 结构体作为函数参数将进行传值(所有成员)而不是传地址
      • 传一个四个成员的结构体与传四个参数没有区别
    2. 结构体的相互赋值将拷贝所有结构体成员
  • 问题: 大量的内存复制
    • 使用指针传递结构体
函数
  • 通过栈传递参数
  • 平衡栈(函数调用约定)
    • cdecl
      • 参数从右至左入栈
      • 调用者清栈
    • stdcall
      • 参数从右至左入栈
      • 自身清栈
    • fastcall:
      • ecx和edx传前两个参数, 剩下的参数从右至左入栈
      • 自身清栈

  • 提升栈使用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):

  • 编码步骤:
    • 转为二进制小数
    • 使用科学计数法表示
    • 按编码规则填入
  • 栗子:
    • 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
转换
[size=0.8125]

二位数组坐标转一维数组坐标

二位数组坐标转一维数组坐标
指针
  • 指针类型的变量宽度永远是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]
结构体
  • 对结构体的操作并不是转为指针操作(数组), 而是结构体类型
    • 结构体作为函数参数将进行传值(所有成员)而不是传地址
      • 传一个四个成员的结构体与传四个参数没有区别
    • 结构体的相互赋值将拷贝所有结构体成员
  • 问题: 大量的内存复制
    • 使用指针传递结构体

免费评分

参与人数 2吾爱币 +2 热心值 +2 收起 理由
kesshei + 1 + 1 我很赞同!
cogi + 1 + 1 热心回复!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

lingxing2320 发表于 2023-9-10 15:19
感谢大佬的笔记
ferifering 发表于 2023-9-10 15:23
yu520 发表于 2023-9-10 16:43
w759003376 发表于 2023-9-10 22:48
感谢,可以学习一波
头像被屏蔽
moruye 发表于 2023-9-10 23:19
提示: 作者被禁止或删除 内容自动屏蔽
HuAI_Wei 发表于 2023-9-11 00:30
厉害,感谢大佬
oxf5deb3 发表于 2023-9-11 00:37
感谢楼主分享。
韩君子 发表于 2023-9-11 09:23
谢谢楼主,感谢
nocofffe 发表于 2023-9-11 09:40
支持大佬!
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 19:39

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表