c语言笔记(4)+汇编笔记(1)
内存寻址模型
主要分为两种:实模式分段模型、保护模式扁平模型
实模式:CPU的原始工作模式(16位),直接物理地址访问,使用分段模型,单任务环境,现代CPU启动时首先进入实模式
保护模式:CPU的现代工作模式(32位/64位),支持虚拟内存和分页机制、提供内存保护、特权级检查和任务隔离,支持多任务,可使用平坦内存模型或分段模型,现代操作系统运行在保护模式下
保护模式比实模式更安全稳定
malloc/calloc/realloc
void calloc(int num,int size) : 分配了numsize个字节长度的内存空间,每个字节的值都是0
void free : 释放address所指向的内存空间,释放动态分配的内存空间
void *malloc(int num) : 用来存放数据。在函数执行完成后不会被初始化,值未知
void *realloc(void *address,int newsize) : 重新分配内存,拓展到newsize
分配内存方法
静态区:通常由编译器自动完成。全局变量和静态变量会被分配到静态区,运行时分配 结束时释放
栈上:局部变量和函数参数通常分配在栈上,被调用时在栈上自动分配,函数返回时自动释放
堆上:使用malloc/calloc/realloc分配,free释放
堆和栈的区别
| 特性 | 栈 | 堆 |
|---|---|---|
| 分配方式 | 自动分配/释放 | 手动分配/释放 |
| 分配速度 | 快 | 慢 |
| 大小限制 | 较小 | 较大 |
| 碎片问题 | 无 | 有 |
| 生长方向 | 向低地址生长 | 向高地址生长 |
| 局部性 | 好 | 差 |
| 管理方式 | 编译器/系统自动管理 | 程序员手动管理 |
| 分配失败 | 栈溢出 | 返回NULL |
| 生命周期 | 函数调用期间 | 直到显式释放 |
内存泄漏
一般指堆内存的泄漏。堆内存:从堆中分配的大小任意的使用完必须显示释放的内存
原因:动态分配的内存程序结束后一直未释放
如何防止内存泄漏:使用智能指针、遵循RAII原则、注意容器清理、避免原始指针、注意循环引用、使用内存池、编码规范
如何检测内存泄漏:使用工具检测、重载、使用内存泄漏检测库、定期检测、单元测试、日志记录、静态分析工具
fprintf和printf
printf()打印输出到屏幕,返回值是输出的字符数、发生错误时返回一个负值;fprintf()打印输出到文件
结构体
由关键字struct和结构体名组成,结构体名自定义
用来储存不同类型的数据项
一般形式
struct结构名
{
成员表列
};
struct结构名 变量名;
struct 结构名
{
成员表列
}变量名1,变量明2;
struct
{
成员表列
}变量名1,变量名2;
typedef struct_结构名
{
成员表列
}结构名,*P结构名;
结构名 变量名;
->比*的优先级高,先算->
使用typedef定义一个链表的结点
typedef struct Node{
int data;
struct Node* next;
}ListNode;
如何使用。和->访问结构体中的成员
typedef struct {
int id;
char name[20];
} Student;
Student s1;
s1.id = 101;
strcpy(s1.name, "Alice");
Student *s2 = &s1;
s2->id = 102;
strcpy(s2->name, "Bob");
Student *s3 = malloc(sizeof(Student));
s3->id = 103;
strcpy(s3->name, "Charlie");
如何将结构体数据存放在栈上
struct Point {
int x;
int y;
};
struct Point p1;
p1.x = 10;
p1.y = 20;
如何将结构体数据存放在堆上
struct Point *p2 = malloc(sizeof(struct Point));
p2->x = 30;
p2->y = 40;
free(p2);
p2 = NULL;
链表
基本操作:插入结点、删除结点、遍历链表、查找结点
联合体/共用体(union)
几个不同的变量共同占用一段内存的结构
定义形式
typedef union_foo
{
char s[10];
int i;
}foo,*pfoo;
foo a;
union foo
{
char s[10];
int i;
}a,b;
union foo
{
char s[10];
int i;
};
union foo a;
typedef union
{
char s[10];
int i;
}foo;
foo a;
union的大小由最大的成员的大小决定,必须是最大基本类型的整数倍
union成员共享同一块大小的内存,一次只能使用其中的一个成员
对某一个成员赋值,会覆盖其他成员的指
union中各个成员存储的起始地址都是相对于基地址的偏移都为0,即起始地址相同
结构体和联合体的区别
在任何同一时刻,共用体只存放了一个被选中的成员,结构体的所有成员都存在。
对联合体的不同成员赋值,原来成员的值不存在了;结构体赋值互不影响。
结构体可以含union成员,union也可以含结构体成员。
结构体:先后存放数据成员;联合体:并排存放数据成员
枚举
枚举类型是一种基本数据类型,不能再分解为任何基本类型
当一个变量的值被限于列出来的值的范围内,可以被定义为一个枚举类型的变量
枚举定义形式
typedef enum_枚举名
{
值1,
值2,
值n
}枚举名,*p枚举名;
枚举名 变量名;
与运算& 有0则0 或运算| 有1则1 异或运算^ 相同则0,相反则1 取反运算~
清除整数a二进制中最右边的1:a&(a-1)
掩码:一串二进制代码对目标字段进行位与运算,屏蔽当前的输入位
宏
用一个标志符代表一个字符串,该标识符称为宏名
宏展开:在程序编译预处理阶段,所有宏名会被替换为宏定义中的字符串
定义格式:不带参数格式:#define 宏名 字符串
带参数格式:#define 宏名(参数表)字符串
带参数的宏比函数效率高;宏不可调试;宏无法做到类型检查;宏是简单的替换 函数先计算,再传参
在宏定义中,# 字符串化运算符,将宏参数转化为字符串常量
连接运算符,将两个标记连接为一个标记
条件编译:
一种在编译阶段根据特定条件选择性地包含或排除代码的技术
条件编译的几种形式:
1.#ifdef(宏已定义)/#ifndef(宏未定义)
2.#if/#elif/#else
3.defined运算符
4.预定义宏
递归
指某个函数直接或间接的调用自身
递归首先需要有一个或多个递归出口,即递归终止的条件,也就是最小子问题的求解,可以允许多个出口
还需要有一个递归式,规定如何将原问题划分成子问题
递归要素:递归函数的参数和返回值、递归终止条件、单层递归的逻辑
递归解决问题的思路:确定基本情况 -- 分解问题 -- 递归调用 -- 合并结果
文本文件和二进制文件
文本文件基于字符编码,以固定长度的二进制序列进行编码和解码;用notepad可读取
二进制文件是基于值编码的文件,二进制文件编码是变长的,具体长度由具体格式决定;要专门工具
文件系统
文件系统是操作系统用于管理存储设备上文件的机制,负责文件的组织、存储、检索和权限控制。
打开文件:
FILE *fopen(const char *filename,const char *mode);
r :打开一个已有的文本文件,允许读取文件
w : 打开一个文本文件,允许写入文件
a : 打开一个文本文件,以追加模式写入文件(如果不存在会创建一个新文件)
r+ : 打开一个文本文件,允许读写文件
w+ :打开一个文本文件,允许读写文件(已存在会被截断为零长度)
a+ : 打开一个文本文件,允许读写文件(读取会从文件的开头开始,写入则只能是追加模式)
关闭文件:
int fclose(FILE *fp);
写入文件:
int fputc(int c,FILE *fp);
把一个以null结尾的字符串写入到流中:
int fputs(const char *s,FILE *fp);
读取文件:
int fgetc(FILE * fp);
从流中读取一个字符串:
char *fgets(char *buf,int n,FILE *fp);
如何移动读写指针:
fseek(FILE *stream,long offset,int whence);
汇编语言
机器语言
机器语言是机器指令的集合
计算机将之转变为一列高低电平,以使计算机的电子器件受到驱动,进行运算
由于硬件设计和内部结构的不同,需要不同的电平脉冲来控制它工作,所有每种微处理器都有自己的机器指令集,也就是机器语言
汇编语言
主体是汇编指令,是机器指令便于记忆的书写格式
寄存器:CPU中可以存储数据的器件,一个CPU中有多个寄存器
编译器:能将汇编指令转换成机器指令的翻译程序
汇编语言的三种指令:汇编指令、伪指令、其他符号
存储器
存储器中存放指令和数据
磁盘上的数据或程序如果不读到内存中,就无法被CPU使用
微型机存储器的存储单元可以存储一个Byte,即8个二进制位
微型存储器的容量是以字节为最小单位来计算的
存储器被划分为多个存储单元,从零开始顺序编号,这些编号可以看着存储单元在存储器中的地址
CPU要想进行数据的读写,必须和外部器件进行3类信息的交互:
1.存储单元的地址(地址信息)
2.器件的选择,读或写的命令(控制信息)
3.读或写的数据(数据信息)
总线
电信号要用导线传送
总线:在计算机中专门有连接CPU和其他芯片的导线
总线分为三类:地址总线、控制总线、数据总线
一根导线可以传送的稳定状态只有两种,高电平或低电平
CPU与内存或其他器件之间的数据传送是通过数据总线来进行的
8根数据总线一次可传送一个8位二进制数据(即一个字节)
CPU对外部器件的控制是通过控制总线来进行的;有多少根控制总线就意味CPU提供了对外部器件的多少中控制
系统中所有存储器中的存储单元都处于一个统一的逻辑储存器中,它的容量受CPU寻址能力的限制,这个逻辑存储器就是内存地址空间
寄存器
一个典型的CPU由运算器、控制器、寄存器等器件构成,靠内部总线相连;外部总线实现CPU和主板上其他器件的联系
寄存器是CPU中可以用指令读写的部件
通用寄存器
8086CPU的所有寄存器都是16位的,可存放2个字节;比如AX、BX、CX、DX可分为两个可独立使用的8位寄存器来用
AX的低8位(0-7位)构成了AL寄存器,高8位(8-15位)构成了AH寄存器
AH和AL中的数据: 既可以看成一个字型数据的高8位和低8位,这个字型数据大小是20000;
也可以看成两个独立的字节,大小分别是78和32
区分不同的进制:十六进制后加H 二进制加B 十进制不加
写汇编指令或一个寄存器名称时不区分大小写
指令的两个操作对象的位数是一致的
物理地址
内存单元:存储器中可以单独寻址的基本单位
所有的内存单元构成的存储空间是一个一维的线性空间,每一个内存单元在这个空间中都有唯一的地址,这个唯一的地址叫物理地址
在CPU向地址总线上发出物理地址之前,必须要在内部先形成这个物理地址
一个16位结构的CPU具有的结构特性:
运算器一次最多可以处理16位的数据;
寄存器的最大宽度为16位;
寄存器和运算器之间的通路为16位
内存单元的地址在送上地址总线之前,必须在CPU中处理、传输、暂时存放
地址加法器采用物理地址=段地址*16+偏移地址合成物理地址
段地址*16 :一个数据的N进制形式左移一位,相当于×N;短地址×16可看作基础地址
将若干地址连续的内存单元看作一个段,用段地址×16定位段的起始地址(基础地址),用偏移地址定位段中的内存单元,一个段的长度最大为64KB
在8086PC机中,存储单元的地址用两个元素来描述,即段地址和偏移地址
根据需要,将地址连续、起始地址为16的倍数的一组内存单元定义为一个段
段寄存器
段地址在8086CPU的段寄存器中存放
8086有四个段寄存器:CS、DS、SS、ES;要访问内存时由这四个段寄存器提供内存单元的段地址
CS为代码段寄存器,IP为指令指针寄存器
CPU将CS:IP指向的内容当作指令执行
CS和IP的内容提供了CPU要执行指令的地址
修改指令
mov指令:传送指令
能改变CS IP内容的指令被统称为转移指令 eg:jmp指令; jmp段地址:偏移地址
要让CPU执行放在代码段中的指令必须要将CS:IP指向所定义的代码段中的第一条指令的首地址
Debug和内存
Debug是DOS、Windows都提供的实模式的调试工具,可以查看CPU各种寄存器中的内容、内存的情况和在机器码级跟踪程序的运行
常用的功能:
R命令:查看、改变CPU寄存器的内容
D命令:查看内存中的内容
E命令:改写内存中的内容
U命令:将内存中的机器指令翻译成汇编指令
T命令:执行一条机器指令
A命令:以汇编指令的格式在内存中写入一条机器指令
eg:修改AX中的值 输入r ax 按Enter ;出现:作为输入提示,输入要写的数据后按Enter,即完成
使用A命令写入汇编指令时,在给出的起始地址后直接按Enter表示操作结束
浙公网安备 33010602011771号