最近,有个好学的小伙子突然问了我一个问题: 全局变量的初始值,是在哪里赋值的? 这个问题虽然说不是很重要,但是我很好奇。 为了给讲清楚这个原理过程,我专门建立一个基于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的源码,感叹小日子真的有一手。
智慧农业解决方案助力农业生产,促进品质增长
rk3588相当于骁龙什么芯片?
浅析收费WIFI与免费WIFI有哪些区别
USB3.0时代来临,你的保护电路准备好了吗?
放大器芯片的作用是什么
全局变量的初始值,是在哪里赋值的?
高效率、易管理的智能照明解决方案
台积电在2021年开始危险生产3纳米Apple Silicon芯片
Boost型PFC电路的控制原理
永磁材料在永磁直流无刷电机中的应用
国芯思辰对标TI THS6212,线驱动器CN6212应用于电力线载波方案
无人机编程轻松学,神奇积木块全面使用指南!
一体化模块贴片机
电路权衡使电池输入电源的噪声降至最低
基于DSP的视频采集驱动程序的实现
高性能云计算,华为云服务器行业遥遥领先
iRobot给力高交会,开启机器人家居新时代
RedBoot引导程序在SmartARM2200开发板中的移植参考研究
小米宣布将降低印度的Mi True Wireless耳机2的价格
用户吸引力逐渐减弱,新iphone能否挽救下滑的在华市场?