GCC下内存映射寄存器声明如何兼容MISRA-C且不使用编译器专属关键字
提问内容
目前知晓两种使用GCC声明内存映射寄存器的主流方式(存在多种衍生实现,例如使用位域、为每个外设定义专属数据结构等):
第一种是定义指针并初始化为对应寄存器的正确地址,示例代码如下:volatile uint32_t *pMyRegister = (uint32_t *)0xDEADBEEFUL;
第二种是先定义变量再手动映射段地址,示例代码如下:volatile uint32_t MyRegister __attribute((section(".register_section")));
后续在LD链接脚本中完成对应段的地址映射。
但上述两种实现方式均不符合MISRA-C编码规范:第一种存在MISRA规则明确禁止的整数转指针类型转换,第二种使用了编译器专属的attribute关键字。
想咨询:针对第二种实现方案,是否存在无需使用编译器特定关键字、不会破坏代码ANSI兼容性的变量段映射方法?
解答
纯ANSI C标准没有提供原生的变量段映射语法,也没有规定如何将变量绑定到固定内存地址,因此完全脱离编译器/工具链扩展的通用实现方案不存在。但可以通过以下兼顾MISRA合规性、ANSI兼容性的工程实践实现需求:
- 第一种:将编译器专属扩展封装到硬件抽象层(HAL)的头文件中,通过编译分支兼容不同工具链,避免业务代码直接接触编译器关键字。示例封装逻辑如下:
// 平台相关hal_reg.h头文件 #if defined(__GNUC__) #define REG_SECTION __attribute__((section(".register_section"))) #elif defined(__ICCARM__) #define REG_SECTION @ ".register_section" #elif defined(__CC_ARM) #define REG_SECTION __attribute__((section(".register_section"), zero_init)) #else #error 不支持的编译器,需补充REG_SECTION宏定义 #endif // 业务代码中直接使用宏,无编译器专属关键字 volatile uint32_t MyRegister REG_SECTION;
这种方式的优势是业务代码完全符合ANSI C语法,仅平台相关头文件涉及编译器扩展,符合MISRA C中对编译器扩展封装使用的例外要求,只要在合规报告中明确说明使用的扩展即可。
- 第二种:如果要求完全禁用任何编译器扩展,可以将内存映射寄存器的地址统一放到单独的汇编文件中定义,汇编文件中直接将变量绑定到指定地址,C代码中仅通过extern声明变量即可:
// C代码中的声明,无任何编译器扩展,完全符合ANSI C extern volatile uint32_t MyRegister;
汇编文件(以ARM架构GAS汇编为例)中添加如下定义:
.section .register_section, "aw", %nobits .global MyRegister .type MyRegister, %object .size MyRegister, 4 MyRegister: .space 4
之后再在链接脚本中给.register_section段分配固定的寄存器地址即可,这种实现方式下所有C代码完全符合ANSI C规范,也不涉及整数转指针的类型转换,满足MISRA编码要求。
注意:MISRA C并没有完全禁止使用编译器扩展,仅要求所有使用的扩展必须明确定义、文档化,且经过可靠性验证,上述两种方案均符合MISRA的相关要求。
内容的提问来源于stack exchange,提问作者Guillaume Petitjean

