C/C++-内存-创新互联

C/C++-内存
  • CPU与内存
    • 虚拟内存/地址
  • 内存空间/进程
  • 内存分区 -- 用户地址空间
    • 静态/动态内存
  • 内存泄漏
    • 栈溢出
    • 调用栈 -函数相关
  • 内存对齐
  • 动态内存分配 -- 内存池 --还没看
  • 野指针/空指针/void指针
    • 空指针
    • void指针
    • 野指针
  • 缓存与缓冲区
    • FILE 与缓冲区

创新互联建站专注于网站建设,为客户提供成都网站设计、成都网站制作、网页设计开发服务,多年建网站服务经验,各类网站都可以开发,成都品牌网站建设,公司官网,公司展示网站,网站设计,建网站费用,建网站多少钱,价格优惠,收费合理。CPU与内存

程序加载到内存后,操作系统会给不同的内存指定不同的权限,拥有读取和执行权限的内存块是代码,拥有读取和写入权限的内存块是数据
CPU一般通过地址来取得内存中的代码和数据
CPU访问内存时需要的是地址,而不是变量名和函数名,当源文件被编译和链接成可执行程序后,都会被替换成地址
(变量名是数据,函数、字符串名、数组名是代码块或数据的首地址)

程序在硬盘中,要载入内存才能运行
CPU只能从内存中读取数据和指令
对于CPU,内存时存放指令和数据,并不能完成计算功能
计算机:硬盘->内存->CPU:缓存->>
CPU=运算器+寄存器+缓存

寄存器:一个寄存器能够存储32/64位的数据,CPU一般由上百个寄存器(多少位的计算机,表示的是寄存器是多少位的)。寄存器用来进行数学运算或流程控制
缓存:内存读取速度小于CPU,因此将频繁使用的数据暂时读取到缓存区。CPU只能从缓存中读取到部分数据,对于使用不是很频繁的数据会直接从内存中读取。

指令集

编译、汇编之后会变成一条条的CPU指令,

虚拟内存/地址

虚拟地址通过CPU的转换才能对应到物理地址:虚拟地址–>内存映射机制 -->物理地址 ;
每次程序运行的时候,操作系统都会重新安排虚拟地址和物理地址的对应关系,哪一段物理内存空闲就使用哪一段
虚拟地址和物理地址的映射关系由操作系统决定,虚拟地址空间的大小由操作系统决定

虚拟地址可以使不同程序的地址空间相互隔离,防止被互相篡改
程序A 和程序B虽然都可以访问同一个地址,但是他们对应的物理地址是不同的,因此不会互相修改对方的内存,因此程序不应该直接使用物理内存地址

虚拟地址 作为一个中间层,用来屏蔽复杂的底层细节,只给用户提供简单的接口

内存映射 ???

只要能够控制这个虚拟地址到物理地址的映射过程,就可以保证程序每次运行时都可以使用相同的地址???

内存分页 ???

内存空间/进程

一个进程用于一个独立的地址空间
一个程序可能会有多个进程,而一个进程对应一个独立的地址空间,所以一个程序可能有多个地址空间

  • 内核空间

存放操作系统内核代码和数据,被所有程序共享
在程序中修改内核空间的数据不仅会影响操作系统本身,还会影响其他程序(一般操作系统进制用户访问内核空间)
内核空间的访问需要操作系统的API函数,执行内核提供的代码
系统调用 访问内核空间,执行内核代码(内核也是程序)叫做 内核模式 (发生系统调用时会暂停用户程序,转而执行内核代码)

内核用于管理硬件,提供接口供上层使用

  • 用户空间

用户空间保存的是引用程序的代码和数据,程序私有
执行程序叫做用户模式
下面的内存分区实际上说的是用户空间中的内存分区

  • 内核空间与用户空间的切换

当运行在用户模式的应用程序需要输入输出、申请内存等底层操作时,就需要调用系统API函数进入内核模式,执行结束后,又回到用户模式

内存分区 – 用户地址空间

数据以二进制的形式保存在内存中,字节是最小的可操作单位
在内存管理中,为每个字节分配了一个编号,使用该字节时,只要知道编号就可以,这个编号,就是地址

注意:内存分区和变量的作用域不是完全等价的,甚至是没有关系的

获取地址

&data,(data可以是数值、字符)

  • 栈区

编译器自动分配
局部变量、形参、返回值,const定义的局部变量也存放在栈区

操作系统自动管理
栈区上的内容只在函数范围内存在,函数结束自动销毁
栈区内存地址:由高到低,即后定义的变量地址低于先定义的变量地址
先进后出
属于动态内存分配

函数中定义的局部变量按照先后定义的顺序一次压入栈中,即,相邻变量的地址之间不会存在其他变量(debug/release不同)
见函数调用栈的例子

实际上,程序启动时会为栈区分配一块大小适当的内存(包括局部函数调用栈的时候),这对于一般的函数调用就已经足够了,函数进出栈只是ebp、esp等寄存器指向的变换,或者是向已有的内存中写入数据,其实不涉及内存的分配和释放
只要当函数中有较大的局部数组时,编译器才会在函数代码中插入针对栈的动态内存分配函数,这样函数被调用时才分配内存,不调用就不分配
因此栈内存的分配效率要高于堆,也就是大部分情况下并没有真的分配栈内存,而是对已有内存的操作

  • 堆区

程序员分配内存和释放,若开发人员不释放,程序结束时有系统回收
malloc()free()
newdelete

堆区内存地址:由低到高
大小由系统内存/虚拟内存上限决定
属于动态内存分配

注意:后申请的内存空间并不一定在先申请的内存空间的后面,因为先申请的内存空间一旦释放,后申请的内存空间则会利用先前被释放的内存,从而导致先后分配的内存空间在地址上不存在先后关系
堆中存储的数据若未释放,则生命周期等同于程序的生命周期
由于系统是通过链表来存储空闲的内存地址,因此堆数据时不连续的内存空间(这个堆区域数据结构的堆不一样,是通过链表实现的)

对于堆分配,操作系统有一个记录空闲内存地址的链表,当申请内存时,会遍历该链表,寻找第一个空间大于所申请空间的堆节点,然后将该节点从空闲节点链表中删除,并将该节点的内存空间分配给程序。另外,对于大多数系统,会在这块内存空间中的首地址出记录本次分配的大小,这样使用delete才能正确释放内存空间。由于找到的堆节点的大小不一定正好等于申请的大小,系统会自动将多余的部分重新放入空间链表

free§并不能改变指针p的值,p依然指向以前的内存,为了防止再次使用该内存,建议将p的值手动置为NULL
free§ 只是释放掉动态分配的内存,p的值并不是NULL,仍然指向之前这个被释放掉的内存,所以if§仍然会执行,但是输出p指向的内存会报错

// c 用malloc

// 注意指针的类型转换
char* p1=(char*)malloc(10);
free(p1);
//free(p)并不能改变指针p的值,p依然指向以前的内存,为了防止再次使用该内存,建议将p的值手动置为NULL

// C++ 用new
char* p2=new char[10];
delete[] p2

malloc 动态内存分配 内存池 – 还没看

  • 全局(静态)区

未初始化全局(静态)区 .bass
已初始化全局(静态)区 .data

在编译期间就能确定 存储大小的变量 的存储区,且在运行期间可以进行修改
包括全局变量,静态变量(包括静态全局变量、静态局部变量)
属于静态内存分配
这块内存具有的读写权限
静态数据区的变量只能初始化一次,也就是可修改但是不能初始化(即使二次初始化了,也会被视为无效)

.bass

读写
未初始化的全局变量或未初始化的静态变量
初始化为0的全局变量或初始化为0的静态变量
.bass段不占用可执行文件空间,由操作系统初始化

.data

读写
已初始化的全局变量 (但初始化不为0的)
已初始化的静态变量 (但初始化不为0的)
.data段占用可执行文件空间,由程序初始化

  • 常量区

.rodata

字符、字面值、字符串等常量
const 修饰的全局变量(const修饰的局部变量存放在栈区)
这部分内存只有读取权限,没写入权限,即运行期间,常量区的内容不可被修改
属于静态内存分配

  • 代码区

存放程序的代码.txt二进制
只读,不可修改
属于静态内存分配

  • 动态链接库

在程序运行期间加载和卸载动态连接库

静态/动态内存
  • 静态内存分配

代码区、常量区、全局数据区的内存在程序启动时就已经分配好了,大小固定、不能由程序分配或释放,只能等到程序运行结束由操作系统回收

  • 动态内存分配

栈区、堆区的内存在程序运行期间可以根据实际需求来分配和释放,不用在程序刚启动时就备足所有内存

char *str1="hello"; // hello字符串在常量区,str1在全局数据区

int n; // 全局数据区

char *func()
{char *str="world"; // world字符串在常量区,str在栈区
    return str;
}

int main()
{int a; // 栈区
    char arr[10]; // 栈区
    char *pstr=func(); // 栈区
}
内存泄漏

一块内存没有被指针指向它,(程序和内存失去了联系,再无法对他进行任何操作),这块内存直到程序结束被系统回收

char *p=(char *)malloc(100*sizeof(char)); // 这段内存存在内存泄漏,无法被释放,只有等程序运行结束由操作系统回收
p=(char *)malloc(50*sizeof(char));
free(p);
p=NULL;
栈 栈溢出

第一种栈溢出

(整个)用于栈的内存空间是有限的,超出大值(跟编译器有关)就会栈溢出
一个程序可以包含多个线程,每个线程都有自己的栈,栈的大是是针对线程的

第二种栈溢出

栈中局部变量的内存空间有限,占用了栈中其他内存空间的地址,导致栈发生错误

调用栈 -函数相关

函数调用与栈有关

栈帧

又叫做活动记录
函数调用过程中,存储全部的信息的栈叫做栈帧

一个不典型的例子:由高到底地址分别为:实参、返回地址、xxx、一块内存(包括局部变量、返回值等)、xxx

函数调用惯例

调用方和被调用方之间遵守的约定

  • 函数参数的传递方式,是通过栈传递还是通过寄存器传递
  • 参数参数传递的方式,是从左到右入栈还是从右到左入栈
  • 参数弹出方式,函数调用结束后需要将压入栈中的参数全部弹出,使得栈在函数调用前后保持一致,(这个弹出的工作可以由调用方完成,也可以由被调用方完成)
    函数调用惯例可以进行修改 –没看,见文档

函数调用栈实例 – 见文档 很重要!!!

局部变量分配内存,是否初始化也与调用栈有关

内存对齐

https://blog.csdn.net/weixin_46251230/article/details/123755070

CPU 通过地址总线访问内存,一次能处理几个字节的数据,就命令地址总线读取几个字节的数据(32位CPU一次可以处理4个字节数据,64位CPU一次可以处理8个字节)

寻址步长4个字节或8个字节
将一个数据尽量放在一个步长内,避免跨步长存储,这称为内存对齐
32位默认4字节对齐,64位默认8字节对齐

具体对齐方式,与编译器有关
一般来讲全局变量会自动内存对齐,而局部变量不会进行内存对齐

动态内存分配 – 内存池 --还没看 野指针/空指针/void指针 空指针

对未初始化的指针赋值为NULL
空指针是不指向任何数据的指针,是无效指针

// NULL 其实是一个宏定义,指向了内存的0地址,
#define NULL ((Void*)0)
char* s=NULL;

if (p==NULL){// ...
}
void指针

void*表示一个有效指针,指向实实在在的数据,只是数据的类型尚未确定,在后序使用过程中需要进行强制类型转换

char* s=(char*)malloc(sizeof(char)*30);
野指针

如果一个指针指向的内存没有访问权限,或者指向一块已经释放掉的内存,那么就无法对该指针进行操作,这样的指针就是野指针

free

free§并不能改变指针p的值,p依然指向以前的内存,为了防止再次使用该内存,建议将p的值手动置为NULL
free§ 只是释放掉动态分配的内存,p的值并不是NULL,仍然指向之前这个被释放掉的内存,所以if§仍然会执行,但是输出p指向的内存会报错

避免野指针

初始化为NULL
free之后,赋值为NULL

缓存与缓冲区

内存中用于临时保存输入输出数据
缓冲区 :内存空间的一部分
作用: 减少磁盘的读写次数 ;
分类

输入缓冲区/输出缓冲区
根据数据刷新时机

全缓冲:缓冲区被填满时(一般是对硬盘的读写)
行缓冲:遇到换行符时 (标准I/O),prinf(“\n”),scanf回车
无缓冲: getche(),getch() 这两个函数没有缓冲
(程序结束的时候 也会刷新缓冲区)
(输出之后有输入的操作,也会刷新缓冲区)

注意:不同的操作系统对缓冲区的定义时不同的,printf在windows下无需缓冲,在linux下有缓冲

不刷新缓冲区的例子

// 这个例子,进入死循环
// 缓冲区没有被填满、没有遇到换行符、程序没有结束,所以标准输出始终没有显示字符串
int main()
{printf("hello world"); // 只要加上printf("hello world\n") 立即刷新
    while (1);

    return 0;
}

刷新

行缓冲,全缓冲:缓冲区满时自动刷新
行缓冲:遇到’\n’时,自动刷新
关闭文件时自动刷新
程序关闭时自动刷新
使用刷新函数

清空输出缓冲区

fflush(stdout) // 一般用于linux系统下,清空标准输出缓冲区,清空屏幕缓冲区

清空输入缓冲区

int c
while (c=getchar()!='\n' && c!=EOF)
FILE 与缓冲区
FILE *fp;

FILE 结构体
int cn // 剩余字符,缓冲区中还有多少个字符未被读取
char *ptr //下一个要被读取的字符的地址
char *base // 缓冲区的基地址
int flag // 读写状态标志位
int fd // 文件描述符

FILE是文件缓冲区的结构体,fp是指向文件缓冲区的指针
缓冲区的刷新表示将 缓冲区的指针变为缓冲区的基地址

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


分享文章:C/C++-内存-创新互联
本文路径:http://azwzsj.com/article/djogpj.html