CODE,RO-DATA,RW-DATA,ZI-DATA+单片机程序执行流程+堆(heap)和栈(stack)==精华详解

最近一直在研究单片机比较核心的东西,像题目中说的这些,于是在网络上找了好多资料,看了好多博文、论坛,再配合自己的实验,现在终于搞得差不多了,网上各位大侠的博文给了我不少帮助,在此先感谢各位大侠^_^


一.CODE,RO-DATA,RW-DATA,ZI-DATA

    CODE:顾名思义,就是代码,指程序中代码即函数体的大小,注意程序中未使用的函数也会算在CODE中,也即会占用FLASH空间,因此不用的函数最好删除掉,以免占用过多FLASH空间;

    RO-DATA:RO就是只读的意思,程序中只读的变量(也就是带Const的)和已初始化的字符串等

    RW-DATA:特指已初始化的可读可写全局/静态变量

    ZI-DATA:未初始化的可读可写全局/静态变量,注意初始化为0也算做未初始化

    CODE+RO-DATA+RW-DATA=程序大小=程序占用的FLASH空间=BIN文件大小,为什么ZI-DATA不算?因为对于未初始化的变量BIN文件里面只需要两句话描述一下就可以了,大概就是记录下ZI-DATA的大小就好,程序运行时直接在RAM中开辟一个相应大小的空间;

    RW-DATA+ZI-DATA=程序所有全局变量的大小=程序固定占用的RAM大小,我看的其中一篇博文中说RW-DATA+ZI-DATA就是程序总共会占用的RAM大小,实则这只是程序固定占用的RAM大小,非固定的是啥呢?比如说函数体里面的局部变量,局部变量是会另外占用空间的。


二.单片机程序执行流程

    1.在RAM中开辟一个RW-DATA大小的空间,并从FLASH中将对应的各个变量的值读取入+赋值

    2.接着上一个开辟的空间的末尾再开辟一个ZI-DATA大小的空间,全部初始化为0

    3.根据PC指针从“FLASH"中读取CODE(指令),并执行,程序开始运转

    注意:对于计算机来说CODE是会加载到内存中的,但对于单片机是在FLASH中的不占用内存,这一点很好测试:你DEBUG一个程序,你会发现PC指针指向的是0x08000000以后的地址的(当你的BOOT设置是从FLASH启动时);其实对于像STM32这种32位的单片机来说其内存最大大小为4GB,flash的地址空间0x08000000~0x0807ffff,在4GB的范围之内,所以STM32对flash的访问也就相当于对其内存的访问,只不过在硬件上这段地址对应的是可读可写断电保存的flash;对寄存器的访问同理。


三.堆和栈

    对于堆和栈,比较统一的说法是:堆是用来存放全局/静态变量的,而栈是用来存放局部变量的;

    而根据测试结果来看,感觉堆是没有什么存在的意义的,因为无论你堆取得大或者小,全局/静态变量都是从内存首地址0x20000000开始占位的,向下生长,甚至你取成0也是这样,并不会影响程序执行;而栈的设置是有影响的,它影响的就是局部变量的首地址。

    值得一提的是,堆是向下(高地址)生长的,而栈是向上生长的,这样若栈的空间设置的不够大的话不是会倒置堆和栈重叠吗(应该就是所谓的堆栈溢出)?的确是这样,所以若要防止堆栈溢出,就要合理的设置栈的空间,如何设置才算合理呢?我的想法是这样的:分析程序,大概知道下程序最大可能会有多少Byte的局部变量存在,那么这个就是你的栈的最小大小;然后计算出全局/静态变量的大小,也就是RW-DATA+ZI-DATA,此即堆的大小,如果是12KB则表示堆空间的末地址在12KB处;最后栈的首地址就是堆的大小加上栈需要的空间大小。

    其实,简单的测试下你会发现,即便把栈的首地址设置成0或者让局部变量的大小明显超过栈的大小程序很多时候也能正常运行(有些时候是会卡死的),那是因为编译器并不会完全按照你代码里的设置去设置栈的首地址,而是会结合实际代码对栈空间的需求去设置,看到这肯定有人会问:既然这样那还需要我们手动去设置栈的大小吗?我的想法是这样:对于简单的程序编译器应该能合理的设置好栈,但是当程序复杂的时候,涉及到的函数体、局部变量很多,函数体的循环嵌套调用很多的时候编译器就很难正确的设置好栈了,毕竟栈的大小不是简单的计算下所有局部变量的大小,而是要根据程序执行逻辑推理得出同一时间程序会占用的最大内存空间,我觉得编译器是很难做到这么智能的,这个操作只能由熟悉此代码的我们来操作。


四.测试方式/流程

    这里才是重点,前面说了很多但是并没有把我所有测试所得都归纳完整,下面我把我部分测试过程写出来,大家可以自行评估总结下:

    首先我们最好有一个还没有任何全局/静态变量的工程(stm32的),原以为这个会比较难找,没想到随便找了个工程模板打开看了下居然就是还完全没有全局/静态变量的,所有C文件里面都没有定义任何全局变量,所有函数体里面也没有定义任何静态变量,不知你们的工程模板是怎样的,如果需要这么个工程的话可以邮件:[email protected]

    1.定义一个全局变量,使用printf打印出它的内存地址,发现就是正正好的0x20000000,说明全局/静态变量就是从内存首地址开始的

    2.再定义一个全局变量,将这两个变量的地址都打印出来(都是无符号8位的变量),发现其地址就是0x20000001,说明堆是向下生长的

    3.定义一个只读变量,打印出其地址,发现其地址是以0x08000000开头的,也就是处于flash区域,说明RO-DATA的确不占用RAM空间

    4.定义一个局部变量,打印出其地址,发现是以0x20000000开头的,并且就在我们定义的栈首地址附近(之所以是附近而不是起始是因为还存在其它局部变量),说明局部变量是处于栈区的,并且我们定义的栈首地址对实际的栈地址是有影响的

    5.定义一个静态变量,打印出其地址,发现为0x20000002,说明静态变量是处于堆区的

    6.再定义一个局部变量,打印出其地址,发现其地址比之前定义的局部变量地址小,说明栈是向上生长的

    7.将栈的大小设置为100字节,并定义一个200字节大小的数组(全局),打印出数组某些元素的地址(这边打印地址并不是为了看其地址,而是为了在程序里面有调用该数组,防止被编译器忽略),发现程序能够正常运行,并且栈首地址向后偏移了,说明编译器自动为我们设置好了栈首地址

    8.栈首地址不变,将数组大小设置为5,再定义一个200字节大小的数组(局部),打印出某些元素的地址(也是为了防止被编译器忽略),发现程序在执行到该局部数组所在的函数体后卡死了(之前的能够正常打印出来),说明在这种情况下编译器并没有合理设置好栈地址

    9.其他不变,将全局数组大小设置为100,再编译运行程序,发现程序能够正常运行,说明在加大了全局变量数量后触发了编译器将栈首地址往后移

    10.设置让编译器生成bin文件,然后编译,将CODE,RO-DATA,RW-DATA相加,发现其大小正好与bin文件的大小一模一样,一字节不差

    11.将Heap_size设置为0,编译运行,发现程序能够正常运行,说明堆的大小并不影响程序运行

    12.定义一个函数,函数里面的内容任意,但是程序里面并不调用该函数,编译发现CODE变大了,说明未使用的函数也是会占用flash空间的


    以上若有说得不对的地方,欢迎指正!


猜你喜欢

转载自blog.csdn.net/u011764302/article/details/80863221