Is there a practical use for a `volatile restrict` pointer?
Asked Answered
E

1

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?

Exobiology answered 24/1, 2012 at 15:17 Comment(2)
Four? const volatile restrict ......Could you tell me what is the fourth qualifier? Sorry for my little knowledge >o<Abed
@Abed C11 defines a qualifier _Atomic.Exobiology
S
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.

Supernal answered 24/1, 2012 at 15:24 Comment(21)
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?Exobiology
@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.Jeanettajeanette
@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.Exobiology
@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.Jeanettajeanette
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...Supernal
OK, clarification added. Practical situations where such a pointer would be useful are still welcome...Exobiology
Including all the comments, and considering that there will be no more new answers, I accept this one.Exobiology
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?Heisenberg
@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.Supernal
@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...Heisenberg
...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.Heisenberg
@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.Heisenberg
@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.Supernal
@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...Heisenberg
...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.Heisenberg
@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.Supernal
@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...Heisenberg
...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.Heisenberg
@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.Supernal
@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...Heisenberg
...(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].Heisenberg

© 2022 - 2024 — McMap. All rights reserved.