原来全局变量是这样初始化的
最近,有个好学的小伙子突然问了我一个问
全局变量的初始值,是在哪里赋值的?
这个问题虽然说不是很重要,但是我很好奇。
为了给讲清楚这个原理过程,我专门建立一个基于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;
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++;
}
/* ...... */
}
这里简单介绍下,带有初始化值(非0)全局变量(例如unsignedint int_55667788 = 0x55667788;),都是定义在data段的,而未定义初始化值的全局变量,是分在bss段的(例如int bss_val;)。
/* 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
到底是不是我说的这样子,直接查看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,也就是说,这些
等等变量的的初始值是来源于0x000009cc这里。 那就直接查看生成的hex文件int limit_value = 1000000;
unsigned char str_aa55[2] = {0xAA,0x55};
unsigned int int_1122334455667788 = 0x11223344;
unsigned int int_55667788 = 0x55667788;
秘密就在于ld文件里的这个语句:.data : AT(__DATA_ROM)意思是,定义在data段的变量对应的初始化值,就放在__DATA_ROM中。就这么简单,剩下的交给编译器就行了。对这个问题一直记在心里,是一个化不开的结。终于某一天,我居然真的无意中发现了__start的源码,感叹小日子真的有一手。
这里涉及到的内容比较多也比较复杂,后续再写个文章讲解下GreenHills编译器的启动链接库到底干了什么,敬请关注!
如果你喜欢我的文章,请关注,并 转发 、 点赞 和 在看 ,这是对我莫大的鼓励!