如何防止PlatformIO中ARM GCC优化掉未显式调用的函数
问题背景
使用NUCLEO-F411RE开发板,在VSCode + PlatformIO环境下基于CMSIS框架开发时,遇到编译优化无法关闭的问题:
- 同一份代码在STM32CubeIDE环境下编译烧录后可正常运行
- PlatformIO环境下编译无报错,但烧录后设备完全无响应,初步判断是未被显式调用的函数被编译器优化移除
- 已尝试在代码开头加
#pragma GCC optimize("O0")、在platformio.ini的编译选项中加-O0、设置build_type = debug,甚至将__io_putchar的返回值声明为volatile,均未解决问题
测试代码
#pragma GCC optimize("O0") #include "stm32f411xe.h" #include <stdint.h> #include <stdio.h> #define GPIOAEN (1U<<0) #define GPIOCEN (1U<<2) #define PIN5 (1U<<5) #define PIN13 (1U<<13) #define LED_PIN PIN5 #define BTN_PIN PIN13 #define UART2EN (1U<<17) void uart_2_init(void); void uart2_write(int ch); void ms_delay(int ms) { while (ms-- > 0) { volatile int x=500; while (x-- > 0) __asm("nop"); } } int __io_putchar(int ch){ uart2_write(ch); return ch; } int main(){ RCC->AHB1ENR |= (GPIOAEN | GPIOCEN); GPIOA->MODER |= (1U<<10); GPIOA->MODER &=~(1U<<11); GPIOC->MODER &=~(1U<<26); GPIOC->MODER &=~(1U<<27); uart_2_init(); while (1) { ms_delay(200); printf("hello again from stm32\n\r"); } } void uart_2_init(void){ RCC->AHB1ENR |= GPIOAEN; RCC->APB1ENR |= UART2EN; //PA2 and PA3 alternate GPIOA->MODER &=~(1U<<4); GPIOA->MODER |= (1U<<5); //Alternate function selection GPIOA->AFR[0] |= (1U<<8); GPIOA->AFR[0] |= (1U<<9); GPIOA->AFR[0] |= (1U<<10); GPIOA->AFR[0] &=~(1U<<11); // USART2 settings USART2->BRR = 0x0683; USART2->CR1 = 0x0008; USART2->CR2 = 0x0000; USART2->CR3 = 0x0000; USART2->CR1 |=0x2000; } void uart2_write(int ch){ while (!(USART2->SR & 0x0080)){} USART2->DR = (ch & 0xFF); }
原有platformio.ini配置
[env:nucleo_f411re] platform = ststm32 board = nucleo_f411re framework = cmsis build_flags = -Wall -O0 -v build_type = debug
无效尝试
将__io_putchar返回值声明为volatile,修改后代码如下,仍无法解决问题:
volatile int __io_putchar(int ch){ uart2_write(ch); return ch; }
解决方法
按以下步骤逐一排查配置即可解决:
- 废弃返回值加volatile的错误写法
volatile修饰函数返回值仅标记返回值本身为易变类型,完全不会阻止编译器对函数的优化、链接时移除操作,这个写法本身没有任何作用。 - 正确覆盖PlatformIO默认优化等级
手动在build_flags里加的-O0会被PlatformIO ststm32平台默认注入的编译参数(Debug模式默认-Og,Release模式默认-Os)覆盖,优先级不足。不需要在build_flags里硬写优化参数,直接用PlatformIO内置的optimize配置项指定优化等级即可,修改后的platformio.ini参考:
编译时加了[env:nucleo_f411re] platform = ststm32 board = nucleo_f411re framework = cmsis optimize = -O0 build_flags = -Wall -v build_type = debug-v参数,可以直接在编译日志里查看gcc调用命令,确认参数中只有-O0、没有-Og/-Os/-O2等其他优化参数,就说明优化等级配置生效。 - 给非显式调用的函数加used属性
类似__io_putchar这类被库函数(这里是newlib的printf)隐式调用的函数,编译器在编译当前源文件时无法识别到调用关系,会判定为死代码直接移除。需要给这类函数加GCC的used属性,强制编译器保留该函数,正确写法如下:
所有中断服务函数、回调函数、库对接桩函数如果没有被显式调用,都建议加这个属性避免被优化。int __attribute__((used)) __io_putchar(int ch){ uart2_write(ch); return ch; } - (可选)关闭链接时死段回收
如果完成以上配置后仍有函数被移除,可以在build_flags中追加以下参数,关闭编译器分段、链接时回收未引用段的默认逻辑:
加完后所有写的函数、全局变量都会被完整保留到最终固件里,不会被链接器删除。-fno-function-sections -fno-data-sections -Wl,--no-gc-sections
内容的提问来源于stack exchange,提问作者Lasse
相关产品推荐
相关产品推荐

