On 2025-01-20 02:55, Jₑₙₛ Gustedt wrote:
Ah, so this is indeed quite another model, basically seeing a pointer
as a bit pattern that is the same.
Yes. It'd help if the standard made it clear what it considers to be
"the same" when it comes to [[reproducible]] and [[unsequenced]], using
wording similar to what I sent you earlier. This is unclear right now.
I have difficulties of understanding the usefullness of such an
interface
You're better off than I, as I'm still having difficulties knowing even
what unsequenced and reproducible mean, much less what they're useful
for. For example, I don't understand why the 'abs' and 'strcmp'
functions are not declared to be [[unsequenced]], as they seem to
satisfy the intended criteria for unsequenced functions.
By "extension", I assume you mean that [[unsequenced]] is intended to
be looser than __attribute__((const)). That is, every const function
is unsequenced, but the reverse is not true. This is what Bruno said
in the above quote.
Yes that's my idea.
Then I'm more confused than ever, unfortunately.
Here's another illustration of my confusion. 6.7.13.8.3 EXAMPLE 2 says
that given this definition:
typedef struct toto toto;
toto const *toto_zero(void) [[unsequenced]];
"a single call can be executed during thread startup and the return
value p and the value of the object *p of type toto const can be
cached." This means a compiler can optimize code like this:
toto const x = *toto_zero();
change_state();
toto y = *toto_zero();
as if it were this:
toto const x = *toto_zero();
change_state();
toto y = x;
That is, EXAMPLE 2 means an unsequenced function not only can examine
storage addressed by pointers passed to it: it also guarantees that when
it returns a pointer, storage addressed by that returned pointer always
has contents derivable from the function's arguments (including the
storage addressed by those arguments). In particular tot_zero's
[[unsequenced]] guarantees that change_state does not modify the storage
addressed by the pointer returned by toto_zero.
But this means that, contrary to stated intent, __attribute__((const))
does not imply [[unsequenced]]. For example:
toto x;
toto const *toto1(void) { return &x; }
void change_state() { memset(x, 1, sizeof x); }
toto1 is a const function, but it's not unsequenced because toto1
returns a pointer to storage that change_state modifies.
In other words, [[unsequenced]] allows some compiler optimizations that
__attribute__((const)) does not - contrary to stated intent.
I don't understand why
the output of 'assert' doesn't count as a side effect,
because the properties and applied optimizations at the call-site
should not change if somebody includes `<assert.h>` with a different
value for `NDEBUG` at a completely unrelated point in the code
I don't see why not. Suppose the program looks like this
[[unsequenced]] int foo(int);
int main(int argc, char **argv)
{
return argc == 1 ? 0 : foo (argc);
}
#undef NDEBUG
#include <assert.h>
int foo(int n)
{
assert (n != 1);
return n;
}
(foo could be defined in a separate compilation unit; that doesn't
change the analysis.)
If a compiler were allowed to move the call foo(argc) to the start of
main (and why not? the function is unsequenced...), that would change
the meaning of the program.
infloops are forbidden in const functions to allow those sorts of
optimizations. If infloops are also forbidden in unsequenced functions,
but 'assert's are allowed, a caller still can't do those optimizations.
This is another reason why it's important to state clearly the
*motivation* for [[unsequenced]] and and [[reproducible]]. The standard
doesn't do that, and the proposed change doesn't help. Without a clearer
explanation of why these attributes are present I fear we'll continue to
have misunderstandings and glitches.
I think the idea is that you cannot move
a function that never returns accross side effects because then a side
effect (such as a call to `printf`) could become invisible that would
otherwise be visible. (or the other way arround?)
That's the basic idea, yes; my example above does that.
the idea is that all of the things that are listed there are allowed
to happen (and are side effects in the strict sense of the standard),
but optimizations should not be concerned about them. For example the
possibility that a signal kicks in, should not have an influence on
code movement for these function calls.
Your phrasing above is clearer than what's in n3424 and I urge that
wording continue to be made clearer along these lines. We're not there
yet, though, as per my previous comments.
Let's not underestimate the difficulty of making these concepts clear.
Oh, no, indeed, I don't think they are. The text that now is in C23 is
probably one of the most delicate texts I ever wrote in my whole
career.
Oh, this means you weren't around when the sequence-point language got
added to the C standard in the 1980s. Although C89 was verrrrry
delicately worded nobody knew what the wording actually meant, not even
its authors. I would ask simple questions like "can f's and g's
instructions be interleaved when executing f(x)+g(y)?" and nobody could
justify their answer from the wording of the standard. For all I know
some of those problems still lurk in C23 (I gave up worrying about it).
My comments this week are in the hope that things have gotten better in
the standardization process.