const& amp; const volatile

时间:2011-01-04 10:48:37

标签: c embedded

如果我们每次更新新值时都将变量声明为volatile 如果我们将变量声明为const,则该变量的值将不会更改

然后const volatile int temp;
如上所述声明变量temp有什么用? 如果我们声明为const int temp会发生什么?

10 个答案:

答案 0 :(得分:120)

代码不允许更改标记为const volatile的对象(由于const限定符而引发错误) - 至少通过该特定名称/指针。

限定符的volatile部分意味着编译器无法优化或重新排序对象的访问权。

在嵌入式系统中,这通常用于访问可由硬件读取和更新的硬件寄存器,但写入(或可能是写入错误)没有意义。

示例可能是串行端口的状态寄存器。各个位将指示字符是否正在等待读取或者发送寄存器是否已准备好接受新字符(即, - 它为空)。每次读取此状态寄存器都可能导致不同的值,具体取决于串行端口硬件中发生的其他情况。

写入状态寄存器(取决于特定的硬件规格)是没有意义的,但是您需要确保每次读取寄存器都会导致硬件的实际读取 - 使用前一个的缓存值read不会告诉你硬件状态的变化。

一个简单的例子:

unsigned int const volatile *status_reg; // assume these are assigned to point to the 
unsigned char const volatile *recv_reg;  //   correct hardware addresses


#define UART_CHAR_READY 0x00000001

int get_next_char()
{
    while ((*status_reg & UART_CHAR_READY) == 0) {
        // do nothing but spin
    }

    return *recv_reg;
}

如果这些指针未标记为volatile,则可能会出现几个问题:

  • while循环测试可能只读取状态寄存器一次,因为编译器可以假设它所指向的任何内容都不会改变(在while循环测试或循环本身中没有任何东西可以改变它)。如果在UART硬件中没有字符等待时输入了该功能,则可能最终处于一个无限循环中,即使收到一个字符也从未停止过。
  • 接收寄存器的读取可以由编译器移动到while循环之前 - 再次因为函数中没有任何东西表明循环改变了*recv_reg,所以没有理由不能在进入循环之前阅读。

volatile限定符可确保编译器不执行这些优化。

答案 1 :(得分:33)

  • volatile会告诉编译器不要优化与变量相关的代码,通常当我们知道它可以从“外部”改变时,例如,另一个主题。
  • const将告诉编译器禁止程序修改变量的值。
  • const volatile是一个非常特别的东西,你可能会看到你生命中使用了0次(tm)。正如预期的那样,这意味着程序无法修改变量的值,但可以从外部修改该值,因此不会对变量执行优化。

答案 2 :(得分:23)

这不是因为变量是const,它可能在两个序列点之间没有变化。

Constness是您不会更改值的承诺,而不是不会更改该值。

答案 3 :(得分:7)

我需要在嵌入式应用程序中使用它,其中一些配置变量位于闪存区域中,可由引导加载程序更新。这些配置变量在运行时是'常量',但没有volatile限定符,编译器会优化这样的......

cantx.id = 0x10<<24 | CANID<<12 | 0;

...通过预先计算常量值并使用立即汇编指令,或从附近位置加载常量,以便忽略配置闪存区域中原始CANID值的任何更新。 CANID必须是不稳定的。

答案 4 :(得分:5)

在C中,const和volatile是类型限定符,这两个是独立的。

基本上,const意味着该程序无法修改该值。

而且volatile意味着该值会突然发生变化(可能来自程序之外)。

实际上,C标准提到了一个有效声明的例子,它既是const又是volatile。例子是

“extern const volatile int real_time_clock;”

其中real_time_clock可由硬件修改,但不能分配,递增或递减。

所以我们应该已经分别处理const和volatile了。此外,这些类型限定符也适用于struct,union,enum和typedef。

答案 5 :(得分:3)

const表示变量不能被c代码修改,而不能改变。这意味着没有指令可以写入变量,但其值可能仍会改变。

volatile表示变量可能随时更改,因此不会使用缓存值;每次访问变量都必须执行到其内存地址。

由于问题被标记为&#34;嵌入&#34;并假设temp是用户声明的变量,而不是与硬件相关的寄存器(因为这些通常在单独的.h文件中处理),请考虑:

嵌入式处理器,具有易失性读写数据存储器(RAM)和非易失性只读数据存储器,例如von​​-Neumann架构中的FLASH存储器,其中数据和程序空间共享公共数据和地址总线

如果声明const temp有一个值(至少与0不同),编译器会将该变量赋值给FLASH空间中的地址,因为即使它已分配给RAM地址,它仍然需要FLASH存储器来存储变量的初始值,使RAM地址浪费空间,因为所有操作都是只读的。

结果:

int temp;是存储在RAM中的变量,在启动时初始化为0(cstart),可以使用缓存值。

const int temp;是存储在(read-ony)FLASH中的变量,在编译时初始化为0,可以使用缓存值。

volatile int temp;是存储在RAM中的变量,在启动时初始化为0(cstart),不会使用缓存值。

const volatile int temp;是存储在(read-ony)FLASH中的变量,在编译时初始化为0,不会使用缓存值

这是有用的部分:

现在大多数嵌入式处理器都能够通过特殊功能模块更改其只读非易失性存储器,在这种情况下const int temp可以在运行时更改,而不是直接更改。换句话说,函数可以修改存储temp的地址处的值。

一个实际的例子是使用temp作为设备序列号。嵌入式处理器第一次运行时,temp将等于0(或声明的值),并且函数可以使用此事实在生产期间运行测试,如果成功,请求分配序列号并修改temp的值通过特殊函数。某些处理器具有特殊的地址范围,其中包含OTP(一次性可编程)存储器。

但差异来了:

如果const int temp是可修改的ID而不是一次性可编程序列号且未声明为volatile,则可能会使用缓存值,直到下一次启动,这意味着新ID可能不会有效,直到下次重启,或者更糟糕的是,某些功能可能会使用新值,而其他功能可能会使用较旧的缓存值,直到重新启动。 如果const int temp被声明为voltaile,则ID更改将立即生效。

答案 6 :(得分:3)

本文讨论了要组合const和volatile限定符的场景。

http://embeddedgurus.com/barr-code/2012/01/combining-cs-volatile-and-const-keywords/

答案 7 :(得分:3)

你可以一起使用const和volatile。例如,假设0x30是端口的值 只有外部条件改变,以下声明将防止任何可能性 意外的副作用:

const volatile char *port = (const volatile char *)0x30;

答案 8 :(得分:1)

当我们不希望程序更改变量时,我们对变量使用'const'关键字。而当我们声明一个变量'const volatile'时,我们告诉程序不要改变它,并且编译器可以从外部世界的输入中意外地改变这个变量。

答案 9 :(得分:1)

简单来说, “ const volatile”变量中的值不能通过编程方式进行修改,但可以通过硬件进行修改。这样做是为了防止任何编译器优化。