内存布局 | 内存分配 | 内存对齐

本文详细介绍了内存的几个关键概念,包括虚拟地址空间的划分,如内核空间、用户空间、栈、堆等;堆和栈在内存管理上的区别,包括分配方式、效率和碎片问题;动态内存分配的malloc和free函数的工作原理;以及内存对齐的规则和影响因素。通过对这些基础知识的讲解,帮助读者深入理解C++程序中的内存操作。
摘要由CSDN通过智能技术生成

1、内存原理

* 虚拟地址空间
  • 内核空间:进程管理、设备管理、虚拟文件管理
  • 用户空间
    • 栈空间 stack:存储局部变量,函数参数
    • 内存映射段 mmap:静态库与动态库,文件映射,匿名映射
    • 堆空间 heap:malloc / calloc / new
    • 读写段:全局变量、静态变量
    • 只读段:文字常量区(字符串常量)和程序代码区(二进制代码)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b0osducZ-1677199731138)(C++基础.assets/内存布局.png)]

* 堆和栈的区别
管理方式程序员手动控制,memory leak编译器管理
空间大小内存区域不连续(空闲链表),受限于虚拟地址空间,例如:32 位系统 4 G内存区域连续,空间大小固定。操作系统预设,一般是 2 M。
内存管理系统遍历内存空闲链表,寻找第一个大于所需空间的空闲结点,将其分配给程序。栈的剩余空间大于申请空间,系统为程序提供内存,否则异常栈溢出。
碎片问题频繁的内存申请释放,造成内存空间不连续,产生大量的碎片栈后进先出,没有内存碎片
生长方向堆向上生长,向高地址方向增长栈向下生长,向低地址空间增长
分配方式动态分配系统提供静态和动态的方式
分配效率效率低,间接寻址,第一次访问指针,第二次访问指针指向的内存效率高,计算机底层提供支持(寄存器、指令等)

2、内存分配

动态内存分配系统调用

int brk(void *addr);			// 返回对的顶部地址
void *sbrk(intptr_t increment);  // 扩展堆,increment 指定要增加的大小

先通过 sbrk 扩展堆,将这部分空闲内存空间作为缓冲池,然后通过 malloc/ free 管理缓冲池中的内存。

* malloc 原理

malloc 使用空闲链表组织堆中的空闲区块,每个内存区块由内存控制块 mem_control_block + 可用内存空间组成。malloc 返回的是内存控制块后的地址,即可用内存空间的起始地址。

struct mem_control_block {
  int is_available; // 是否可用(如果还没被分配出去,就是 1)
  int size;         // 实际空间的大小
};

malloc 分配时遍历空闲链表,以首次适应算法为例,寻找第一个大于所需空间的空闲区块,然后将其分配给程序,返回这可用空间的指针。如果没有这样的内存块,则向操作系统申请扩展堆内存。若空闲区块空间有剩余,系统自动将多余的部分重新放入空闲链表。

常见的内存匹配算法有

  • 首次适应法
  • 最佳适应法
  • 最差适应法
  • 下一个适应法
* free 原理

free 将内存区块重新插入到空闲链表,free 只接受一个指针,却可以释放恰当大小的内存,这是因为在内存控制块保存了该区域的大小。

被 free 回收的内存首先被 ptmalloc 使用双向链表保存起来,当用户下一次申请内存时,会尝试从这些内存中寻找合适的返回,避免了频繁的系统调用,占用过多的系统资源。同时 ptmalloc 也会尝试对小块内存进行合并,避免产生过多的内存碎片。

3、内存对齐

内存的自然对齐:按照结构体中最大数据成员对齐。

  • 结构体内成员按照声明顺序存储,第一个成员地址和整个结构体地址相同。
  • 结构体的首地址必须是最大数据成员大小的整数倍
  • 每个成员相对于结构体首地址的偏移量必须是该成员大小的整数倍,否则填充字节。
  • 整个结构体的大小必须是最大数据成员大小的整数倍,否则填充字节。

对齐系数:#pragma pack(n)

#pragma pack(n) // 按照 n (2 的 n 次幂)个字节对齐
#pragma pack()  // 取消自定义字节对齐方式
* 内存对齐的规则
  • 数据成员对齐规则:第一个数据成员放在偏移 0 的地方,每个数据成员的对齐按照#pragma pack(n)和该数据成员自身长度,比较小的那个进行
  • 结构(或联合)的整体对齐规则:数据成员对齐后,结构(或联合)整体也要进行对齐,对齐按照#pragma pack(n)和结构(联合)最大数据成员长度中,比较小的那个进行
  • 结构体作为成员:内部结构体成员从最大元素的整数倍和#pragma pack(n)最小的一个的整数倍地址开始存储。

例如:

struct SS {
    int a;
    char b;
    short c;
    int d;
    struct FF {
        int a1;
        char b1;
        short c1;
        char d1;
    } MyStructFF;
    int e;
    double ww; 
} MyStructSS;

其内存对齐方式如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6VZExY9L-1677199731139)(C++基础.assets/内存对齐_内部结构体.png)]

获得结构体成员相当于结构体首地址的偏移量:offsetof

#include <stddef.h>
offsetof(S, x)

测试代码:

#include <iostream>
/* #pragma pack(4) */

using std::cout;
using std::endl;

struct x {
    char a;
    int b;
    short c;
    char d;
} MyStructX;//12

struct y {
    int b;
    char a;
    char d;
    short c;
} MyStructY; // 8

struct SS {
    int a;
    char b;
    short c;
    int d;
    struct FF {
        int a1;
        char b1;
        short c1;
        char d1;
    } MyStructFF;

#if 1
    /* char e; // 28 */
    int e;
    double ww; // 40
#endif
} MyStructSS;


struct DD {
    int a;
    char b;
    short c;
    int d;

    struct FF {
        double a1;
        char b1;
        short c1;
        char d1;
    } MyStructFF;

    char e; // 40
    
} MyStructDD;

struct GG {
    char e[2];
    short h;

    struct A {
        int a;
        double b;
        float c;
    } MyStructA;
} MyStructGG; // 32

int main(int argc, char **argv) {
    cout <<"sizeof(MyStructX) = " << sizeof(MyStructX) << endl;
    cout <<"sizeof(MyStructY) = " << sizeof(MyStructY) << endl;
    cout <<"sizeof(MyStructSS) = " << sizeof(MyStructSS) << endl;
    cout <<"sizeof(MyStructDD) = " << sizeof(MyStructDD) << endl;
    cout <<"sizeof(MyStructGG) = " << sizeof(MyStructGG) << endl;
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值