开发者

C/C++: casting away volatile considered harmful?

(related to this question Is It Safe to Cast Away volatile?, but not quite the same, as that question relates to a specific instance)

Is there ever a case where casting away volatile is not considered a dangerous practice?

(o开发者_如何学Gone particular example: if there is a function declared

void foo(long *pl);

and I have to implement

void bar(volatile long *pl);

with part of my implementation requiring bar() to call foo(pl), then it seems like I can't get this to work as is, because the assumptions made by the compilation of foo() and the compilation of the caller of bar() are incompatible.)


As a corollary, if I have a volatile variable v, and I want to call foo(&v) with someone else's function void foo(long *pl), and that person tells me it's safe, I can just cast the pointer before the call, my instinct is to tell them they're wrong because there's no way to guarantee that, and that they should change the declaration to void foo(volatile long *pl) if they want to support the use of volatile variables. Which one of us is correct?


If the variable is declared volatile then it is undefined behaviour to cast away the volatile, just as it is undefined behaviour to cast away the const from a variable declared const. See Annex J.2 of the C Standard:

The behavior is undefined in the following circumstances:

...

— An attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type (6.7.3).

— An attempt is made to refer to an object defined with a volatile-qualified type through use of an lvalue with non-volatile-qualified type (6.7.3).

If, however, you just have a volatile pointer or a volatile reference to a non-volatile variable then you can freely cast away 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


Casting away volatile would be ok, once the value is in fact no longer volatile. In SMP/multi-threading situations, this could become true after acquiring a lock (and passing a memory barrier, which is most often implicit in acquiring the lock).

So a typical pattern for this would be

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

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

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

But I could come up with a number of other scenarios in which values stop being volatile. I won't suggest them here, as I'm sure you can come up with them yourself, if you know what you're doing. Otherwise, the locking scenarios seems solid enough to provide as an example


With a signature of foo(long *pl), the programmer is declaring that they are not expecting the pointed-to long value to change externally during the execution of foo. Passing a pointer to a long value that is being concurrently modified throughout an invocation might even lead to erroneous behavior if the compiler emits code that dereferences the pointer multiple times due to lack of registers and by it choosing not to store the value of the first dereference on the stack. For example, in:

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 could overrun the buffer in the "zero out buf" step if the long value is increased while the busy work is being performed.

If the foo() function is supposed to atomically increment the long value pointed to by pl, then it would be incorrect for the function to take long *pl and not volatile long *pl because the function clearly requires that accesses of the long value be a sequence point. If foo() only atomically incremented, the function might work, but it would not be correct.

Two solutions to this problem have already been suggested in comments:

  1. Wrap foo taking long * in an overload taking volatile long *:

    inline void foo(volatile long *pvl) {
        long l = *pvl;
        foo(&l);
        *pvl = l;
    }
    
  2. Change the declaration of foo to void foo(volatile long *pvl).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜