参考:程序运行时对应的内存分布关系
作者:嵌入式基地(公众号)
发布时间: 2021-04-28
网址:https://mp.weixin.qq.com/s/AVDPZawSjg9HtxEm8vsFBA
参考:静态变量与动态变量的定义与区别
作者:JoannaJuanCV
发布时间: 2019-01-23 13:14:33
网址:https://blog.csdn.net/zfjBIT/article/details/86608393?spm=1001.2014.3001.5502
内存相关博文:
1、内存四区(代码区 静态区 栈区 堆区)
2、程序运行时对应的内存分布(BSS段、数据段、代码段、堆、栈)关系
3、深入理解STM32内存管理
目录
- 前言
- 全局变量初始化
- 局部变量(栈的引入)
- 内存分区介绍
- 程序运行时的内存分区主要分为BSS段、数据段、代码段、堆、栈
- 静态内存
- 动态内存
- 静态变量和动态变量
- 代码测试
- 简单说明
前言
以下是韦东山老师讲解笔记: https://www.bilibili.com/video/BV1VM4y137Pm?p=14&spm_id_from=pageDriver
全局变量初始化
注:全局变量全部copy到RAM,也就是重定位。
局部变量(栈的引入)
main()函数里面的局部变量在栈里面
之前全局变量是整段复制,局部变量没有那么高的效率,是一个一个执行汇编指令写入到栈里面
内存分区介绍
程序运行时的内存分区主要分为BSS段、数据段、代码段、堆、栈
-
BSS段:Block Started by Symbol,一般是指存放程序中未初始化的全局变量的一块内存区域。BSS段属于静态内存(下面有介绍,韦东山老师称之为ZI段)分配。
-
数据段:data segment,一般是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。
-
代码段:code segment/text segment,通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读,某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些 只读的常数变量 ,例如字符串常量等。程序段为程序代码在内存中的映射。一个程序可以在内存中有多个副本。
-
堆:heap,堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc/free等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张)/释放的内存从堆中被剔除(堆被缩减)。
-
栈:stack,栈又称堆栈,存放程序的局部变量(但不包括static声明的变量, static 意味着在数据段中 存放变量)。除此以外,在函数被调用时,栈用来传递参数和返回值。
静态内存
静态内存是由系统自动分配内存,由系统自动释放。静态内存是在栈中分配的,假如main函数调用另一个函数,那么就把被调用函数压到一个栈里面。执行这个被调函数就是使系统为这个函数分配的所有内存空间逐个出栈。出栈全部结束就是被调用函数执行完毕。
出栈的顺序是先进后出,也就是先进栈的后执行(前面介绍的print输出例子),因为先进栈被压在下面,所以执行的永远是栈顶的内容。程序执行完毕的意思就是“栈里面所有的内容全部都出栈了”。出栈就是“释放”。栈顶全部出栈后原来位于栈顶就会成为栈顶,然后继续执行该栈定内容,继续出栈。整个程序全部执行完毕的意思就是“系统所分配的内存空间全部出栈”,内存全部释放完毕。所以系统为静态变量分配的内存空间在程序中执行完毕后都会被释放。
动态内存
而动态内存是由程序员手动释放,函数终止不会被系统自动释放。这说明他肯定不是在栈里面分配的。那他是在什么地方分配的呢?是在“堆中”分配的,栈是一种存储结构,堆不是一种存储结构,堆是分配内存的一种排序方式。也就是说,动态内存是以堆排序的方式分配的。以前讲排序,如冒泡排序,插入排序,选择排序,快速排序。堆排序也是一种排序方式,因为动态内存是在堆中分配的,是以排序的方式分配的,不是在栈中,所以函数运行结束后也不会被释放。
也因为动态内存是由程序员手动分配,手动释放,所以这时候就会有一个比较严重的问题:如果忘记释放了,就会导致内存泄露,所以动态分配内存有优点,也有缺点。动态内存的使用非常灵活,但需要注意的问题也很多。
静态变量和动态变量
1.定义上,静态变量比动态变量在多一个关键字static,比如:
动态变量::int i;
静态变量:static int i;
2.动态变量在子程序中,每次调用都会从它的初始值开始调用,而不管他在函数中经历了什么变化;静态变量会从变化后的值继续改变。
void fun()
{int j=0;j++;printf("%d",j);
}void fun1()
{static int j=0;j++;printf("%d",j);
}void main()
{int i;for(i=0;i<5;i++)fun();//输出结果为11111printf("\n");for(i=0;i<5;i++)fun1();//输出结果为12345
}
程序中内存分布图
APUE中的C内存分布图
代码测试
#include <stdio.h>
#include <stdlib.h>int g1=0, g2=0, g3=0;static int max(int i) {int m1 = 0, m2 , m3 = 0, *p_max;static n1_max = 0, n2_max , n3_max = 0;p_max = (int*)malloc(10);printf("打印max程序地址\n");printf("in max: %p\n\n",max);printf("打印max传入参数地址\n");printf("in max: %p\n\n",&i);printf("打印max函数中静态变量地址\n");printf("%p\n",&n1_max); < 打印各本地变量的内存地址printf("%p\n",&n2_max);printf("%p\n\n",&n3_max);printf("打印max函数中局部变量地址\n");printf("%p\n",&m1); < 打印各本地变量的内存地址printf("%p\n",&m2);printf("%p\n\n",&m3);printf("打印max函数中malloc分配地址\n");printf("%p\n\n",p_max); < 打印各本地变量的内存地址if(i) {return 1;} else {return 0;}}int main(int argc, char **argv) {static int s1 = 0, s2, s3 = 0;int v1 = 0, v2, v3 = 0;int *p; p = (int*)malloc(10);printf("打印各全局变量(已初始化)的内存地址\n");printf("%p\n",&g1); < 打印各全局变量的内存地址printf("%p\n",&g2);printf("%p\n\n",&g3);printf("======================\n");printf("打印程序初始程序main地址\n");printf("main: %p\n\n", main);printf("打印主参地址\n");printf("argv: %p\n\n",argv);printf("打印各静态变量的内存地址\n");printf("%p\n",&s1); < 打印各静态变量的内存地址printf("%p\n",&s2);printf("%p\n\n",&s3);printf("打印各局部变量的内存地址\n");printf("%p\n",&v1); < 打印各本地变量的内存地址printf("%p\n",&v2);printf("%p\n\n",&v3);printf("打印malloc分配的堆地址\n");printf("malloc: %p\n\n",p);printf("======================\n");max(v1);printf("======================\n");printf("打印子函数起始地址\n");printf("max: %p\n\n",max);system("pause");return 0;}
根据输出结果可以看出,传入的参数,局部变量,都是在栈顶分布,随着子函数的增多而向下增长。函数的调用地址(函数运行代码),全局变量,静态变量都是在分配内存的低部存在,而malloc分配的堆则存在于这些内存之上,并向上生长。
打印各全局变量(已初始化)的内存地址
00405008
0040500C
00405010======================
打印程序初始程序main地址
main: 00401457打印主参地址
argv: 00DE15C8打印各静态变量的内存地址
00405014
00405018
0040501C打印各局部变量的内存地址
0060FEF8
0060FEF4
0060FEF0打印malloc分配的堆地址
malloc: 00DE15D8======================
打印max程序地址
in max: 00401334打印max传入参数地址
in max: 0060FEE0打印max函数中静态变量地址
00405020
00405024
00405028打印max函数中局部变量地址
0060FEC8
0060FEC4
0060FEC0打印max函数中malloc分配地址
00DE3E38======================
打印子函数起始地址
max: 00401334请按任意键继续. . .
简单说明
左边的是UNIX/LINUX系统的执行文件,右边是对应进程逻辑地址空间的划分情况。
首先是堆栈区(stack),堆栈是由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。栈的申请是由系统自动分配,如在函数内部申请一个局部变量 int h,同时判别所申请空间是否小于栈的剩余空间,如若小于的话,在堆栈中为其开辟空间,为程序提供内存,否则将报异常提示栈溢出。
其次是堆(heap),堆一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。堆的申请是由程序员自己来操作的,在C中使用malloc函数,而C++中使用new运算符,但是堆的申请过程比较复杂:当系统收到程序的申请时,会遍历记录空闲内存地址的链表,以求寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,此处应该注意的是有些情况下,新申请的内存块的首地址记录本次分配的内存块大小,这样在delete尤其是 delete[]时就能正确的释放内存空间。
接着是全局数据区(静态区) (static),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。另外文字常量区,常量字符串就是放在这里,程序结束后有系统释放。
最后是程序代码区,放着函数体的二进制代码。
内存分布对应关系