进程的内存管理

news/2025/10/16 8:42:35/文章来源:https://www.cnblogs.com/janio/p/19143435

> 「C语言进程虚拟内存」:栈、堆、数据段、代码段各自职责、生命周期、典型API 。


一、虚拟内存总览:四个大区

任何一个程序,正常运行都需要内存资源,用来存放诸如变量、常量、函数代码等等。这些不同的内容,所存储的内存区域是不同的,且不同的区域有不同的特性。因此我们需要研究财经处内存布局,逐个了解不同内存区域的特性。

每个C语言进程都拥有一片结构相同的虚拟内存,所谓的虚拟内存,就是从实际物理内存映射出来的地址规范范围,最重要的特征是**所有的虚拟内存布局都是相同的,极大地方便内核管理不同的进程。例如三个完全不相干的进程p1、p2、p3,它们很显然会占据不同区段的物理内存,但经过系统的变换和映射,它们的虚拟内存的布局是完全一样的。

  • PM:Physical Memory,物理内存
  • VM:Virtual Memory,虚拟内存
虚拟内存四大区
区段 生长方向 谁管理 存放内容 生命周期
高→低 系统自动 局部变量、形参、返回地址 函数调用~返回
低→高 开发者 动态申请内存 malloc~free
数据段 固定 系统 全局变量、static 局部 程序开始~结束
代码段 只读 系统 机器指令、常量字符串 程序开始~结束

> 内核区与 0x0~0x08048000 为禁闭区,应用程序无法访问。

虚拟内存中各个区段的详细内容:

详细分区

二、栈内存:自动分配与释放

① 存放内容

  • 命令行参数 argc/argv(在命令行中运行程序时,携带的参数)
  • 环境变量 envp(用于指明一些默认的数据,比如用户名,工作路径,可执行文件的路径,库的路径..)
  • 局部变量(含数组、结构体)( 在函数体内部 { } 定义的所有变量都属于局部变量 )(包括形参)
  • 函数返回地址、寄存器现场

② 特点

  • 空间有限,默认 (kbytes, -s) 8192 = 8 MB(可 ulimit -s 查看)
  • 从高地址向低地址增长
  • 每当一个函数被调用,栈就会向下增长一段(该区域内存从高到低分配),用以存储该函数的局部变量。
  • 每当一个函数退出,栈就会向上缩减一段,将该函数的局部变量所占内存归还给系统。
  • 注意: 栈内存的分配和释放,都是由系统规定(自动化完成)的,我们无法干预。

③ 内存分配规则

栈内存是从高地址往低地址分配的

void func4(void) { int a4; printf("&a4:%p\n", &a4); }
void func3(void) { int a3; printf("&a3:%p\n", &a3); func4(); }
void func2(void) { int a2; printf("&a2:%p\n", &a2); func3(); }
void func1(void) { int a1; printf("&a1:%p\n", &a1); func2(); }
int main(void){int a; printf("&a:%p\n", &a); func1(); return 0; 
}

运行结果

&a :0x7ffd'efbf'f96c
&a1:0x7ffd'efbf'f94c  ↓ 地址递减 → **向下生长**
&a2:0x7ffd'efbf'f92c
&a3:0x7ffd'efbf'f90c
&a4:0x7ffd'efbf'f8ec
栈内存分配

三、获取环境变量 & 命令行参数

① 环境变量

#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]:你好

四、堆内存

堆内存(heap)又被称为动态内存、自由内存,简称堆。堆是唯一可被开发者自定义的区段,开发者可以根据需要申请内存的大小、决定使用的时间长短等。但又由于这是一块系统“飞地”,所有的细节均由开发者自己把握,系统不对此做任何干预,给予开发者绝对的“自由”,但也正因如此,对开发者的内存管理提出了很高的要求。对堆内存的合理使用,几乎是软件开发中的一个永恒的话题。

堆内存基本特征:

  • 相比栈内存,堆的总大小仅受限于物理内存,在物理内存允许的范围内,系统对堆内存的申请不做限制。

  • 相比栈内存,堆内存从下往上增长。

  • 堆内存是匿名的,只能由指针来访问(申请堆内存时系统会返回该内存的入口地址)。

  • 自定义分配的堆内存,除非开发者主动释放,否则永不释放,直到程序退出(系统会在程序运行结束时把整个虚拟内存进行销毁)。

  • 因此堆内存也是造成内存泄露的一大灾区

栈内存分配

① 基本 API 速查

函数 功能 是否清零 返回
malloc(size) 申请一块字节 void *
calloc(n, size) 申请 n 块 void *
realloc(ptr, new_size) 扩张/收缩 void *
free(ptr) 释放内存 - void

规则:malloc/calloc/realloc 必须与 free 成对出现!

malloc 申请堆内存

void *malloc(size_t size);size --> 需要申请的内存区尺寸,以字节为单位
返回值:成功 返回申请到的入口地址失败 返回NULL
注意:该函数不会对申请到的内存进行初始化

calloc 申请堆内存(堆数组)

void *calloc(size_t nmemb, size_t size);nmemb --> N块内存size --> 每一块多大
返回值:成功 返回申请到的入口地址失败 返回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

清零内存

 #include <strings.h>void bzero(void s[.n], size_t n);
参数分析:s --> 需要清空的目标内存空间(内存必须连续)n --> 希望清空的字节数
返回值:无#include <string.h>void *memset(void s[.n], int c, size_t n);
参数分析:s --> 需要设置的目标内存空间(内存必须连续)c --> 需要设置的目标内存中的数据(实际上他是字符的ASCII默认是1字节)n  --> 希望设置的字节数
返回值:返回指针指向第一个参数 s 

释放堆内存

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

②如何检测是否有内存泄露:Valgrind 一键扫描

1.安装一个内存泄露的检测工具

sudo apt install  valgrind

2.使用该工具来运行程序代码

$ valgrind ./a.out 
==5822== Memcheck, a memory error detector
==5822== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==5822== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==5822== Command: ./a.out
==5822== 
==5822== error calling PR_SET_PTRACER, vgdb might block
==5822== 
==5822== HEAP SUMMARY:   // 着重关注此处!
==5822==     in use at exit: 0 bytes in 0 blocks
==5822==   total heap usage: 1 allocs, 1 frees, 100 bytes allocated
==5822== 
==5822== All heap blocks were freed -- no leaks are possible
==5822== 
==5822== For lists of detected and suppressed errors, rerun with: -s
==5822== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

五、静态数据

概念

静态指的是数据的生命周期是静态的,也就是不会随着程序于运行的过程产生或释放,他的生命周期于整个进程保持一致,也就是只要程序运行静态区的内存就会被分配,直到程序退出才能(由系统自动)释放。

C语言中,静态数据有两种:

  • 全局变量:定义在函数体 { } 外部的变量。
  • 静态局部变量:定义在函数内部,且被static修饰的变量。

示例:

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

为什么需要静态数据?

  • 1.全局变量在默认的情况下,对所有文件可见,为某些需要在各个不同文件和函数间访问的数据提供操作上的方便。
  • 2.当我们希望一个函数退出后依然能保留局部变量的值,以便于下次调用时还能用时,静态局部变量可帮助实现这样的功能。

注意1:

  • 若定义时未初始化,则系统会将所有的静态数据自动初始化为0
  • 静态数据初始化语句,只会执行一遍。
  • 静态数据从程序开始运行时便已存在,直到程序退出时才释放。

注意2static的作用:

  • static修饰局部变量:使之由栈内存临时数据,变成了静态数据(存储区从栈转移到静态区【数段】)。
  • static修饰全局变量:使之由各文件可见的静态数据,变成了本文件可见的静态数据【拓展多文件编译】。
  • static修饰函数:使之由各文件可见的函数,变成了本文件可见的静态函数【拓展多文件编译】。

六、数据段与代码段

分段 段名 存放内容 读写属性 生命周期
数据段 .bss 未初始化全局/静态,它们将被系统自动初始化为0(该区域的所有数据初始化时都是0) RW 程序开始~结束
数据段 .data 已初始化全局/静态 RW 程序开始~结束
数据段 .rodata 常量字符串、const 全局 RO 程序开始~结束
代码段 .text 机器指令(函数体)用户代码 RO 程序开始~结束
代码段 .init 系统初始化代码(由编译器自动生成并添加,用于初始化本程序所需的内存空间) RO 程序开始~结束
数据段与代码段
>- 注意:数据段和代码段内存的分配和释放,都是由系统规定的,我们无法干预。

七、作用域

基本概念

C语言中,标识符(变量名、函数名等....)都有一定的可见范围(可访问范围),这些可见范围保证了标识符只能在一个有限的区域内使用,这个可见范围,被称为作用域(scope)。
软件开发中,尽量缩小标识符的作用域是一项基本原则,一个标识符的作用域超过它实际所需要的范围时,就会对整个软件的命名空间造成污染,导致一些不必要的名字冲突和误解

函数声明作用域

  • 概念: 在函数的声明式中定义的变量,其可见范围仅限于该声明式。

  • 示例:

void func(int fileSize, char *fileName);

要点:

  • 变量 fileSize fileName 只在函数声明式中可见。
  • 变量 fileSizefileName 可以省略,但一般不这么做,它们的作用是对参数的注解,方便函数的使用者更直观地使用函数,没必要一定去查询手册。

局部作用域

  • 概念: 在代码块内部定义的变量,其可见范围从其定义处开始,到代码块结束为止 }
  • 示例:
int main()
{int a=1;int b=2;     // 变量 c 的作用域是第4行到第9行{int c=4;int d=5; // 变量 d 的作用域是第7行到第8行int a = 100;}
}

要点:

  • 代码块指的是一对花括号 { } 括起来的区域。
  • 代码块可以嵌套包含,外层的标识符会被内嵌的同名标识符临时掩盖变得暂时不可见。
  • 代码块作用域的变量,由于其可见范围是局部的,因此被称为局部变量。

全局作用域

  • 概念: 在代码块外定义的变量,其可见范围可以跨越多个文件。
  • 示例:
// 文件:a.c
int global = 888; // 变量 global 的作用域是第2行到本文件结束
int main()
{
}
void f()
{ 
}
// 文件:b.c
extern int global; // 声明在 a.c 中定义的全局变量,使其在 b.c 中也可见
void f1()
{
}
void f2()
{
}

要点:

  • 代码块之外定义的标识符,即处于任何 {} 之外。
  • 整个翻译单元可见;可被 extern 扩展到其它源文件。
  • 生命周期 = 进程开始 ~ 进程结束。
  • 未显式初始化时系统自动清零(.bss),已初始化进 .data
  • 默认外部链接(全局可共享);加 static 后变内部链接,仅当前文件可见。

作用域的临时掩盖

如果有多个不同的作用域相互嵌套,那么小范围的作用域会临时 “遮蔽” 大范围的作用域中的同名标识符,被 “遮蔽” 的标识符不会消失,只是临时失去可见性

  • 示例代码:
int a = 100;// 函数代码块1
int main(void)
{printf("%d\n", a); // 输出100int a = 200;printf("%d\n", a); // 输出200// 代码块2 {printf("%d\n", a); // 输出200int a = 300;printf("%d\n", a); // 输出300}printf("%d\n", a); // 输出200
}void f()
{printf("%d\n", a); // 输出100
}

static关键字

C语言的一大特色,是相同的关键字,在不同的场合下,具有不同的含义。
static关键字在C语言中有两个不同的作用:
1.将可见范围设定为标识符所在的文件(缩小标识符的可见范围):

  • 修饰全局变量:使得全局变量由原来的跨文件可见,变成仅限于本文件可见
  • 修饰普通函数:使得函数由原来的跨文件可见,变成仅限于本文件可见

2.将存储区域设定为数据段:

  • 修饰局部变量:使得局部变量由原来存储在栈内存,变成存储在数据段

  • 示例:
       int a; // 普通全局变量,跨文件可见
static int b; // 静态全局变量,仅限本文件可见void f1()        // 普通函数,跨文件可见
{}static void __f2() // 静态函数,仅限本文件可见
{}int main()
{int c; // 普通局部变量,存储于栈内存static int d; // 静态局部变量,存储于数据段
}

八、存储期

基本概念

C语言中,变量都是有一定的生存周期的,所谓生存周期指的是从分配到释放的时间间隔。为变量分配内存相当于变量的诞生,释放其内存相当于变量的死亡。从诞生到死亡就是一个变量的生命周期
根据定义方式的不同,变量的生命周期有三种形式:

  1. 自动存储期
  2. 静态存储期
  3. 自定义存储期
储存期

自动存储期

栈内存中分配的变量,统统拥有自动存储期,因此也都被称为自动变量。这里自动的含义,指的是这些变量的内存管理不需要开发者操心,都是全自动的:在变量定义处自动分配,出了变量的作用域后自动释放

  • 以下三个概念是等价的:
    • 自动变量:从存储期的角度,描述变量的时间特性
    • 临时变量:同上。
    • 局部变量:从作用域的角度,描述变量的空间特性

可以统一把它们称为栈变量,下面是示例代码:

int main()
{int a, b;     // 自动存储期static int c;f(a, b);
}void f(int x, int y) // 自动存储期
{
}

静态存储期

在数据段中分配的变量,统统拥有静态存储期,因此也都被称为静态变量。这里静态的含义,指的是这些变量的不会因为程序的运行而发生临时性的分配和释放,它们的生命周期是恒定的,跟整个程序一致。
静态变量包含:

  • 全局变量:不管加不加 static,任何全局变量都是静态变量
  • static 局部变量

示例代码:

int g1;        // 静态存储期
static int g2; // 静态存储期int main()
{int a, b;static int c; // 静态存储期
}

注意1:

  • 若定义时未初始化,则系统会将所有的静态数据自动初始化为0
  • 静态数据初始化语句,只会执行一遍。
  • 静态数据从程序开始运行时便已存在,直到程序退出时才释放。

注意2:

  • static修饰局部变量:使之由栈内存临时数据,变成了静态数据。
  • static修饰全局变量:使之由各文件可见的静态数据,变成了本文件可见的静态数据。

自定义存储期

在堆中分配的变量,统统拥有自定义存储期,也就是说这些变量的分配和释放,都是由开发者自己决定的。由于堆内存拥有高度自治权,因此堆是程序开发中用得最多的一片区域。

自定义储存期

相关API:

  • 申请堆内存:malloc() / calloc()
  • 清零堆内存:bzero()
  • 释放堆内存:free()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/937988.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入理解Java内存模型与volatile关键字:从理论到实践

1. 引言:为什么需要理解内存模型? 在多核处理器成为主流的今天,并发编程已成为每个Java程序员的必备技能。然而,编写正确的并发程序远比单线程程序复杂,主要原因在于我们需要处理两个核心问题:线程之间如何通信?…

完整教程:【stm32】cube固件解析和放入工程(HAL_F4)

完整教程:【stm32】cube固件解析和放入工程(HAL_F4)pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&q…

312、金缕衣

312、金缕衣312、金缕衣 唐●杜秋娘 劝君莫惜金缕衣,劝君惜取少年时。 花开堪折直须折,莫待无花空折枝。【现代诗意译】 我劝你啊 不要看重华丽衣裳 但是 一定要珍惜 青春年少美好时光花开宜折的时候 就要立即把它摘…

使用 Visual Studio 快速创建 NuGet 程序包并发布到 NuGet 官网

前言 在前面的 .NET EF Core 快速入门实战教程章节中我们创建了一个名为 EFCoreGenericRepository 的 .NET 9 通用仓储类库,今天我们来把这个 EF Core 通用仓储类库打包成 NuGet 程序包并发布到 NuGet 官网(https://…

反配容斥

反配容斥模拟赛考了这个 trick,感觉挺牛的。 直接放题。 题意 给定一个长度为 \(n\) 的序列 \(\{ a_i \}\),令全集 \(U = \{ 1,2,3,\cdots,n \}\),定义子集 \(S\) 的权值 \(g(S)=1+\oplus_{i\in S} a_i\)。 我们称集…

怎么激活win11?笔记本重装系统后怎么激活Windows?

我可以肯定99%的人并不完全了解Windows的几种激活方式,或者知其一,不知其二。 windows10 突然右下角出现激活windows? 光广告就能搜索出一大堆,必定广告是要收费的,说明市场是有的,但我这是分享的是免费使用教程…

AVG Clear:彻底卸载AVG产品的专业工具

AVG Clear是一款专业的AVG产品卸载工具,当传统卸载方式失效时,可自动扫描并彻底删除AVG相关的文件、注册表项和安装文件,确保系统完全清理。适用于Windows 10/11系统。AVG Clear 下载 立即在作者网站下载 作者: AV…

深入解析:安卓 WPS Office v18.21.0 国际版

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

现代 PHP8+ 实战特性介绍 Enums、Fibers 和 Attributes

现代 PHP8+ 实战特性介绍 Enums、Fibers 和 Attributes PHP 一直是 Web 开发领域使用最广泛的语言之一。这些年来,它的特性不断演进,每个版本都让语言变得更有表现力、更高效、对开发者更友好。如果你一直在关注最近…

用【WPF+Dlib68】实现 侧脸 眼镜虚拟佩戴 - 用平面图表现空间视觉 - 行人-

一个精致的眼镜3D模型,在模型网站中的售价几百人民币是非常常见的,它的制作成本以及制作周期,对于实际落地的项目来说是一个非常大的阻碍。于是我尝试使用 平面眼镜图 来表现 眼镜在佩戴时的空间感 ,尤其是 侧脸 时…

比 26ai 更震撼的,是 Oracle AI 向量搜索改写的生命答案

比 "26ai" 更震撼的,是 Oracle AI 向量搜索改写的生命答案2025-10-16 08:02 AlfredZhao 阅读(0) 评论(0) 收藏 举报在 Oracle AI World 上,“26ai” 的名字成为外界讨论的焦点,引发广泛好奇与热议。 …

科学背景如何赋能云计算业务战略

本文讲述了一位神经科学博士如何将科研背景应用于云计算业务发展,通过理解科研工作者需求,帮助企业客户将研究负载迁移至云端,加速从原始数据到研究成果的转化过程。科学背景如何赋能云计算业务战略 安德烈亚皮尔斯…

.netframework中自带的dll

.netframework中自带的dllSystem.Data.OracleClient.dll .netframework中自带的dll, "C:\Windows\Microsoft.NET\Framework64\v2.0.50727\System.Data.OracleClient.dll" ``

通过pypdfium2-team/ctypesgen 快速生成ctypes 代码

通过pypdfium2-team/ctypesgen 快速生成ctypes 代码以前说过ctypesgen 的作用,以下是一个简单试用 项目准备代码结构├── add.c ├── add.h ├── add.so ├── app.py ├── common.h ├── init_patch.py ├…

【GitHub每日速递 251016】23k star,Daytona:90ms内极速运行AI代码,安全弹性基础设施来袭!

原文: https://mp.weixin.qq.com/s/QkAYh9t3n41cADcQUi6FXw Daytona:90ms内极速运行AI代码,安全弹性基础设施来袭! 项目地址:https://github.com/daytonaio/daytona 主要语言:TypeScript stars: 23k仓库概述 Dayt…

用 【C# + Winform + Dlib68点】 实现静图眼镜虚拟佩戴 - 行人-

基于DlibDotNet,识别人脸68点关键点,通过眼镜标定功能,使虚拟眼镜能更自然地贴合人脸。采用.NET Framework 4.6.2开发,通过计算眼镜标定点与人眼位置的相对关系,实现眼镜对齐和缩放。关键步骤:人脸检测模型加载、…

图神经网络前沿技术与应用探索

本文深入探讨图神经网络在建模长距离依赖关系、提升计算效率以及新型因果模型方面的最新进展,涵盖算法优化、系统设计和硬件协同等多个技术层面,并介绍在知识图谱推理和多智能体系统等领域的创新应用。KDD 2023:图神…

MVCC、幻读、间隙锁与临键锁(三)

一、MVCC解决了什么问题? MVCC 解决了数据库高并发场景下的两大核心问题:读写阻塞:在传统的锁机制下,读操作可能会阻塞写操作,写操作也一定会阻塞读操作。当有大量读写操作并发时,数据库性能会急剧下降。事务隔离…

MVCC、幻读、间隙锁与临键锁

一、MVCC 解决了什么问题? 🌱 背景:并发读写冲突 当多个事务同时操作同一行时,最经典的冲突是:A 在读;B 在写;A 还没提交,B 改了数据;如何让 A 看到一致的结果?MVCC(Multi-Version Concurrency Control,多…