Discussion:
is "x *= ++f * ++f" a valid statement ?
(too old to reply)
Bonita Montero
2024-05-23 14:14:16 UTC
Permalink
Is "x *= ++f * ++f" a valid statement ?
Or is there implementation defined behaviour ?
Marcel Mueller
2024-05-23 17:11:19 UTC
Permalink
Post by Bonita Montero
Is "x *= ++f * ++f" a valid statement ?
Or is there implementation defined behaviour ?
AFAIK depending on the sequence of execution for arguments (of operator
*) is UB.


Marcel
James Kuyper
2024-05-23 23:08:43 UTC
Permalink
Post by Marcel Mueller
Post by Bonita Montero
Is "x *= ++f * ++f" a valid statement ?
Or is there implementation defined behaviour ?
AFAIK depending on the sequence of execution for arguments (of operator
*) is UB.
You're close to the right idea, but you've got the wording wrong. When
support for threading was added to C++ many years ago, they introduced
new jargon to make it possible to talk more precisely about which events
were, or were not, guaranteed to be sequenced before other events. That
jargon allowed them to more precisely specify what you need to do when
writing multi-threaded code, in order to avoid problems.

The key statement is:

"Except where noted, evaluations of operands of individual operators and
of subexpressions of individual expressions are unsequenced." (6.9.1p10)

Both ++f expressions are sub-expressions of the multiplication
expression. Thus, the executions are unsequenced, which is not, in
itself, a problem. However, they both have a side effect on the same
memory location, and that is a problem, because that same clause goes on
to say:

"If a side effect on a memory location (6.7.1) is unsequenced relative
to either another side effect on the same memory location or a value
computation using the value of any object in the same memory location,
and they are not potentially concurrent (6.9.2), the behavior is undefined."

These two expressions are not potentially concurrent: they are in the
expression, so they must be in the same thread, and if they are in a
signal handler, they must both be in the same signal handler. Therefore,
the expression has undefined behavior.

Before support for multi-threading was added to the language, the
behavior was undefined, but the clause that said so was much harder to
understand, and was frequently the subject of arguments about how it
applied. It takes time to understand the new jargon, but once you do,
the relevant rules are much easier to understand.
Stuart Redmann
2024-05-25 06:50:58 UTC
Permalink
Post by James Kuyper
Post by Marcel Mueller
Post by Bonita Montero
Is "x *= ++f * ++f" a valid statement ?
Or is there implementation defined behaviour ?
AFAIK depending on the sequence of execution for arguments (of operator
*) is UB.
You're close to the right idea, but you've got the wording wrong. When
support for threading was added to C++ many years ago, they introduced
new jargon to make it possible to talk more precisely about which events
were, or were not, guaranteed to be sequenced before other events. That
jargon allowed them to more precisely specify what you need to do when
writing multi-threaded code, in order to avoid problems.
"Except where noted, evaluations of operands of individual operators and
of subexpressions of individual expressions are unsequenced." (6.9.1p10)
Do I remember correctly that operator<< is one of the operators that
explicitly is mentioned to introduce sequence points, so

std::cout << ++f << ++f;

is legal, but ++f * ++f is UB?

TIA
Stuart
Marcel Mueller
2024-05-25 08:00:10 UTC
Permalink
Post by Stuart Redmann
Do I remember correctly that operator<< is one of the operators that
explicitly is mentioned to introduce sequence points, so
std::cout << ++f << ++f;
is legal, but ++f * ++f is UB?
AFAIK only constexpr functions can become part of a single expression.


Marcel
David Brown
2024-05-25 10:31:46 UTC
Permalink
Post by Stuart Redmann
Post by James Kuyper
Post by Marcel Mueller
Post by Bonita Montero
Is "x *= ++f * ++f" a valid statement ?
Or is there implementation defined behaviour ?
AFAIK depending on the sequence of execution for arguments (of operator
*) is UB.
You're close to the right idea, but you've got the wording wrong. When
support for threading was added to C++ many years ago, they introduced
new jargon to make it possible to talk more precisely about which events
were, or were not, guaranteed to be sequenced before other events. That
jargon allowed them to more precisely specify what you need to do when
writing multi-threaded code, in order to avoid problems.
"Except where noted, evaluations of operands of individual operators and
of subexpressions of individual expressions are unsequenced." (6.9.1p10)
Do I remember correctly that operator<< is one of the operators that
explicitly is mentioned to introduce sequence points, so
std::cout << ++f << ++f;
is legal, but ++f * ++f is UB?
TIA
Stuart
I don't believe the C++ standards use the term "sequence point", at
least not as found in the C standards. But it is correct that in C++17,
for E1 << E2, the evaluation of E1 is sequenced before the evaluation of
E2. (They are unsequenced in C and earlier C++ standards.)

So I believe you are correct that for C++17 onwards, "std::cout << ++f
<< ++f;" has fully defined behaviour while "std::cout << ++f * ++f;" has
undefined behaviour.

(But it's worth noting that this is for C++17 onwards - many people use
older C++ standards.)
Sam
2024-05-25 10:39:32 UTC
Permalink
Post by Stuart Redmann
Do I remember correctly that operator<< is one of the operators that
explicitly is mentioned to introduce sequence points, so
std::cout << ++f << ++f;
is legal, but ++f * ++f is UB?
Yes, but only as of C++17. Both are UB prior to C++17.
Andrey Tarasevich
2024-06-19 01:53:03 UTC
Permalink
Post by Bonita Montero
Is "x *= ++f * ++f" a valid statement ?
Or is there implementation defined behaviour ?
The question is meaningless without knowing the types of objects
involved. It has no specific answer
--
Best regards,
Andrey
Bonita Montero
2024-06-19 15:40:44 UTC
Permalink
Post by Andrey Tarasevich
Post by Bonita Montero
Is "x *= ++f * ++f" a valid statement ?
Or is there implementation defined behaviour ?
The question is meaningless without knowing the types of objects
involved. It has no specific answer
You feel uncertain for nothing.
olcott
2024-06-21 21:55:20 UTC
Permalink
Post by Andrey Tarasevich
Post by Bonita Montero
Is "x *= ++f * ++f" a valid statement ?
Or is there implementation defined behaviour ?
The question is meaningless without knowing the types of objects
involved. It has no specific answer
If it works correctly for int then it is
syntactically correct:
int x = 1;
int f = 1;
x = 1 * (2 * 3);

++f could be defined as exit(0) for some UDT.
--
Copyright 2024 Olcott "Talent hits a target no one else can hit; Genius
hits a target no one else can see." Arthur Schopenhauer
Richard Damon
2024-06-21 22:10:50 UTC
Permalink
Post by olcott
Post by Andrey Tarasevich
Post by Bonita Montero
Is "x *= ++f * ++f" a valid statement ?
Or is there implementation defined behaviour ?
The question is meaningless without knowing the types of objects
involved. It has no specific answer
If it works correctly for int then it is
int x = 1;
int f = 1;
x = 1 * (2 * 3);
++f could be defined as exit(0) for some UDT.
But, the DEFINITON of ++ doesn't requring that one of the f's are
incremented first, then you use the value, then the othero one. (unless
the rules were actually changed).

x *= ++f + ++f

could be implemented as

f = f+1;
f = f+1;

x *= f * f;

it could even be done as
;
tf1 = f+1
tf2 = f+1;

x *= tf1 + tf2;

f = tf1;
f = tf2;

so, we have no promise that f even increases by 2


At least this is what it could be under the original rules. I would have
to see if it was tightened when threading was allowed.
olcott
2024-06-21 23:06:53 UTC
Permalink
Post by Richard Damon
Post by olcott
Post by Andrey Tarasevich
Post by Bonita Montero
Is "x *= ++f * ++f" a valid statement ?
Or is there implementation defined behaviour ?
The question is meaningless without knowing the types of objects
involved. It has no specific answer
If it works correctly for int then it is
int x = 1;
int f = 1;
x = 1 * (2 * 3);
++f could be defined as exit(0) for some UDT.
But, the DEFINITON of ++ doesn't requring that one of the f's are
incremented first, then you use the value, then the othero one. (unless
the rules were actually changed).
When we assume the *= assigns
the result of the RHS * the LHS to the LHS
"x *= ++f * ++f"

means x = x * (++f * ++f)
thus cannot have implementation defined behavior.
--
Copyright 2024 Olcott "Talent hits a target no one else can hit; Genius
hits a target no one else can see." Arthur Schopenhauer
Richard Damon
2024-06-22 00:14:08 UTC
Permalink
Post by olcott
Post by Richard Damon
Post by olcott
Post by Andrey Tarasevich
Post by Bonita Montero
Is "x *= ++f * ++f" a valid statement ?
Or is there implementation defined behaviour ?
The question is meaningless without knowing the types of objects
involved. It has no specific answer
If it works correctly for int then it is
int x = 1;
int f = 1;
x = 1 * (2 * 3);
++f could be defined as exit(0) for some UDT.
But, the DEFINITON of ++ doesn't requring that one of the f's are
incremented first, then you use the value, then the othero one.
(unless the rules were actually changed).
When we assume the *= assigns
the result of the RHS * the LHS to the LHS
"x *= ++f * ++f"
means x = x * (++f * ++f)
thus cannot have implementation defined behavior.
Sure it can, because the order of the parts of the operation of the two
++f is unspecified. In fact, the implementation doesn't need to define
it at all, it is just unspecified (and it used to invoke Undefined
Behavior, I am not sure if that clause is still there)

Detail explaination of the operation of ++f has effectively 4 steps

1) read the value of f
2) increment the value read
3) write that value back into f
4) use that value in the expression

Sequence wise, we have that 1 is before 2 is before 3, and 2 is before
4, but 3 and 4 are not ordered with respect to one another.

And, the sequence of operations between the two different ++f operations
have no sequeenc between each other, only that both of their step 4 are
working at the same time to make the final result of that operation.

Thus, both reads of the value might occur before either write backs of
either updated value, and thus f is only increased by 1 by the statement.

In fact, the optimizer could well see that option and just read the
value once increment it once, and write it back just once.
Keith Thompson
2024-06-22 00:35:52 UTC
Permalink
Post by olcott
Post by Richard Damon
Post by olcott
Post by Andrey Tarasevich
Post by Bonita Montero
Is "x *= ++f * ++f" a valid statement ?
Or is there implementation defined behaviour ?
The question is meaningless without knowing the types of objects
involved. It has no specific answer
If it works correctly for int then it is
int x = 1;
int f = 1;
x = 1 * (2 * 3);
Yes, it's syntactically valid, but that wasn't the question.
Post by olcott
Post by Richard Damon
Post by olcott
++f could be defined as exit(0) for some UDT.
But, the DEFINITON of ++ doesn't requring that one of the f's are
incremented first, then you use the value, then the othero
one. (unless the rules were actually changed).
When we assume the *= assigns
the result of the RHS * the LHS to the LHS
"x *= ++f * ++f"
means x = x * (++f * ++f)
thus cannot have implementation defined behavior.
We don't have to assume anything. The meaning of "*=" is well known
(and you left out the fact that x is evaluated only once).

It doesn't have implementation-defined behavior. It has undefined
behavior. The C++ standard says so:

If a side effect on a memory location is unsequenced relative to
either another side effect on the same memory location or a value
computation using the value of any object in the same memory
location, and they are not potentially concurrent, the behavior is
undefined.

If x and f are objects of type int, then the evaluation of `++f * ++f`
has undefined behavior because the two modifications of f are
unsequenced. There isn't some limited number of possible behaviors.
The standard says nothing about what happens when the expression is
evaluated.
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
olcott
2024-06-22 03:58:48 UTC
Permalink
Post by Keith Thompson
Post by olcott
Post by Richard Damon
Post by olcott
Post by Andrey Tarasevich
Post by Bonita Montero
Is "x *= ++f * ++f" a valid statement ?
Or is there implementation defined behaviour ?
The question is meaningless without knowing the types of objects
involved. It has no specific answer
If it works correctly for int then it is
int x = 1;
int f = 1;
x = 1 * (2 * 3);
Yes, it's syntactically valid, but that wasn't the question.
Post by olcott
Post by Richard Damon
Post by olcott
++f could be defined as exit(0) for some UDT.
But, the DEFINITON of ++ doesn't requring that one of the f's are
incremented first, then you use the value, then the othero
one. (unless the rules were actually changed).
When we assume the *= assigns
the result of the RHS * the LHS to the LHS
"x *= ++f * ++f"
means x = x * (++f * ++f)
thus cannot have implementation defined behavior.
We don't have to assume anything. The meaning of "*=" is well known
(and you left out the fact that x is evaluated only once).
It doesn't have implementation-defined behavior. It has undefined
If a side effect on a memory location is unsequenced relative to
either another side effect on the same memory location or a value
computation using the value of any object in the same memory
location, and they are not potentially concurrent, the behavior is
undefined.
If x and f are objects of type int, then the evaluation of `++f * ++f`
has undefined behavior because the two modifications of f are
unsequenced. There isn't some limited number of possible behaviors.
The standard says nothing about what happens when the expression is
evaluated.
That is weird I wold have chosen left to right sequence.
I thought that the order of arithmetic operations specifies
left to right sequence.
--
Copyright 2024 Olcott "Talent hits a target no one else can hit; Genius
hits a target no one else can see." Arthur Schopenhauer
Keith Thompson
2024-06-22 06:18:26 UTC
Permalink
olcott <***@gmail.com> writes:
[...]
Post by olcott
That is weird I wold have chosen left to right sequence.
I thought that the order of arithmetic operations specifies
left to right sequence.
You may well have thought that. You were wrong. Do you understand that
now?
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
olcott
2024-06-22 12:38:33 UTC
Permalink
Post by Keith Thompson
[...]
Post by olcott
That is weird I wold have chosen left to right sequence.
I thought that the order of arithmetic operations specifies
left to right sequence.
You may well have thought that. You were wrong. Do you understand that
now?
"x *= ++f * ++f"
int x = 5;
int y = 3;

For the calculation is question is seems to make no difference to the
result.
x = 5 * (4 * 5)
x = 5 * (5 * 4)
--
Copyright 2024 Olcott "Talent hits a target no one else can hit; Genius
hits a target no one else can see." Arthur Schopenhauer
Richard Damon
2024-06-22 13:45:24 UTC
Permalink
Post by Bonita Montero
[...]
Post by olcott
That is weird I wold have chosen left to right sequence.
I thought that the order of arithmetic operations specifies
left to right sequence.
You may well have thought that.  You were wrong.  Do you understand that
now?
"x *= ++f * ++f"
int x = 5;
int y = 3;
For the calculation is question is seems to make no difference to the
result.
x = 5 * (4 * 5)
x = 5 * (5 * 4)
In this case no, but if the operation was - it would,

And, as pointed out there is no requirement on the ordering of even the
sub-parts except determinism (we can't use a value we haven't computed
yet). This means the ++ can be interleaved.

And, because of the EXPLICIT requirement on updates to a value needing
to be ordered to avoid undefined behavior, the compiler, seeing that
Undefined Behavior exist there can do ANYTHING it wants with that code.
olcott
2024-06-22 14:13:26 UTC
Permalink
Post by Richard Damon
Post by Bonita Montero
[...]
Post by olcott
That is weird I wold have chosen left to right sequence.
I thought that the order of arithmetic operations specifies
left to right sequence.
You may well have thought that.  You were wrong.  Do you understand that
now?
"x *= ++f * ++f"
int x = 5;
int y = 3;
For the calculation is question is seems to make no difference to the
result.
x = 5 * (4 * 5)
x = 5 * (5 * 4)
In this case no, but if the operation was - it would,
And, as pointed out there is no requirement on the ordering of even the
sub-parts except determinism (we can't use a value we haven't computed
yet). This means the ++ can be interleaved.
And, because of the EXPLICIT requirement on updates to a value needing
to be ordered to avoid undefined behavior,
In other words you fail to comprehend that: (5 * 4) == (4 * 5)
Post by Richard Damon
the compiler, seeing that
Undefined Behavior exist there can do ANYTHING it wants with that code.
--
Copyright 2024 Olcott "Talent hits a target no one else can hit; Genius
hits a target no one else can see." Arthur Schopenhauer
Richard Damon
2024-06-22 14:23:35 UTC
Permalink
Post by olcott
Post by Richard Damon
Post by Bonita Montero
[...]
Post by olcott
That is weird I wold have chosen left to right sequence.
I thought that the order of arithmetic operations specifies
left to right sequence.
You may well have thought that.  You were wrong.  Do you understand that
now?
"x *= ++f * ++f"
int x = 5;
int y = 3;
For the calculation is question is seems to make no difference to the
result.
x = 5 * (4 * 5)
x = 5 * (5 * 4)
In this case no, but if the operation was - it would,
And, as pointed out there is no requirement on the ordering of even
the sub-parts except determinism (we can't use a value we haven't
computed yet). This means the ++ can be interleaved.
And, because of the EXPLICIT requirement on updates to a value needing
to be ordered to avoid undefined behavior,
In other words you fail to comprehend that: (5 * 4) == (4 * 5)
no, the issue is that the two ++f are unsequenced, so one possible
result (ignore the allowance of total undefined behavior) is (4*4), sine
the code COULD be compiled to the equivalent of:

int __t1 = f;
int __t2 = __t1+1;
int __t3 = f;
int __t4 = __t3+1;
x *= __t2 * __t4;
f = __t2;
f = __t4;
Post by olcott
Post by Richard Damon
the compiler, seeing that Undefined Behavior exist there can do
ANYTHING it wants with that code.
Mikko
2024-06-23 08:48:54 UTC
Permalink
Post by Richard Damon
Post by Bonita Montero
[...]
Post by olcott
That is weird I wold have chosen left to right sequence.
I thought that the order of arithmetic operations specifies
left to right sequence.
You may well have thought that.  You were wrong.  Do you understand that
now?
"x *= ++f * ++f"
int x = 5;
int y = 3;
For the calculation is question is seems to make no difference to the result.
x = 5 * (4 * 5)
x = 5 * (5 * 4)
In this case no, but if the operation was - it would,
And, as pointed out there is no requirement on the ordering of even the
sub-parts except determinism (we can't use a value we haven't computed
yet). This means the ++ can be interleaved.
And, because of the EXPLICIT requirement on updates to a value needing
to be ordered to avoid undefined behavior, the compiler, seeing that
Undefined Behavior exist there can do ANYTHING it wants with that code.
For example, a valid interpretation is to store the value 17 in z.
--
Mikko
Keith Thompson
2024-06-23 00:54:11 UTC
Permalink
Post by Bonita Montero
Post by Keith Thompson
[...]
Post by olcott
That is weird I wold have chosen left to right sequence.
I thought that the order of arithmetic operations specifies
left to right sequence.
You may well have thought that. You were wrong. Do you understand
that now?
"x *= ++f * ++f"
int x = 5;
int y = 3;
For the calculation is question is seems to make no difference to the
result.
x = 5 * (4 * 5)
x = 5 * (5 * 4)
So no, you don't understand. Do you want to?

I already quoted the wording from the ISO C++ standard (the document
that defines the language) that clearly states that the behavior
is undefined. I'm not trying to convince you that it should or
shouldn't be, I'm telling you that it is.

The specification that the behavior is undefined is not based on
examining some finite number of possible outcomes and determining
whether they all happen to be the same. The behavior is undefined
because the two modifications of f are unsequenced.

Apparently you don't believe me. I can't do anything about that.

There are valid reasons for the way it's currently defined.
You don't seem to care about those reasons.

I had already configured my newsreader to filter out anything
you post in this newsgroup, but you've changed your email address
yet again. I plan to update my filter soon.

(Anyone who takes olcott seriously is advised to take a look at his
posts in comp.theory.)
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
James Kuyper
2024-06-22 03:42:18 UTC
Permalink
On 6/21/24 7:06 PM, olcott wrote:
...
Post by olcott
When we assume the *= assigns
the result of the RHS * the LHS to the LHS
"x *= ++f * ++f"
means x = x * (++f * ++f)
thus cannot have implementation defined behavior.
You are correct about that conclusion, though I don't see what your
premise has to do with it. The expression ++f * ++f has, all by itself,
undefined behavior, independent of what larger expression it might be
part of. "implementation-defined behavior" is defined by the standard as
"behavior, for a well-formed program construct and correct data, that
depends on the implementation and that each implementation documents"
(3.13). Since this program is not well formed, implementations have no
obligation to document what it's behavior will be, so it cannot be
implementation-defined behavior.

Repeating what I said in an earlier message (with one minor correction):

"Except where noted, evaluations of operands of individual operators and
of subexpressions of individual expressions are unsequenced." (6.9.1p10)

Both ++f expressions are sub-expressions of the multiplication
expression. Thus, the executions are unsequenced, which is not, in
itself, a problem. However, they both have a side effect on the same
memory location, and that is a problem, because that same clause goes on
to say:

"If a side effect on a memory location (6.7.1) is unsequenced relative
to either another side effect on the same memory location or a value
computation using the value of any object in the same memory location,
and they are not potentially concurrent (6.9.2), the behavior is undefined."

These two expressions are not potentially concurrent: they are in the
same expression, so they must be in the same thread, and if they are in
a signal handler, they must both be in the same signal handler.
Therefore, the expression ++f * ++f has undefined behavior.
olcott
2024-06-22 04:02:15 UTC
Permalink
Post by James Kuyper
...
Post by olcott
When we assume the *= assigns
the result of the RHS * the LHS to the LHS
"x *= ++f * ++f"
means x = x * (++f * ++f)
thus cannot have implementation defined behavior.
You are correct about that conclusion, though I don't see what your
premise has to do with it. The expression ++f * ++f has, all by itself,
undefined behavior,
It would seem to be naturally defined to be Left to right
++f from 2 to 3
++f from 3 to 4
3 * 4
Post by James Kuyper
independent of what larger expression it might be
part of. "implementation-defined behavior" is defined by the standard as
"behavior, for a well-formed program construct and correct data, that
depends on the implementation and that each implementation documents"
(3.13). Since this program is not well formed, implementations have no
obligation to document what it's behavior will be, so it cannot be
implementation-defined behavior.
"Except where noted, evaluations of operands of individual operators and
of subexpressions of individual expressions are unsequenced." (6.9.1p10)
Both ++f expressions are sub-expressions of the multiplication
expression. Thus, the executions are unsequenced, which is not, in
itself, a problem. However, they both have a side effect on the same
memory location, and that is a problem, because that same clause goes on
"If a side effect on a memory location (6.7.1) is unsequenced relative
to either another side effect on the same memory location or a value
computation using the value of any object in the same memory location,
and they are not potentially concurrent (6.9.2), the behavior is undefined."
These two expressions are not potentially concurrent: they are in the
same expression, so they must be in the same thread, and if they are in
a signal handler, they must both be in the same signal handler.
Therefore, the expression ++f * ++f has undefined behavior.
--
Copyright 2024 Olcott "Talent hits a target no one else can hit; Genius
hits a target no one else can see." Arthur Schopenhauer
David Brown
2024-06-22 11:09:50 UTC
Permalink
Post by olcott
Post by James Kuyper
...
Post by olcott
When we assume the *= assigns
the result of the RHS * the LHS to the LHS
"x *= ++f * ++f"
means x = x * (++f * ++f)
thus cannot have implementation defined behavior.
You are correct about that conclusion, though I don't see what your
premise has to do with it. The expression ++f * ++f has, all by itself,
undefined behavior,
It would seem to be naturally defined to be Left to right
++f from 2 to 3
++f from 3 to 4
3 * 4
Some programming languages define the order of evaluation for
subexpressions like this. Neither C nor C++ do (except for specific
operators, which do not include multiplication).

It doesn't really matter what you consider "natural" or not - it matters
what the standards say.
olcott
2024-06-22 12:40:03 UTC
Permalink
Post by David Brown
Post by olcott
Post by James Kuyper
...
Post by olcott
When we assume the *= assigns
the result of the RHS * the LHS to the LHS
"x *= ++f * ++f"
means x = x * (++f * ++f)
thus cannot have implementation defined behavior.
You are correct about that conclusion, though I don't see what your
premise has to do with it. The expression ++f * ++f has, all by itself,
undefined behavior,
It would seem to be naturally defined to be Left to right
++f from 2 to 3
++f from 3 to 4
3 * 4
Some programming languages define the order of evaluation for
subexpressions like this.  Neither C nor C++ do (except for specific
operators, which do not include multiplication).
It doesn't really matter what you consider "natural" or not - it matters
what the standards say.
It would be bad for the standards to be counter-intuitive.
--
Copyright 2024 Olcott "Talent hits a target no one else can hit; Genius
hits a target no one else can see." Arthur Schopenhauer
Richard Damon
2024-06-22 13:56:45 UTC
Permalink
Post by olcott
Post by David Brown
Post by olcott
Post by James Kuyper
...
Post by olcott
When we assume the *= assigns
the result of the RHS * the LHS to the LHS
"x *= ++f * ++f"
means x = x * (++f * ++f)
thus cannot have implementation defined behavior.
You are correct about that conclusion, though I don't see what your
premise has to do with it. The expression ++f * ++f has, all by itself,
undefined behavior,
It would seem to be naturally defined to be Left to right
++f from 2 to 3
++f from 3 to 4
3 * 4
Some programming languages define the order of evaluation for
subexpressions like this.  Neither C nor C++ do (except for specific
operators, which do not include multiplication).
It doesn't really matter what you consider "natural" or not - it
matters what the standards say.
It would be bad for the standards to be counter-intuitive.
No, the Standards are to define the "contract" between the programmer
and the implementations.

C (and to a lesser extent C++) were envisioned as "High Performance"
languages designed to get efficiencies of code close to what could be
created with pure assembly language. Leaving things unspecified that
still allow the program to be ABLE to express what he wants, and also
allowing the compiler to generate the most efficient code possible was
the goal.

Forcing a left-to-right (or right-to-left) order for expressions adds
possible cost to the code generation due to possible need for spilling
registers to temporary locations to compute other results.

If you NEED the specific ordering, you can always pre-compute the
sub-expressions to explicit temporaries, so forcing the order isn't that
useful\.

One of the main design ideas was that the program is assumed to be
competent and knows the language well. (This may be less true for C++)
and gives the programmer powerful tools that do allow them to shoot
themselves in the foot.
Chris M. Thomasson
2024-06-22 19:14:44 UTC
Permalink
Post by Richard Damon
Post by olcott
Post by David Brown
Post by olcott
Post by James Kuyper
...
Post by olcott
When we assume the *= assigns
the result of the RHS * the LHS to the LHS
"x *= ++f * ++f"
means x = x * (++f * ++f)
thus cannot have implementation defined behavior.
You are correct about that conclusion, though I don't see what your
premise has to do with it. The expression ++f * ++f has, all by itself,
undefined behavior,
It would seem to be naturally defined to be Left to right
++f from 2 to 3
++f from 3 to 4
3 * 4
Some programming languages define the order of evaluation for
subexpressions like this.  Neither C nor C++ do (except for specific
operators, which do not include multiplication).
It doesn't really matter what you consider "natural" or not - it
matters what the standards say.
It would be bad for the standards to be counter-intuitive.
No, the Standards are to define the "contract" between the programmer
and the implementations.
C (and to a lesser extent C++) were envisioned as "High Performance"
languages designed to get efficiencies of code close to what could be
created with pure assembly language.
[...]

I guess it depends on how one uses C++. It can be just as fast as C,
with a little "less" work... Fair enough?
David Brown
2024-06-22 14:52:47 UTC
Permalink
Post by olcott
Post by David Brown
Post by olcott
Post by James Kuyper
...
Post by olcott
When we assume the *= assigns
the result of the RHS * the LHS to the LHS
"x *= ++f * ++f"
means x = x * (++f * ++f)
thus cannot have implementation defined behavior.
You are correct about that conclusion, though I don't see what your
premise has to do with it. The expression ++f * ++f has, all by itself,
undefined behavior,
It would seem to be naturally defined to be Left to right
++f from 2 to 3
++f from 3 to 4
3 * 4
Some programming languages define the order of evaluation for
subexpressions like this.  Neither C nor C++ do (except for specific
operators, which do not include multiplication).
It doesn't really matter what you consider "natural" or not - it
matters what the standards say.
It would be bad for the standards to be counter-intuitive.
People's "intuition" varies wildly. Pretty much any definition would be
counter-intuitive to someone. That's why language standards try to have
precise definitions, rather than just saying "it all works pretty much
like you'd expect".

We realise the definition of C and C++ expression evaluation does not
match what you thought was "intuitive". That does not matter - it would
not matter even if lots of C and C++ programmers agreed with you. What
matters for these languages is what their standards /say/. That way, we
can look at clear facts written in official documents, rather than
trying to rely on what random people think about to be how they imagine
things ought to be.

You can happily feel that C would be better if evaluation order were
strictly define. Some people will agree with that, others will disagree
- but it will not make a blind bit of difference to the actual /facts/
of the matter.
olcott
2024-06-22 16:27:33 UTC
Permalink
Post by olcott
Post by David Brown
Post by olcott
Post by James Kuyper
...
Post by olcott
When we assume the *= assigns
the result of the RHS * the LHS to the LHS
"x *= ++f * ++f"
means x = x * (++f * ++f)
thus cannot have implementation defined behavior.
You are correct about that conclusion, though I don't see what your
premise has to do with it. The expression ++f * ++f has, all by itself,
undefined behavior,
It would seem to be naturally defined to be Left to right
++f from 2 to 3
++f from 3 to 4
3 * 4
Some programming languages define the order of evaluation for
subexpressions like this.  Neither C nor C++ do (except for specific
operators, which do not include multiplication).
It doesn't really matter what you consider "natural" or not - it
matters what the standards say.
It would be bad for the standards to be counter-intuitive.
People's "intuition" varies wildly.  Pretty much any definition would be
counter-intuitive to someone.  That's why language standards try to have
precise definitions, rather than just saying "it all works pretty much
like you'd expect".
We realise the definition of C and C++ expression evaluation does not
match what you thought was "intuitive".  That does not matter - it would
not matter even if lots of C and C++ programmers agreed with you.  What
matters for these languages is what their standards /say/.  That way, we
can look at clear facts written in official documents, rather than
trying to rely on what random people think about to be how they imagine
things ought to be.
You can happily feel that C would be better if evaluation order were
strictly define.  Some people will agree with that, others will disagree
- but it will not make a blind bit of difference to the actual /facts/
of the matter.
Then make the rule strict left to right unless otherwise specified.
int x = 2 + 3 * 5; // is otherwise specified by operator precedence.
--
Copyright 2024 Olcott "Talent hits a target no one else can hit; Genius
hits a target no one else can see." Arthur Schopenhauer
Richard Damon
2024-06-22 17:20:42 UTC
Permalink
Post by olcott
Post by olcott
Post by David Brown
Post by olcott
Post by James Kuyper
...
Post by olcott
When we assume the *= assigns
the result of the RHS * the LHS to the LHS
"x *= ++f * ++f"
means x = x * (++f * ++f)
thus cannot have implementation defined behavior.
You are correct about that conclusion, though I don't see what your
premise has to do with it. The expression ++f * ++f has, all by itself,
undefined behavior,
It would seem to be naturally defined to be Left to right
++f from 2 to 3
++f from 3 to 4
3 * 4
Some programming languages define the order of evaluation for
subexpressions like this.  Neither C nor C++ do (except for specific
operators, which do not include multiplication).
It doesn't really matter what you consider "natural" or not - it
matters what the standards say.
It would be bad for the standards to be counter-intuitive.
People's "intuition" varies wildly.  Pretty much any definition would
be counter-intuitive to someone.  That's why language standards try to
have precise definitions, rather than just saying "it all works pretty
much like you'd expect".
We realise the definition of C and C++ expression evaluation does not
match what you thought was "intuitive".  That does not matter - it
would not matter even if lots of C and C++ programmers agreed with
you.  What matters for these languages is what their standards /say/.
That way, we can look at clear facts written in official documents,
rather than trying to rely on what random people think about to be how
they imagine things ought to be.
You can happily feel that C would be better if evaluation order were
strictly define.  Some people will agree with that, others will
disagree - but it will not make a blind bit of difference to the
actual /facts/ of the matter.
Then make the rule strict left to right unless otherwise specified.
int x = 2 + 3 * 5; // is otherwise specified by operator precedence.
Join the ISO Committe and try to lobby for it.

There are good reasons it isn't defined that way.
Keith Thompson
2024-06-23 00:55:25 UTC
Permalink
olcott <***@gmail.com> writes:
[...]
Post by olcott
Then make the rule strict left to right unless otherwise specified.
int x = 2 + 3 * 5; // is otherwise specified by operator precedence.
No.

Are we done here?
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
David Brown
2024-06-23 12:46:09 UTC
Permalink
Post by olcott
Post by olcott
Post by David Brown
Post by olcott
Post by James Kuyper
...
Post by olcott
When we assume the *= assigns
the result of the RHS * the LHS to the LHS
"x *= ++f * ++f"
means x = x * (++f * ++f)
thus cannot have implementation defined behavior.
You are correct about that conclusion, though I don't see what your
premise has to do with it. The expression ++f * ++f has, all by itself,
undefined behavior,
It would seem to be naturally defined to be Left to right
++f from 2 to 3
++f from 3 to 4
3 * 4
Some programming languages define the order of evaluation for
subexpressions like this.  Neither C nor C++ do (except for specific
operators, which do not include multiplication).
It doesn't really matter what you consider "natural" or not - it
matters what the standards say.
It would be bad for the standards to be counter-intuitive.
People's "intuition" varies wildly.  Pretty much any definition would
be counter-intuitive to someone.  That's why language standards try to
have precise definitions, rather than just saying "it all works pretty
much like you'd expect".
We realise the definition of C and C++ expression evaluation does not
match what you thought was "intuitive".  That does not matter - it
would not matter even if lots of C and C++ programmers agreed with
you.  What matters for these languages is what their standards /say/.
That way, we can look at clear facts written in official documents,
rather than trying to rely on what random people think about to be how
they imagine things ought to be.
You can happily feel that C would be better if evaluation order were
strictly define.  Some people will agree with that, others will
disagree - but it will not make a blind bit of difference to the
actual /facts/ of the matter.
Then make the rule strict left to right unless otherwise specified.
int x = 2 + 3 * 5; // is otherwise specified by operator precedence.
What do you mean, "Make the rule" ? /I/ didn't write the C++ or C
standards - I can't make any rules there. Nor can anyone else here.
And even if I could, I would not change this. C and C++ made these
design decisions for good reasons, and I agree with the decision.
James Kuyper
2024-06-22 19:48:43 UTC
Permalink
Post by olcott
Post by James Kuyper
...
Post by olcott
When we assume the *= assigns
the result of the RHS * the LHS to the LHS
"x *= ++f * ++f"
means x = x * (++f * ++f)
thus cannot have implementation defined behavior.
You are correct about that conclusion, though I don't see what your
premise has to do with it. The expression ++f * ++f has, all by itself,
undefined behavior,
It would seem to be naturally defined to be Left to right
++f from 2 to 3
++f from 3 to 4
3 * 4
C is deliberately underspecified, to allow implementations more freedom
to optimize their code. The key point is that ++f * ++f modifies the
same object twice without any requirement that the updates occur in a
particular order. This means an implementation is allowed to generate
code that implements those modifications in such a way that they could
interfere with each other.

Consider, for example, an implementation where f is a 64-bit long long
on a system with no hardware support for types longer than 16 bits, so
that it needs to generate multiple machine instructions to implement
++f. If you were calculating ++f * ++g, it would be entirely feasible to
interleave the instructions that calculate ++f and ++g; there might, in
fact, be some advantage to doing so. When the implementor writes the
code that implements that interleaving, the fact that ++f * ++f would
have undefined behavior relieves the implementor of any responsibility
for checking whether the two ++ operations apply to the same object,
which simplifies the logic of that code.

I'm not saying that these rules were chosen with this particular example
in mind, but only that examples like this one were the reason why C
takes the general approach that it does: unless otherwise specified,
operations on sub-expressions are unordered with respect to each other,
and when an action modifying an object is unordered with respect to
another operation that either reads or modifies that object, the
behavior is undefined.
Richard Damon
2024-06-19 02:27:00 UTC
Permalink
Post by Bonita Montero
Is "x *= ++f * ++f" a valid statement ?
Or is there implementation defined behaviour ?
For a built in type, the behavior of the two ++ operations on f are
unordered, and that will lead to undefined behavior.

If f is of a user defined type, with an operator++ defined, I think
while the order the two operations is unspecified, they can't get
intermixed, so I think it may just be unspecified behavior, and no nasal
demons allowed.

There is no requirement for the implementation to define the behavior,
and it doesn't need to be consistant.


"Valid" would be a question of semantics. There is nothing that requires
a diagnostic, and the implementation has no grounds for not creating a
program. but for built in types, there is no requirements on what
happens, and for user types the order things happen is unspecified.
olcott
2024-06-23 02:51:23 UTC
Permalink
Post by Bonita Montero
Is "x *= ++f * ++f" a valid statement ?
Or is there implementation defined behaviour ?
For that specific case the result would seem to be identical
no matter the sequence of the incrementations for int type: f.
I might not understand this fully.
--
Copyright 2024 Olcott "Talent hits a target no one else can hit; Genius
hits a target no one else can see." Arthur Schopenhauer
Keith Thompson
2024-06-23 04:51:38 UTC
Permalink
Post by olcott
Post by Bonita Montero
Is "x *= ++f * ++f" a valid statement ?
Or is there implementation defined behaviour ?
For that specific case the result would seem to be identical
no matter the sequence of the incrementations for int type: f.
I might not understand this fully.
It's been explained to you in great detail, and for whatever reason you
choose not to understand it. For the sake of others who might believe
you, I urge you to stop.
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
Richard Damon
2024-06-23 11:32:36 UTC
Permalink
Post by olcott
Post by Bonita Montero
Is "x *= ++f * ++f" a valid statement ?
Or is there implementation defined behaviour ?
For that specific case the result would seem to be identical
no matter the sequence of the incrementations for int type: f.
I might not understand this fully.
Nope, the issue is that increment isn't an atomic operation, but the
storing of the incremented result is allowed to happen at any point
prior to the end expression it is part of. (In older terms, the sequence
point);

If f was a user defined type, with a user define operator++ then yes, it
is bracketed with sequencing and the two will not overlap, but not for
primative types.
olcott
2024-06-23 12:59:18 UTC
Permalink
Post by Richard Damon
Post by olcott
Post by Bonita Montero
Is "x *= ++f * ++f" a valid statement ?
Or is there implementation defined behaviour ?
For that specific case the result would seem to be identical
no matter the sequence of the incrementations for int type: f.
I might not understand this fully.
Nope, the issue is that increment isn't an atomic operation, but the
storing of the incremented result is allowed to happen at any point
prior to the end expression it is part of. (In older terms, the sequence
point);
If f was a user defined type, with a user define operator++ then yes, it
is bracketed with sequencing and the two will not overlap, but not for
primative types.
It is not f++, it is ++f meaning the the operation must occur before
the value is accessed.
--
Copyright 2024 Olcott "Talent hits a target no one else can hit; Genius
hits a target no one else can see." Arthur Schopenhauer
Richard Damon
2024-06-23 18:25:41 UTC
Permalink
Post by olcott
Post by Richard Damon
Post by olcott
Post by Bonita Montero
Is "x *= ++f * ++f" a valid statement ?
Or is there implementation defined behaviour ?
For that specific case the result would seem to be identical
no matter the sequence of the incrementations for int type: f.
I might not understand this fully.
Nope, the issue is that increment isn't an atomic operation, but the
storing of the incremented result is allowed to happen at any point
prior to the end expression it is part of. (In older terms, the
sequence point);
If f was a user defined type, with a user define operator++ then yes,
it is bracketed with sequencing and the two will not overlap, but not
for primative types.
It is not f++, it is ++f meaning the the operation must occur before
the value is accessed.
But the writing back of the result does not.

The ++ and -- operators are NOT defined to be in any way "atomic".
olcott
2024-06-23 19:18:13 UTC
Permalink
Post by Richard Damon
Post by olcott
Post by Richard Damon
Post by olcott
Post by Bonita Montero
Is "x *= ++f * ++f" a valid statement ?
Or is there implementation defined behaviour ?
For that specific case the result would seem to be identical
no matter the sequence of the incrementations for int type: f.
I might not understand this fully.
Nope, the issue is that increment isn't an atomic operation, but the
storing of the incremented result is allowed to happen at any point
prior to the end expression it is part of. (In older terms, the
sequence point);
If f was a user defined type, with a user define operator++ then yes,
it is bracketed with sequencing and the two will not overlap, but not
for primative types.
It is not f++, it is ++f meaning the the operation must occur before
the value is accessed.
But the writing back of the result does not.
The ++ and -- operators are NOT defined to be in any way "atomic".
In other words it is not a direct memory increment?
--
Copyright 2024 Olcott "Talent hits a target no one else can hit; Genius
hits a target no one else can see." Arthur Schopenhauer
Richard Damon
2024-06-23 20:23:34 UTC
Permalink
Post by olcott
Post by Richard Damon
Post by olcott
Post by Richard Damon
Post by olcott
Post by Bonita Montero
Is "x *= ++f * ++f" a valid statement ?
Or is there implementation defined behaviour ?
For that specific case the result would seem to be identical
no matter the sequence of the incrementations for int type: f.
I might not understand this fully.
Nope, the issue is that increment isn't an atomic operation, but the
storing of the incremented result is allowed to happen at any point
prior to the end expression it is part of. (In older terms, the
sequence point);
If f was a user defined type, with a user define operator++ then
yes, it is bracketed with sequencing and the two will not overlap,
but not for primative types.
It is not f++, it is ++f meaning the the operation must occur before
the value is accessed.
But the writing back of the result does not.
The ++ and -- operators are NOT defined to be in any way "atomic".
In other words it is not a direct memory increment?
Not required to be, since not all computers have one.

It CAN be, (and may well be on many processors) but isn't required to be.
David Brown
2024-06-24 07:11:29 UTC
Permalink
Post by Richard Damon
Post by olcott
Post by Richard Damon
Post by olcott
Post by Richard Damon
Post by olcott
Post by Bonita Montero
Is "x *= ++f * ++f" a valid statement ?
Or is there implementation defined behaviour ?
For that specific case the result would seem to be identical
no matter the sequence of the incrementations for int type: f.
I might not understand this fully.
Nope, the issue is that increment isn't an atomic operation, but
the storing of the incremented result is allowed to happen at any
point prior to the end expression it is part of. (In older terms,
the sequence point);
If f was a user defined type, with a user define operator++ then
yes, it is bracketed with sequencing and the two will not overlap,
but not for primative types.
It is not f++, it is ++f meaning the the operation must occur before
the value is accessed.
But the writing back of the result does not.
The ++ and -- operators are NOT defined to be in any way "atomic".
In other words it is not a direct memory increment?
Not required to be, since not all computers have one.
It CAN be, (and may well be on many processors) but isn't required to be.
Very few modern architectures support direct memory operations now.
Even ISA's that have instructions that appear to be direct memory
operations will often split them into separate read, modify and write
microops. And for multi-core systems, atomic memory operations are very
costly as they involve bus locks and synchronisation - so compilers
don't generate them unless you specifically ask for them.

And of course operations like ++f or f++ are very rarely atomic if you
are using the result of the operation, even if the processor supports an
atomic memory increment - you still need to read the memory, before or
after the increment.
testuseri2p
2024-07-22 17:51:43 UTC
Permalink
I'd say in simple words, if you alter a variable multiple times in one
statement, you get UB.
Keith Thompson
2024-07-22 21:58:14 UTC
Permalink
Post by testuseri2p
I'd say in simple words, if you alter a variable multiple times in one
statement, you get UB.
That's a little too simple. You can alter a variable multiple times in
one statement as long as the two modifications are *sequenced*, for
example if they're separated by a comma operator.

In the code fragment in the subject line, the two occurrences of "++f"
are unsequenced, so the behavior is undefined.
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
James Kuyper
2024-07-23 01:57:39 UTC
Permalink
Post by testuseri2p
I'd say in simple words, if you alter a variable multiple times in one
statement, you get UB.
The correct (and admittedly more complicated) statement of the relevant
rule is:
"If a side effect on a memory location (6.7.1) is unsequenced relative
to either another side effect on the same memory location or a value
computation using the value of any object in the same memory location,
and they are not potentially concurrent (6.9.2), the behavior is undefined."

The biggest difference between the exact rule and what you said is that
two different updates to a variable may occur in the same statement, so
long as they are sequenced relative to each other. Sub-expressions of an
expression are sequenced before evaluation of the expression itself. In
general, the sub-expressions of a expression are unsequenced, but there
several exceptions: ||, &&, ?:, the comma operator.

A minor detail is that a variable must be declared, whereas memory
locations can, for instance, be part of allocated memory for which no
declaration exists - it is still undefined behavior to write code that
applies unsequence side-effects to such memory locations.
Keith Thompson
2024-07-23 03:40:37 UTC
Permalink
Post by James Kuyper
Post by testuseri2p
I'd say in simple words, if you alter a variable multiple times in one
statement, you get UB.
The correct (and admittedly more complicated) statement of the relevant
"If a side effect on a memory location (6.7.1) is unsequenced relative
to either another side effect on the same memory location or a value
computation using the value of any object in the same memory location,
and they are not potentially concurrent (6.9.2), the behavior is undefined."
The biggest difference between the exact rule and what you said is that
two different updates to a variable may occur in the same statement, so
long as they are sequenced relative to each other. Sub-expressions of an
expression are sequenced before evaluation of the expression itself. In
general, the sub-expressions of a expression are unsequenced, but there
several exceptions: ||, &&, ?:, the comma operator.
"Sub-expressions of an expression are sequenced before evaluation of the
expression itself." isn't quite correct. What the C++ standard says is
that "The value computations of the operands of an operator are
sequenced before the value computation of the result of the operator."

In `++f * ++f`, the values yielded by the two ++f subexpressions are
determined before the "*" operator can be evaluated, but the side
effects of incrementing f (twice) can happen any time before the end of
the evaluation of the full expression.

The full expression is `x *= ++f * ++f`. There are three side effects,
modifications to x, f, and f, and they can happen in any order. (It's
because the two modifications of f are unsequenced that the behavior is
undefined.

`x *= ++f * ++g` would be well defined, as long as there f and g are
initialized and aren't any hidden games with any of x, f, and g being
references to the same object. And the side effects on f and g could
happen before or after the multiplication and assignment are evaluated.
Post by James Kuyper
A minor detail is that a variable must be declared, whereas memory
locations can, for instance, be part of allocated memory for which no
declaration exists - it is still undefined behavior to write code that
applies unsequence side-effects to such memory locations.
Digression: I'm not even sure what "variable" means in C++. The
standard defines the term, but not in a way that really tells us what it
means.

"A *variable* is introduced by the declaration of a reference other than
a non-static data member or of an object. The variable’s name, if any,
denotes the reference or object."
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
James Kuyper
2024-07-23 14:15:29 UTC
Permalink
Post by Keith Thompson
Post by James Kuyper
Post by testuseri2p
I'd say in simple words, if you alter a variable multiple times in one
statement, you get UB.
The correct (and admittedly more complicated) statement of the relevant
"If a side effect on a memory location (6.7.1) is unsequenced relative
to either another side effect on the same memory location or a value
computation using the value of any object in the same memory location,
and they are not potentially concurrent (6.9.2), the behavior is undefined."
The biggest difference between the exact rule and what you said is that
two different updates to a variable may occur in the same statement, so
long as they are sequenced relative to each other. Sub-expressions of an
expression are sequenced before evaluation of the expression itself. In
general, the sub-expressions of a expression are unsequenced, but there
several exceptions: ||, &&, ?:, the comma operator.
"Sub-expressions of an expression are sequenced before evaluation of the
expression itself." isn't quite correct. What the C++ standard says is
that "The value computations of the operands of an operator are
sequenced before the value computation of the result of the operator."
Yes, you're right. I should have checked before posting. Since this rule
is about side-effects, not value computations, the sequencing provided
by that clause is irrelevant to what I was talking about. I remember
thinking that it didn't sound right, and I would probably have realized
my mistake if I'd taken a few more minutes to think about it, but I was
in a hurry to get a pair of 9-year olds to bed.
Post by Keith Thompson
Post by James Kuyper
A minor detail is that a variable must be declared, whereas memory
locations can, for instance, be part of allocated memory for which no
declaration exists - it is still undefined behavior to write code that
applies unsequence side-effects to such memory locations.
Digression: I'm not even sure what "variable" means in C++. The
standard defines the term, but not in a way that really tells us what it
means.
"A *variable* is introduced by the declaration of a reference other than
a non-static data member or of an object. The variable’s name, if any,
denotes the reference or object."
That's the relevant clause. A declaration always declares an identifier
to be the name of the thing declared. Therefore, a memory location that
doesn't have a declared name doesn't qualify as a C variable. However,
such memory locations are still subject to the above rule.
Tim Rentsch
2024-07-23 14:28:44 UTC
Permalink
[...]
Post by James Kuyper
A minor detail is that a variable must be declared, whereas memory
locations can, for instance, be part of allocated memory for which
no declaration exists - it is still undefined behavior to write
code that applies unsequence side-effects to such memory locations.
Digression: I'm not even sure what "variable" means in C++. The
standard defines the term, but not in a way that really tells us
what it means.
"A *variable* is introduced by the declaration of a reference other
than a non-static data member or of an object. The variable's name,
if any, denotes the reference or object."
What part do you find confusing or hard to understand?
Keith Thompson
2024-07-23 21:42:07 UTC
Permalink
Post by Tim Rentsch
[...]
Post by James Kuyper
A minor detail is that a variable must be declared, whereas memory
locations can, for instance, be part of allocated memory for which
no declaration exists - it is still undefined behavior to write
code that applies unsequence side-effects to such memory locations.
Digression: I'm not even sure what "variable" means in C++. The
standard defines the term, but not in a way that really tells us
what it means.
"A *variable* is introduced by the declaration of a reference other
than a non-static data member or of an object. The variable's name,
if any, denotes the reference or object."
What part do you find confusing or hard to understand?
The missing part that should tell us what a variable *is*.

It says that certain declarations "introduce" a variable. That's a
statement about variables, but it doesn't say what a variable is.

Given:

int n;

we know that the declaration introduces a variable. Is the object
itself a "variable"? That's the obvious meaning, and it's consistent
with what the standard says. Or is a "variable" some kind of logical
binding between an object and a name? That's also consistent with what
the standard says. Under the latter interpretation, the "variable" has
a name, and that name denotes an object, but the variable is not the
object.

Given the above declaration, is the introduced variable an object? If
so, or if not, how does your answer follow from what the standard says?
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
Keith Thompson
2024-07-23 22:05:43 UTC
Permalink
Post by Keith Thompson
Post by Tim Rentsch
[...]
Post by James Kuyper
A minor detail is that a variable must be declared, whereas memory
locations can, for instance, be part of allocated memory for which
no declaration exists - it is still undefined behavior to write
code that applies unsequence side-effects to such memory locations.
Digression: I'm not even sure what "variable" means in C++. The
standard defines the term, but not in a way that really tells us
what it means.
"A *variable* is introduced by the declaration of a reference other
than a non-static data member or of an object. The variable's name,
if any, denotes the reference or object."
What part do you find confusing or hard to understand?
The missing part that should tell us what a variable *is*.
It says that certain declarations "introduce" a variable. That's a
statement about variables, but it doesn't say what a variable is.
int n;
we know that the declaration introduces a variable. Is the object
itself a "variable"? That's the obvious meaning, and it's consistent
with what the standard says. Or is a "variable" some kind of logical
binding between an object and a name? That's also consistent with what
the standard says. Under the latter interpretation, the "variable" has
a name, and that name denotes an object, but the variable is not the
object.
Given the above declaration, is the introduced variable an object? If
so, or if not, how does your answer follow from what the standard says?
On further thought, I think the intent has to be that a variable is not
an object. A variable is introduced by a declaration, and a declaration
is not necessarily a definition.

For example, given:

int var = 42;
int main() {
extern int var;
}

There are two declarations for "var"; only the first is a definition.
If I understand correctly, each of these declarations introduces a
variable. So apparently there are two variables with the name "var",
but only one object. Or is there just one variable that's "introduced"
twice?

But the following paragraph says:

A *local entity* is a variable with automatic storage duration
(6.7.5.4), a structured binding (9.6) whose corresponding variable
is such an entity, or the *this object (7.5.3).

Storage duration is an attribute of objects. If a variable can have
automatic storage duration, then apparently a variable is an object.

Informally, I think of a "variable" as an object whose name is an
identifer. Given `int foo[10];`, foo would be a variable, but foo[1]
would not.

Perhaps I'm missing something obvious, but I don't know what.
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
Tim Rentsch
2024-08-11 13:22:26 UTC
Permalink
Post by Keith Thompson
Post by Keith Thompson
Post by Tim Rentsch
[...]
Post by James Kuyper
A minor detail is that a variable must be declared, whereas memory
locations can, for instance, be part of allocated memory for which
no declaration exists - it is still undefined behavior to write
code that applies unsequence side-effects to such memory locations.
Digression: I'm not even sure what "variable" means in C++. The
standard defines the term, but not in a way that really tells us
what it means.
"A *variable* is introduced by the declaration of a reference other
than a non-static data member or of an object. The variable's name,
if any, denotes the reference or object."
What part do you find confusing or hard to understand?
The missing part that should tell us what a variable *is*.
It says that certain declarations "introduce" a variable. That's a
statement about variables, but it doesn't say what a variable is.
int n;
we know that the declaration introduces a variable. Is the object
itself a "variable"? That's the obvious meaning, and it's consistent
with what the standard says. Or is a "variable" some kind of logical
binding between an object and a name? That's also consistent with what
the standard says. Under the latter interpretation, the "variable" has
a name, and that name denotes an object, but the variable is not the
object.
Given the above declaration, is the introduced variable an object? If
so, or if not, how does your answer follow from what the standard says?
[...]
Post by Keith Thompson
A *local entity* is a variable with automatic storage duration
(6.7.5.4), a structured binding (9.6) whose corresponding variable
is such an entity, or the *this object (7.5.3).
Storage duration is an attribute of objects. If a variable can have
automatic storage duration, then apparently a variable is an object.
It seems clear that the quoted sentence is meant to be read as

A *local entity* is a variable [associated with an object that
has] automatic storage duration, [etc].

I'm not sure if C++ references also have storage durations, in which
case the word "object" in that sentence might need to be replaced with
"object or reference". The key point though is that the property of
having automatic storage duration is meant to be associated with the
affiliated object or reference rather than with the variable itself.
Keith Thompson
2024-08-11 21:09:41 UTC
Permalink
Post by Tim Rentsch
Post by Keith Thompson
Post by Keith Thompson
Post by Tim Rentsch
[...]
Post by James Kuyper
A minor detail is that a variable must be declared, whereas memory
locations can, for instance, be part of allocated memory for which
no declaration exists - it is still undefined behavior to write
code that applies unsequence side-effects to such memory locations.
Digression: I'm not even sure what "variable" means in C++. The
standard defines the term, but not in a way that really tells us
what it means.
"A *variable* is introduced by the declaration of a reference other
than a non-static data member or of an object. The variable's name,
if any, denotes the reference or object."
What part do you find confusing or hard to understand?
The missing part that should tell us what a variable *is*.
It says that certain declarations "introduce" a variable. That's a
statement about variables, but it doesn't say what a variable is.
int n;
we know that the declaration introduces a variable. Is the object
itself a "variable"? That's the obvious meaning, and it's consistent
with what the standard says. Or is a "variable" some kind of logical
binding between an object and a name? That's also consistent with what
the standard says. Under the latter interpretation, the "variable" has
a name, and that name denotes an object, but the variable is not the
object.
Given the above declaration, is the introduced variable an object? If
so, or if not, how does your answer follow from what the standard says?
[...]
Post by Keith Thompson
A *local entity* is a variable with automatic storage duration
(6.7.5.4), a structured binding (9.6) whose corresponding variable
is such an entity, or the *this object (7.5.3).
Storage duration is an attribute of objects. If a variable can have
automatic storage duration, then apparently a variable is an object.
It seems clear that the quoted sentence is meant to be read as
A *local entity* is a variable [associated with an object that
has] automatic storage duration, [etc].
It seems clear to you. It's not clear to me. If the only way to
understand a passage is to assume it includes extra words, it is at best
poorly written -- and I can't be sure that any assumptions about what it
*really* means are correct.
Post by Tim Rentsch
I'm not sure if C++ references also have storage durations, in which
case the word "object" in that sentence might need to be replaced with
"object or reference". The key point though is that the property of
having automatic storage duration is meant to be associated with the
affiliated object or reference rather than with the variable itself.
Perhaps so, but that still doesn't tell me what a "variable" is.

Apparently a "variable" is something *associated with* an object. But
what is it, and how is the concept useful when we already have the
concept of an "object"?

I tried following your advice and searchin for uses of "variable" in the
C++ standard (which, as James Kuyper pointed out, should not be
necessary to understand the definition). It was not helpful. For
example, "Variables with static storage duration are initialized as a
consequence of program initiation.". So a variable is a thing that can
have a storage duration and be initialized? And it's not an object?

I get the impression (quite possibly mistaken) that the authors of the
standard implicitly assume in some places that a variable is an object,
and in other places that it's something else.

The most obvious meaning is that a "variable" is an object that has a
name introduced by a declaration. (I'll gloss over the question of
whether something that's const-qualified is a "variable", but I'm
willing to accept that "variable" doesn't have to mean that something
can vary.) But that's not what the standard says.

I'll note that the C standard does not define or use the term "variable"
(though it does use the word in its non-tecnical sense). Instead it
refers to "objects". Perhaps C++ could have done the same.

If I were to ask you "What is a variable in C++?", could you give a
clear answer? What would that answer be? Is a variable an object or
is it not?
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+***@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
Tim Rentsch
2024-08-11 13:11:24 UTC
Permalink
Post by Keith Thompson
Post by Tim Rentsch
[...]
Post by James Kuyper
A minor detail is that a variable must be declared, whereas memory
locations can, for instance, be part of allocated memory for which
no declaration exists - it is still undefined behavior to write
code that applies unsequence side-effects to such memory locations.
Digression: I'm not even sure what "variable" means in C++. The
standard defines the term, but not in a way that really tells us
what it means.
"A *variable* is introduced by the declaration of a reference other
than a non-static data member or of an object. The variable's name,
if any, denotes the reference or object."
What part do you find confusing or hard to understand?
The missing part that should tell us what a variable *is*.
It says that certain declarations "introduce" a variable. That's a
statement about variables, but it doesn't say what a variable is.
int n;
we know that the declaration introduces a variable. Is the object
itself a "variable"? That's the obvious meaning, and it's consistent
with what the standard says. Or is a "variable" some kind of logical
binding between an object and a name? That's also consistent with
what the standard says. Under the latter interpretation, the
"variable" has a name, and that name denotes an object, but the
variable is not the object.
Given the above declaration, is the introduced variable an object?
If so, or if not, how does your answer follow from what the standard
says?
Even moreso than the C standard, the C++ standard needs to be read
holistically. That may be a damning commentary on the quality of
writing in the C++ standard, but I think it matches the reality.

Given that, when confronted with a question like the ones you ask
about the term "variable", a natural course of action to find
answers to these questions might be to open the C++ standard in
a PDF viewer, and use the viewer's search facility to look at
places where the term in question is used. The more of those that
can be looked at, the more likely it is that one will be able to
discern answers to such questions. That's probably what I would
do if I felt I needed a better understanding of, for example, what
the term "variable" is supposed to mean.
Chris Ahlstrom
2024-08-11 13:25:27 UTC
Permalink
Post by Tim Rentsch
Post by Keith Thompson
Post by Tim Rentsch
[...]
Post by James Kuyper
A minor detail is that a variable must be declared, whereas memory
locations can, for instance, be part of allocated memory for which
no declaration exists - it is still undefined behavior to write
code that applies unsequence side-effects to such memory locations.
Digression: I'm not even sure what "variable" means in C++. The
standard defines the term, but not in a way that really tells us
what it means.
"A *variable* is introduced by the declaration of a reference other
than a non-static data member or of an object. The variable's name,
if any, denotes the reference or object."
What part do you find confusing or hard to understand?
The missing part that should tell us what a variable *is*.
It says that certain declarations "introduce" a variable. That's a
statement about variables, but it doesn't say what a variable is.
int n;
we know that the declaration introduces a variable. Is the object
itself a "variable"? That's the obvious meaning, and it's consistent
with what the standard says. Or is a "variable" some kind of logical
binding between an object and a name? That's also consistent with
what the standard says. Under the latter interpretation, the
"variable" has a name, and that name denotes an object, but the
variable is not the object.
Given the above declaration, is the introduced variable an object?
If so, or if not, how does your answer follow from what the standard
says?
Even moreso than the C standard, the C++ standard needs to be read
holistically. That may be a damning commentary on the quality of
writing in the C++ standard, but I think it matches the reality.
Given that, when confronted with a question like the ones you ask
about the term "variable", a natural course of action to find
answers to these questions might be to open the C++ standard in
a PDF viewer, and use the viewer's search facility to look at
places where the term in question is used. The more of those that
can be looked at, the more likely it is that one will be able to
discern answers to such questions. That's probably what I would
do if I felt I needed a better understanding of, for example, what
the term "variable" is supposed to mean.
This one gives me a headache:

https://en.cppreference.com/w/cpp/language/value_category#:~:text=The%20expressions%20that%20have%20identity,and%20xvalues%20are%20rvalue%20expressions.

Value categories

Each C++ expression (an operator with its operands, a literal, a variable
name, etc.) is characterized by two independent properties: a type and a
value category. Each expression has some non-reference type, and each
expression belongs to exactly one of the three primary value categories:
prvalue, xvalue, and lvalue.

Not to mention rvalue and glvalue.
--
Things past redress and now with me past care.
-- William Shakespeare, "Richard II"
James Kuyper
2024-08-11 18:17:17 UTC
Permalink
...
Post by Tim Rentsch
Post by Keith Thompson
Post by Tim Rentsch
Post by Keith Thompson
"A *variable* is introduced by the declaration of a reference other
than a non-static data member or of an object. The variable's name,
if any, denotes the reference or object."
[That's 6.1p6]
Post by Tim Rentsch
Post by Keith Thompson
Post by Tim Rentsch
What part do you find confusing or hard to understand?
The missing part that should tell us what a variable *is*.
It says that certain declarations "introduce" a variable. That's a
statement about variables, but it doesn't say what a variable is.
int n;
we know that the declaration introduces a variable. Is the object
itself a "variable"? That's the obvious meaning, and it's consistent
with what the standard says. Or is a "variable" some kind of logical
binding between an object and a name? That's also consistent with
what the standard says. Under the latter interpretation, the
"variable" has a name, and that name denotes an object, but the
variable is not the object.
Given the above declaration, is the introduced variable an object?
If so, or if not, how does your answer follow from what the standard
says?
Even moreso than the C standard, the C++ standard needs to be read
holistically. That may be a damning commentary on the quality of
writing in the C++ standard, but I think it matches the reality.
Given that, when confronted with a question like the ones you ask
about the term "variable", a natural course of action to find
answers to these questions might be to open the C++ standard in
a PDF viewer, and use the viewer's search facility to look at
places where the term in question is used. The more of those that
can be looked at, the more likely it is that one will be able to
discern answers to such questions. That's probably what I would
do if I felt I needed a better understanding of, for example, what
the term "variable" is supposed to mean.
It should not be necessary to do that. In 6.1p6, the word "variable" is
italicized, an ISO convention identifying the sentence in which it
occurs as the official definition of the term. As such, it is supposed
to be sufficient to answer that question; there should be no need to
infer the intended meaning by reading all of the uses of that term in
the document.
I agree, that counts as a damning statement about the quality of writing
of at least that part of the standard.
Tim Rentsch
2024-07-23 14:32:56 UTC
Permalink
Post by James Kuyper
Post by testuseri2p
I'd say in simple words, if you alter a variable multiple times in
one statement, you get UB.
The correct (and admittedly more complicated) statement of the
"If a side effect on a memory location (6.7.1) is unsequenced
relative to either another side effect on the same memory location
or a value computation using the value of any object in the same
memory location, and they are not potentially concurrent (6.9.2),
the behavior is undefined."
The biggest difference between the exact rule and what you said is
that two different updates to a variable may occur in the same
statement, so long as they are sequenced relative to each other.
Sub-expressions of an expression are sequenced before evaluation of
the expression itself. In general, the sub-expressions of a
expression are unsequenced, but there several exceptions: ||, &&,
?:, the comma operator.
Don't forget << (and also >>?).
Post by James Kuyper
A minor detail is that a variable must be declared, whereas memory
locations can, for instance, be part of allocated memory for which
no declaration exists - it is still undefined behavior to write code
that applies unsequence side-effects to such memory locations.
The original statement most likely meant "variable" as a stand-in
for "object".
Andrey Tarasevich
2024-07-23 02:53:49 UTC
Permalink
Post by testuseri2p
I'd say in simple words, if you alter a variable multiple times in one
statement, you get UB.
In simple words, if the variable(s) in the above expression have
user-defined types and, consequently, the operators in the above
expression are user-defined (as opposed to being built-in), then your
above statement does not apply. You can end up with unspecified
behavior, but not with undefined behavior.

Which is why, once again, there's no specific answer to the original
question without knowing more about the types of the variables involved.

As for more formal words - see the adjacent answers.
--
Best regards,
Andrey
James Kuyper
2024-07-23 14:01:28 UTC
Permalink
Post by testuseri2p
I'd say in simple words, if you alter a variable multiple times in one
statement, you get UB.
The correct (and admittedly more complicated) statement of the relevant
rule is:
"If a side effect on a memory location (6.7.1) is unsequenced relative
to either another side effect on the same memory location or a value
computation using the value of any object in the same memory location,
and they are not potentially concurrent (6.9.2), the behavior is undefined."

The biggest difference between the exact rule and what you said is that
two different updates to a variable may occur in the same statement, so
long as they are sequenced relative to each other. Sub-expressions of an
expression are sequenced before evaluation of the expression itself. In
general, the sub-expressions of a expression are unsequenced, but there
several exceptions: ||, &&, ?:, the comma operator.

A minor detail is that a variable must be declared, whereas memory
locations can, for instance, be part of allocated memory for which no
declaration exists - it is still undefined behavior to write code that
applies unsequence side-effects to such memory locations.
Loading...