In fact, the very example given in the article shows that without the ACCESS_ONCE, the compiler moved the access outside of the loop---thereby ensuring that it's accessed just once, ironically!!! Whereas by using ACCESS_ONCE, we ensure that it's accessed as many times as the loop is iterated, not only once.
Basically this just does some kind of volatile access, and so the name should reflect that:
foo = VOLATILE_READ(bar);
Then the number of times it is accessed is implied from the agreement between abstract and actual semantics. In the abstract semantics, and expression is evaluated once for each invocation of whatever encloses it. If an expression is unconditionally evaluated in a loop that iterates 100 times, then it is evaluated 100 times. Hoisting it out of the loop would be an optimization which is forbidden by agreement with the abstract semantics.But that is just a consequence of putting a call to ACCESS_ONCE inside a loop. But it is retaining the property that it is only read from memory once per iteration.
It would be way more confusing if the programmer only wanted read once for the whole loop and still put the statement inside the loop.
I do think that knowing that it is being called in a loop context, implies that it should only be accessed once per iteration
for (;;) {
/* ... */
if (pthread_once(&once_var, once_routine))
/* error */;
/* ... */
}
Here, we are ensuring that once_routine is called exactly once.ACCESS_ONCE to me strongly suggests that the value is accessed once and cached forever. E.g. ACCESS_ONCE(x) could expand to something like:
({
static __flag;
static typeof(x) __cached;
if (!__flag) {
__cached = (x);
__flag = 1;
}
__cached;
})
ANSI Common Lisp has load-time-value. You can use this anywhere: (defun myfunc ()
(let ((foo (bar))
... ;; deeply nested
(let ((x (load-time-value (whatever))))
...))))
The load-time-value form is evaluated when the module is loaded and the value is stashed. Then whenever myfunc is called and that code is evaluated, the previously stashed value is retrieved; the (whatever) is not evaluated any more.But if you always access a certain object through casts to ((volatile )&x), I don't see how this should be different than accesses to a globally declared "volatile x" variable, as pointers are guaranteed to not change if casted hence-and-forth (§6.3.2.3/7).