文档章节

IAR中使用堆和栈的问题——Working with the Stack and Heap

Jr小王子
 Jr小王子
发布于 2015/05/07 12:01
字数 2100
阅读 132
收藏 0

英文版PDF地址: http://www.iar.com/Global/Resources/Seminars/Working_with_the_Stack_and_Heap.pdf

——————————————  以下为我翻译的  ——————————————————–

议题
heap(堆)是什么?

怎样决定堆的大小

使用堆时潜在的问题

堆分配注意事项

使用栈(Stack)

决定栈的大小

使用栈时的潜在问题

静态栈检查

Embedded Workbench中的栈插件工具

示例

堆是什么?

堆是内存空间里为动态内存分配保留的一部分区域

当一个应用需要临时使用一定数量的内存时可以从堆空间分配或借用,C中通过调用malloc()函数实现,C++中通过’new’来实现

当这个内存不再需要时可以通过调用free()函数或者使用delet操作符来释放。一旦该内存被释放,可以再次分配使用。

堆的位置和大小是在编译时静态设置的。

为你的应用分配合适的堆空间很重要,否则在运行时可能崩溃。

 

怎样决定堆大小

考虑应用使用的动态内存时的首要问题是我需要多少堆空间?

    相比来说这更像是估计而不是计算

    如果你知道你的应用怎样行动,那么你应该大概知道每个时刻应该分配多少内存

另外需要考虑在管理堆空间是有一些额外的开销

    堆管理器需要跟踪已经使用的和剩余的空间数量,已经分配了的块大小,通常还包含一个指向下一个可用内存的指针

    另外,开发工具可能为了维护内存体系结构保留一定的内存块。譬如,EWARM编译器通常为保证栈对齐而保留8字节倍数的内存块。

 

潜在的问题

当不同规模的内存块频繁分配、释放时,动态内存分配是最常发生的问题随之而来。

    当内存释放时,就会有一个空位

    如果下一个要分配的空间比所有的空位都要大,这就会是个问题

    这为debugging带来困难,因为堆上总的空闲内存空间比要分配的要大,但是内存申请可鞥因为空闲空间不连续而失败。

    例如,假设如下:

        堆的位置为0x20000-0x20FFF

        一个8字节的内存块分布在该区间的开始位置

        紧接着是一个1024字节的块

    之后,第一个块被释放并可以再次使用。然而,应用需要分配大于8字节的空间,所以开始 的8字节空间就无法使用。这就是内存碎片化,如果应用用于不在需要8字节或更少的内存空间,那么这8字节的内存空间就浪费了。这个例子也说明了为什么堆中的小内存块是低效的,这种开销等于应用可用数据的数量。

 

堆的布局

    有些情况下确切的知道堆上有些什么,位于什么位置是非常有用的。

    堆上包含的不只是分派的数据:

        有一些维护堆的额外开销

        每一个分配的内存块都包含两个整数,指针的大小取决于CPU的架构,e.g. ARM器件会使用32bit的整数。

            第一个整数指明分派的内存块的大小。

            最后一个堆对象后会有一个32bit的值来说明剩余的堆空间。

            最后一个堆对象开始位置的32bit值说明这个数据的地址。

    堆同时跟踪下一个分配的位置,使用一个驻留在堆外部的结构体中。

        typedef struct {

        __data_Cell __data * __data *_Plast;

        __data_Cell __data *_Head; }

        __data_Altab;

    指针_Head说明堆的当前状态。指针_Plast说明从哪里开始搜索目前未使用的可用块。

    __data_Cell结构体定义如下:

    typedef struct __data_Cell

    

        __data_size_t _Size; 

        struct __data_Cell __data *_Next; 

    } __data_Cell;

    其中_Size 说明堆的剩余数量, _Next 说明下一个可用分配的内存地址。如果堆空间耗尽,_Next指针则为NULL。

堆的最终思考

如果没有一些工具辅助你分析动态内存需求,直接估计堆空间是比较困难的。

        在桌面Java中有这样的工具(HAT,Heap Analysis Tool)

        嵌入式C/C++中还没有类似工具

针对资源有限的嵌入式系统,由于额外的开销和可能的堆内存碎片化,动态内存应该尽量少用。

        在你的代码中(测试用例中)你可以创建多个结构体或者对象

        MISRA C要求所有内存都必须在编译时静态分配,因此你永远也不会跑出堆空间。

EWARM中的堆统计

EWARM中的C-Spy debugger让你可以看到当前堆的使用情况

要看到这个功能,必须在工程中包含 dlmalloc.c

调用 __iar_dlmallinfo() 和 __iar_dlmalloc_stats() 会在Terminal I/O窗口打印堆统计情况。

更多信息请访问:http://supp.iar.com/Support/?note=28545

使用栈(Stack)

栈的概念相对来说比较容易理解

栈是一个LIFO街头,类似于打包东西的盒子

    最后放入盒子的是第一个从盒子里移出的物体

    同时,栈也有固定的大小,不能溢出

栈时一个固定的内存块,被分为两部分:

    分配给当前函数的内存和分配给调用当前函数的函数的内存

    可以用于分配的空闲内存

两个区域的分界线称为栈顶,使用栈指针SP表示,这是一个专用的处理器寄存器。

通过移动栈指针来分配栈内存。

函数永远不应该引用栈空间的内存(变量)——如果一个中断发生,中断服务函数会:

    申请栈内存

    更新内存

    释放内存

上述情况发生时,函数从来不知道自己的内存以及被破坏。

栈的主要优势是程序中不同函数可以使用相同的内存区域存放他们的数据。

与堆空间不同,栈空间永远不会碎片化或者因为内存泄露而一团糟。

可以允许函数调用自身——递归函数——每个调用都能在栈空间存储自身的数据。

决定栈大小

为了决定栈的大小,我们必须理解什么东西会放到栈空间:

    auto变量,即没有放到寄存器的局部临时变量和参数

    表达式的临时结果

    函数的返回值(除非passed in a register)

    中断时的处理器状态

    函数返回前要恢复的处理器寄存器内容

正如你所见,栈的大小主要取决于你代码中的函数调用数量。

使用栈时的潜在问题

1.栈的工作方式决定了函数返回后他的数据就不存在了。下面的代码演示了一个常见的编码错误。它返回了一个指向函数内部变量的指针。

int *MyFunction()

{

int x;

/* Do something here. */

return &x; /* Incorrect */

}

2.另一个问题是栈溢出。该情况会在函数调用时发生,多层函数调用消耗的栈空间大于栈的总大小。

3.如果栈空间存放大数据对象,则这种风险更高,或者递归函数调用时。

4.如果给定太大的栈空间,RAM就浪费了,如果给定的栈空间太小,会出现两种情况(取决于栈在内存空间的位置):

    a 变量会被改写,导致未定义的行为

    b 栈会超出内存空间,导致应用的异常终止

因为第二种选择比较用于检测到,你应该考虑将栈设置为朝内存重点增长。

静态栈检查器

有很多静态的C语言检查器

    Express Logic’s StackX

    Abslnt’s StackAnalyzer

    John Regher’s Stack Analysis(for AVR/430 only)

    AdaCore’s GNATStack

Embedded Workbench’s Stack Plug-in

EW包含一个易用的栈插件,可以监控CSTACK的大小,同时如果你超出指定的栈门限将输出消息到debug日志。

警告:该插件对于RTOS是无效的,使用RTOS时他始终汇报堆栈溢出。

本文转载自:http://www.elecbench.com/?p=1349

Jr小王子
粉丝 11
博文 119
码字总数 18368
作品 0
深圳
程序员
私信 提问
IAR DLIB Library heap usage statistics IAR heap 分析

翻译自 IAR Technical Note 28545 《IAR DLIB Library heap usage statistics》 update 2017/9/22 介绍 关于堆的描述在《IAR C/C++ Development Guide for ARM》的 Dynamic memory on the he......

u011303443
2018/01/03
0
0
大神教你JVM运行原理及Stack和Heap的实现过程

Java语言写的源程序通过Java编译器,编译成与平台无关的‘字节码程序’(.class文件,也就是0,1二进制程序),然后在OS之上的Java解释器中解释执行,而JVM是java的核心和基础,在java编译器和...

问题终结者
01/07
61
0
JVM内存区域与多线程

Java并发的机制的背后是Java虚拟机(JVM)的工作机制,本文从几个关于并发和多线程的疑问开始,引出Java内存区域的介绍,希望能帮助大家更好的理解Java并发机制。 1. 线程创建和切换的代价—...

登高且赋
2017/12/08
0
0
如何确定一个 Go 变量会被分配在哪里?

原文链接:http://russellluo.com/2019/07/how-to-confirm-where-a-go-variable-will-be-allocated.html...

Go中国
07/23
0
0
IAR调试使用技巧:数据断点、CallStack、设置堆栈、查看栈使用率和栈深度、Memory、Set Next Statement、编译文件含义等

目录 使用数据断点 Set next statement 手动执行到某行代码 设置堆栈大小 查看程序Stack栈使用情况,以及栈深度 Call stack查看当前栈空间 使用Memory查看程序运行中各种过程数据 IAR各编译文...

HowieXue
2018/05/20
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Mybatis Plus性能分析

一、配置 /** * 性能分析 * @return */@Bean@Profile({"dev","test"})public PerformanceInterceptor performanceInterceptor (){ PerformanceInterceptor performanceInterceptor......

一个yuanbeth
27分钟前
4
0
一次写shell脚本的经历记录——特殊字符惹的祸

本文首发于微信公众号“我的小碗汤”,扫码文末二维码即可关注,欢迎一起交流! redis在容器化的过程中,涉及到纵向扩pod实例cpu、内存以及redis实例的maxmemory值,statefulset管理的pod需要...

码农实战
30分钟前
4
0
为什么阿里巴巴Java开发手册中不建议在循环体中使用+进行字符串拼接?

之前在阅读《阿里巴巴Java开发手册》时,发现有一条是关于循环体中字符串拼接的建议,具体内容如下: 那么我们首先来用例子来看看在循环体中用 + 或者用 StringBuilder 进行字符串拼接的效率...

武培轩
43分钟前
6
0
队列-链式(c/c++实现)

队列是在线性表功能稍作修改形成的,在生活中排队是不能插队的吧,先排队先得到对待,慢来得排在最后面,这样来就形成了”先进先出“的队列。作用就是通过伟大的程序员来实现算法解决现实生活...

白客C
51分钟前
68
0
聊聊nacos的notifyConfigInfo

序 本文主要研究一下nacos的notifyConfigInfo CommunicationController nacos-1.1.3/config/src/main/java/com/alibaba/nacos/config/server/controller/CommunicationController.java @Cont......

go4it
昨天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部