Multiple Assignment Evaluation Debate

B

berns

Hi All,

A coworker and I have been debating the 'correct' expectation of
evaluation for the phrase a = b = c. Two different versions of GCC
ended up compiling this as b = c; a = b and the other ended up
compiling it as a = c; b = c. (Both with no optimizations enabled).
How did we notice you may ask? Well, in our case 'b' was a memory
mapped register that only has a select number of writable bits. He
claims it has been a 'C Standard' that it will be evaluted as a = c; b
= c. I personally believe that it would make more sense for it to be
evaluated as b = c; a = b, although I would never write code that has a
questionable operation. Can anyone settle this debate?
 
R

Richard Heathfield

(e-mail address removed) said:
Hi All,

A coworker and I have been debating the 'correct' expectation of
evaluation for the phrase a = b = c. Two different versions of GCC
ended up compiling this as b = c; a = b and the other ended up
compiling it as a = c; b = c. (Both with no optimizations enabled).
How did we notice you may ask? Well, in our case 'b' was a memory
mapped register that only has a select number of writable bits. He
claims it has been a 'C Standard' that it will be evaluted as a = c; b
= c. I personally believe that it would make more sense for it to be
evaluated as b = c; a = b, although I would never write code that has a
questionable operation. Can anyone settle this debate?

The associativity is: a = (b = c); but the order of evaluation of a, b, and
c is unspecified. The Standard only requires that the new values of a and b
are provided at some time after the previous, and before the following,
sequence point.

So gcc is quite within its rights to compile it to: a = c; b = c; or b = c;
a = c; or b = c; a = b; or tmp = c; a = tmp; b = tmp; or any other
combination that gives the same result.

If you depend on the order of evaluation, any side effects of a, b, and c
are likely to bite you eventually.
 
J

Jordan Abel

Hi All,

A coworker and I have been debating the 'correct' expectation of
evaluation for the phrase a = b = c. Two different versions of GCC
ended up compiling this as b = c; a = b and the other ended up
compiling it as a = c; b = c. (Both with no optimizations enabled).
How did we notice you may ask? Well, in our case 'b' was a memory
mapped register that only has a select number of writable bits.

If you do that, a sprinkling of the "volatile" keyword may do something
for predictability
He claims it has been a 'C Standard' that it will be evaluted as a =
c; b = c.

His interpretaion of the standard is the same as mine.
I personally believe that it would make more sense for it to be
evaluated as b = c; a = b, although I would never write code that has a
questionable operation. Can anyone settle this debate?

Use volatile. You might still get the wrong answer, but it's more likely
to be consistent.
 
R

Richard G. Riley

"Jordan"posted the following on 2006-03-10:
If you do that, a sprinkling of the "volatile" keyword may do something
for predictability


His interpretaion of the standard is the same as mine.


Use volatile. You might still get the wrong answer, but it's more likely
to be consistent.

Could you explain how volatile changes anything here? It doesnt alter
the limited bits in his memory mapped register and I cant quite work
out how it would make anything better : having said that I only ever
used volatile on HW port maps. Is that the issue here? b is changing
between "b=c" and "a=b"? Is this register a read/write with some bits
changing externally? Then I see how volatile would work, but certainly
wouldnt be using a=b=c no matter what in this case.
 
E

ena8t8si

Richard said:
(e-mail address removed) said:


The associativity is: a = (b = c); but the order of evaluation of a, b, and
c is unspecified. The Standard only requires that the new values of a and b
are provided at some time after the previous, and before the following,
sequence point.

So gcc is quite within its rights to compile it to: a = c; b = c; or b = c;
a = c; or b = c; a = b; or tmp = c; a = tmp; b = tmp; or any other
combination that gives the same result.

If you depend on the order of evaluation, any side effects of a, b, and c
are likely to bite you eventually.

You missed the point of his question. Regardless of anything having
to do with evaluation order, the intermediate assignment to b can
affect the value that is assigned to a. int = unsigned char = int,
for example. If b is volatile that may also change the picture,
as another thread discusses. It isn't safe to assume (because
it's open to debate) that a = b = c may be replaced by b = c, a = b
even if b is volatile. But saying a = b = c means (in whatever
order) a = c and b = c is simply wrong.
 
R

Richard G. Riley

"(e-mail address removed)"posted the following on 2006-03-10:
You missed the point of his question. Regardless of anything having
to do with evaluation order, the intermediate assignment to b can
affect the value that is assigned to a. int = unsigned char = int,
for example. If b is volatile that may also change the picture,
as another thread discusses. It isn't safe to assume (because
it's open to debate) that a = b = c may be replaced by b = c, a = b
even if b is volatile. But saying a = b = c means (in whatever
order) a = c and b = c is simply wrong.

Why? If you write the code properly and a,b,c are the same types and
none of them are physically volative and this is a single thread app
then I think the code can indeed infer equality. If not then we might
as well give up.

If they are not the same types then without the right casts you have
no right coding like this IMO because of hiccups down the line when
someone less familiar with the code comes along.
 
R

Richard Heathfield

(e-mail address removed) said:
You missed the point of his question. Regardless of anything having
to do with evaluation order, the intermediate assignment to b can
affect the value that is assigned to a.

I invite you to re-read my reply, especially the bit about side effects
(retained above).
 
E

ena8t8si

Richard said:
"(e-mail address removed)"posted the following on 2006-03-10:


Why? If you write the code properly and a,b,c are the same types and
none of them are physically volative and this is a single thread app
then I think the code can indeed infer equality. If not then we might
as well give up.

If they are not the same types then without the right casts you have
no right coding like this IMO because of hiccups down the line when
someone less familiar with the code comes along.

Please try to pay attention. The question asked isn't about
whether a = b = c is good programming or whether anyone has
the "right" to use it in a program, but what the C Standard
says it means.
 
R

Richard G. Riley

"(e-mail address removed)"posted the following on 2006-03-10:
Please try to pay attention. The question asked isn't about
whether a = b = c is good programming or whether anyone has
the "right" to use it in a program, but what the C Standard
says it means.

Do pay attention : it moved on to the affects of volatility : and it
is this I was asking about. It was also discussing real world issues
at this stage and the "standard" was another issue. Pay particular
attention to my comments about using C in the real world in the
paragraph beginngin with "Why?" - as you will see, it was a question
and not a statement : a question to the a=c and b=c is simply wrong
statement.
 
R

Richard Heathfield

(e-mail address removed) said:
Please try to pay attention. The question asked isn't about
whether a = b = c is good programming or whether anyone has
the "right" to use it in a program, but what the C Standard
says it means.


You are Dan Pop, and I claim my five pounds!
 
C

Chris Torek

A coworker and I have been debating the 'correct' expectation of
evaluation for the phrase a = b = c. Two different versions of GCC
ended up compiling this as b = c; a = b and the other ended up
compiling it as a = c; b = c. (Both with no optimizations enabled).
How did we notice you may ask? Well, in our case 'b' was a memory
mapped register that only has a select number of writable bits. ...

Well, first, if you have not applied the "volatile" qualifier, you
have no right to expect anything in particular, because a compiler
(even without optimization turned on) is allowed to believe that
non-"volatile" objects behave as if they are ordinary RAM. That is,
if you store 3 in some "int", and do not store another value in that
"int", it will still have 3 in it later. So:

void f(void) {
int i = 3;
... /* code that does not modify i */
printf("i is %d\n", i);
...
}

could be compiled to the same object code as:

void f(void) {
... /* code that does not modify i */
puts("i is 3");
...
}

(note the removal of the newline, since puts() adds one; and yes,
gcc really will turn printf() calls into puts() calls in some
cases).
He claims it has been a 'C Standard' that it will be evaluted as a = c; b
= c. I personally believe that it would make more sense for it to be
evaluated as b = c; a = b, although I would never write code that has a
questionable operation. Can anyone settle this debate?

The C89 and C99 standards both use similar (maybe even identical)
wording:

[#3] An assignment operator stores a value in the object
designated by the left operand. An assignment expression
has the value of the left operand after the assignment, but
is not an lvalue. The type of an assignment expression is
the type of the left operand unless the left operand has
qualified type, in which case it is the unqualified version
of the type of the left operand. The side effect of
updating the stored value of the left operand shall occur
between the previous and the next sequence point.

The second sentence is particularly important here. It tells us
that, for instance, if we write:

unsigned int a;
unsigned char b;
unsigned int c = 12345;

a = b = c;

the result stored in "a" is almost certainly *not* 12345, as it is
actually (12345 % (UCHAR_MAX + 1)). Typically UCHAR_MAX is 255 so
this is (12345 % 256), or 57. Of course, the compiler can use the
"as-if" rules to compile this as:

a = 57;
b = 57;
c = 12345;

(with the stores to a, b, and c happening in any order in the
actual underlying machine code -- or even being combined, if
there is some quick way to write to two or all three variables).

In the more difficult example where "b" is somehow mapped to a
hardware register, you -- the programmer -- *must* declare b using
the "volatile" qualifier, to tell the compiler "this thing is *not*
ordinary RAM, so you, Mr Compiler, must not play games with
optimizations that assume it *is* ordinary RAM." More typically,
you might replace b with *p where p has type "volatile T *" for
some type T. Consider the example of a memory-mapped device
that has a control-and-status register, in which writes to the
location cause the device to take actions, and reads from the
location return the device's status:

#define CSR_W_DMA 0x01 /* start DMA */
#define CSR_W_LDA 0x02 /* load DMA address from addr reg */
#define CSR_W_EI 0x04 /* enable interrupts */
#define CSR_W_RD 0x08 /* read from device (write to RAM) */
#define CSR_W_WR 0x00 /* write to device (read from RAM) - pseudo */
...
#define CSR_S_DMA 0x01 /* DMA is occuring now */
#define CSR_S_ERR 0x02 /* error occurred doing DMA */
#define CSR_S_IP 0x04 /* interrupt pending / op done-or-failed */
...

volatile int *csr;
volatile int *addr;
...
*addr = dma_addr; /* tell device where to do the op */
*csr = CSR_W_LDA;

/* do read from device, without using interrupts (poll for done) */
*csr = CSR_W_RD | CSR_W_DMA; /* read op with DMA */
while ((*csr & CSR_W_IP) == 0)
continue;

Without the "volatile" qualifier, the compiler can remove the first
write to *csr entirely (because the next write to *csr clobbers
the previous one), and replace the while loop with one that never
terminates (because we did not set CSR_W_EI and CSR_W_IP is the
same bit, so obviously that bit will never turn on).

Note that on some hardware (SPARC V9 for instance), the CPU may
need special instructions inserted at various points. These
instructions may even depend on the memory model set in the CPU.
(In this case, if I remember right, at least one, maybe two, "membar
#StoreStore"s and/or a "membar #MemIssue" in RMO, nothing at all
in TSO, and just one "StoreStore" in PSO; this depends on the fact
that the CPU recognizes the same address for the reads and writes
of the CSR. If the status register had a different address, one
more membar would sometimes be required.)

See also the ongoing thread "Volatiles in assignment expressions".
 
J

Jordan Abel

"Jordan"posted the following on 2006-03-10:


Could you explain how volatile changes anything here?

As far as I can tell, volatile restricts the compiler from making
optimizations that change the number or sense of accesses to the
variable in question.
It doesnt alter the limited bits in his memory mapped register and I
cant quite work out how it would make anything better : having said
that I only ever used volatile on HW port maps. Is that the issue
here? b is changing between "b=c" and "a=b"? Is this register a
read/write with some bits changing externally?

That's what the OP apparently says [i.e. some bits are changed to 0 at
the time of the write]
 
K

Keith Thompson

Richard Heathfield said:
(e-mail address removed) said:

You are Dan Pop, and I claim my five pounds!

A questionable assumption in the absence of the phrase "engage your
brain".
 
P

pete

Please try to pay attention. The question asked isn't about
whether a = b = c is good programming or whether anyone has
the "right" to use it in a program, but what the C Standard
says it means.

The point of interest is that
"assignment" is not a sequence point.

If
sorted -> next = *node;
sorted = *node;
is defined, then
sorted = sorted -> next = *node;
is undefined.
 
M

Mark F. Haigh

Keith said:
A questionable assumption in the absence of the phrase "engage your
brain".

Chapter and verse please.

Nyuk nyuk.


Mark F. Haigh
(e-mail address removed)
 
E

ena8t8si

Richard said:
(e-mail address removed) said:


I invite you to re-read my reply, especially the bit about side effects
(retained above).

I invite you to read the OP's posting again, and also the
description of how assignment expressions work in section
6.5.16. Your comment may have given useful advice but
wasn't really addressing the question asked. My comment was
on point, and also correct (of course you snipped the part
that explains that).

Chris Torek gave the best answer, being both on point and
a good explanation of the various issues.
 
E

ena8t8si

pete said:
The point of interest is that
"assignment" is not a sequence point.

If
sorted -> next = *node;
sorted = *node;
is defined, then
sorted = sorted -> next = *node;
is undefined.

Even if that ridiculous claim were true, it isn't responsive
to the OP's question. Does the intermediate assignment
influence what value is assigned to a? The answer to that
is YES. Must the value that is written into b be read out
again to assign to a? The answer to that is NO (if b isn't
volatile) or MAYBE (if b is volatile). Sequence points are
an irrelevant side issue.
 
E

ena8t8si

Richard said:
"(e-mail address removed)"posted the following on 2006-03-10:


Do pay attention : it moved on to the affects of volatility : and it
is this I was asking about. It was also discussing real world issues
at this stage and the "standard" was another issue. Pay particular
attention to my comments about using C in the real world in the
paragraph beginngin with "Why?" - as you will see, it was a question
and not a statement : a question to the a=c and b=c is simply wrong
statement.

Don't be an idiot. The only one talking about "real world
issues" was you. Any questions about whether something is
"physically volatile" or we are dealing with a "single
threaded app" are completely irrelevant to the original
question and to my comments. Whether or not any of the
variables is volatile, the intermediate assignment to b
influences the value assigned to a; read section 6.5.16.
And don't presume that other people's assumptions match
your assumptions. More often than you expect, they don't.
 
R

Richard G. Riley

"(e-mail address removed)"posted the following on 2006-03-11:
Don't be an idiot. The only one talking about "real world
issues" was you. Any questions about whether something is
"physically volatile" or we are dealing with a "single
threaded app" are completely irrelevant to the original
question and to my comments. Whether or not any of the

Sorry your hugeness : I was under the impression that threads tend to
change as they go on. Hence my question.
variables is volatile, the intermediate assignment to b
influences the value assigned to a; read section 6.5.16.

Yes. I know. hence my comment on using this statement only with
matching types or properly implicit conversions.
And don't presume that other people's assumptions match
your assumptions. More often than you expect, they don't.

I didnt, hence I mentioned other peoples assumptions and how shocked I
was at something so "seemingly" obvious being mangled in such a long
winded manner.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,057
Latest member
KetoBeezACVGummies

Latest Threads

Top