当前位置:   article > 正文

原来全局变量是这样初始化的

原来全局变量是这样初始化的

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

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

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

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

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

  1. int counter, accumulator = 0, limit_value = 1000000;
  2. unsigned char str_aa55[2] = {0xAA,0x55};
  3. unsigned int int_1122334455667788 = 0x11223344;
  4. unsigned int int_55667788 = 0x55667788;
  5. int bss_val;
  6. void main(void)
  7. {
  8.    
  9. }

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

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

单片机的启动程序一般都是很简单的,即使汇编也没多少行,直接翻出来看看也许会知道答案。

  1. -- Clear local RAM
  2.   mov ___ghs_ramstart, r6  -- start of local RAM
  3.   mov ___ghs_ramend, r7    -- end of local RAM
  4.   mov r0, r1
  5. 1:
  6.   st.dw   r0, 0[r6]
  7.   addi    8, r6, r6
  8.   cmp r7, r6
  9.   bl 1b
  10.   -- Jump to the HW initialisation function
  11.   jarl  ___lowinit, lp
  12.   -- Jump to the initialisation functions of the library
  13.   -- and from there to main()
  14.   jr __start

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

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

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

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

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

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

但是我在这个RH850的代码工程里面是找不到这个__start的源码内容的,仿真看汇编折腾了半天,突然想了下,为啥不换个其他工程试试,例如试试NXP S32K的?

于是,我创建了一个NXP S32K1xx的代码工程,仍然定义这几个变量

  1. int counter, accumulator = 0, limit_value = 1000000;
  2. unsigned char str_aa55[2] = {0xAA,0x55};
  3. unsigned int  int_1122334455667788 = 0x11223344;
  4. unsigned int  int_55667788 = 0x55667788;
  5. int bss_val;

同样的讨论,直接翻启动文件的汇编代码

  1. /* Init .data and .bss sections */
  2.     ldr     r0,=init_data_bss
  3.     blx     r0

还是欧美的芯片简单粗暴,不像小日子做的初始化还有藏进库里。这不是很明显嘛,init_data_bss就是初始化全局变量的,以下截取了部分代码,也很容易理解。

  1. void init_data_bss(void)
  2. {
  3. /* ...... */
  4.     /* Data */
  5.     data_ram        = (uint8_t *)__DATA_RAM;
  6.     data_rom        = (uint8_t *)__DATA_ROM;
  7.     data_rom_end    = (uint8_t *)__DATA_END;
  8.     /* ...... */
  9.     /* BSS */
  10.     bss_start       = (uint8_t *)__BSS_START;
  11.     bss_end         = (uint8_t *)__BSS_END;
  12.     /* ...... */
  13.    
  14.     /* Copy initialized data from ROM to RAM */
  15.     while (data_rom_end != data_rom)
  16.     {
  17.         *data_ram = *data_rom;
  18.         data_ram++;
  19.         data_rom++;
  20.     }
  21.    
  22.     /* ...... */
  23.     /* Clear the zero-initialized data section */
  24.     while(bss_end != bss_start)
  25.     {
  26.         *bss_start = 0;
  27.         bss_start++;
  28.     }
  29.     /* ...... */
  30. }

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

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

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

  1. /* Specify the memory areas */
  2. MEMORY
  3. {
  4.   /**/
  5.   /* SRAM_L */
  6. m_data   (RW)  : ORIGIN = 0x1FFF8000, LENGTH = 0x00008000
  7.  m_data_2  (RW)  : ORIGIN = 0x20000000, LENGTH = 0x00007000
  8.   /* … */
  9. .data : AT(__DATA_ROM)
  10.   {
  11.     . = ALIGN(4);
  12.     __DATA_RAM = .;
  13.     __data_start__ = .;      /* Create a global symbol at data start. */
  14.     *(.data)                 /* .data sections */
  15.     *(.data*)                /* .data* sections */
  16.     . = ALIGN(4);
  17.     __data_end__ = .;        /* Define a global symbol at data end. */
  18.   } > m_data
  19.   __DATA_END = __DATA_ROM + (__data_end__ - __data_start__);
  20.   __CODE_ROM = __DATA_END; /* Symbol is used by code initialization. */
  21.   /* Uninitialized data section. */
  22.   .bss :
  23.   {
  24.     /* This is used by the startup in order to initialize the .bss section. */
  25.     . = ALIGN(4);
  26.     __BSS_START = .;
  27.     __bss_start__ = .;
  28.     *(.bss)
  29.     *(.bss*)
  30.     *(COMMON)
  31.     . = ALIGN(4);
  32.     __bss_end__ = .;
  33.     __BSS_END = .;
  34.   } > 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,也就是说,这些

  1. int limit_value = 1000000;
  2. unsigned char str_aa55[2] = {0xAA,0x55};
  3. unsigned int int_1122334455667788 = 0x11223344;
  4. unsigned int int_55667788 = 0x55667788;

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

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

8b44bb722df716de74398c085260ba31.png

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

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

秘密就在于ld文件里的这个语句:.data : AT(__DATA_ROM)

意思是,定义在data段的变量对应的初始化值,就放在__DATA_ROM中。就这么简单,剩下的交给编译器就行了。

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

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

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

9ada0b6bdcacc77470bf11a9610a5e74.jpeg

fcca2d6c572acd46456adf9fe63a6e84.jpeg

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/272059
推荐阅读
相关标签
  

闽ICP备14008679号