【问题标题】:Pointer declared as constant as well as volatile指针声明为常量和易失
【发布时间】:2015-10-06 00:30:05
【问题描述】:

在阅读时我遇到了这种类型的声明和以下行 -

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

p 的值仅受外部条件的影响

我不明白外部条件是什么。还有这种声明的实际用途是什么?

【问题讨论】:

  • All const 表示 /you/ 无法更改。考虑int a=5;int& x=a; 你仍然可以使用a=6;(这会影响x!)
  • @AlecTeal a 在您的示例中未声明为 const 。你的意思是const int a=5;
  • @TuttiFruttiJacuzzi 我的意思是const int& x - 你不能通过x 改变aa 仍然可以改变!
  • 非常好的问题。我喜欢它

标签: c constants volatile


【解决方案1】:

const 表示您的程序流程不会修改p 所指向的内容。取消引用指针后任何修改值的尝试都将导致编译时错误:

*p = 'A'; // will not compile

请注意,这不是一个特别强的合同; 0x30 位置的值仍然可以通过别名非常量指针更改,p 除外:

volatile char *q = 0x30;
*q = 'A'; // will compile

另一种打破此合同的方法是从p 中丢弃const

*(volatile char *) p = 'A'; // will compile

但是,volatile 不排除可能由另一个线程、内核、异步信号处理程序或可以访问相同内存空间的外部设备引起的任何修改。这样编译器就不会错误地假设 p 指向的值不会改变,并且每次引用它时都会从内存中加载它:

/*
 The character at 0x30 will be read on every iteration,
 even if the compiler has proven that the program itself
 doesn't modify the value at that address.
*/
while (*p) {
    ...
}

如果编译器错误地优化了这样的构造,它可能会发出指令,从内存中只加载一次值,然后将其保存在寄存器中。寄存器本质上是一个独立的副本,对原始位置的任何更改都不会反映在那里,不用说,这可能会导致一些非常讨厌的错误。

【讨论】:

  • 内存和寄存器中的值有可能不同吗?这个声明会阻止它吗?
  • 是的,如果编译器决定将值保存在寄存器中并且外部设备在指定位置修改内存,则更改不会反映在寄存器中,因为它是一个单独的存储位置. volatile 是为了防止这种情况发生。
  • @ameyCU:请注意,这在特殊情况下是必需的——主要是硬件驱动程序和信号处理程序。 Even for multi-threaded programming it is of limited, if any, use。在“通用”编程中你不需要volatile——在这里,寄存器通过编译器与内存同步。
  • @DevSolar 好吧,我现在明白了,在我们不进行系统级编程之前,我们不需要这种类型。
  • @Blagovest Buyukliev:嗯,第一句话是错误的; const 并没有说你说的是什么。 (您可以添加“... via lvalue p”以使您的第一句话正确。)此外,在程序中修改 *p 不需要丢弃 const,可以使用不相关的左值;可以写 (char *) 0x30 = 'a'。参见 AlecTeal 的 cmets。关于这个问题。
【解决方案2】:

考虑一个只读硬件寄存器,例如您的网卡。

它可能会在程序的控制之外发生变化,因此不允许编译器将其值缓存在寄存器中或对其进行优化。因此,volatile

而且它是只读的,所以你不应该写它。因此,const

【讨论】:

    【解决方案3】:

    我们可以使用文章Introduction to the volatile keyword 说:

    当变量的值可能发生变化时,应将其声明为 volatile 不料。在实践中,只有三种类型的变量可以改变:

    • 内存映射外设寄存器
    • 由中断服务例程修改的全局变量
    • 多线程应用程序中的全局变量

    和:

    嵌入式系统包含真正的硬件,通常具有复杂的 外围设备。这些外设包含寄存器,其值可能 与程序流程异步更改。作为一个非常简单的例子, 考虑地址 0x1234 的 8 位状态寄存器。这是必需的 您轮询状态寄存器,直到它变为非零。中殿 不正确的实现如下:

    UINT1 * ptr = (UINT1 *) 0x1234;
    
    // Wait for register to become non-zero.
    while (*ptr == 0);
    // Do something else.
    

    一旦你打开优化器,这几乎肯定会失败, 因为编译器会生成看起来像的汇编语言 像这样:

    mov    ptr, #0x1234     mov    a, @ptr loop     bz    loop
    

    const 表示您的程序不会更改变量,但正如文章中所述,外部资源可以和 volatile 阻止它被优化掉。

    【讨论】:

      【解决方案4】:

      const 不会将变量设为常量。它只是让编译器拒绝一些写访问。这仍然可以写入变量(例如通过 const-casted 指针)。

      您可以将const 视为防止编码错误的保护措施。

      此声明确保您不会无意中写入p,同时告诉编译器不要优化访问(缓存、乱序执行(?),...),因为外部事件可能会写入@987654325 @。

      【讨论】:

      • 这个“...因为外部进程可能会写在p.”我觉得这很模棱两可。因为它可能并不意味着任何正在运行的程序意义上的“进程”。请参阅 Sourav Ghosh 的回答 (stackoverflow.com/a/31456549/694576) 了解原因。
      • @alk 你说的完全正确,谢谢!我不知道如何概括。我确实用“事件”替换了“过程”。我仍然不为这个公式感到自豪。
      【解决方案5】:

      首先,让我引用C11 标准第 §6.7.3 章中的示例,类型限定符

      声明的对象

      extern const volatile int real_time_clock;

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

      另外,相关脚注 (134),

      volatile 声明可用于描述对应于内存映射输入/输出端口的对象或由异步中断函数访问的对象。除非评估表达式的规则允许,否则对如此声明的对象的操作不应被实现“优化”或重新排序。

      这意味着,变量的值可以由硬件修改(通过内存映射),但不能“以编程方式”修改。

      所以,这里的优势是双重的,

      • 无论何时使用该值,都会从内存中读取(不允许缓存),为您提供最新更新的值(如果已更新)。
      • 该值不能被程序有意或无意地更改(覆盖)。

      【讨论】:

      • 如果一个变量被声明为volatile const char msg[] = "supercalifragilisticexpealidocious"; char const *const msg2 = "us";,并且使用实用程序在生成的二进制文件中找到msg的地址并对其进行修补,那么编译器是否需要同时读取该内存的内容在运行时(而不是假设它包含指示的字符串),并避免将其存储用于其他任何内容[例如 msg2 指向的“us”字面值]?
      • @supercat 这对我来说听起来像是一个全新的language-lawyer 问题。
      • @supercat 我认为编译器会假定它每次被引用时都包含指定的字符串,而不会重新检查它是否存在于赋值表达式的左侧。
      • @supercat:两个字符串文字是否是不同的实体在开始时的标准明确未指定。不,AFAICT msgmsg2 无法共享,因为您明确指出 msg 可能会因外部影响而改变——如果它们被共享,这也会改变非易失性 msg2
      • @ameyCU:如果变量不是volatile,编译器可以假设msg的内容在编译代码和代码检查这些内容之间不能改变;在某些系统硬件上可以保证msg 在代码开始执行和代码检查它之间不能改变。另一方面,有时让构建系统的一部分在二进制文件中搜索“supercalifraglisticexpialodicious”[或者可能是 GUID] 之类的键并替换其他内容可能会很有用。问题是是否需要来自 GCC 的代码才能“看到”这样的变化。
      【解决方案6】:

      *const volatile char *p=(const volatile char ) 0x30;
      什么意思:p的值只受外部条件的影响。

      从概念上讲,您可以将此类变量视为逻辑查看器。在概念上类似于门上的窥视孔。窥视孔允许您查看门另一侧的内容,但不允许您更改另一侧的内容 (const)。但是,门外的条件可以自行改变(它们是易变的)。你可以看到发生了什么,但你无法改变发生了什么。

      例如,在嵌入式系统中,有一些硬件寄存器旨在提供有关外部世界中发生的事件的状态信息。例如,用于检测 RPM 的光学编码器将在寄存器中设置一个值。每次旋转,它都会感应来自 LED 的光并修改硬件寄存器中的值。这就是外部条件的含义。在图片的另一边,即在您的代码(可能是 PID 控制回路)中,您可以读取此信息以用于对回路进行调整,但您无法更改此值,也不想更改。 (常量)

      那么从您的软件的角度来看,这说明了:

      【讨论】:

      • 我真的在想象窥视孔的事情。 :)
      • 哈哈,喜欢你在“意志”和volatile之间建立的虚假联系:D
      • btw "there"/"their" 紧接在前面
      • 关于外部条件的非常好的解释。
      猜你喜欢
      • 2012-04-04
      • 1970-01-01
      • 1970-01-01
      • 2011-04-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多