Ubuntu、stm32下的C程序中堆、栈、全局、局部等变量的分配地址

  • 一、总体介绍
    • 1.栈区(stack)
    • 2.堆区(heap)
    • 3.全局区(静态区)
      • (1).bss段
      • (2).data段
    • 4.常量区
    • 5.代码区
    • 6.RAM和ROM、Flash Memory的物理特性
      • (1)RAM
      • (2)ROM
      • (3)Flash Memory
    • 7.不同数据的存放位置
    • 8.Keil 的Build Output窗口
  • 二、堆和栈的比较
    • 1.性能
    • 2.申请方式
    • 3.申请后系统的响应
    • 4.申请大小的限制
    • 5.申请效率的比较
    • 6.堆和栈中的存储内容
    • 7.存取效率的比较
  • 三、实验过程
    • 1.Ubuntu(x86)系统中编程验证
      • 1.代码
      • 2.Ubuntu下运行
    • 2.Keil下验证
      • 1.keil 环境下默认的内存配置说明
      • 2.代码
      • 3.编译
      • 4.烧录后结果展示
      • 5.对比分析
  • 参考博客

一、总体介绍

在一个STM32程序代码中,从内存高地址到内存低地址,依次分布着栈区、堆区、全局区(静态区)、常量去、代码区,其中全局区中高地址分布着.bss段,低地址分布着.data段。总的分布如下图所示

下面分别对每一个区做详细的介绍。

1.栈区(stack)

  • 临时创建的局部变量存放在栈区。
  • 函数调用时,其入口参数存放在栈区。
  • 函数返回时,其返回值存放在栈区。
  • const定义的局部变量存放在栈区。

2.堆区(heap)

  • 堆区用于存放程序运行中被动态分布的内存段,可增可减。
  • 可以有malloc等函数实现动态分布内存。
  • 有malloc函数分布的内存,必须用free进行内存释放,否则会造成内存泄漏。

3.全局区(静态区)

全局区有.bss段和.data段组成,可读可写。

(1).bss段

  • 未初始化的全局变量存放在.bss段。
  • 初始化为0的全局变量和初始化为0的静态变量存放在.bss段。
  • .bss段不占用可执行文件空间,其内容有操作系统初始化。

(2).data段

  • 已经初始化的全局变量存放在.data段。
  • 静态变量存放在.data段。
  • .data段占用可执行文件空间,其内容有程序初始化。
  • const定义的全局变量存放在.rodata段。

4.常量区

  • 字符串存放在常量区。
  • 常量区的内容不可以被修改。

5.代码区

  • 程序执行代码存放在代码区。
  • 字符串常量也有可能存放在代码区。

6.RAM和ROM、Flash Memory的物理特性

(1)RAM

RAM又称随机存取存储器,存储的内容可通过指令随机读写访问。RAM中的存储的数据在掉电是是会丢失,因而只能在开机运行时存储数据。其中RAM又可以分为两种,一种是Dynamic RAM(DRAM动态随机存储器),另一种是Static RAM(SRAM,静态随机存储器)。

(2)ROM

ROM又称只读存储器,只能从里面读出数据而不能任意写入数据。ROM与RAM相比,具有价格高,容量小的缺点。但由于其具有掉电后数据可保持不变的优点,因此常用也存放一次性写入的程序和数据,比如主版的BIOS程序的芯片就是ROM存储器。

(3)Flash Memory

由于ROM具有不易更改的特性,后面就发展了Flash Memory。Flash Memory不仅具有ROM掉电不丢失数据的特点,又可以在需要的时候对数据进行更改,不过价格比ROM要高。

7.不同数据的存放位置

由前面的分析我们知道,代码区和常量区的内容是不允许被修改的,ROM(STM32就是Flash Memory)也是不允许被修改的,所以代码区和常量区的内容编译后存储在ROM中。

而栈、堆、全局区(.bss段、.data段)都是存放在RAM中。

至此,关于不同数据存放哪个区域已经全部介绍完了。下面还将介绍一下Keil 的Build Output窗口。

8.Keil 的Build Output窗口


如上图,存在Code、RO-data、RW-data、ZI-data四个代码段大小。

其中Code就是代码占用大小,RO-data是只读常量、RW-data是已初始化的可读可写变量,ZI-data是未初始化的可读可写变量。

有些时候,我们需要知道RAM和ROM的使用情况如何,那么我们就可以使用下面的公式计算。

RAM = RW-data + ZI-data

ROM = Code + RO-data + RW-data
这个是 MDK 编译之后能够得到的每个段的大小,也就能得到占用相应的FLASH和RAM的大小,但是还有两个数据段也会占用RAM,但是是在程序运行的时候,才会占用,那就是堆和栈。

在stm32的启动文件.s文件里面,就有堆栈的设置,其实这个堆栈的内存占用就是在上面RAM分配给RW-data+ZI-data之后的地址开始分配的。

堆 是编译器调用动态内存分配的内存区域;

栈 是程序运行的时候局部变量的地方,所以局部变量用数组太大了都有可能造成栈溢出。

堆栈的大小在编译器编译之后是不知道的,只有运行的时候才知道,所以需要注意不要造成堆栈溢出,会出现 hardfault 问题。

二、堆和栈的比较

堆栈是内存中的一个连续的块。一个叫堆栈指针的寄存器(SP)指向堆栈的栈顶。堆栈的底部是一个固定地址。堆栈有一个特点就是,后进先出。也就是说,后放入的数据第一个取出。它支持两个操作,PUSH和POP。PUSH是将数据放到栈的顶端,POP是将栈顶的数据取出。
在高级语言中,程序函数调用、函数中的临时变量都用到堆栈。为什么呢?因为在调用一个函数时,我们需要对当前的操作进行保护,也为了函数执行后,程序可以正确的找到地方继续执行,所以参数的传递和返回值也用到了堆栈。通常对局部变量的引用是通过给出它们对SP的偏移量来实现的。另外还有一个基址指针(FP,在Intel芯片中是BP),许多编译器实际上是用它来引用本地变量和参数的。通常,参数的相对FP的偏移是正的,局部变量是负的。
当程序中发生函数调用时,计算机做如下操作:首先把参数压入堆栈;然后保存指令寄存器(IP)中的内容,做为返回地址(RET);第三个放入堆栈的是基址寄存器(FP);然后把当前的栈指针(SP)拷贝到FP,做为新的基地址;最后为本地变量留出一定空间,把SP减去适当的数值。

在函数体中定义的变量通常是在栈上,用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上。在所有函数体外定义的是全局量,加了static修饰符后不管在哪里都存放在全局区(静态区),在所有函数体外定义的static变量表示在该文件中有效,不能extern到别的文件用;在函数体内定义的static表示只在该函数体内有效。另外,函数中的"adgfdf"这样的字符串存放在常量区。

对比:

1.性能

栈:栈存在于RAM中。栈是动态的,它的存储速度是第二快的。stack
堆:堆位于RAM中,是一个通用的内存池。所有的对象都存储在堆中。heap

2.申请方式

stack【栈】: 由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间 。
heap【堆】: 需要程序员自己申请,并指明大小,在c中malloc函数 如p1 = (char *)malloc(10); 在C++中用new运算符 如p2 = (char *)malloc(10); 但是注意:p1、p2本身是在栈中的。

3.申请后系统的响应

栈【stack】:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。

堆【heap】:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序;另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

4.申请大小的限制

栈【stack】:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

堆【heap】:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

5.申请效率的比较

栈【stack】:由系统自动分配,速度较快。但程序员是无法控制的。

堆【heap】:是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。

6.堆和栈中的存储内容

栈【stack】:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。

当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
堆【heap】:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排

7.存取效率的比较

char s1[] = “aaaaaaaaaaaaaaa”;
char *s2 = “bbbbbbbbbbbbbbbbb”;
aaaaaaaaaaa是在运行时刻赋值的; 而bbbbbbbbbbb是在编译时就确定的; 但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
比如:
#include
void main()
{
char a = 1;
char c[] = “1234567890”;
char *p =“1234567890”;
a = c[1];
a = p[1];
return;
}
对应的汇编代码
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了。

三、实验过程

1.Ubuntu(x86)系统中编程验证

1.代码

#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] = "yaoyao";//栈//定义常量字符串char *var1 = "1234567890";char *var2 = "abcdefghij";//动态分配——堆区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;
}

2.Ubuntu下运行



可以发现,Ubuntu在栈区和堆区的地址值都是从上到下增长的。

2.Keil下验证

1.keil 环境下默认的内存配置说明

① 默认分配的ROM区域是0x8000000开始,大小是0x80000的一片区域,那么这篇区域是只读区域,不可修改,也就是存放的代码区和常量区
② 默认分配的RAM区域是0x20000000开始,大小是0x10000的一片区域,这篇区域是可读写区域,存放的是静态区、栈区和堆区。

2.代码

在工程中的text.c文件复制下面代码,工程我会放在文后。

#include "sys.h"
#include "usart.h"
#include "delay.h"
#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(void)
{                u16 t; u16 len; u16 times=0;Stm32_Clock_Init(9);  //??????delay_init(72);         //?????uart_init(72,115200);    //??????115200while(1){//定义局部变量int a=2;static int inits_local_c=2, uninits_local_c;int init_local_d = 1;output(a);char *p;char str[10] = "yaoyao";//定义常量字符串char *var1 = "1234567890";char *var2 = "abcdefghij";//动态分配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;}
} 

需要添加串口初始化函数,参考以前的博客

点击魔法棒,点击c/c++,勾选c99 mode

点击target,勾选use microlib
点击OK

3.编译

4.烧录后结果展示

5.对比分析

一般而言,程序内变量在堆栈上的分配,栈是由高地址到低地址,堆是由低地址到高地址。

在Ubuntu下,栈区的地址存储是向上增长,堆区的地址存储也是向上增长;
在STM32下,栈区的地址存储是向下增长,堆区的地址存储却是向上增长。

可是为什么Ubuntu下,栈区的地址值也是增长的?
查找了很多资料发现,大部分提到linux的栈,地址都是向下生长的,不过,也有以下解释:

第一种解释:
栈向低地址扩展(即”向下生长”),是连续的内存区域;堆向高地址扩展(即”向上生长”),是不连续的内存区域。

这是由于系统用链表来存储空闲内存地址,自然不连续,而链表从低地址向高地址遍历。

第二种解释:(暂时放到下面,还需要花时间理解分析这一点)

进程地址空间的分布取决于操作系统,栈向什么方向增长取决于操作系统与CPU的组合。
这里说的“栈”是函数调用栈,是以“栈帧”(stack frame)为单位的。
每一次函数调用会在栈上分配一个新的栈帧,在这次函数调用结束时释放其空间。
被调用函数(callee)的栈帧相对调用函数(caller)的栈帧的位置反映了栈的增长方向:如果被调用函数的栈帧比调用函数的在更低的地址,那么栈就是向下增长;反之则是向上增长。

在一个栈帧内,局部变量是如何分布到栈帧里的(所谓栈帧布局,stack frame layout),这完全是编译器的自由。

在简化的32位Linux/x86进程地址空间模型里,(主线程的)栈空间确实比堆空间的地址要高——它已经占据了用户态地址空间的最高可分配的区域,并且向下(向低地址)增长。

keil工程:
链接:https://pan.baidu.com/s/1aCkk2s845sZ2lWKVGYrDVg
提取码:qwer

参考博客

https://blog.csdn.net/lin_duo/article/details/103019390
https://blog.csdn.net/jamestaosh/article/details/4513188
https://blog.csdn.net/qq_43279579/article/details/110308101
https://blog.csdn.net/qq_46467126/article/details/121875496

【嵌入式】Ubuntu、stm32下的C程序中堆、栈、全局、局部等变量的分配地址相关推荐

  1. Ubuntu、stm32下的C程序中堆、栈、全局、局部等变量的重温

    一.C程序的内存分配 1.栈区(stack) 由编译器自动分配释放,存放函数的参数值,局部变量的值等.其操作方式类似于数据结构中的栈. 2.堆区(heap) 一般由程序员分配释放,若程序员不释放,程序 ...

  2. STM32对SD卡数据读取和在Ubuntu、stm32下的C程序中堆、栈、全局、局部等变量的分配地址的对比分析

    一.SD卡协议原理 1.SD卡简介 SD存储卡是一种基于半导体快闪记忆器的新一代记忆设备,由于它体积小.数据传输速度快.可热插拔等优良的特性,被广泛地于便携式装置上使用,例如数码相机.平板电脑和多媒体 ...

  3. Ubuntu、stm32下的C程序中堆、栈、全局、局部等变量的分配地址

    目录 一.C程序中的一些变量及内存分配 1.全局变量 2.局部变量 3.内存分配 4.内存段 5.内存管理 二.在Ubuntu和Keil中显示变量地址分配 1.Ubuntu中运行 代码撰写 运行结果 ...

  4. Ubuntu、stm32下的C程序各变量的分配地址分析

    文章目录 一.C程序的内存分配 1. 栈区(stack) 2. 堆区(heap) 3. 全局区(静态区) 3.1 .bss段 3.2 .data段 4. 常量区 5. 代码区 二. 栈区.堆区等区存放 ...

  5. 在Ubuntu系统下编写简单程序

    在Ubuntu系统下编写简单程序 一.使用gcc命令行方式编译程序 1.编写C语言程序 编写主程序 vi main.c 编写main1.c #include<stdio.h> #inclu ...

  6. ubuntu、stm32下的C程序的内存分配

    文章目录 一.c程序的内存分配 二.内存分配方式 1.从静态存储区分配 2.在栈上创建 3.从堆上分配 三.全局变量与局部变量 1.全局变量 2.局部变量 3.全局变量与局部变量的区别 四.堆和栈 1 ...

  7. linux文件损坏怎么修复工具,在Ubuntu操作系统下修复损坏程序包的三种办法

    如果在 Ubuntu 操作系统下出现损坏的程序包,通常有三种办法可以修复它们,分别是:使用 apt 或 apt-get.使用 dpkg 及解除 dpkg 锁,下面为你一一介绍. 背景 apt 是 Ub ...

  8. cmake重新编译matlab,ubuntu系统下cmake 编译matlab中mex文件

    cmake 编译工程具有独特的优势,特别对于复杂的工程更是如此.利用matlab直接编译mex文件时,对于文件数据多的工程时,需要列举所有文件,并且要按照依赖关系排 cmake 编译工程具有独特的优势 ...

  9. c语言全局变量结构怎么定义,C语言中如何定义全局结构体变量

    匿名用户 1级 2014-12-29 回答 结构的定义 用户定义自己所需要的结构型,可以采用下列定义语句: 1 2 3 4 5 6 7 struct结构型名 { 数据类型符1 成员名1: 数据类型符2 ...

最新文章

  1. 重磅!分布式数据库解决方案Apache ShardingSphere毕业成为顶级项目
  2. [Linux]从控制台一次读取一个字符,无需等待回车键
  3. JAVA并发编程学习笔记------FutureTask
  4. android热修复原理底层替换,Android 热修复 - 各框架原理学习及对比
  5. phpMyAdmin 安装错误解决方法
  6. MySQL—设置数据库(库、表等)不区分大小写
  7. Netty的EventLoop
  8. linux 查找文件 locate,linux文件查找(find,locate)
  9. linux内存管理(六)-伙伴分配器
  10. 鸿蒙系统当贝市场,鸿蒙os2.0系统怎么安装?ota即可!能与当贝d3x投影仪大屏玩?...
  11. c语言转意字符 s,第2章 C语言初探:12、C语言转义字符
  12. Android 之度量单位px,dp,dip,sp,in,mm详解
  13. xp计算机用户名和密码忘记了怎么办,XP系统开机密码忘记了怎么办?
  14. ddwrt 扩张linux分区,FON2405e在引进自定义固件OpenWRTDDWRT.doc
  15. 4k纸是几厘米乘几厘米_4k素描纸是多大? 是几乘几的?
  16. 计算机中分页符号是什么,分节符和分页符有什么区别?
  17. 实现权重抽奖算法(java)
  18. Android获取系统邮件账号
  19. python 输出纯音频_提取视频中的音频python三行程序搞定
  20. PC上网页端屏蔽知乎上的视频

热门文章

  1. 微信 html 选不上文件,微信内网页某些安卓手机不能上传图片文件的问题
  2. Android的几种布局方式
  3. 仙剑考卷 (测试一下你迷仙剑的程度有多深)
  4. 小学课后兴趣班选课平台的设计与实现(ASP.NET,SQLServer)
  5. 王者荣耀专区系统服务器繁忙,王者荣耀转区系统预上线,狂暴弱化削弱,钻石消耗回归!...
  6. python html转TXT python读取html指定区域文本内容转成txt文件
  7. javascript DOM艺术
  8. 借助iMazing工具重新安装或升级 iOS系统
  9. C语言与汉语结构类比理解入门之指针
  10. 基于java的网上宠物销售商城