目录
- 一、内存分区概念
-
- 1.c语言在内存中分区
- 2.动静态分配内存
- 3.内存分区详解
- 二、STM32上分区验证
-
- 1.代码编写
- 2.编译 烧录 输出结果
- 三、在Ubuntu中进行分区验证
- 四、关于分区的分析
- 五、总结
- 六、参考资料
一、内存分区概念
1.c语言在内存中分区
- 栈区(stack)
由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 - 堆区(heap)
一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收 。它与数据结构中的堆不同,分配方式类似于链表。 - 全局区(静态区)(static)
全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量、未初始化的静态变量在相邻的另一块区域。当程序结束后,变量由系统释放 。 - 文字常量区
存放常量字符串。当程序结束后,常量字符串由系统释放 。 - 程序代码区
存放函数体的二进制代码。
存储区图解
2.动静态分配内存
属性 | 描述 |
---|---|
静态分配内存 | 在程序编译和链接时就确定好的内存 |
动态分配内存 | 在程序加载、调入、执行的时候分配 / 回收的内存 |
3.内存分区详解
属性 | 描述 |
---|---|
.text | 也称为代码段(Code),用来存放程序执行代码,同时也可能会包含一些常量(如一些字符串常量等)。该段内存为静态分配,只读(某些架构可能允许修改)。 这块内存是共享的,当有多个相同进程(Process)存在时,共用同一个 text 段 |
.data | 也有的地方叫 GVAR (global value),用来存放程序中已经初始化的非零全局变量。静态分配。data又可分为读写(RW)区域和只读(RO)区域。RO 段保存常量所以也被称为 .constdata ;RW 段则是普通非常全局变量,静态变量就在其中 |
.bss | 存放程序中为初始化的和零值全局变量。静态分配。在程序开始时通常会被清零 |
注:
text 和 data 段都在可执行文件中,由系统从可执行文件中加载;而 bss 段不在可执行文件中,由系统初始化。 这三段内存就组成了我们编写的程序的本体,但是一个程序运行起来,还需要更多的数据和数据间的交互,否则这个程序就是死的,无用的。所以我们还需要为更多的数据和数据交互提供一块内存——堆栈。
堆和栈都属于动态分配内存,两者空间大小都是可变的。
属性 | 描述 |
---|---|
Stack:栈 | 存放 Automatic Variables ,按内存地址由高到低方向生长,其最大值由编译时确定,速度快,但自由性差,最大空间不大 |
Heap:堆 | 自由申请的空间,按内存地址由低到高方向生长,其大小由系统内存 / 虚拟内存上限决定,速度较慢,但自由性大,可用空间大。 |
对比内容 | 栈 | 堆 | 全局/静态存储区 | 常量存储区 |
---|---|---|---|---|
存储内容 | 局部变量 | 变量 | 全局变量、静态变量 | 常量 |
作用域 | 函数作用域、语句块作用域 | 函数作用域、语句块作用域 | ||
编译期间大小是否确定 | 是 | 否 | 是 | 是 |
大小 | 1MB | 4GB | ||
内存分配方式 | 地址由高向低减少 | 地址由低向高增加 | ||
内容是否可以修改 | 是 | 是 | 否 | 否 |
二、STM32上分区验证
说明
:本部分内容基于stm32f103c8tx芯片采用中断方式向上位机(win10)发送“hello windows”———USART串口通讯进行改进
1.代码编写
在串口通信所建的工程上修改代码
bsp_usart.h
文件:
添加头文件
#include <stdio.h>
#include <stdlib.h>
bsp_usart.c 文件:
添加一个 fputc
函数
int fputc(int ch, FILE *f)
{
USART_SendData(DEBUG_USARTx, (uint8_t)ch);while(USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);return (ch);
}
main.c 文件:
编写如下代码:
#include "stm32f10x.h"
#include "bsp_usart.h" //添加 bsp_usart.h 头文件int init_global_a = 1;
int uninit_global_a;
static int inits_global_b = 2;
static int uninits_global_b;void output(int a)
{
printf("hello");printf("%d",a);printf("\n");
}int main(void)
{
//定义局部变量int a=2;static int inits_local_c=2, uninits_local_c;int init_local_d = 1;char *p;char str[10] = "lyy";//定义常量字符串char *var1 = "1234567890";char *var2 = "qwertyuiop";//动态分配int *p1=malloc(4);int *p2=malloc(4);USART_Config();//串口初始化output(a);//释放free(p1);free(p2);printf("栈区-变量地址\n");printf(" a:%p\n", &a);printf(" init_local_d:%p\n", &init_local_d);printf(" p:%p\n", &p);printf(" str:%p\n", str);printf("\n堆区-动态申请地址\n");printf(" %p\n", p1);printf(" %p\n", p2);printf("\n全局区-全局变量和静态变量\n");printf("\n.bss段\n");printf("全局外部无初值 uninit_global_a:%p\n", &uninit_global_a);printf("静态外部无初值 uninits_global_b:%p\n", &uninits_global_b);printf("静态内部无初值 uninits_local_c:%p\n", &uninits_local_c);printf("\n.data段\n");printf("全局外部有初值 init_global_a:%p\n", &init_global_a);printf("静态外部有初值 inits_global_b:%p\n", &inits_global_b);printf("静态内部有初值 inits_local_c:%p\n", &inits_local_c);printf("\n文字常量区\n");printf("文字常量地址 :%p\n",var1);printf("文字常量地址 :%p\n",var2);printf("\n代码区\n");printf("程序区地址 :%p\n",&main);printf("函数地址 :%p\n",&output);return 0;
}
2.编译 烧录 输出结果
编译:
烧录
打开串口助手,打开串口后,按一下芯片上的RESET
,显示输出结果:
三、在Ubuntu中进行分区验证
使用命令创建一个hello.c
文件
gedit hello.c
在hello.c文件中输入代码
#include <stdio.h>
#include <stdlib.h>
//定义全局变量
int init_global_a = 1;
int uninit_global_a;
static int inits_global_b = 2;
static int uninits_global_b;
void output(int a)
{
printf("hello");printf("%d",a);printf("\n");
}int main( )
{
//定义局部变量int a=2;static int inits_local_c=2, uninits_local_c;int init_local_d = 1;output(a);char *p;char str[10] = "lyy";//定义常量字符串char *var1 = "1234567890";char *var2 = "qwertyuiop";//动态分配int *p1=malloc(4);int *p2=malloc(4);//释放free(p1);free(p2);printf("栈区-变量地址\n");printf(" a:%p\n", &a);printf(" init_local_d:%p\n", &init_local_d);printf(" p:%p\n", &p);printf(" str:%p\n", str);printf("\n堆区-动态申请地址\n");printf(" %p\n", p1);printf(" %p\n", p2);printf("\n全局区-全局变量和静态变量\n");printf("\n.bss段\n");printf("全局外部无初值 uninit_global_a:%p\n", &uninit_global_a);printf("静态外部无初值 uninits_global_b:%p\n", &uninits_global_b);printf("静态内部无初值 uninits_local_c:%p\n", &uninits_local_c);printf("\n.data段\n");printf("全局外部有初值 init_global_a:%p\n", &init_global_a);printf("静态外部有初值 inits_global_b:%p\n", &inits_global_b);printf("静态内部有初值 inits_local_c:%p\n", &inits_local_c);printf("\n文字常量区\n");printf("文字常量地址 :%p\n",var1);printf("文字常量地址 :%p\n",var2);printf("\n代码区\n");printf("程序区地址 :%p\n",&main);printf("函数地址 :%p\n",&output);return 0;
}
- 输入完成后保存并关闭文件
- 输入命令编译执行
gcc hello.c -o hello
./hello
输出结果
四、关于分区的分析
从stm32和Ubuntu都可以看出:从高地址到低地址分区情况
五、总结
该实验主要是对c程序内存分配的进一步认识,之前的了解基本都是基于一些代码表面上的一些简单的分析。内存中的主要部分包括程序段、数据段、堆栈三个部分。在不同系统下区域内的地址值变化是不相同的。
六、参考资料
https://blog.csdn.net/ssj925319/article/details/110727925?spm=1001.2014.3001.5501
https://blog.csdn.net/ssj925319/article/details/110727904
【IoT】STM32 内存分配详解