2017-09-29 44 views
1

这是一个引人注目的人,任何能够回答的人都应该得到大家的认可!这实际上是一些我想要获得更好理解的相关问题。volatile关键字如何影响静态常量数组?

为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 keywork?

我的理解是,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)的值是零,但似乎在一些点有复制到它的预分频器查找表的地址:

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关键字可以解决问题!

+0

可能值得注意的是,有问题的代码由Keil交叉编译器人员提供,并被观察到可以在该板上正常工作多年 - 但使用Keil编译器而不是GCC编译器。 – AlastairG

+0

有很多程序使用'volatile'作为魔法护身符,而不理解它的实际功能。我怀疑这是其中的一个程序。 – zwol

+1

'__I' = UB。你应该重命名这个符号。 – Bathsheba

回答

0

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

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

当变量使用“volatile”关键字定义时,它与所有其他变量放在二进制文件的同一节中。

我最近发现了3个缓冲区溢出,我相信还有其他的。很多代码写得不好。很有可能当指定了“volatile”关键字时,这个变量就会被放置在内存中,使得它容易受到缓冲区溢出的影响。根本没有理由将这个特定的变量标记为volatile,所以简单的修复就是删除该关键字。正确的解决方法是做到这一点,并追查缓冲区溢出并修复它。

5

我的理解是volatile关键字意味着数组的内容 可以改变,但const表示它们不能。

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

这解释了您观察到的行为:如果没有volatile,编译器会认为可以读取一次并多次使用该值。

+0

一般来说,其他的事情也可能改变程序,而不仅仅是操作系统。在这个特殊情况下,我不知道任何,但你可以通过同一个程序的另一个线程,共享内存的另一个程序,以及其他一些我没有想到的东西来改变volatile const。 –

+1

是的,还有其他的东西可能会改变数值,例如硬件,但另一个线程可能不会改变'const'值,'volatile'在这里没有什么区别。 – alain

+0

在这种情况下,我将硬件作为操作系统的一部分改变它的数量。如果涉及指针,另一个线程可以改变一个值*你的线程*认为是'const',或者如果引用是C++端的话就可以改变它。如果它从一开始就像这样被声明为“const”,那么不应该这样做,但这就是为什么我说在这个特殊情况下我不确定其他来源。 –

1

volatile const结构可以通过实时时钟用于发布当前时间:

volatile const struct tm TheTimeNow; 
  • 时钟不能被你的程序改变了,所以它应该是const
  • 时钟在你和编译器的后面永久地和奇迹般地滴答,所以最好使用volatile来强制编译器总是获取当前时间而不是旧时间戳。

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

+0

RTC知道如何改变这个变量或在哪里找到它?它在哪里定义,这是变化的名称而不是别的? – AlastairG

+0

@AlastairG:基本上有两种方法:[内存映射I/O](https://en.wikipedia.org/wiki/Memory-mapped_I/O)在地址空间中的给定地址处,某些硬件仅表现为RAM 。但实际上它反映了端口引脚或其他端口的状态。通常有这个的微控制器。另一种方式是一些软件改变你背后的价值。操作系统可能会提供一个固定的内存位置,您可以在其中读取可用内存,磁盘空间等。这些对象的确切地址通常是固定且众所周知的,因此编译器/链接器可以解析它们。 – user5329483

+0

内存映射IO我明白了,但事实并非如此。它不能是软件更改(禁止缓冲区溢出或其他),因为它是静态的,因此无法在声明的文件外寻址。此外它也是const。这个数组只在其声明的文件中被读取。 – AlastairG