const和const volatile之间的区别


89

如果我们在volatile每次更新新值时都
声明一个变量,如果我们声明一个变量,const则该变量的值将保持不变

那么,如上所述const volatile int temp;
声明变量temp有什么用?
如果声明为,会发生什么const int temp


您不会const volatile int temp;在块范围内使用(即,在内{ }),那里没有用。
MM

Answers:


145

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

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

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

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

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

一个简单的例子:

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预选赛确保这些优化不是由编译器执行。


5
+1以作解释。我有一个问题:const volatile方法如何?如果我有一个可以被许多线程访问的类(尽管访问与互斥锁同步),我的const方法是否也必须是可变的(因为某些变量可以被其他线程更改)
Sasa 2012年

39
  • volatile 会告诉编译器不要优化与该变量相关的代码,通常是在我们知道可以从“外部”更改该变量时(例如,由另一个线程更改)。
  • const 会告诉编译器禁止程序修改变量的值。
  • const volatile这是非常特殊的事情,您可能会发现生命(tm)中使用了0次。可以预期,这意味着程序无法修改变量的值,但是可以从外部修改该值,因此不会对该变量进行优化。

12
我以为volatile变量通常是当您开始与硬件而不是其他线程打交道时发生的事情。我见过的地方const volatile是内存映射状态寄存器之类的东西。
正确的意见

2
当然,您是完全正确的,多线程只是一个示例,但不是唯一的一个示例:)。
mingos 2011年

25
如果您使用嵌入式系统,则会经常看到这种情况。
Daniel Grillo

28

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

不变性是您做出的不更改值的承诺,而不是值不会更改。


9
再加上一个指出const数据不是“恒定的”。
Bogdan Alexandru

7

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

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

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


7

在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

您可以将const和volatile一起使用。例如,如果假设0x30是仅由外部条件更改的端口的值,则以下声明将防止任何可能的副作用:

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

4

const表示该变量不能用C代码修改,不是不能更改。这意味着没有指令可以写入变量,但其值可能仍会更改。

volatile意味着该变量可以随时更改,因此不可以使用任何缓存的值;每次对变量的访问都必须执行到其内存地址。

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

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

如果声明const temp具有值(至少等于0),则编译器会将变量分配给FLASH空间中的地址,因为即使将其分配给RAM地址,它仍需要FLASH存储器来存储初始值因为所有操作都是只读的,所以使RAM地址浪费空间。

结果:

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

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

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

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为IS ,则ID更改将立即生效。


哇,答案很长


2

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


1

当我们不希望程序更改变量时,可以对变量使用“ const”关键字。而当我们声明一个变量“ const volatile”时,我们是在告诉程序不要对其进行更改,并且告诉编译器该变量可能会因来自外界的输入而意外更改。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.