如何配置GCC编译器,使其不假设数组访问不会越界?嵌入式场景
我们在ARM Cortex-M嵌入式设备上使用GNU GCC/G++编译器,经常采用类似Windows API中BITMAPINFO的结构体设计——结构体末尾用长度为1的数组作为柔性数组,实际长度由malloc分配的内存大小决定:
struct { someType foo; /* bar[]的实际长度由malloc()传入的大小决定 */ someType bar[1]; } example;
以下是简化的内存管理代码(原代码超1000行):
#include <cstdint> typedef struct { union { uint8_t * pointers[ 1 ]; uint8_t buffer[ 1000 ]; } memory; uint8_t first; uint8_t last; } StorageType; void moveElements( StorageType & dataStorage ) { for(uint8_t i = dataStorage.first; i < dataStorage.last; i++) { dataStorage.memory.pointers[i] = dataStorage.memory.pointers[i - 1]; } }
使用GCC 11.3.1(针对Cortex-M)开启-O1优化后,编译器生成的汇编代码直接将循环优化消除:
ldrb r3, [r0, #1000] @ zero_extendqisi2 ldrb r2, [r0, #1001] @ zero_extendqisi2 cmp r2, r3 bls .L1 add r2, r0, r3, lsl #2 ldr r2, [r2, #-4] str r2, [r0, r3, lsl #2] .L1: bx lr
编译器假设pointers[]仅含1个元素,将循环中的越界访问判定为未定义行为,进而错误优化了循环。由于项目规模极大(数百万行代码),大部分为第三方或构建时覆盖的生成代码,无法修改代码,需要找到编译器层面的解决方案,告知编译器不要做出数组访问不越界的假设。
解决方案
1. 全局或编译单元级启用-fno-strict-array-bounds
这是最直接的解决方案:给编译命令添加-fno-strict-array-bounds选项。该选项会禁用GCC基于“数组访问始终在合法边界内”的优化假设,允许编译器保留那些访问数组声明边界之外、但实际在分配内存范围内的代码。
2. 配合MSVC扩展兼容选项-fms-extensions
由于Windows API的BITMAPINFO采用的是[1]式柔性数组的非标准写法,添加-fms-extensions选项可以让GCC更好地识别这种模式,减少对这类结构体的错误优化判断,与-fno-strict-array-bounds配合使用效果更佳。
3. 局部调整优化策略(若允许)
如果不想全局修改编译选项,可以针对包含问题代码的编译单元单独添加上述选项。若能通过宏或编译注入修改函数属性,还可以给目标函数添加__attribute__((optimize("-fno-strict-array-bounds"))),局部禁用数组边界优化假设。
验证方法
添加上述编译选项后重新编译,检查生成的汇编代码,确认循环逻辑被正确保留,而非被优化消除。
内容的提问来源于stack exchange,提问作者Martin Rosenau

