云计算百科
云计算领域专业知识百科平台

深度解析内存分区与布局

进程 & 虚拟内存

在这里插入图片描述

  • 每个C语言进程都拥有一片结构相同的虚拟内存
  • 所有的虚拟内存布局都是相同的,极大地方便内核管理不同的进程
  • 虚拟内存:从实际物理内存映射出来的地址规范范围

内存布局

在这里插入图片描述

  • 内核区段对于应用程序而言无法访问,用于存放操作系统的关键性代码
  • 由于 Linux 系统的历史性原因,在虚拟内存的最底端 0x000000000 ~ 0x08048000 之间也有一段不可访问区段
  • 内存四区:栈、堆、代码段、数据段

内存布局 (详细)

  • 数据段和代码段内存的分配和释放,都是由系统规定的,程序员无法干预。

  • 大小:默认为 8 M,空间有限,在嵌入式环境下,不可以用来存储尺寸太大的变量
  • 分配
    • 每当一个函数被调用,栈就会向下增长一段(该区域内存从高到低分配),用以存储该函数的局部变量。
    • 每当一个函数退出,栈就会向上缩减一段,将该函数的局部变量所占内存归还给系统
    • 栈内存的分配和释放,都是由系统规定(自动化完成)的,程序员无法干预
  • 存储
    • 环境变量:用户名,工作路径,可执行文件的路径,库的路径
    • 命令行参数:在命令行中运行程序时,携带的参数
    • 局部变量 (在函数体内部 {}定义的所有变量都属于局部变量,包括形参)

获取环境变量

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{

char * name = getenv("LOGNAME");
printf("当前用户名是:%s\\n" , name );

char * pwd = getenv("PWD");
printf("当前工作路径是:%s\\n" , pwd );

char * homePath = getenv("HOME");
printf("当前用户家目录是:%s\\n" , homePath );

return 0;
}

获取命令行参数

#include <stdio.h>

// int argc 传递的参数数量
// char const *argv[] 数组用于存储命令行中的每一个参数的值(入口地址)
int main(int argc, char const *argv[])
{

printf("命令行的参数数量argc:%d\\n" , argc );

for (int i = 0; i < argc ; i++)
{
printf("argv[%d]:%s\\n" , i , argv[i]);
}

return 0;
}

// 运行时,通过命令进行传递并使用空白符进行分割:
$ ./a.out Hello 123 Even GZ2536 你好

运行结果:
命令行的参数数量argc:6
argv[0]:./a.out
argv[1]:Hello
argv[2]:123
argv[3]:Even
argv[4]:GZ2536
argv[5]:你好

  • 存储:程序运行时动态分配的对象和数据。动态分配的结构体/类对象: 在 C 语言中通过 malloc()、calloc() 分配的空间,或在 C++/Java/Python 中通过 new 关键字创建的对象,都存放在堆中
  • 特点
    • 堆的总大小仅受限于物理内存,在物理内存允许的范围内,系统对堆内存的申请不做限制
    • 堆内存从下往上增长
    • 堆内存是匿名的,只能由指针来访问(申请堆内存时系统会返回该内存的入口地址)
    • 自定义分配的堆内存,除非开发者主动释放,否则永不释放,直到程序退出(系统会在程序运行结束时把整个虚拟内存进行销毁),因此堆内存也是造成内存泄露的一大灾区

内存泄漏检测

sudo apt install valgrind
valgrind ./a.out

相关API

// 分配
void *malloc(size_t size);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *_Nullable ptr, size_t size);
void *reallocarray(void *_Nullable ptr, size_t nmemb, size_t size);

// 清零
void bzero(void s[.n], size_t n);
void *memset(void s[.n], int c, size_t n);

// 释放
void free(void *_Nullable ptr);

  • 注意:
    • malloc()申请的堆内存,默认情况下是随机值,一般需要用 bzero() 或 memset() 来清零。
    • calloc()申请的堆内存,默认情况下是已经清零,不需要再清零。
    • free()只能释放堆内存,并且只能释放整块堆内存,不能释放**别的区段(栈、数据段、代码段)**的内存或者释放一部分堆内存。
    • free函数只能对堆内存的入口地址进行操作
  • 释放内存的含义:
    • 释放内存意味着将内存的使用权归还给系统。
    • 释放内存并不会改变指针的指向(因此指针直接变成了野指针,需要手动设置为NULL)。
    • 释放内存并不会对内存做任何修改,更不会将内存清零。

realloc()

void *realloc(void *_Nullable ptr, size_t size);
ptr > 目前已有的内存入口地址
size > 新的目标内存尺寸
返回值:
成功 返回申请到的入口地址
失败 返回NULL

  • 该函数如果发生了内存扩张,那么就有可能需要把我们原本的数据进行拷贝到新的内存空间中,原来的指针所执行的内存会被该函数释放掉,不能再访问。
  • 该函数如果发生了内存缩小,那么新内存不包含的区域会被释放掉一部分,如果再次访问则造成非法访问。
  • 因此该函数的返回值,建议都使用原来的指针进行接收

reallocarray()

void *reallocarray(void *_Nullable ptr, size_t nmemb, size_t size);
ptr > 目前已有的内存入口地址
nmemb > 新内存需要N块内存
size > 新内存每一块多大
返回值:
成功 返回申请到的入口地址
失败 返回NULL

数据段

  • .bss:存储 未初始化 或 初始化为零 的全局变量和静态变量。它在磁盘上不占空间,只在程序加载时在内存中分配并清零。
  • .data:存储 已初始化且**非零 **的全局变量和静态变量
  • .rodata:存储常量数据 (只读数据)。比如字符串常量(如 "Hello World")或者用 const 修饰的全局变量

静态数据

  • 生命周期:不会随着程序于运行的过程产生或释放,与整个进程的生命周期保持一致。只要程序运行静态区的内存就会被分配,直到程序退出才能(由系统自动)释放
  • 分类
    • 全局变量:定义在函数体 { } 外部的变量。
    • 静态局部变量:定义在函数内部,且被 static 修饰的变量
  • 特点
    • 若定义时未初始化,则系统会将所有的静态数据自动初始化为0
    • 静态数据初始化语句,只会执行一遍。
    • 静态数据从程序开始运行时便已存在,直到程序退出时才释放。

int a; // 全局变量,退出整个程序之前不会释放
void f(void)
{
// 所有的静态数据的初始化语句只会被执行一次
static int b = 1 ; // 静态局部变量,退出整个程序之前不会释放
printf("%d\\n", b);
b++;
}

int main(void)
{
f();
f(); // 重复调用函数 f(),会使静态局部变量 b 的值不断增大
}

代码段

  • .text:存放用户代码
  • .init:存放系统初始化代码(由编译器自动生成并添加,用于初始化本程序所需的内存空间)
赞(0)
未经允许不得转载:网硕互联帮助中心 » 深度解析内存分区与布局
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!