volatile 关键字如何影响静态常量数组?

How can the volatile keyword affect a static const array?

这是一个令人兴奋的问题,任何能够回答它的人都应该得到广泛的认可!实际上,我要问的是几个相互关联的问题,以便更好地理解。

STM32 ARM Cortex平台的驱动里面有如下代码:

static __I uint8_t APBAHBPrescTable[16] = {0, 0, 0, 0, 1, 2, 3, 4, 1, 2, 3, 4, 6, 7, 8, 9};

__I 定义为:

#ifdef __cplusplus
  #define     __I     volatile                /*!< defines 'read only'   permissions      */
#else
  #define     __I     volatile const          /*!< defines 'read only'   permissions      */
#endif

我的程序是用GCC交叉编译器编译的C程序。因此数组声明实际上是:

static volatile const uint8_t APBAHBPrescTable[16] = {0, 0, 0, 0, 1, 2, 3, 4, 1, 2, 3, 4, 6, 7, 8, 9};

问题 1:

鉴于这是一个常量数组,为什么要在这里使用 volatile 关键字?

我的理解是volatile关键字表示数组的内容可以改变,而const表示不能。

这个数组在代码中的唯一用途是像这样的三个用途:

tmp = RCC->CFGR & CFGR_PPRE1_Set_Mask;
tmp = tmp >> 8;
presc = APBAHBPrescTable[tmp];

当我转储 tmppresc 的值时,我发现 tmp 的值为 4,presc 的值为 0。索引 4 是数组的第 5 个元素,值为 1。没有其他访问或使用此值...完全...任何地方。

问题 2:

值在声明之间可能发生怎样的变化?

当我转储数组时,我看到它充满了零。

它可靠地发生...直到我从数组声明中删除 __I。这使我认为这不是缓冲区溢出。除此之外我想不出任何东西。

我认为 volatile 关键字在那里是有原因的,除了我还在中断处理程序中看到如下代码,据我所知,volatile 关键字是多余的:

volatile uint32_t status = USART2->SR;

这个变量是函数的局部变量,因此永远不能被其他地方的代码更改。

======== 额外的细节 ========

这是相关代码段的注释反汇编。 (RCC_GetClocksFreq+128) 处的值为零,但在某些时候似乎已将预分频器查找地址 table 复制到其中:

0x000001d0 <+56>:    ldr     r1, [pc, #68]   ; (0x218 <RCC_GetClocksFreq+128>)
       ...
   tmp = RCC->CFGR & CFGR_PPRE1_Set_Mask;
   tmp = tmp >> 8;
0x000001de <+70>:    ldr     r4, [r2, #4]
0x000001e0 <+72>:    ubfx    r4, r4, #8, #3
   presc = APBAHBPrescTable[tmp];
0x000001e4 <+76>:    ldrb    r4, [r1, r4]
   RCC_Clocks->PCLK1_Frequency = RCC_Clocks->HCLK_Frequency >> presc;
0x000001e6 <+78>:    lsr.w   r4, r3, r4
0x000001ea <+82>:    str     r4, [r0, #8]

这里是一样的,但是 volatile const 宏替换为 const:

0x000001d0 <+56>:    ldr     r4, [pc, #68]   ; (0x218 <RCC_GetClocksFreq+128>)
       ...
   tmp = RCC->CFGR & CFGR_PPRE1_Set_Mask;
   tmp = tmp >> 8;
0x000001de <+70>:    ldr     r1, [r2, #4]
0x000001e0 <+72>:    ubfx    r1, r1, #8, #3
   presc = APBAHBPrescTable[tmp];
0x000001e4 <+76>:    ldrb    r1, [r4, r1]
   RCC_Clocks->PCLK1_Frequency = RCC_Clocks->HCLK_Frequency >> presc;
0x000001e6 <+78>:    lsr.w   r1, r3, r1
0x000001ea <+82>:    str     r1, [r0, #8]

它们本质上是相同的。然而,以某种方式删除 volatile 关键字解决了问题!

My understanding is that the volatile keyword means that the contents of the array can change, but the const means that they cannot.

volatile表示程序每次使用时都必须从内存中读取值。 const 表示 程序 可能不会更改该值,但环境(或 "OS")可能会更改。

这解释了您观察到的行为:在没有 volatile 的情况下,编译器假定可以读取一次值并多次使用它。

实时时钟可以使用 volatile const 构造来发布当前时间:

volatile const struct tm TheTimeNow;
  • 您的程序无法更改时钟,所以它应该是 const
  • 时钟在您和编译器的背后永久而神奇地滴答作响,因此最好使用 volatile 强制编译器始终获取当前时间而不是旧时间戳。

RTC 可能在地址 space 中有自己的部分,它显示当前时间。

首先,感谢所有让我得出这个答案的评论和回答。

当变量定义时没有 "volatile" 关键字,它被放入二进制文件的只读部分。

当使用 "volatile" 关键字定义变量时,它将与所有其他变量放在二进制文件的同一部分。

我最近发现了 3 个缓冲区溢出,而且我确信还有其他的。很多代码写得不是很好。很可能当指定 "volatile" 关键字时,变量在内存中的放置方式使其容易受到缓冲区溢出的影响。根本没有理由将此特定变量标记为易失性,因此简单的解决方法是删除该关键字。正确的解决方法是这样做并跟踪缓冲区溢出并修复它。