进程 & 虚拟内存

- 每个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:存放系统初始化代码(由编译器自动生成并添加,用于初始化本程序所需的内存空间)
网硕互联帮助中心




评论前必须登录!
注册