内存 寄存器
C语言内存运行时不同变量是怎样分配的?
怎样验证C语言编译后的内存地址分配是否合理?
回帖(1)
2022-2-25 13:41:04
(一)C程序内存分配
1.内存分配图解
其中C程序的内存分配为栈区、堆区、全局区、常量区和代码区这五大区域,而全局区又分为.bss段和.data段。一般来说,代码区、数据区、.bss在程序运行之后,大小都是固定的,而栈区和堆区是根据数据的多少再自由的扩张,堆区从小到大扩张,栈区从大到小扩张(即堆区由低地址向高地址生长,栈区由高地址向低地址生长),合理分配和使用内存,能够使内存利用率最大化。
2.内存分配介绍
1、栈区(stack)
- 栈区介绍
- 栈区与堆区是相互毗邻的,并且生长方向相反;当栈指针触及到堆指针时,意味着栈空间已经被耗尽(如今地址空间越来越大,及虚拟内存技术发展,栈与堆可能放置在内存的任何地方,但生长方向依然还是相向的)。
- 栈区域包含一个LIFO结构的程序栈,其通常放置在内存的高地址处,栈区按内存地址由高到低方向生长,其最大大小由编译时确定,速度快,但自由性差,最大空间不大。
- 栈指针寄存器跟踪栈顶位置,每当有数值被压入栈中,栈顶指针会被调整,在一个函数的调用过程中,压入的一系列数值被称作“栈帧”,栈帧至少包含一个返回地址。
- 存放内容
- 临时创建和const定义的局部变量存放在栈区。
- 函数调用和返回时,其入口参数和返回值存放在栈区。
2、堆区(heap)
- 堆区介绍
堆区通常用作动态内存分配,堆空间起始于BSS段的末尾,并按内存地址由低到高方向生长,其大小由系统内存/虚拟内存上限决定,速度较慢,但自由性大,可用空间大。
- 存放内容
- 堆区用于存放程序运行中被动态分布的内存段,可增可减。
- 可以用malloc等函数实现动态分布内存。当有malloc函数分配的内存时必须用free函数进行内存释放,否则会造成内存泄漏。
3、全局区(静态区)
- 全局区介绍
和栈区一样,通常是用于那些在编译期间就能确定存储大小的变量的存储区,但它用于的是在整个程序运行期间都可见的全局变量和静态变量。全局区有.bss段和.data段组成,可读可写。
- .bss段
- 未初始化的全局变量存放在.bss段。
- 初始化为0的全局变量和初始化为0的静态变量存放在.bss段。
- .bss段不占用可执行文件空间,其内容有操作系统初始化。
- .data段
- 已经初始化的全局变量存放在.data段。
- 静态变量存放在.data段。
- .data段占用可执行文件空间,其内容有程序初始化。
- const定义的全局变量存放在.rodata段。
4、常量区
- 字符串、数字等常量存放在常量区。
- 程序运行期间,常量区的内容不可以被修改。
5、代码区
- 程序执行代码存放在代码区,其值不能修改(若修改则会出现错误)。
- 字符串常量也有可能存放在代码区。
(二)Ubuntu系统下实验验证
1.详细代码
#include
#include
int k1 = 1; //已初始化全局int型变量k1
int k2; //未初始化全局int型变量k2
static int k3 = 2; //已初始化静态全局int型变量k3
static int k4; //未初始化静态全局int型变量k4
int test()
{
int j1;
int j2;
printf("未初始化局部int型变量j1 :%pn", &j1);
printf("未初始化局部int型变量j2 :%pn", &j2);
return 0;
}
int main()
{
static int m1 = 2; //已初始化静态局部int型变量m1
static int m2; //未初始化静态局部int型变量m2
int i1; //未初始化局部int型变量i1
int i2; //未初始化局部int型变量i2
char *p; //未初始化局部char型指针变量p
char str[10] = "hello"; //已初始化局部char型数组str
char *var1 = "123456"; //已初始化局部char型指针变量var1
char *var2 = "abcdef"; //已初始化局部char型指针变量var2
int *p1 = malloc(4); //已初始化局部int型指针变量p1
int *p2 = malloc(4); //已初始化局部int型指针变量p2
printf("栈区-变量地址n");
printf("未初始化局部int型变量i :%pn", &i1);
printf("未初始化局部int型变量i2 :%pn", &i2);
printf("未初始化局部char型指针变量p :%pn", &p);
printf("已初始化局部char型数组str :%pn", str);
test();
printf("n堆区-动态申请地址n");
printf("已初始化局部int型指针变量p1 :%pn", p1);
printf("已初始化局部int型指针变量p2 :%pn", p2);
printf("n.bss段地址n");
printf("未初始化全局int型变量 k2 :%pn", &k2);
printf("未初始化静态全局int型变量k4 :%pn", &k4);
printf("未初始化静态局部int型变量m2 :%pn", &m2);
printf("n.data段地址n");
printf("已初始化全局int型变量k1 :%pn", &k1);
printf("已初始化静态全局int型变量k3 :%pn", &k3);
printf("已初始化静态局部int型变量m1 :%pn", &m1);
printf("n常量区地址n");
printf("已初始化局部char型指针变量var1:%pn", var1);
printf("已初始化局部char型指针变量var2:%pn", var2);
printf("n代码区地址n");
printf("程序代码区main函数入口地址 :%pn", &main);
free(p1);
free(p2);
return 0;
}
2.实验结果
3.实验结果分析
- 栈区分配
- 从理论上来说,栈区的地址应该是从高地址向低地址分配,但是我们可以看到上面的程序中同一main函数和test函数内的变量却是按照从低地址向高地址分配,这是为什么呢?经过查阅网上资料,得出的结果如下:
同一函数内的局部变量的地址分配的大小不能说明问题,因为编译器分配空间可以随意,得看多个函数调用时分配局部变量的地址大小。
- 所以这里我使用test函数和main函数里的局部变量定义来分析栈区地址分配是否是从高到低,结果如上图:
其中:main函数的变量i的地址为0x7ffc838074e8,test函数的变量j1分配的地址为0x7ffc838074c0,可以看出栈区地址分配是从高地址往低地址生长。最终实验结果还是符合理论的。
- 堆区分配
可以看出,堆区地址分配是从高地址往低地址分配的,实验结果与理论相符合。
(三)STM32系统下实验验证
1.详细代码
#include "led.h"
#include "delay.h"
//#include "key.h"
#include "sys.h"
#include "usart.h"
#include
#include
int k1 = 1; //已初始化全局int型变量k1
int k2; //未初始化全局int型变量k2
static int k3 = 2; //已初始化静态全局int型变量k3
static int k4; //未初始化静态全局int型变量k4
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init(); //LED端口初始化
//KEY_Init(); //初始化与按键连接的硬件接口
while(1)
{
static int m1 = 2; //已初始化静态局部int型变量m1
static int m2; //未初始化静态局部int型变量m2
int i1; //未初始化局部int型变量i1
int i2; //未初始化局部int型变量i2
char *p; //未初始化局部char型指针变量p
char str[10] = "hello"; //已初始化局部char型数组str
char *var1 = "123456"; //已初始化局部char型指针变量var1
char *var2 = "abcdef"; //已初始化局部char型指针变量var2
int *p1 = malloc(4); //已初始化局部int型指针变量p1
int *p2 = malloc(4); //已初始化局部int型指针变量p2
printf("栈区-变量地址rn");
printf("未初始化局部int型变量i1 :0x%prn", &i1);
printf("未初始化局部int型变量i2 :0x%prn", &i2);
printf("未初始化局部char型指针变量p :0x%prn", &p);
printf("已初始化局部char型数组str :0x%prn", str);
//test();
printf("n堆区-动态申请地址rn");
printf("已初始化局部int型指针变量p1 :0x%prn", p1);
printf("已初始化局部int型指针变量p2 :0x%prn", p2);
printf("n.bss段地址rn");
printf("未初始化全局int型变量k2 :0x%prn", &k2);
printf("未初始化静态全局int型变量k4 :0x%prn", &k4);
printf("未初始化静态局部int型变量m2 :0x%prn", &m2);
printf("n.data段地址rn");
printf("已初始化全局int型变量k1 :0x%prn", &k1);
printf("已初始化静态全局int型变量k3 :0x%prn", &k3);
printf("已初始化静态局部int型变量m1 :0x%prn", &m1);
printf("n常量区地址rn");
printf("已初始化局部char型指针变量var1:0x%prn", var1);
printf("已初始化局部char型指针变量var2:0x%prn", var2);
printf("n代码区地址rn");
printf("程序代码区main函数入口地址 :0x%prn", &main);
free(p1);
free(p2);
}
}
2.实验结果
- 编译信息
- 程序占用空间
Code:表示程序所占用 FLASH 的大小(FLASH)。
RO-data:即 Read Only-data,表示程序定义的常量,如 const 类型(FLASH)。
RW-data:即 Read Write-data,表示已被初始化的全局变量(SRAM)
ZI-data:即 Zero Init-data,表示未被初始化的全局变量(SRAM)
FLASH占用大小计算公式:Code+RO-data
SRAM占用大小计算公式:RW-data+ZI-data
- 这里简要介绍一下FLASH和SRAM的区别:
单片机中的FLASH和SRAM都有存储功能,但是存储类型不一样,FLASH是闪存,一般用来存储烧录的源码程序,断电不会丢失数据,相对于我们电脑来说是ROM(外存);而SRAM存储的一般是CPU运行时产生的临时文件,相对于电脑来说是RAM(内存)。
程序占用FLASH 大小为:6680字节(6332+348),所用的 SRAM 大小为:1824 个字节(84+1740)
栈区-变量地址
未初始化局部int型变量i1 :0x2000071c
未初始化局部int型变量i2 :0x20000718
未初始化局部char型指针变量p :0x20000714
已初始化局部char型数组str :0x20000708
堆区-动态申请地址
已初始化局部int型指针变量p1 :0x20000128
已初始化局部int型指针变量p2 :0x20000130
.bss段地址
未初始化全局int型变量k2 :0x20000004
未初始化静态全局int型变量k4 :0x2000000c
未初始化静态局部int型变量m2 :0x20000014
.data段地址
已初始化全局int型变量k1 :0x20000000
已初始化静态全局int型变量k3 :0x20000008
已初始化静态局部int型变量m1 :0x20000010
常量区地址
已初始化局部char型指针变量var1:0x08000230
已初始化局部char型指针变量var2:0x08000238
代码区地址
程序代码区main函数入口地址 :0x08000145
3.实验结果分析
- 从stm32串口输出的数据可以看出:
- stm32的代码区和常量区都分配在0x08000000储存地址(低地址)这一块,全局和静态变量、栈区和堆区的地址都分配在0x20000000储存地址(高地址)处。
- stm32的栈区地址分配是按照高地址到低地址的方式进行分配,而堆区是按照低地址到高地址的方式进行分配,其他的区域基本上都按照从低地址向高地址的方式进行内存分配。
- Keil软件里的芯片地址
从keil软件的option中可以看出关于STM32内存的起始地址和总大小。比如说,ROM的起始地址为0x8000000,大小为0x80000;RAM的起始地址为0x20000000,大小为0x10000。
(四)总结
通过在不同平台进行编程验证C程序编译后在内存的地址分配,我更加深入地了解了不同变量及代码在内存中地址分配的不同,同时对于不同平台的C程序的内存分配也有了更加清晰的认识。由于作者水平有限,文中难免有疏漏之处,希望读者批评指正。
(一)C程序内存分配
1.内存分配图解
其中C程序的内存分配为栈区、堆区、全局区、常量区和代码区这五大区域,而全局区又分为.bss段和.data段。一般来说,代码区、数据区、.bss在程序运行之后,大小都是固定的,而栈区和堆区是根据数据的多少再自由的扩张,堆区从小到大扩张,栈区从大到小扩张(即堆区由低地址向高地址生长,栈区由高地址向低地址生长),合理分配和使用内存,能够使内存利用率最大化。
2.内存分配介绍
1、栈区(stack)
- 栈区介绍
- 栈区与堆区是相互毗邻的,并且生长方向相反;当栈指针触及到堆指针时,意味着栈空间已经被耗尽(如今地址空间越来越大,及虚拟内存技术发展,栈与堆可能放置在内存的任何地方,但生长方向依然还是相向的)。
- 栈区域包含一个LIFO结构的程序栈,其通常放置在内存的高地址处,栈区按内存地址由高到低方向生长,其最大大小由编译时确定,速度快,但自由性差,最大空间不大。
- 栈指针寄存器跟踪栈顶位置,每当有数值被压入栈中,栈顶指针会被调整,在一个函数的调用过程中,压入的一系列数值被称作“栈帧”,栈帧至少包含一个返回地址。
- 存放内容
- 临时创建和const定义的局部变量存放在栈区。
- 函数调用和返回时,其入口参数和返回值存放在栈区。
2、堆区(heap)
- 堆区介绍
堆区通常用作动态内存分配,堆空间起始于BSS段的末尾,并按内存地址由低到高方向生长,其大小由系统内存/虚拟内存上限决定,速度较慢,但自由性大,可用空间大。
- 存放内容
- 堆区用于存放程序运行中被动态分布的内存段,可增可减。
- 可以用malloc等函数实现动态分布内存。当有malloc函数分配的内存时必须用free函数进行内存释放,否则会造成内存泄漏。
3、全局区(静态区)
- 全局区介绍
和栈区一样,通常是用于那些在编译期间就能确定存储大小的变量的存储区,但它用于的是在整个程序运行期间都可见的全局变量和静态变量。全局区有.bss段和.data段组成,可读可写。
- .bss段
- 未初始化的全局变量存放在.bss段。
- 初始化为0的全局变量和初始化为0的静态变量存放在.bss段。
- .bss段不占用可执行文件空间,其内容有操作系统初始化。
- .data段
- 已经初始化的全局变量存放在.data段。
- 静态变量存放在.data段。
- .data段占用可执行文件空间,其内容有程序初始化。
- const定义的全局变量存放在.rodata段。
4、常量区
- 字符串、数字等常量存放在常量区。
- 程序运行期间,常量区的内容不可以被修改。
5、代码区
- 程序执行代码存放在代码区,其值不能修改(若修改则会出现错误)。
- 字符串常量也有可能存放在代码区。
(二)Ubuntu系统下实验验证
1.详细代码
#include
#include
int k1 = 1; //已初始化全局int型变量k1
int k2; //未初始化全局int型变量k2
static int k3 = 2; //已初始化静态全局int型变量k3
static int k4; //未初始化静态全局int型变量k4
int test()
{
int j1;
int j2;
printf("未初始化局部int型变量j1 :%pn", &j1);
printf("未初始化局部int型变量j2 :%pn", &j2);
return 0;
}
int main()
{
static int m1 = 2; //已初始化静态局部int型变量m1
static int m2; //未初始化静态局部int型变量m2
int i1; //未初始化局部int型变量i1
int i2; //未初始化局部int型变量i2
char *p; //未初始化局部char型指针变量p
char str[10] = "hello"; //已初始化局部char型数组str
char *var1 = "123456"; //已初始化局部char型指针变量var1
char *var2 = "abcdef"; //已初始化局部char型指针变量var2
int *p1 = malloc(4); //已初始化局部int型指针变量p1
int *p2 = malloc(4); //已初始化局部int型指针变量p2
printf("栈区-变量地址n");
printf("未初始化局部int型变量i :%pn", &i1);
printf("未初始化局部int型变量i2 :%pn", &i2);
printf("未初始化局部char型指针变量p :%pn", &p);
printf("已初始化局部char型数组str :%pn", str);
test();
printf("n堆区-动态申请地址n");
printf("已初始化局部int型指针变量p1 :%pn", p1);
printf("已初始化局部int型指针变量p2 :%pn", p2);
printf("n.bss段地址n");
printf("未初始化全局int型变量 k2 :%pn", &k2);
printf("未初始化静态全局int型变量k4 :%pn", &k4);
printf("未初始化静态局部int型变量m2 :%pn", &m2);
printf("n.data段地址n");
printf("已初始化全局int型变量k1 :%pn", &k1);
printf("已初始化静态全局int型变量k3 :%pn", &k3);
printf("已初始化静态局部int型变量m1 :%pn", &m1);
printf("n常量区地址n");
printf("已初始化局部char型指针变量var1:%pn", var1);
printf("已初始化局部char型指针变量var2:%pn", var2);
printf("n代码区地址n");
printf("程序代码区main函数入口地址 :%pn", &main);
free(p1);
free(p2);
return 0;
}
2.实验结果
3.实验结果分析
- 栈区分配
- 从理论上来说,栈区的地址应该是从高地址向低地址分配,但是我们可以看到上面的程序中同一main函数和test函数内的变量却是按照从低地址向高地址分配,这是为什么呢?经过查阅网上资料,得出的结果如下:
同一函数内的局部变量的地址分配的大小不能说明问题,因为编译器分配空间可以随意,得看多个函数调用时分配局部变量的地址大小。
- 所以这里我使用test函数和main函数里的局部变量定义来分析栈区地址分配是否是从高到低,结果如上图:
其中:main函数的变量i的地址为0x7ffc838074e8,test函数的变量j1分配的地址为0x7ffc838074c0,可以看出栈区地址分配是从高地址往低地址生长。最终实验结果还是符合理论的。
- 堆区分配
可以看出,堆区地址分配是从高地址往低地址分配的,实验结果与理论相符合。
(三)STM32系统下实验验证
1.详细代码
#include "led.h"
#include "delay.h"
//#include "key.h"
#include "sys.h"
#include "usart.h"
#include
#include
int k1 = 1; //已初始化全局int型变量k1
int k2; //未初始化全局int型变量k2
static int k3 = 2; //已初始化静态全局int型变量k3
static int k4; //未初始化静态全局int型变量k4
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init(); //LED端口初始化
//KEY_Init(); //初始化与按键连接的硬件接口
while(1)
{
static int m1 = 2; //已初始化静态局部int型变量m1
static int m2; //未初始化静态局部int型变量m2
int i1; //未初始化局部int型变量i1
int i2; //未初始化局部int型变量i2
char *p; //未初始化局部char型指针变量p
char str[10] = "hello"; //已初始化局部char型数组str
char *var1 = "123456"; //已初始化局部char型指针变量var1
char *var2 = "abcdef"; //已初始化局部char型指针变量var2
int *p1 = malloc(4); //已初始化局部int型指针变量p1
int *p2 = malloc(4); //已初始化局部int型指针变量p2
printf("栈区-变量地址rn");
printf("未初始化局部int型变量i1 :0x%prn", &i1);
printf("未初始化局部int型变量i2 :0x%prn", &i2);
printf("未初始化局部char型指针变量p :0x%prn", &p);
printf("已初始化局部char型数组str :0x%prn", str);
//test();
printf("n堆区-动态申请地址rn");
printf("已初始化局部int型指针变量p1 :0x%prn", p1);
printf("已初始化局部int型指针变量p2 :0x%prn", p2);
printf("n.bss段地址rn");
printf("未初始化全局int型变量k2 :0x%prn", &k2);
printf("未初始化静态全局int型变量k4 :0x%prn", &k4);
printf("未初始化静态局部int型变量m2 :0x%prn", &m2);
printf("n.data段地址rn");
printf("已初始化全局int型变量k1 :0x%prn", &k1);
printf("已初始化静态全局int型变量k3 :0x%prn", &k3);
printf("已初始化静态局部int型变量m1 :0x%prn", &m1);
printf("n常量区地址rn");
printf("已初始化局部char型指针变量var1:0x%prn", var1);
printf("已初始化局部char型指针变量var2:0x%prn", var2);
printf("n代码区地址rn");
printf("程序代码区main函数入口地址 :0x%prn", &main);
free(p1);
free(p2);
}
}
2.实验结果
- 编译信息
- 程序占用空间
Code:表示程序所占用 FLASH 的大小(FLASH)。
RO-data:即 Read Only-data,表示程序定义的常量,如 const 类型(FLASH)。
RW-data:即 Read Write-data,表示已被初始化的全局变量(SRAM)
ZI-data:即 Zero Init-data,表示未被初始化的全局变量(SRAM)
FLASH占用大小计算公式:Code+RO-data
SRAM占用大小计算公式:RW-data+ZI-data
- 这里简要介绍一下FLASH和SRAM的区别:
单片机中的FLASH和SRAM都有存储功能,但是存储类型不一样,FLASH是闪存,一般用来存储烧录的源码程序,断电不会丢失数据,相对于我们电脑来说是ROM(外存);而SRAM存储的一般是CPU运行时产生的临时文件,相对于电脑来说是RAM(内存)。
程序占用FLASH 大小为:6680字节(6332+348),所用的 SRAM 大小为:1824 个字节(84+1740)
栈区-变量地址
未初始化局部int型变量i1 :0x2000071c
未初始化局部int型变量i2 :0x20000718
未初始化局部char型指针变量p :0x20000714
已初始化局部char型数组str :0x20000708
堆区-动态申请地址
已初始化局部int型指针变量p1 :0x20000128
已初始化局部int型指针变量p2 :0x20000130
.bss段地址
未初始化全局int型变量k2 :0x20000004
未初始化静态全局int型变量k4 :0x2000000c
未初始化静态局部int型变量m2 :0x20000014
.data段地址
已初始化全局int型变量k1 :0x20000000
已初始化静态全局int型变量k3 :0x20000008
已初始化静态局部int型变量m1 :0x20000010
常量区地址
已初始化局部char型指针变量var1:0x08000230
已初始化局部char型指针变量var2:0x08000238
代码区地址
程序代码区main函数入口地址 :0x08000145
3.实验结果分析
- 从stm32串口输出的数据可以看出:
- stm32的代码区和常量区都分配在0x08000000储存地址(低地址)这一块,全局和静态变量、栈区和堆区的地址都分配在0x20000000储存地址(高地址)处。
- stm32的栈区地址分配是按照高地址到低地址的方式进行分配,而堆区是按照低地址到高地址的方式进行分配,其他的区域基本上都按照从低地址向高地址的方式进行内存分配。
- Keil软件里的芯片地址
从keil软件的option中可以看出关于STM32内存的起始地址和总大小。比如说,ROM的起始地址为0x8000000,大小为0x80000;RAM的起始地址为0x20000000,大小为0x10000。
(四)总结
通过在不同平台进行编程验证C程序编译后在内存的地址分配,我更加深入地了解了不同变量及代码在内存中地址分配的不同,同时对于不同平台的C程序的内存分配也有了更加清晰的认识。由于作者水平有限,文中难免有疏漏之处,希望读者批评指正。
举报
更多回帖