一、什么是内存
算机内存就是内部存储器(内存条),用来临时存储数据,是一个稀缺资源。 外部存储器(硬盘)是静态保存数据,掉电不丢失,内存 动态保存数据掉电丢失。
计算机运行一般不从外部存储直接抓取数据,而是先将数据加载到内存中进行读取,内存相当于一个缓冲,CPU寄存器>内存的读取速度>外部存储器;所以通过内存可以加快读取速度,里面大多存放的是编译和运行的程序。
因为内存是稀缺资源所以变成要注意内存的管理,这也是C语言与其他语言的一个不同,可以由程序员自己进行管理。
二、内存的管理方式
系统管理 (GC垃圾回收机制) 能有效防止内存泄露,开销大,实时性差,由于无法干预
程序员自己管理 对用户要求高,但开销小,实时性高
三、内存分段
计算机中的内存是分段来管理的,程序和程序之间的内存是独立的,不能互相访问。其中数据段、代码段、bss段在程序编译阶段进行分配,堆和栈在程序运行时进行分配。
假设有4G内存
.bss段 存放未初始化的全局变量 系统管理
.data段 初始化的全局变量 系统管理
.rodata段 常量 系统管理
.text段 用户编译后的二进制代码和部分常量 系统管理,有时可修改
堆 (heap)由用户进行动态分配内存,空间比栈大 用户管理
栈(stack) 先进后出,从高到低存储(与内核程序相邻,防止内存泄露)系统管理
四、内存泄露
什么是内存泄露?
内存泄漏:是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放(内存一直被占用,系统失去对这块内存的控制),造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
内存泄露产生原因
内存未分配或未分配成功就使用
内存配成功单未初始化(malloc)就使用
分配成成功并初始化,但操作越界
使用后未释放(free并置空)
释放后继续使用
C语言如何避免:
1.程序员方面:编程规范 先申请再使用,是否申请成功检测,使用完了释放并置空
2.系统内部: 内存划分
五、内存分配方式
1、全局数据区域分配
2、栈上分配
3、堆上分配(动态分配)malloc、calloc、realloc、free
动态分配与静态分配
静态分配:开销小,空间利用率不高;
动态分配:开销大,提高利用率;
C语言中,从变量存在的时间生命周期角度上,把变量分为静态存储变量和动态存储变量两类。
静态存储变量是指在程序运行期间分配了固定存储空间的变量而动态存储变量是指在程序运行期间根据实际需要进行动态地分配存储空间的变量。在内存中供用户使用的内存空间分为三部分:
1.程序存储区
2.静态存储区
3.动态存储区
程序中所用的数据分别存放在静态存储区和动态存储区中。静态存储区数据在程序的开始就分配好内存区,在整个程序执行过程中它们所占的存储单元是固定的,在程序结束时就释放,因此静态存储区数据一般为全局变量。动态存储区数据则是在程序执行过程中根据需要动态分配和动态释放的存储单元,动态存储区数据有三类:
1.函数形参变量
2.局部变量
3.函数调用时的现场保护与返回地址
六、如何检测内存泄露
安装内存检测工具 valgrind
预处理
一、预处理内容
C语言的编译过程
预处理(-E生成.i)——>编译(-S 生成.s)——>汇编(-c生成.o)——>链接(.elf)
预处理内容
1、头文件展开
2、宏替换
3、条件编译
二、头文件展开——库文件展开
三、宏定义
定义常量 #define MAX 100
定义表达式
定义函数
替换逻辑 宏替换只做简单的替换,不检查语法,在预处理阶段进行
宏定义的使用 定义预定俗成 字母大写
优点:
定义常数时可以杜绝幻数(具体的数,不知道为啥是这些,可以用宏定义例如直接在程序中表现176表达不清楚 可以 #define high 176,),用一些自定义标识符去代替,提高代码可读性,方便修改数值
定义宏函数 对于一些小而频繁使用的函数功能用宏定义,本质就是一个宏,只在预处理阶段进行替换,不占用运行时的时间,减少了函数调用、返回、和系统为函数分配释放空间的过程(不绝对比它慢),用编译时间和内存空间换运行时间
缺点:简单替换,不做与语法检查,宏函数的定义中常使用do{}while(0)防止出现语法错误
内置宏函数 系统内置宏常用__***__表示,自定义时应避免,
__LINE__ 显示行号
__func__ 显示函数名
__DATE--显示日期
__TIME__显示时间
#和## #字符串化运算符 ## 并接运算符
1.不替换参数,只替换对应字符
2、将对应字符字符串化
3、把宏参数名和宏定义代码连接在一起,形成一个新的标识符(只能两个)
条件编译 按照给定条件编译代码(只影响编译,不影响运行)
常见宏定义
#define 定义一个预处理宏
#undef 取消宏的定义
#if 编译预处理中的条件命令,相当于C语法中的if语句
#ifdef 判断某个宏是否被定义,若已定义,执行随后的语句
#ifndef 与#ifdef相反,判断某个宏是否未被定义
#elif 若#if, #ifdef, #ifndef或前面的#elif条件不满足,则执行#elif之后的语句,相当于C语法中的else-if
#else 与#if, #ifdef, #ifndef对应, 若这些条件不满足,则执行#else之后的语句,相当于C语法中的else
#endif #if, #ifdef, #ifndef这些条件命令的结束标志.
defined 与#if, #elif配合使用,判断某个宏是否被定义
关键字
register
作用:
修饰寄存器变量,变量可能放到CPU内部寄存器中,运行优化提高运行效率。不能修饰函数、全局变量
使用场景:频繁访问的变量,可以优化运行速率。
注意事项:
不能修饰全局变量()
不能通过&来获取register来获取修饰变量的地址(CPU内部寄存器地址不允许随意访问)
修饰的类型必须是CPU能处理的类型
volatile
作用:修饰全局变量,防止编译器优化,告诉编译器该变量可能随时发生变化
static
既能修饰局部变量(保存在静态数据区),也能修饰全局变量/函数,修饰的函数要放在最前面
修饰局部变量:延长局部变量的生命周期,但只执行一次,到程序结束后释放(代替全局变量)
修饰全局变量/函数:只能在本文件的函数被调用,限定作用域,方便开源代码的合作开发,防止命名冲突
只在当前文件用加static,给外部文件用不加
extern
在当前文件访问其他文件的全局变量或函数(当前文件没有这个函数),用在变量或函数的声明前,用来说明此变量变量/函数在别处定义,要在此处引用(声明)
优点:
加强编译速度
在工程多个文件共同使用全局变量时进行声明不报错
在同一文件中的函数不要用声明,不然编译时会先到外找这个函数再回到该文件找这个函数
const
修饰变量(局部、全局)只读变量,在程序运行期间它的值不能别修改,在运行阶段时用
注意:不能通过变量名来更改变量的内容,但对应空间是可变的(通过修改地址来修改内容)
使用场景:修饰函数的形参提供安全的访问接口函数,防止函数实现过程中修改实参变量的接口
例:
const int *p = &count //不能通过p修改p 指向的内存空间
int const *p = &count //不能通过p修改p 指向的内存空间
int *const p = &count //不能通过p修改p 对应的内存空间
const int *const p = &count // 不能修改
typedef
给数据类型重命名,不是创造新类型
作用:
解决不同编译器默认数据signed、unsigned的问题,提高代码的移植性
简化拼写,提高编码效率
改变原有代码标识,提高代码可读性
inline
作用:把函数指定为内嵌函数,用空间换运行速度,和register一样只是向CPU请求,CPU判断后决定是否执行
原理:编译阶段处理,只编译函数头部,在调用点将代码展开。与函数相比减少了调用函数、返回函数、分配空间、释放空间的开销,但在调用处将文件展开,占用内存
特点
以空间换时间,以代码膨胀为代价,减去了函数开栈清栈的过程(调用函数会跳转到函数体里,这就要在内存上开栈,压参,读取指令,完了清栈。因为inline是在调用点将代码展开,所以不会跳转,只会在调用点这个函数的栈上操作)