C++常量关键字异常行为

C++ constant keyword abnormal behaviour

我们正在为STM32F103 MCU 开发。我们使用带有 ARM GCC 工具链的裸机 C++ 代码。在与一个可疑的表达式进行了几个小时的斗争之后,我们发现 constant 关键字会触发该表达式的不同结果。用x86 GCC工具链测试同一段代码时,问题不存在。
我们正在使用STM的GPIO进行调试。
这是完全重现问题的代码:

#include "stm32f10x.h"
#include "system_stm32f10x.h"

#include "stdlib.h"
#include "stdio.h"

  const unsigned short RTC_FREQ = 62500;
  unsigned short prescaler_1ms = RTC_FREQ/1000;

int main()
{

//********** Clock Init **********
   RCC->CFGR |= RCC_CFGR_ADCPRE_0 | RCC_CFGR_ADCPRE_1; // ADC prescaler
   RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; // Alternate Function I/O clock enable
   RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; // I/O port C clock enable
   RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; // I/O port A clock enable
   RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; // ADC 1 interface clock enable
   RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // Timer 2 clock enable 
   RCC->AHBENR = RCC_AHBENR_DMA1EN; // DMA1 clock enable
   RCC->CSR = RCC_CSR_LSION; // Internal Low Speed oscillator enable
//********************************

/* GPIO Configuration */
   GPIOC->CRH = GPIO_CRH_MODE12_0; //GPIO Port C Pin 12 
   GPIOC->CRH |= GPIO_CRH_MODE13_1 | GPIO_CRH_MODE13_0;
   GPIOC->CRH |= GPIO_CRH_MODE10_0;
   GPIOC->CRH |= GPIO_CRH_MODE9_0;
   GPIOC->CRH |= GPIO_CRH_MODE8_0;
   GPIOC->CRL = GPIO_CRL_MODE7_0;
   GPIOC->CRL |= GPIO_CRL_MODE6_0;
   GPIOC->CRL |= GPIO_CRL_MODE4_0;
   GPIOC->CRL |= GPIO_CRL_MODE3_0;


   while(1){

      if(prescaler_1ms & (1<<0))GPIOC->BSRR |= GPIO_BSRR_BR13;
      else GPIOC->BSRR |= GPIO_BSRR_BS13;
      if(prescaler_1ms & (1<<1))GPIOC->BSRR |= GPIO_BSRR_BR12;
      else GPIOC->BSRR |= GPIO_BSRR_BS12;
      if(prescaler_1ms & (1<<2))GPIOC->BSRR |= GPIO_BSRR_BR10;
      else GPIOC->BSRR |= GPIO_BSRR_BS10;
      if(prescaler_1ms & (1<<3))GPIOC->BSRR |= GPIO_BSRR_BR9;
      else GPIOC->BSRR |= GPIO_BSRR_BS9;
      if(prescaler_1ms & (1<<4))GPIOC->BSRR |= GPIO_BSRR_BR8;
      else GPIOC->BSRR |= GPIO_BSRR_BS8;
      if(prescaler_1ms & (1<<5))GPIOC->BSRR |= GPIO_BSRR_BR7;
      else GPIOC->BSRR |= GPIO_BSRR_BS7;
      if(prescaler_1ms & (1<<6))GPIOC->BSRR |= GPIO_BSRR_BR6;
      else GPIOC->BSRR |= GPIO_BSRR_BS6;
      if(prescaler_1ms & (1<<7))GPIOC->BSRR |= GPIO_BSRR_BR4;
      else GPIOC->BSRR |= GPIO_BSRR_BS4;
      if(prescaler_1ms & (1<<8))GPIOC->BSRR |= GPIO_BSRR_BR3;
      else GPIOC->BSRR |= GPIO_BSRR_BS3;

   }
   return 0;
}

当该代码编译时,我们期待 GPIOS 的结果 0b111110。当我们改变

const unsigned short RTC_FREQ = 62500;  

unsigned short RTC_FREQ = 62500;  

我们得到 0b111111111
这是我们使用的 Makefile:

EABI_PATH=$(ROOT_DIR)"arm_toolchain/gcc-arm-none-eabi-6-2017-q2-update/arm-none-eabi/"
CMSIS_INC_PATH=$(ROOT_DIR)"STMLib/STM32F10x_StdPeriph_Lib_V3.5.0/Libraries/CMSIS/CM3/"
PROJECT_INC=$(ROOT_DIR)

CXXINCS = -I$(EABI_PATH)"include" -I$(CMSIS_INC_PATH)"CoreSupport" -I$(CMSIS_INC_PATH)"DeviceSupport/ST/STM32F10x" -I$(PROJECT_INC)"Source" -I$(PROJECT_INC)"Includes"

CXXLIBS = -L$(EABI_PATH)"lib" -L$(EABI_PATH)"6.3.1"
CXXFLAGS = --specs=nosys.specs -DSTM32F10X_MD -DVECT_TAB_FLASH -fdata-sections -ffunction-sections -fno-exceptions -mthumb -mcpu=cortex-m3 -march=armv7-m -O2
LDFLAGS = -lstdc++ -Wl,--gc-sections

CC = $(EABI_PATH)"../bin/arm-none-eabi-gcc"
CXX = $(EABI_PATH)"../bin/arm-none-eabi-g++"
LD = $(EABI_PATH)"../bin/arm-none-eabi-ld"
STRIP = $(EABI_PATH)"../bin/arm-none-eabi-strip"

all:
        $(CC) $(CXXINCS) -c $(PROJECT_INC)"Source/syscalls.c" $(PROJECT_INC)"Source/startup.c" $(CXXFLAGS)
        $(CXX) $(CXXINCS) -c $(PROJECT_INC)"Source/main.cpp" $(CMSIS_INC_PATH)"DeviceSupport/ST/STM32F10x/system_stm32f10x.c" $(CXXFLAGS)
        $(CXX) $(CXXLIBS) -o main syscalls.o main.o startup.o -T linker.ld system_stm32f10x.o $(LDFLAGS)
        $(STRIP) --strip-all main
        $(EABI_PATH)"bin/objcopy" -O binary main app
        $(EABI_PATH)"bin/objdump" -b binary -m arm_any -D app > app_disasm
        rm -f *.o main adc timer task solenoid dma startup syscalls system_stm32f10x

有人知道什么会导致这样的问题吗?那是编译器错误吗?我们错过了什么吗?

将我的理论推广到一个答案,因为它已被启动代码和LD脚本证实。

本应将 62 复制到 prescaler_1ms 中的 C++ 初始化代码未被调用。当您将 RTC_FREQ 定义为 const 时,此计算的结果在编译时已知,62 存在于闪存中并且不需要初始化。

C++ 初始化由许多生成的函数执行,名称类似于 _Z41__static_initialization_and_destruction_0ii。指向这些函数的指针由编译器收集在 .init_array.pre_init_array 部分中。在调用 main() 之前,启动代码应该遍历这些指针并调用它们中的每一个。这些指针数组的边界对于启动代码来说是已知的,因为这些特殊符号是由链接描述文件定义的:

__preinit_array_start, __preinit_array_end
__init_array_start, __init_array_end

我还不清楚 _preinit_array__init_array 之间的区别。前一部分在调用 _init 函数之前被调用,而后者在之后被调用。在我的项目中 gcc 提供的 _init 函数似乎不是一个有效的函数,所以我没有调用它。

当使用__fini_array_start__fini_array_end 调用全局对象的C++ 析构函数时,程序终止时有一个对称过程。但是,对于嵌入式系统,它可能不相关。

使项目调用 C++ 初始化内容的最少步骤是:

  1. 在您的链接描述文件中包含 .init_array 部分。

从您提供的文档来看,.init_array 部分似乎已经定义为:

. = ALIGN(4);
__preinit_array_start = .;
KEEP(*(.preinit_array))
__preinit_array_end = .;

. = ALIGN(4);
__init_array_start = .;
KEEP(*(SORT(.init_array.*)))
KEEP(*(.init_array))
__init_array_end = .;
  1. 拥有在程序启动时调用这些指针的代码。 这部分似乎没有出现在您的设置中,这才是问题的真正原因。

您可以将以下代码(或类似代码)添加到 startup.c 中的 __Init_Data() 函数:

// usually these are defined with __attribute__((weak)) but I prefer to get errors when required things are missing
extern void (*__preinit_array_start[])(void);
extern void (*__preinit_array_end[])(void);
extern void (*__init_array_start[])(void);
extern void (*__init_array_end[])(void);

void __Init_Data(void) {

    // copying initialized data from flash to RAM
    ...

    // zeroing bss segment
    ...

    // calling C++ initializers
    void (**f)(void);
    for (f = __preinit_array_start; f != __preinit_array_end; f++)
        (*f)();
    // init(); // _init and _fini do not work for me
    for (f = __init_array_start; f != __init_array_end; f++)
        (*f)();
}

同样,我不确定_init函数,所以在这里被注释掉了。稍后我可能会问自己的问题。