C / C ++:抛弃挥发性被认为有害吗?

时间:2011-09-09 21:22:06

标签: c++ c volatile

(与此问题Is It Safe to Cast Away volatile?相关,但不完全相同,因为该问题与特定实例有关)

是否曾有一种情况,即抛弃volatile

(一个特殊的例子:如果有一个声明的函数

void foo(long *pl);

我必须实施

void bar(volatile long *pl);

我的部分实现要求bar()调用foo(pl),然后好像我不能让它按原样运行,因为foo()编译和编译的假设bar()的调用者是不兼容的。)


作为推论,如果我有一个volatile变量v,并且我想用其他人的函数foo(&v)来呼叫void foo(long *pl),那个人告诉我这是安全的,我可以在调用之前抛出指针,我的直觉是告诉他们他们错了,因为没有办法保证,如果他们想支持使用volatile变量,他们应该将声明更改为void foo(volatile long *pl)。我们哪一个是正确的?

3 个答案:

答案 0 :(得分:17)

如果变量声明 volatile,那么未定义的行为就会抛弃volatile,就像它是未定义的行为一样远离声明为const的变量的const。见C标准的附录J.2:

  

在以下情况下,行为未定义:

     

...

     

- 尝试修改使用const限定类型定义的对象   使用非const限定类型的左值(6.7.3)。

     

- 尝试引用使用volatile限定类型定义的对象   使用具有非易失性限定类型的左值(6.7.3)。

但是,如果您只有一个volatile指针或volatile对非volatile变量的引用,那么您可以自由地抛弃volatile

volatile int i=0;
int j=0;

volatile int* pvi=&i; // ok
volatile int* pvj=&j; // ok can take a volatile pointer to a non-volatile object

int* pi=const_cast<int*>(pvi); // Danger Will Robinson! casting away true volatile
int* pj=const_cast<volatile int*>(pvj); // OK
*pi=3; // undefined behaviour, non-volatile access to volatile variable
*pj=3; // OK, j is not volatile

答案 1 :(得分:10)

一旦该值实际上不再是不稳定的,抛弃volatile就可以了。在SMP /多线程情况下,此可能在获取锁定(并传递内存屏障,这通常隐含在获取锁定中)后变为真。

因此,典型的模式是

 volatile long *pl = /*...*/;

 //
 {
      Lock scope(m_BigLock);   /// acquire lock
      long *p1nv = const_cast<long *>(p1);

      // do work
 } // release lock and forget about p1nv!

但是我可以提出一些其他情况,其中值不会变化。我不会在这里建议他们,因为我相信如果你知道你在做什么,你可以自己拿出来。否则,锁定方案看起来足够坚固,可以作为示例提供

答案 2 :(得分:5)

签名为foo(long *pl)时,程序员声明在执行long期间,他们不希望指向foo值在外部进行更改。如果编译器发出的代码由于缺少寄存器而多次取消引用指针,那么将指针传递给在整个调用中同时修改 long值的指针可能会导致错误行为它选择不在堆栈上存储第一个取消引用的值。例如,在:

void foo(long *pl) {

    char *buf = (char *) malloc((size_t) *pl);

    // ... busy work ...

    // Now zero out buf:
    long l;
    for (l = 0; l < *pl; ++l) {
        buf[l] = 0;
    }

    free(buf);
}
如果在忙碌工作期间foo值增加,

long可能会在“零输出buf”步骤中超出缓冲区。

如果foo()函数应该以原子方式递增long指向的pl值,那么函数取long *pl而不是{{}}是不正确的{1}}因为该函数明确要求volatile long *pl值的访问是序列点。如果long仅以原子方式递增,则该函数可能有效,但它不正确。

评论中已经提出了两个解决此问题的方法:

  1. 在过载foo()时将foo换过long *

    volatile long *
  2. inline void foo(volatile long *pvl) { long l = *pvl; foo(&l); *pvl = l; } 的声明更改为foo