0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

嵌入式全局变量的初始化原理详解

嵌入式软件实战派 来源: 嵌入式软件实战派 2023-10-27 10:15 次阅读

最近,有个好学的小伙子突然问了我一个问题:


全局变量的初始值,是在哪里赋值的?


问题虽然说不是很重要,但是我很好奇。

为了给讲清楚这个原理过程,我专门建立一个基于Renesas RH850的简单工程,挖一挖里面的技术细节。

我在main.c文件中定义了随便这几个变量

int counter, accumulator = 0, limit_value = 1000000;
unsigned char str_aa55[2] = {0xAA,0x55};
unsigned int int_1122334455667788 = 0x11223344;
unsigned int int_55667788 = 0x55667788;
int bss_val;
void main(void)
{
  
}



然后,直接仿真查看,跟你我想的一样,在main函数之前就初始化完成了,即这些变量都自动初始化赋值了。

让人好奇的是,它是怎么做到的?

单片机的启动程序一般都是很简单的,即使汇编也没多少行,直接翻出来看看也许会知道答案。
 -- Clear local RAM
 mov ___ghs_ramstart, r6 -- start of local RAM
 mov ___ghs_ramend, r7  -- end of local RAM
 mov r0, r1
1:
 st.dw  r0, 0[r6]
 addi  8, r6, r6
 cmp r7, r6
 bl 1b
 -- Jump to the HW initialisation function
 jarl ___lowinit, lp
 -- Jump to the initialisation functions of the library
 -- and from there to main()
 jr __start



以上这段汇编,根据旁边的注释其实很容易理解,前半部分就是将内存Local RAM初始化清零,即这段汇编可以见到梳理成

RAM清零--->执行___lowinit--->执行__start--->进入main函数

既然前面给RAM清零了,那么此时的全局变量应该全是0值吧,那可以推测,给全局变量赋初始值应该是在___lowinit或__start了,但是这两个东西是编译环境里某个库的,暂时看不到源码。

但是,最终通过仿真查看变量值的方式,可以定位,给全局变量赋初始值是在__start里面。

此时,虽然我知道了它在哪里给全局变量初始化了,但是并不知道是怎样初始化的。

我还是很好奇,本着刨根问底的精神继续挖掘。

但是我在这个RH850的代码工程里面是找不到这个__start的源码内容的,仿真看汇编折腾了半天,突然想了下,为啥不换个其他工程试试,例如试试NXP S32K的?
于是,我创建了一个NXP S32K1xx的代码工程,仍然定义这几个变量
int counter, accumulator = 0, limit_value = 1000000;
unsigned char str_aa55[2] = {0xAA,0x55};
unsigned int int_1122334455667788 = 0x11223344;
unsigned int int_55667788 = 0x55667788;
intbss_val;
同样的讨论,直接翻启动文件的汇编代码
  /* Init .data and .bss sections */
  ldr   r0,=init_data_bss
  blx   r0
还是欧美的芯片简单粗暴,不像小日子做的初始化还有藏进库里。这不是很明显嘛,init_data_bss就是初始化全局变量的,以下截取了部分代码,也很容易理解。
void init_data_bss(void)
{
    /*......*/
  /* Data */
  data_ram    = (uint8_t *)__DATA_RAM;
  data_rom    = (uint8_t *)__DATA_ROM;
  data_rom_end  = (uint8_t *)__DATA_END;
/*......*/
  /* BSS */
  bss_start    = (uint8_t *)__BSS_START;
  bss_end     = (uint8_t *)__BSS_END;
  /* ...... */
  
  /* Copy initialized data from ROM to RAM */
  while (data_rom_end != data_rom)
  {
    *data_ram = *data_rom;
    data_ram++;
    data_rom++;
  }
  
  /* ...... */
  /* Clear the zero-initialized data section */
  while(bss_end != bss_start)
  {
    *bss_start = 0;
    bss_start++;
  }
/*......*/
}




data段data_ram的初始化内容就是从data_rom来,而data_rom是从__DATA_ROM来。

那么,__DATA_ROM是什么东西,从哪里呢?

搜一搜工程里面的代码,很简单,这是从ld文件来

/* Specify the memory areas */
MEMORY
{
 /* … */
 /* SRAM_L */
  m_data  (RW) : ORIGIN = 0x1FFF8000, LENGTH = 0x00008000
m_data_2(RW):ORIGIN=0x20000000,LENGTH=0x00007000


 /* … */
  .data : AT(__DATA_ROM)
 {
  . = ALIGN(4);
  __DATA_RAM = .;
  __data_start__ = .;   /* Create a global symbol at data start. */
  *(.data)         /* .data sections */
  *(.data*)        /* .data* sections */
  . = ALIGN(4);
  __data_end__ = .;    /* Define a global symbol at data end. */
 } > m_data


 __DATA_END = __DATA_ROM + (__data_end__ - __data_start__);
 __CODE_ROM = __DATA_END; /* Symbol is used by code initialization. */




 /* Uninitialized data section. */
 .bss :
 {
  /* This is used by the startup in order to initialize the .bss section. */
  . = ALIGN(4);
  __BSS_START = .;
  __bss_start__ = .;
  *(.bss)
  *(.bss*)
  *(COMMON)
  . = ALIGN(4);
  __bss_end__ = .;
  __BSS_END = .;
 } > m_data_2




这里简单介绍下,带有初始化值(非0)全局变量(例如unsigned int int_55667788 = 0x55667788;),都是定义在data段的,而未定义初始化值的全局变量,是分在bss段的(例如intbss_val;)。


到底是不是我说的这样子,直接查看map文件中的变量名和对应地址或段名就知道了
.data      0x1fff8400   0x42c load address 0x000009cc
        0x1fff8400        . = ALIGN (0x4)
        0x1fff8400        __DATA_RAM = .
        0x1fff8400        __data_start__ = .
*(.data)
*(.data*)
.data.limit_value
        0x1fff8400    0x4 ./src/main.o
        0x1fff8400        limit_value
.data.str_aa55
        0x1fff8404    0x2 ./src/main.o
        0x1fff8404        str_aa55
*fill*     0x1fff8406    0x2 
.data.int_11223344
        0x1fff8408    0x4 ./src/main.o
        0x1fff8408        int_11223344
.data.int_55667788
        0x1fff840c    0x4 ./src/main.o
        0x1fff840c        int_55667788




.bss      0x20000000    0x28
        0x20000000        . = ALIGN (0x4)
        0x20000000        __BSS_START = .
        0x20000000        __bss_start__ = .
*(.bss)
*(.bss*)
.bss.accumulator
        0x2000001c    0x4 ./src/main.o
        0x2000001c        accumulator
*(COMMON)
COMMON     0x20000020    0x8 ./src/main.o
        0x20000020        bss_val
        0x20000024        counter
        0x20000028        . = ALIGN (0x4)
        0x20000028        __bss_end__ = .
0x20000028__BSS_END=.


0x000009cc__DATA_ROM=.


另外,从这map文件里也可以看到,这个__DATA_ROM对应的地址是0x000009cc,也就是说,这些
int limit_value = 1000000;
unsigned char str_aa55[2] = {0xAA,0x55};
unsigned int  int_1122334455667788 = 0x11223344;
unsigned int  int_55667788 = 0x55667788;


等等变量的的初始值是来源于0x000009cc这里。

那就直接查看生成的hex文件

f4fe89f4-746a-11ee-939d-92fbcf53809c.png



是不是很巧,是不是很妙?!

不过,还是很好奇,这是怎么做到的,怎么恰巧这些值就在这个地址呢?


秘密就在于ld文件里的这个语句:.data : AT(__DATA_ROM)
意思是,定义在data段的变量对应的初始化值,就放在__DATA_ROM中。就这么简单,剩下的交给编译器就行了。

这里顺便提一下,排查跟踪这些信息需要掌握编译链接相关知识,特别是链接脚本和生成的map内容,我之前也跟我身边的很多小伙伴讲过这些内容,但是我建议系统地看看这些内容。我之前是无意中从《程序员的自我修养》这本书学到的,最大的感受就是让我得到了启发,在往后的编程日子里更关注MCU地址、编译和链接方面的内容,对排查底层问题相当有帮助。



S32K1的工程很清楚了,但是RH850的呢?一根筋的程序员,还是想知道__start到底干了啥?它所在的库又是怎么链接进来的,是怎么初始化的?

对这个问题一直记在心里,是一个化不开的结。终于某一天,我居然真的无意中发现了__start的源码,感叹小日子真的有一手。

这里涉及到的内容比较多也比较复杂,后续再写个文章讲解下GreenHills编译器的启动链接库到底干了什么,敬请关注!


审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 单片机
    +关注

    关注

    6001

    文章

    43973

    浏览量

    620841
  • 嵌入式
    +关注

    关注

    4982

    文章

    18281

    浏览量

    288459
  • 仿真
    +关注

    关注

    50

    文章

    3872

    浏览量

    132161
  • 全局变量
    +关注

    关注

    1

    文章

    26

    浏览量

    8914
收藏 人收藏

    评论

    相关推荐

    嵌入式C编程经验:全局变量和常用工具软件【转】

    嵌入式C编程经验:全局变量和常用工具软件【转】
    发表于 07-31 13:17

    功能型全局变量的实现

    听说功能型全局变量是通过:只有一次循环加上为初始化的移位寄存器实现的。最近在读别人的程序,发现一个while循环中出现了4-5个为初始化的移位寄存器,(说一下这个循环中嵌套了一个多选择结构)一下子
    发表于 09-07 15:52

    关于全局变量和功能全局变量的创建和使用

    关于LabVIEW中的全局变量,很多教程和网上的资料都讲的很简单,功能全局变量讲得更少,对于新手来说,理解和使用还是比较困难的。因此今天来详细说一说他们的使用。这里就不说全局变量和功能全局变
    发表于 01-25 20:58

    谨慎使用局部变量全局变量

    取代连线板或用其访问顺序结构中每一帧中的数值,可能在VI中导致不可预期的行为。滥用局部变量全局变量,如用来避免程序框图间的过长连线或取代数据流,将会降低执行速度。 局部变量全局变量
    发表于 01-30 10:58

    请问STM32 C++类构造函数怎么修改才能实现全局变量初始化呢?

    全部为0)。 当把全局变量改为函数内部的临时变量时,是有效的。猜想是因为程序写到FLASH上去了,全局变量都为0,而不考虑对应类的构造函数。需要怎么修改才能实现全局变量
    发表于 10-10 08:45

    嵌入式C中,全局变量滥用的后果竟如此严重?

    全局变量作为一个嵌入式工程师肯定有前人提示过你不要滥用  就在之前丰田公司就出过这么一档子事儿某位软件工程师因使用超过10000 个全局变量在法庭上被“喷”是“一坨”代码  在工程实践中总共采用5
    发表于 04-09 09:24

    【原创分享】变量初始化技巧

    由于在嵌入式系统中必须考虑程序规模的问题,因此,对程序中的变量初始化也需要进行慎重的考虑。在C语言中,基本数据结构(字符型、整型)的初始化相对简单;数组、结构体属于C语言中的构造类型
    发表于 09-08 15:28

    RAM的局部静态变量全局变量消耗

    因为在连接时,全局变量和局部静态变量都是在data区,所以在这里给放一起讨论了。在原文中自己的表述犯了一个错误,就是初始化全局变量或者局部静态变量
    发表于 11-03 08:54

    qboot跳转APP,APP声明全局变量时无法初始化

    1、程序添加了kawaii_mqtt软件包2、声明全局变量时进行初始化,程序无法跳转3、声明全局变量时不初始化,在函数中初始化,则程序正常跳
    发表于 03-06 10:32

    static的全局变量与局部变量的使用,看完你就懂了

    函数体内声明的自动变量的值是随机的,除非它被显初始化,而在函数体外被声明的自动变量也会被初始化为 0); (3)静态
    发表于 06-27 08:54

    关于单片机全局变量初始化的问题

    变量分为局部与全局,局部变量又可称之为内部变量。由某对象或某个函数所创建的变量通常都是局部变量
    发表于 11-22 15:16 5068次阅读
    关于单片机<b class='flag-5'>全局变量</b><b class='flag-5'>初始化</b>的问题

    在51平台下初始化文件的引入导致全局变量无法初始化的问题如何解决

    本文档的主要内容详细介绍的是在51平台下初始化文件的引入导致全局变量无法初始化的问题如何解决。
    发表于 08-20 17:31 0次下载
    在51平台下<b class='flag-5'>初始化</b>文件的引入导致<b class='flag-5'>全局变量</b>无法<b class='flag-5'>初始化</b>的问题如何解决

    在C语言中,全局变量滥用的后果是什么

    01 啥是全局变量 说起全局变量,就不得不提到全局变量,局部变量,静态全局变量,静态局部变量,这
    发表于 06-24 17:14 1497次阅读

    鸿蒙内核源码:内核空间是怎么初始化的?

    data段 该段用于存储初始化全局变量初始化为0的全局变量出于编译优化的策略还是被保存在BSS段。
    的头像 发表于 04-26 14:43 1521次阅读
    鸿蒙内核源码:内核空间是怎么<b class='flag-5'>初始化</b>的?

    嵌入式C编程中全局变量问题分享

    嵌入式特别是单片机os-less的程序,最易范的错误是全局变量满天飞。这个现象在早期汇编转型过来的程序员以及初学者中常见,这帮家伙几乎把全局变量当作函数形参来用。
    发表于 07-17 16:53 520次阅读