const&const volatile之间的区别

use*_*208 80 c embedded

如果我们volatile每次更新新值时
声明一个变量如果我们声明一个变量,const那么该变量的值将不会被更改

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

Mic*_*urr 126

标记为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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

volatile预选赛确保这些优化不是由编译器执行.

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

min*_*gos 34

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

  • 如果您使用嵌入式系统,您会经常看到这一点. (18认同)
  • 我曾经认为`volatile'变量通常是当你开始搞乱硬件而不是其他线程时会发生什么.在我看到`const volatile`使用的地方就是内存映射状态寄存器等. (10认同)
  • 当然,你是绝对正确的,多线程只是一个例子,但不是唯一的一个:). (2认同)

Ale*_* C. 24

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

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

  • 另外一个指出`const`数据不是"常数". (8认同)

pus*_*ect 7

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

cantx.id = 0x10<<24 | CANID<<12 | 0;
Run Code Online (Sandbox Code Playgroud)

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


Mic*_*sch 6

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

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

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

具有易失性读写数据存储器 (RAM) 和非易失性只读数据存储器的嵌入式处理器,例如冯诺依曼架构中的闪存,其中数据和程序空间共享公共数据和地址总线。

如果你声明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; 是存储在(只读)FLASH 中的变量,在编译器时初始化为 0,不会使用缓存值

有用的部分来了:

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

一个实际的例子是temp用于设备序列号。在第一时间嵌入式处理器运行时,temp将等于0(或声明的值)和一个函数可以利用这一点来生产期间,如果sucessfull运行测试,要求被分配的序列号和修改的值temp通过装置的特殊功能。一些处理器有一个特殊的地址范围,带有 OTP(一次性可编程)内存。

但不同之处在于:

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


use*_*536 6

在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;
Run Code Online (Sandbox Code Playgroud)