我搞了十几年嵌入式,才发现全局变量是这样初始化的

原创 嵌入式软件实战派 2023-10-27 08:27
最近,有个好学的小伙子突然问了我一个问题:
全局变量的初始值,是在哪里赋值的?
这个问题虽然说不是很重要,但是我很好奇。
为了给讲清楚这个原理过程,我专门建立一个基于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, r11:  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;int bss_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段的(例如int bss_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文件

是不是很巧,是不是很妙?!
不过,还是很好奇,这是怎么做到的,怎么恰巧这些值就在这个地址呢?
秘密就在于ld文件里的这个语句:.data : AT(__DATA_ROM)
意思是,定义在data段的变量对应的初始化值,就放在__DATA_ROM中。就这么简单,剩下的交给编译器就行了。
这里顺便提一下,排查跟踪这些信息需要掌握编译链接相关知识,特别是链接脚本和生成的map内容,我之前也跟我身边的很多小伙伴讲过这些内容,但是我建议系统地看看这些内容。我之前是无意中从《程序员的自我修养》这本书学到的,最大的感受就是让我得到了启发,在往后的编程日子里更关注MCU地址、编译和链接方面的内容,对排查底层问题相当有帮助。

S32K1的工程很清楚了,但是RH850的呢?一根筋的程序员,还是想知道__start到底干了啥?它所在的库又是怎么链接进来的,是怎么初始化的?
对这个问题一直记在心里,是一个化不开的结。终于某一天,我居然真的无意中发现了__start的源码,感叹小日子真的有一手。
这里涉及到的内容比较多也比较复杂,后续再写个文章讲解下GreenHills编译器的启动链接库到底干了什么,敬请关注!
果这个文章能对你有帮助,请点个赞👍🏻点个在看,感谢!
关注公众号“嵌入式软件实战派”,我给你分享更多面试知识和技巧。

如果你喜欢我的文章,请关注,并转发点赞在看,这是对我莫大的鼓励!

本文版权归本号所有,同时欢迎各路朋友转载转发。

嵌入式软件实战派 专注嵌入式软件开发领域知识传授,包括C语言精粹,RTOS原理与使用,MCU驱动开发,AUTOSAR搭建,软件架构方法设计等。
评论 (0)
  • 引言随着物联网和智能设备的快速发展,语音交互技术逐渐成为提升用户体验的核心功能之一。在此背景下,WT588E02B-8S语音芯片,凭借其创新的远程更新(OTA)功能、灵活定制能力及高集成度设计,成为智能设备语音方案的优选。本文将从技术特性、远程更新机制及典型应用场景三方面,解析该芯片的技术优势与实际应用价值。一、WT588E02B-8S语音芯片的核心技术特性高性能硬件架构WT588E02B-8S采用16位DSP内核,内部振荡频率达32MHz,支持16位PWM/DAC输出,可直接驱动8Ω/0.5W
    广州唯创电子 2025-04-01 08:38 133浏览
  • REACH和RoHS欧盟两项重要的环保法规有什么区别?适用范围有哪些?如何办理?REACH和RoHS是欧盟两项重要的环保法规,主要区别如下:一、核心定义与目标RoHS全称为《关于限制在电子电器设备中使用某些有害成分的指令》,旨在限制电子电器产品中的铅(Pb)、汞(Hg)、镉(Cd)、六价铬(Cr6+)、多溴联苯(PBBs)和多溴二苯醚(PBDEs)共6种物质,通过限制特定材料使用保障健康和环境安全REACH全称为《化学品的注册、评估、授权和限制》,覆盖欧盟市场所有化学品(食品和药品除外),通过登
    张工13144450251 2025-03-31 21:18 105浏览
  • 职场之路并非一帆风顺,从初入职场的新人成长为团队中不可或缺的骨干,背后需要经历一系列内在的蜕变。许多人误以为只需努力工作便能顺利晋升,其实核心在于思维方式的更新。走出舒适区、打破旧有框架,正是让自己与众不同的重要法宝。在这条道路上,你不只需要扎实的技能,更需要敏锐的观察力、不断自省的精神和前瞻的格局。今天,就来聊聊那改变命运的三大思维转变,让你在职场上稳步前行。工作初期,总会遇到各式各样的难题。最初,我们习惯于围绕手头任务来制定计划,专注于眼前的目标。然而,职场的竞争从来不是单打独斗,而是团队协
    优思学院 2025-04-01 17:29 93浏览
  • 文/郭楚妤编辑/cc孙聪颖‍不久前,中国发展高层论坛 2025 年年会(CDF)刚刚落下帷幕。本次年会围绕 “全面释放发展动能,共促全球经济稳定增长” 这一主题,吸引了全球各界目光,众多重磅嘉宾的出席与发言成为舆论焦点。其中,韩国三星集团会长李在镕时隔两年的访华之行,更是引发广泛热议。一直以来,李在镕给外界的印象是不苟言笑。然而,在论坛开幕前一天,李在镕却意外打破固有形象。3 月 22 日,李在镕与高通公司总裁安蒙一同现身北京小米汽车工厂。小米方面极为重视此次会面,CEO 雷军亲自接待,小米副董
    华尔街科技眼 2025-04-01 19:39 81浏览
  • 提到“质量”这两个字,我们不会忘记那些奠定基础的大师们:休哈特、戴明、朱兰、克劳士比、费根堡姆、石川馨、田口玄一……正是他们的思想和实践,构筑了现代质量管理的核心体系,也深远影响了无数企业和管理者。今天,就让我们一同致敬这些质量管理的先驱!(最近流行『吉卜力风格』AI插图,我们也来玩玩用『吉卜力风格』重绘质量大师画象)1. 休哈特:统计质量控制的奠基者沃尔特·A·休哈特,美国工程师、统计学家,被誉为“统计质量控制之父”。1924年,他提出世界上第一张控制图,并于1931年出版《产品制造质量的经济
    优思学院 2025-04-01 14:02 105浏览
  • 文/Leon编辑/cc孙聪颖‍步入 2025 年,国家进一步加大促消费、扩内需的政策力度,家电国补政策将持续贯穿全年。这一利好举措,为行业发展注入强劲的增长动力。(详情见:2025:消费提振要靠国补还是“看不见的手”?)但与此同时,也对家电企业在战略规划、产品打造以及市场营销等多个维度,提出了更为严苛的要求。在刚刚落幕的中国家电及消费电子博览会(AWE)上,家电行业的竞争呈现出胶着的态势,各大品牌为在激烈的市场竞争中脱颖而出,纷纷加大产品研发投入,积极推出新产品,试图提升产品附加值与市场竞争力。
    华尔街科技眼 2025-04-01 19:49 81浏览
  • 在智能交互设备快速发展的今天,语音芯片作为人机交互的核心组件,其性能直接影响用户体验与产品竞争力。WT588F02B-8S语音芯片,凭借其静态功耗<5μA的卓越低功耗特性,成为物联网、智能家居、工业自动化等领域的理想选择,为设备赋予“听得懂、说得清”的智能化能力。一、核心优势:低功耗与高性能的完美结合超低待机功耗WT588F02B-8S在休眠模式下待机电流仅为5μA以下,显著延长了电池供电设备的续航能力。例如,在电子锁、气体检测仪等需长期待机的场景中,用户无需频繁更换电池,降低了维护成本。灵活的
    广州唯创电子 2025-04-02 08:34 50浏览
  • 引言在语音芯片设计中,输出电路的设计直接影响音频质量与系统稳定性。WT588系列语音芯片(如WT588F02B、WT588F02A/04A/08A等),因其高集成度与灵活性被广泛应用于智能设备。然而,不同型号在硬件设计上存在关键差异,尤其是DAC加功放输出电路的配置要求。本文将从硬件架构、电路设计要点及选型建议三方面,解析WT588F02B与F02A/04A/08A的核心区别,帮助开发者高效完成产品设计。一、核心硬件差异对比WT588F02B与F02A/04A/08A系列芯片均支持PWM直推喇叭
    广州唯创电子 2025-04-01 08:53 145浏览
  • 据先科电子官方信息,其产品包装标签将于2024年5月1日进行全面升级。作为电子元器件行业资讯平台,大鱼芯城为您梳理本次变更的核心内容及影响:一、标签变更核心要点标签整合与环保优化变更前:卷盘、内盒及外箱需分别粘贴2张标签(含独立环保标识)。变更后:环保标识(RoHS/HAF/PbF)整合至单张标签,减少重复贴标流程。标签尺寸调整卷盘/内盒标签:尺寸由5030mm升级至**8040mm**,信息展示更清晰。外箱标签:尺寸统一为8040mm(原7040mm),提升一致性。关键信息新增新增LOT批次编
    大鱼芯城 2025-04-01 15:02 152浏览
  •        在“软件定义汽车”的时代浪潮下,车载软件的重要性日益凸显,软件在整车成本中的比重逐步攀升,已成为汽车智能化、网联化、电动化发展的核心驱动力。车载软件的质量直接关系到车辆的安全性、可靠性以及用户体验,因此,构建一套科学、严谨、高效的车载软件研发流程,确保软件质量的稳定性和可控性,已成为行业共识和迫切需求。       作为汽车电子系统领域的杰出企业,经纬恒润深刻理解车载软件研发的复杂性和挑战性,致力于为O
    经纬恒润 2025-03-31 16:48 82浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦