15

I can see practical use for a const volatile qualified variable, like

const volatile uint64_t seconds_since_1970;

if an underlying hardware mechanism updates the value every second, but the variable is not writable in the (possibly embedded) hardware. And since all three (four in C11) type qualifiers are considered independent, all combinations do seem to be allowed. But I'm at a loss imagining a real-life situation where a restrict volatile qualified pointer would really make sense:

uint32_t * restrict volatile pointer_to_some_uint32;

[EDIT: To clarify: Both volatile and restrict apply to the pointer, not to the object pointed to!]

Is this a construct allowed by the language but useless by itself, or am I missing some application area where this can be valuable?

Mat
  • 202,337
  • 40
  • 393
  • 406
Johan Bezem
  • 2,582
  • 1
  • 20
  • 47

1 Answers1

12

Without restrict, a non-volatile pointer could alias a volatile pointer. Thus, after every modification of an object through the volatile pointer, register-cached values of all potentially-pointer-referenced objects of the same type must be discarded.

With restrict, you can tell the compiler the volatile pointer will not alias, so that the overhead of volatile only applies to the object pointed to, and not all other objects of the same type that might be accessible via pointers.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • In my example the `volatile` applies to the pointer, not to the object it points to. I know that using `volatile uint32_t * restrict pointer;` may be very useful indeed, but declaring the pointer itself both `volatile` and `restrict` doesn't move the `volatile` to the object pointed to, does it? – Johan Bezem Jan 24 '12 at 15:40
  • 1
    @Johan: Consider that perhaps an underlying hardware mechanism updates `pointer_to_some_uint32` from time to time (which is far-fetched, but an implementation could do it), and that furthermore the address it writes there is guaranteed not to be aliased by any other pointer. Then it's `volatile restrict`. Normally though, `restrict` is good for function arguments and `volatile` is pointless for local variables, so you've picked an example that's unlikely to be used even though it's not strictly useless. – Steve Jessop Jan 24 '12 at 15:51
  • @SteveJessop Yes, that's my interpretation as well. The only thing I've come up with is some kind of garbage collector rearranging memory and running in a different thread/process context. Such pointers could validly be both `volatile` (since the GC might change the pointers) and `restrict` if no other pointers point to the GC-admin sections of the memory chunks maintained by the GC. But the answer from @R.. seems to indicate the object pointed to "inherits" the `volatile` ("the overhead of volatile only applies to the object pointed to"), which is incorrect IMHO. – Johan Bezem Jan 24 '12 at 16:03
  • @Johan: I suspect he just misread the second example in the question -- I did on the first attempt, and it wasn't until I saw your first comment above that I realised what you're asking. – Steve Jessop Jan 24 '12 at 16:08
  • Yes, I misread it. In this case the `volatile` and `restrict` really have no interaction, but they're each useful the same ways they would always be useful... – R.. GitHub STOP HELPING ICE Jan 24 '12 at 16:10
  • OK, clarification added. Practical situations where such a pointer would be useful are still welcome... – Johan Bezem Jan 24 '12 at 16:12
  • Including all the comments, and considering that there will be no more new answers, I accept this one. – Johan Bezem May 07 '14 at 12:25
  • Would that imply that things like hardware registers should generally be qualified as `volatile restrict` rather than merely `volatile`? Given e.g. `#define OUT_REG ((volatile restrict uint32_t*)0x12345678) void out_twice(uint32_t *src) { OUT_REG = (*src)+1; OUT_REG = (*src)+1; }` a compiler need only read `src` once, but without the `restrict` it would have to read it twice? – supercat Apr 29 '15 at 15:40
  • @supercat: I think that's correct, but of course the `out_twice` function could use a local `uint32_t tmp = *src;` before either write-out. – R.. GitHub STOP HELPING ICE Aug 16 '15 at 01:21
  • @R..: If the I/O pointer isn't `volatile restrict` and no temporary variable is used, the compiler would have to read the source twice. If a temporary variable is used, the compiler would be required to read it only once. By my understanding, if `volatile restrict` is used (but no temporary variable) a compiler would have the option to have the second write either re-read the variable or use the previously-read value at its convenience. For the simple code above, re-reading the variable likely wouldn't be the best choice, but if there were enough code between the two uses of `*src`... – supercat Aug 16 '15 at 20:55
  • ...re-reading twice might be cheaper than reading once, spilling the value out to memory, and the reloading the value later. The programmer knows what's necessary; the compiler knows what's fastest. If the programmer can indicate to the compiler that either approach will work, the compiler may be better equipped than the programmer to decide which approach should be used. – supercat Aug 16 '15 at 20:58
  • @R..: I wonder if there would be a good way of encouraging the providers of platform-dependent header files to use `volatile restrict` rather than `volatile`? If `volatile restrict` would entitle a compiler to assume that a pointer can't alias anything which is used as a plain (not volatile) lvalue, that would allow a lot of optimizations which would not otherwise be possible. If `x` is a global variable, saying `OUTPORT = x; OUTPORT = x|1; OUTPORT = x;` would require loading global variable `x` three times if `OUTPUT` isn't restrict, but I think with `retrict` would only need one. – supercat Aug 16 '15 at 21:50
  • @supercat: I don't think the problem arises if `x` is a global variable; it can 'prove' non-aliasing on its own. It's only a problem when the access is via a pointer. – R.. GitHub STOP HELPING ICE Aug 17 '15 at 01:54
  • @R..: Given `extern uint32_t x; #define PORTA (*((uint32_t volatile *)0x1234))`, how can a compiler know they won't alias? By my understanding, if a compiler and linke define a means by which another module can declare `x` and force it to be assigned address 0x1234, then given `x=1; PORTA = 2; PORTA = x+4;` the compiler would be required to set PORTA to 6 rather than 5 [i.e. recognize that the write to PORTA was aliased to x]. Though thinking about it more leaves me unsure of whether `restrict` on the pointer cast would help, since changes made via `restrict` pointers must be... – supercat Aug 17 '15 at 15:11
  • ...globally visible once the pointers in question go out of scope. Given `{uint32_t volatile * restrict PORTA = (uint32_t volatile*)0x1234; x=1; *PORTA=2; *PORTA=x+4;}` the compiler would be entitled to assume that the address of `x` isn't 0x1234 since it would be accessed during the lifetime of PORTA, but for a direct-declared constant there is no meaningful lifetime for which writes could be deferred. – supercat Aug 17 '15 at 15:15
  • @supercat: I would consider it reasonable to assume user-provided fixed addresses do not overlap with objects created by the compiler, but I can see where special linker features would invalidate that assumption, so you're right that compilers might opt not to make this assumption. – R.. GitHub STOP HELPING ICE Aug 17 '15 at 15:48
  • @R..: If C code could be linked in such a way as to force an external object to a fixed address, I would consider it grossly unreasonable for a compiler to assume that an `extern volatile` object will not alias or share an address with another `extern volatile` object or a pointer-to-volatile formed by casting an integer. If the `extern` object isn't volatile, the situation is a bit less clear. My interpretation would be that a non-volatile `extern` object should be allowed to share an address with anything else if it's never accessed, and a compiler should not assume anything about... – supercat Aug 17 '15 at 18:50
  • ...whether its address could compare equal to anything else, but it's probably reasonable for a compiler to assume that a write to a hard-coded address will not affect the content of any non-`volatile`-qualified object. I don't know that the Standard addresses the situation, however. – supercat Aug 17 '15 at 18:52
  • @supercat: It's totally valid to modify a non-volatile object via a pointer-to-volatile, so if such explicit-address-assignment is allowed by the implementation, the compiler could not assume the volatile access via an explicit address does not alias a non-volatile object of the same type. – R.. GitHub STOP HELPING ICE Aug 17 '15 at 20:20
  • @R..: I'd posit that it's legitimate for a compiler to assume that no `extern` object will alias any other object unless it is declared `volatile`. Given `extern uint32_t foo,bar; for (int i=0; i<100; i++) { foo+=bar; bar+=foo;}` I would say that a compiler would be entitled to load `foo` and `bar` into registers, run the loop on those registers, and store them back, without having to worry about the possibility that `foo` and `bar` might be the same variable. I would posit that it would be legitimate for a compiler to specify that... – supercat Aug 17 '15 at 20:29
  • ...`(uint32_t volatile *)0x1234` where `N` is a non-zero integer constant, will be translated as `extern volatile uint32_t __PTR_00001234;`, and the linker will behave as though it has built-in definitions for `PTR_00000000` through `PTR_FFFFFFFF`. Under such semantics, the compiler would be entitled to assume that such objects do not overlay any other, non-volatile, externally-defined objects. Is there anything in the C Standard which would contradict such an implementation [btw, I've seen a build system which used predefined variables IIRC __PORT_0000 through __PORT_FFFF for I/O]. – supercat Aug 17 '15 at 20:33