只读内存映射寄存器在C中使用' volatile const '定义,但在c++中仅使用' volatile '

Read-only memory-mapped registers defined with `volatile const` in C but only `volatile` in C++

本文关键字:volatile 定义 但在 c++ const 映射 只读 寄存器 内存      更新时间:2023-10-16

在使用Atmel SAM3X8E的嵌入式系统项目中工作时,我注意到在一些CMSIS头文件中有以下代码位:

#ifndef __cplusplus
typedef volatile const uint32_t RoReg; /**< Read only 32-bit register (volatile const unsigned int) */
#else
typedef volatile       uint32_t RoReg; /**< Read only 32-bit register (volatile const unsigned int) */
#endif

为什么c++的类型定义不包括const ?我在某个地方看到提到c++不存储运行时内存中的整数const变量,如果这是真的,这意味着const将需要被删除,因为微控制器寄存器是如何内存映射的,但我似乎找不到任何其他说c++这样做的东西(尽管我的搜索无可否认相当简短)。没有太多的c++经验,我也认为这可能是c++不允许const结构成员,因为这些类型主要用于寄存器集合的结构类型,但情况似乎也不是这样。

如果使用const声明,c++标准将要求您初始化变量的内容。在微控制器寄存器的情况下,您不希望这样做。

因为从来没有实例化过RoReg对象,所以没有很好的理由在类型定义中省略const限定符。

RoReg的每次使用都是在一个宏中,该宏定义了指向类型的指针…

#define REG_WDT_SR (*(RoReg*)0x400E1A58U) /**< brief (WDT) Status Register */

…或者使用类似的宏访问struct声明。

typedef struct {
  WoReg WDT_CR; /**< brief (Wdt Offset: 0x00) Control Register */
  RwReg WDT_MR; /**< brief (Wdt Offset: 0x04) Mode Register */
  RoReg WDT_SR; /**< brief (Wdt Offset: 0x08) Status Register */
} Wdt;
#define WDT        ((Wdt    *)0x400E1A50U) /**< brief (WDT) Base Address */

即使使用const限定符,代码在C和c++中也应该表现相同。

也许作者误解了标准。为了保证c++结构体具有与C中相同的布局,它要求类"对所有非静态数据成员具有相同的访问控制(第11条)"。作者可能把constvolatile误认为是访问控制说明符。如果它们是,那么您将希望所有结构体成员具有相同的cv-限定符,以确保C和c++(以及硬件)布局之间的兼容性。但是publicprotectedprivate定义了访问控制。

正如@fanl所提到的,const确实改变了c++中全局变量的默认链接,并且防止在没有初始化的情况下定义变量。

但是有比删除const更好的方法来获得外部链接。Chris链接的头文件中保留数组的使用也非常脆弱。我想说这段代码还有很大的改进空间——不要模仿它。

此外,这些变量不被定义(这会导致编译器和链接器选择一个地址),它们总是通过指针访问,地址根据内存映射固定。

对于纯粹用于c++的头文件,我是这样做的(内存映射匹配TI Stellaris芯片)。

看起来很复杂,但优化编译器将其简化为每次访问一条指令。地址偏移量是编码的,不依赖于结构内字段的顺序和填充,所以它不那么脆弱,更容易根据数据表进行验证。

template<uintptr_t extent>
struct memory_mapped_peripheral
{
    char data[extent];
    volatile       uint32_t* offset( uintptr_t off )       { return reinterpret_cast<volatile       uint32_t*>(data+off); }
    volatile const uint32_t* offset( uintptr_t off ) const { return reinterpret_cast<volatile const uint32_t*>(data+off); }
};
struct LM3S_SYSTICK : private memory_mapped_peripheral<0x1000>
{
    volatile       uint32_t& CTRL   (void)             { return offset(0x010)[0]; }
    volatile       uint32_t& RELOAD (void)             { return offset(0x014)[0]; }
    volatile       uint32_t& CURRENT(void)             { return offset(0x018)[0]; }
}* const SYSTICK = reinterpret_cast<LM3S_SYSTICK*>(0xE000E000);
struct LM3S_NVIC : private memory_mapped_peripheral<0x1000>
{
    volatile       uint32_t& EN    (uintptr_t i)       { return offset(0x100)[i]; }
    volatile       uint32_t& DIS   (uintptr_t i)       { return offset(0x180)[i]; }
    volatile       uint32_t& PEND  (uintptr_t i)       { return offset(0x200)[i]; }
    volatile       uint32_t& UNPEND(uintptr_t i)       { return offset(0x280)[i]; }
    volatile const uint32_t& ACTIVE(uintptr_t i) const { return offset(0x300)[i]; }
    volatile       uint32_t& PRI   (uintptr_t i)       { return offset(0x400)[i]; }
    volatile       uint32_t& SWTRIG(void)              { return offset(0xF00)[0]; }
}* const NVIC = reinterpret_cast<LM3S_NVIC*>(0xE000E000);