背景
我们知道main函数之外的全局变量,如果在不同的cpp文件,其构造函数的执行顺序是不可控,可能会出现指针指飞等异常,因此,构造函数里只做一些简单的变量的初始化。而在STM32平台使用C++编码的过程中,我发现全局对象和静态对象,在使用中出现异常,我能够在程序中调用这个对象的方法。但是执行后与预期结果不符。
分析
通过串口打印这个对象的属性,发现这个对象的属性没有在构造时间设置成功,由此我怀疑这个对象在生成时根本没有调用,为了验证这个想法,定义了全局变量,在构造函数中去改变它,分别查看生成全局对象和生成局部对象时,这个值的变化情况。可以确定全局的对象并没有调用构造函数。通过检索解决方案我发现一个介绍Linux的C++程序全局对象不调用构造函数的问题。该文章提出导致全局对象不调用构造函数的主要原因是编译器的问题。(主要是其中的链接器部分)。同样我想到了STM32现在也是先用g++生成中间文件,再用ld链接生成最终的可执行文件。查看使用stm32cubemx生成的ld链接文件,之前了解过一点ld脚本知道一些段的含义,查找资料解读其中一些不认识的段,发现存在.init_array和.preinit_array段。
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .);
} >FLASH
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .);
} >FLASH
该段用于保存程序或共享对象加载时的初始化函数指针,这不就是全局对象的构造函数嘛?
.preinit_array 0x08044fbc 0x0
0x08044fbc PROVIDE (__preinit_array_start, .)
*(.preinit_array*)
0x08044fbc PROVIDE (__preinit_array_end, .)
.init_array 0x08044fbc 0x1c
0x08044fbc PROVIDE (__init_array_start, .)
*(SORT(.init_array.*))
*(.init_array*)
.init_array 0x08044fbc 0x4 c:/arm/bin/../lib/gcc/arm-none-eabi/4.5.1/thumb2/crtbegin.o
.init_array 0x08044fc0 0x4 OutPut/bsp.o
.init_array 0x08044fc4 0x4 OutPut/CSoftPwm.o
.init_array 0x08044fc8 0x4 OutPut/CSysTime.o
.init_array 0x08044fcc 0x4 OutPut/CMainApp.o
.init_array 0x08044fd0 0x4 OutPut/CParser.o
.init_array 0x08044fd4 0x4 c:/arm/bin/../lib/gcc/arm-none-eabi/4.5.1/../../../../arm-none-eabi/lib/thumb2\libstdc++.a(eh_alloc.o)
0x08044fd8 PROVIDE (__init_array_end, .)
再次查看生成的map文件发现该段下都是包含于全局对象或者静态对象的文件,这个时候我们知道这些对象的初始化指针都存放在该段下,问题就是如何访问该地址下的函数指针了。查找资料发现libc库下的_libc_init_array() 改函数就是对preinit_array段和init_array段的初始化函数指针进行调用执行。
结论
综上,解决C++全局对象不调用构造函数的问题,只需要在stm32的启动 文件中,在调用main()函数前调用_libc_init_array()即可
/* Call the clock system intitialization function.*/
bl SystemInit
/* Call static constructors */
bl __libc_init_array
/* Call the application's entry point.*/
bl main
bx lr
