You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

arm-gcc编译代码体积大于armclang的原因及解决方法

Keil armclang与arm-none-eabi-gcc编译STM32 C++项目的体积差异原因及解决办法

问题背景

通过STM32CubeMX生成相同项目,分别创建Keil MDK项目(使用armclang v6.19)与Makefile项目(使用arm-none-eabi-gcc v12.2.1),均将main.c改为main.cpp,核心代码如下:

uint8_t* data;

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART3_UART_Init();
  MX_USB_OTG_FS_PCD_Init();

  data = new uint8_t[16];
  for(int i = 0; i < 16; i++)data[i] = i+1;
  
  while (1)
  {
    HAL_GPIO_TogglePin(LD3_GPIO_Port, GPIO_PIN_14);
    HAL_Delay(500);
  }
}

不同优化级下的编译体积差异如下:

O0优化

  • Keil:
    Program Size: Code=11950 RO-data=510 RW-data=12 ZI-data=3012
    
  • arm-none-eabi-gcc:
    text    data     bss     dec     hex 
    17572     100    3268   20940    51cc
    

O3优化

  • Keil:
    Program Size: Code=8238 RO-data=510 RW-data=12 ZI-data=3012  
    
  • arm-none-eabi-gcc:
    text    data     bss     dec     hex
    12448     100    3268   15816    3dc8
    

Oz优化

  • Keil:
    Program Size: Code=6822 RO-data=510 RW-data=12 ZI-data=3012   
    
  • arm-none-eabi-gcc:
    text    data     bss     dec     hex
    11876     100    3268   15244    3b8c
    

体积差异的核心原因

  1. 优化标志的默认行为差异
    armclang与gcc的-O系列优化标志默认启用的优化子集并不完全一致:

    • 比如Oz(侧重代码大小的优化),armclang默认会更激进地进行函数内联、死代码消除、常量折叠,而gcc的Oz可能保留了更多默认的运行时检查逻辑;
    • 两者对C++特性的优化策略不同,比如new操作的异常处理代码,clang默认可能更精简,而gcc默认保留了完整的异常抛出框架。
  2. 标准库实现差异

    • Keil配套的是ARM官方优化的C++标准库,针对STM32等嵌入式平台做了深度精简,尤其是内存分配(malloc/new)、IO等模块;
    • gcc默认使用的是newlib标准库,未针对极致体积优化,若未指定newlib-nano,代码体积会显著大于ARM的精简库。
  3. 编译/链接选项的隐含差异

    • Keil MDK生成项目时,默认添加了针对STM32的专属优化选项,比如-ffunction-sections -fdata-sections(拆分函数/数据段)和链接时的--gc-sections(垃圾代码消除),而手动编写的Makefile可能未配置这些选项,导致未使用的代码、数据未被移除;
    • Keil默认可能关闭了C++的RTTI(运行时类型信息)和异常支持,而gcc默认开启这些特性,带来额外的代码开销。
  4. 编译器版本与架构针对性差异
    armclang v6.19是ARM专为自家架构打造的编译器,对Cortex-M系列的指令优化更精准;而gcc v12.2.1是通用编译器,虽然版本更新,但在嵌入式平台的极致体积优化上,针对性不如armclang。

解决办法

  1. 统一编译与链接选项
    在gcc的Makefile中添加以下关键选项,对齐Keil的优化策略:

    # 编译选项
    CXXFLAGS += -mthumb -mcpu=cortex-m4  # 根据你的STM32型号调整
    CXXFLAGS += -ffunction-sections -fdata-sections
    CXXFLAGS += -fno-exceptions -fno-rtti  # 关闭C++异常和RTTI
    CXXFLAGS += -Os  # 或Oz,对应Keil的Oz优化
    
    # 链接选项
    LDFLAGS += --gc-sections
    LDFLAGS += --specs=nano.specs  # 使用newlib-nano精简标准库
    
  2. 替换动态内存分配为静态数组
    代码中new uint8_t[16]属于动态内存分配,会引入malloc/free的实现代码,可直接替换为静态数组消除这部分开销:

    uint8_t data[16];  // 替换原有的uint8_t* data;
    
    int main(void)
    {
      // ... 初始化代码 ...
      for(int i = 0; i < 16; i++)data[i] = i+1;
      // ... 循环代码 ...
    }
    

    若必须使用动态分配,改用nothrow版本配合-fno-exceptions:

    data = new(nothrow) uint8_t[16];
    
  3. 对齐标准库配置
    确保gcc项目使用newlib-nano(通过--specs=nano.specs链接选项),该库是newlib的精简版本,移除了很多嵌入式场景不需要的功能,能大幅减小代码体积。

  4. 导出Keil编译参数对齐
    在Keil中查看编译时的完整命令行(可通过Build Output窗口查看),将除编译器本身外的所有参数复制到Makefile中,完全对齐编译选项,消除因配置不同带来的体积差异。

内容的提问来源于stack exchange,提问作者lazba

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 11:35:30