login
Header Space

 
 

Linux: Volatile Superstition

August 17, 2007 - 1:50pm
Submitted by Jeremy on August 17, 2007 - 1:50pm.
Linux news

"People who think 'volatile' really matters are just fooling themselves," Linus Torvalds quipped during a lengthy discussion on the Linux Kernel mailing list. The thread began with a series of patches to "make atomic_read() behave consistently across all architectures" which included "removing the volatile keyword from all atomic_t and atomic64_t definitions that currently have it, and instead explicitly [casting] the variable as volatile in atomic_read()."

Earlier in the discussion Linus had suggested that while it didn't actually fix any bugs it did help hide bugs and make them less likely to be triggered, "and hey, sometimes 'hiding bugs well enough' is ok. In this case, I'd argue that we've successfully *not* had the volatile there for eight months on x86-64, and that should tell people something. " But later in the discussion he related it to superstitions like the fear of a black cat crossing the road, which "has no bigger and longer-lasting direct affects":

"In other words, this whole discussion has just convinced me that we should *not* add back 'volatile' to 'atomic_read()' - I was willing to do it for practical and 'hide the bugs' reasons, but having seen people argue for it, thinking that it actually fixes something, I'm now convinced that the *last* thing we should do is to encourage that kind of superstitious thinking."


From:	Chris Snook [email blocked]
To: 	linux-kernel
Subject: [PATCH 0/24] make atomic_read() behave consistently across all architectures
Date:	Thu, 9 Aug 2007 09:14:23 -0400

As recent discussions[1], and bugs[2] have shown, there is a great deal of
confusion about the expected behavior of atomic_read(), compounded by the
fact that it is not the same on all architectures.  Since users expect calls
to atomic_read() to actually perform a read, it is not desirable to allow
the compiler to optimize this away.  Requiring the use of barrier() in this
case is inefficient, since we only want to re-load the atomic_t variable,
not everything else in scope.

This patchset makes the behavior of atomic_read uniform by removing the
volatile keyword from all atomic_t and atomic64_t definitions that currently
have it, and instead explicitly casts the variable as volatile in
atomic_read().  This leaves little room for creative optimization by the
compiler, and is in keeping with the principles behind "volatile considered
harmful".

Busy-waiters should still use cpu_relax(), but fast paths may be able to
reduce their use of barrier() between some atomic_read() calls.

	-- Chris

1)	http://lkml.org/lkml/2007/7/1/52
2)	http://lkml.org/lkml/2007/8/8/122


From: Segher Boessenkool [email blocked] Subject: Re: [PATCH 0/24] make atomic_read() behave consistently across all architectures Date: Thu, 16 Aug 2007 03:26:02 +0200 >> Part of the motivation here is to fix heisenbugs. If I knew where >> they > > By the same token we should probably disable optimisations > altogether since that too can create heisenbugs. Almost everything is a tradeoff; and so is this. I don't believe most people would find disabling all compiler optimisations an acceptable price to pay for some peace of mind. Segher
From: Nick Piggin [email blocked] Subject: Re: [PATCH 0/24] make atomic_read() behave consistently across all architectures Date: Thu, 16 Aug 2007 12:23:10 +1000 Segher Boessenkool wrote: >>> Part of the motivation here is to fix heisenbugs. If I knew where they >> >> >> By the same token we should probably disable optimisations >> altogether since that too can create heisenbugs. > > > Almost everything is a tradeoff; and so is this. I don't > believe most people would find disabling all compiler > optimisations an acceptable price to pay for some peace > of mind. So why is this a good tradeoff? I also think that just adding things to APIs in the hope it might fix up some bugs isn't really a good road to go down. Where do you stop? On the actual proposal to make atomic_operators volatile: I think the better approach in the long term, for both maintainability of the code and education of coders, is to make the use of barriers _more_ explicit rather than sprinkling these "just in case" ones around. You may get rid of a few atomic_read heisenbugs (in noise when compared to all bugs), but if the coder was using a regular atomic load, or a test_bit (which is also atomic), etc. then they're going to have problems. It would be better for Linux if everyone was to have better awareness of barriers than to hide some of the cases where they're required. A pretty large number of bugs I see in lock free code in the VM is due to memory ordering problems. It's hard to find those bugs, or even be aware when you're writing buggy code if you don't have some feel for barriers. -- SUSE Labs, Novell Inc.
From: Segher Boessenkool [email blocked] Subject: Re: [PATCH 0/24] make atomic_read() behave consistently across all architectures Date: Thu, 16 Aug 2007 21:32:03 +0200 >>>> Part of the motivation here is to fix heisenbugs. If I knew where >>>> they >>> >>> >>> By the same token we should probably disable optimisations >>> altogether since that too can create heisenbugs. >> Almost everything is a tradeoff; and so is this. I don't >> believe most people would find disabling all compiler >> optimisations an acceptable price to pay for some peace >> of mind. > > So why is this a good tradeoff? It certainly is better than disabling all compiler optimisations! > I also think that just adding things to APIs in the hope it might fix > up some bugs isn't really a good road to go down. Where do you stop? I look at it the other way: keeping the "volatile" semantics in atomic_XXX() (or adding them to it, whatever) helps _prevent_ bugs; certainly most people expect that behaviour, and also that behaviour is *needed* in some places and no other interface provides that functionality. [some confusion about barriers wrt atomics snipped] Segher
From: Nick Piggin [email blocked] Subject: Re: [PATCH 0/24] make atomic_read() behave consistently across all architectures Date: Fri, 17 Aug 2007 12:19:30 +1000 Segher Boessenkool wrote: >>>>> Part of the motivation here is to fix heisenbugs. If I knew where >>>>> they >>>> >>>> >>>> >>>> By the same token we should probably disable optimisations >>>> altogether since that too can create heisenbugs. >>> >>> Almost everything is a tradeoff; and so is this. I don't >>> believe most people would find disabling all compiler >>> optimisations an acceptable price to pay for some peace >>> of mind. >> >> >> So why is this a good tradeoff? > > > It certainly is better than disabling all compiler optimisations! It's easy to be better than something really stupid :) So i386 and x86-64 don't have volatiles there, and it saves them a few K of kernel text. What you need to justify is why it is a good tradeoff to make them volatile (which btw, is much harder to go the other way after we let people make those assumptions). >> I also think that just adding things to APIs in the hope it might fix >> up some bugs isn't really a good road to go down. Where do you stop? > > > I look at it the other way: keeping the "volatile" semantics in > atomic_XXX() (or adding them to it, whatever) helps _prevent_ bugs; Yeah, but we could add lots of things to help prevent bugs and would never be included. I would also contend that it helps _hide_ bugs and encourages people to be lazy when thinking about these things. Also, you dismiss the fact that we'd actually be *adding* volatile semantics back to the 2 most widely tested architectures (in terms of test time, number of testers, variety of configurations, and coverage of driver code). This is a very important different from just keeping volatile semantics because it is basically a one-way API change. > certainly most people expect that behaviour, and also that behaviour > is *needed* in some places and no other interface provides that > functionality. I don't know that most people would expect that behaviour. Is there any documentation anywhere that would suggest this? Also, barrier() most definitely provides the required functionality. It is overkill in some situations, but volatile is overkill in _most_ situations. If that's what you're worried about, we should add a new ordering primitive. > [some confusion about barriers wrt atomics snipped] What were you confused about? -- SUSE Labs, Novell Inc.
From: Paul Mackerras [email blocked] Subject: Re: [PATCH 0/24] make atomic_read() behave consistently across all architectures Date: Fri, 17 Aug 2007 13:16:00 +1000 Nick Piggin writes: > So i386 and x86-64 don't have volatiles there, and it saves them a > few K of kernel text. What you need to justify is why it is a good I'm really surprised it's as much as a few K. I tried it on powerpc and it only saved 40 bytes (10 instructions) for a G5 config. Paul.
From: Nick Piggin [email blocked] Subject: Re: [PATCH 0/24] make atomic_read() behave consistently across all architectures Date: Fri, 17 Aug 2007 13:32:10 +1000 Paul Mackerras wrote: > Nick Piggin writes: > > >>So i386 and x86-64 don't have volatiles there, and it saves them a >>few K of kernel text. What you need to justify is why it is a good > > > I'm really surprised it's as much as a few K. I tried it on powerpc > and it only saved 40 bytes (10 instructions) for a G5 config. > > Paul. > I'm surprised too. Numbers were from the "...use asm() like the other atomic operations already do" thread. According to them, text data bss dec hex filename 3434150 249176 176128 3859454 3ae3fe atomic_normal/vmlinux 3436203 249176 176128 3861507 3aec03 atomic_volatile/vmlinux The first one is a stock kenel, the second is with atomic_read/set cast to volatile. gcc-4.1 -- maybe if you have an earlier gcc it won't optimise as much? -- SUSE Labs, Novell Inc.
From: Linus Torvalds [email blocked] Subject: Re: [PATCH 0/24] make atomic_read() behave consistently across all architectures Date: Thu, 16 Aug 2007 20:50:30 -0700 (PDT) On Fri, 17 Aug 2007, Nick Piggin wrote: > > I'm surprised too. Numbers were from the "...use asm() like the other > atomic operations already do" thread. According to them, > > text data bss dec hex filename > 3434150 249176 176128 3859454 3ae3fe atomic_normal/vmlinux > 3436203 249176 176128 3861507 3aec03 atomic_volatile/vmlinux > > The first one is a stock kenel, the second is with atomic_read/set > cast to volatile. gcc-4.1 -- maybe if you have an earlier gcc it > won't optimise as much? No, see my earlier reply. "volatile" really *is* an incredible piece of crap. Just try it yourself: volatile int i; int j; int testme(void) { return i <= 1; } int testme2(void) { return j <= 1; } and compile with all the optimizations you can. I get: testme: movl i(%rip), %eax subl $1, %eax setle %al movzbl %al, %eax ret vs testme2: xorl %eax, %eax cmpl $1, j(%rip) setle %al ret (now, whether that "xorl + setle" is better than "setle + movzbl", I don't really know - maybe it is. But that's not thepoint. The point is the difference between movl i(%rip), %eax subl $1, %eax and cmpl $1, j(%rip) and imagine this being done for *every* single volatile access. Just do a git grep atomic_read to see how atomics are actually used. A lot of them are exactly the above kind of "compare against a value". Linus
From: Linus Torvalds [email blocked] Subject: Re: [PATCH 0/24] make atomic_read() behave consistently across all architectures Date: Thu, 16 Aug 2007 20:42:23 -0700 (PDT) On Fri, 17 Aug 2007, Paul Mackerras wrote: > > I'm really surprised it's as much as a few K. I tried it on powerpc > and it only saved 40 bytes (10 instructions) for a G5 config. One of the things that "volatile" generally screws up is a simple volatile int i; i++; which a compiler will generally get horribly, horribly wrong. In a reasonable world, gcc should just make that be (on x86) addl $1,i(%rip) on x86-64, which is indeed what it does without the volatile. But with the volatile, the compiler gets really nervous, and doesn't dare do it in one instruction, and thus generates crap like movl i(%rip), %eax addl $1, %eax movl %eax, i(%rip) instead. For no good reason, except that "volatile" just doesn't have any good/clear semantics for the compiler, so most compilers will just make it be "I will not touch this access in any way, shape, or form". Including even trivially correct instruction optimization/combination. This is one of the reasons why we should never use "volatile". It pessimises code generation for no good reason - just because compilers don't know what the heck it even means! Now, people don't do "i++" on atomics (you'd use "atomic_inc()" for that), but people *do* do things like if (atomic_read(..) <= 1) .. On ppc, things like that probably don't much matter. But on x86, it makes a *huge* difference whether you do movl i(%rip),%eax cmpl $1,%eax or if you can just use the value directly for the operation, like this: cmpl $1,i(%rip) which is again a totally obvious and totally safe optimization, but is (again) something that gcc doesn't dare do, since "i" is volatile. In other words: "volatile" is a horribly horribly bad way of doing things, because it generates *worse*code*, for no good reason. You just don't see it on powerpc, because it's already a load-store architecture, so there is no "good code" for doing direct-to-memory operations. Linus
From: Linus Torvalds [email blocked] Subject: Re: [PATCH 0/24] make atomic_read() behave consistently across all architectures Date: Thu, 16 Aug 2007 20:03:55 -0700 (PDT) On Fri, 17 Aug 2007, Paul Mackerras wrote: > > Volatile doesn't mean it can't be reordered; volatile means the > accesses can't be eliminated. It also does limit re-ordering. Of course, since *normal* accesses aren't necessarily limited wrt re-ordering, the question then becomes one of "with regard to *what* does it limit re-ordering?". A C compiler that re-orders two different volatile accesses that have a sequence point in between them is pretty clearly a buggy compiler. So at a minimum, it limits re-ordering wrt other volatiles (assuming sequence points exists). It also means that the compiler cannot move it speculatively across conditionals, but other than that it's starting to get fuzzy. In general, I'd *much* rather we used barriers. Anything that "depends" on volatile is pretty much set up to be buggy. But I'm certainly also willing to have that volatile inside "atomic_read/atomic_set()" if it avoids code that would otherwise break - ie if it hides a bug. Linus
From: Linus Torvalds [email blocked] Subject: Re: [PATCH 0/24] make atomic_read() behave consistently across all architectures Date: Fri, 17 Aug 2007 09:48:39 -0700 (PDT) On Fri, 17 Aug 2007, Nick Piggin wrote: > > That's not obviously just taste to me. Not when the primitive has many > (perhaps, the majority) of uses that do not require said barriers. And > this is not solely about the code generation (which, as Paul says, is > relatively minor even on x86). I prefer people to think explicitly > about barriers in their lockless code. Indeed. I think the important issues are: - "volatile" itself is simply a badly/weakly defined issue. The semantics of it as far as the compiler is concerned are really not very good, and in practice tends to boil down to "I will generate so bad code that nobody can accuse me of optimizing anything away". - "volatile" - regardless of how well or badly defined it is - is purely a compiler thing. It has absolutely no meaning for the CPU itself, so it at no point implies any CPU barriers. As a result, even if the compiler generates crap code and doesn't re-order anything, there's nothing that says what the CPU will do. - in other words, the *only* possible meaning for "volatile" is a purely single-CPU meaning. And if you only have a single CPU involved in the process, the "volatile" is by definition pointless (because even without a volatile, the compiler is required to make the C code appear consistent as far as a single CPU is concerned). So, let's take the example *buggy* code where we use "volatile" to wait for other CPU's: atomic_set(&var, 0); while (!atomic_read(&var)) /* nothing */; which generates an endless loop if we don't have atomic_read() imply volatile. The point here is that it's buggy whether the volatile is there or not! Exactly because the user expects multi-processing behaviour, but "volatile" doesn't actually give any real guarantees about it. Another CPU may have done: external_ptr = kmalloc(..); /* Setup is now complete, inform the waiter */ atomic_inc(&var); but the fact is, since the other CPU isn't serialized in any way, the "while-loop" (even in the presense of "volatile") doesn't actually work right! Whatever the "atomic_read()" was waiting for may not have completed, because we have no barriers! So if "volatile" makes a difference, it is invariably a sign of a bug in serialization (the one exception is for IO - we use "volatile" to avoid having to use inline asm for IO on x86) - and for "random values" like jiffies). So the question should *not* be whether "volatile" actually fixes bugs. It *never* fixes a bug. But what it can do is to hide the obvious ones. In other words, adding a volatile in the above kind of situation of "atomic_read()" will certainly turn an obvious bug into something that works "practically all of the time). So anybody who argues for "volatile" fixing bugs is fundamentally incorrect. It does NO SUCH THING. By arguing that, such people only show that you have no idea what they are talking about. So the only reason to add back "volatile" to the atomic_read() sequence is not to fix bugs, but to _hide_ the bugs better. They're still there, they are just a lot harder to trigger, and tend to be a lot subtler. And hey, sometimes "hiding bugs well enough" is ok. In this case, I'd argue that we've successfully *not* had the volatile there for eight months on x86-64, and that should tell people something. (Does _removing_ the volatile fix bugs? No - callers still need to think about barriers etc, and lots of people don't. So I'm not claiming that removing volatile fixes any bugs either, but I *am* claiming that: - removing volatile makes some bugs easier to see (which is mostly a good thing: they were there before, anyway). - removing volatile generates better code (which is a good thing, even if it's just 0.1%) - removing volatile removes a huge mental *bug* that lots of people seem to have, as shown by this whole thread. Anybody who thinks that "volatile" actually fixes anything has a gaping hole in their head, and we should remove volatile just to make sure that nobody thinks that it means soemthign that it doesn't mean! In other words, this whole discussion has just convinced me that we should *not* add back "volatile" to "atomic_read()" - I was willing to do it for practical and "hide the bugs" reasons, but having seen people argue for it, thinking that it actually fixes something, I'm now convinced that the *last* thing we should do is to encourage that kind of superstitious thinking. "volatile" is like a black cat crossing the road. Sure, it affects *something* (at a minimum: before, the black cat was on one side of the road, afterwards it is on the other side of the road), but it has no bigger and longer-lasting direct affects. People who think "volatile" really matters are just fooling themselves. Linus
From: Linus Torvalds [email blocked] Subject: Re: [PATCH 0/24] make atomic_read() behave consistently across all architectures Date: Fri, 17 Aug 2007 12:08:31 -0700 (PDT) On Fri, 17 Aug 2007, Chris Friesen wrote: > > I assume you mean "except for IO-related code and 'random' values like > jiffies" as you mention later on? Yes. There *are* valid uses for "volatile", but they have remained the same for the last few years: - "jiffies" - internal per-architecture IO implementations that can do them as normal stores. > I assume other values set in interrupt handlers would count as "random" > from a volatility perspective? I don't really see any valid case. I can imagine that you have your own "jiffy" counter in a driver, but what's the point, really? I'd suggest not using volatile, and using barriers instead. > > > So anybody who argues for "volatile" fixing bugs is fundamentally > > incorrect. It does NO SUCH THING. By arguing that, such people only > > show that you have no idea what they are talking about. > What about reading values modified in interrupt handlers, as in your > "random" case? Or is this a bug where the user of atomic_read() is > invalidly expecting a read each time it is called? Quite frankly, the biggest reason for using "volatile" on jiffies was really historic. So even the "random" case is not really a very strong one. You'll notice that anybody who is actually careful will be using sequence locks for the jiffy accesses, if only because the *full* jiffy count is actually a 64-bit value, and so you cannot get it atomically on a 32-bit architecture even on a single CPU (ie a timer interrupt might happen in between reading the low and the high word, so "volatile" is only used for the low 32 bits). So even for jiffies, we actually have: extern u64 __jiffy_data jiffies_64; extern unsigned long volatile __jiffy_data jiffies; where the *real* jiffies is not volatile: the volatile one is using linker tricks to alias the low 32 bits: - arch/i386/kernel/vmlinux.lds.S: ... jiffies = jiffies_64; ... and the only reason we do all these games is (a) it works and (b) it's legacy. Note how I do *not* say "(c) it's a good idea". Linus



Related Links:

*sigh* Volatile sucks.

August 18, 2007 - 6:59pm

The "volatile" keyword really is a tragedy, in so many ways.

For instance, consider the following dubious code:

volatile int a, b, c;

a = b = c;

How many reads and writes of each variable happen? Turns out, it's unspecified, or at least underspecified! For certain, 'c' gets read once, and 'a' gets written once. But what about 'b'?

Under one interpretation, the code above is equivalent to the following sequence that implies 'b' gets written once and read once:

b = c;
a = b;

But, there's a different way to interpret the code. See, the assignment operator (the '=' sign) is like any other operator. 'a' gets assigned the value of the computation 'b = c', which just also happens to be the value of 'c'. In other words, the original code sequence could also be taken as equivalent to:

tmp = c;
b = tmp;
a = tmp;

...where 'tmp' is an invisible compiler-generated temporary. And, I've seen:

b = c;
a = c;

...which implies 'c' gets read twice! It turns out I've seen compilers that interpret it each of the three ways above. That's just one place where volatile sucks.

Another is that all volatile variable accesses get "strongly ordered" relative to all other volatile accesses, but only when it comes to the compiler generating code. Nothing informs the hardware of the intent. So, here you've tried to, say, modify a structure under a lock. But, the stores that release the lock get ahead of your updates to the structure somewhere in the memory system. Oops! You're b0rken. That's where memory barriers comes in.

The C standard really needs to grow up on this point and offer standardized memory barriers. As a memory system architect (that's part of my day job), I find it really, really frustrating that I can't speed up more code, because too much information on intent gets lost. For the processor family I work with, we've managed to get by, but it's really been a pain.

--
Program Intellivision and play Space Patrol!

a=b=c is well-defined and

August 19, 2007 - 11:08am
Anonymous (not verified)

a=b=c is well-defined and there is only one way to interpret this code: operator = is right-associative, so this statement is interpreted as a = (b=c). The C99 standard that any access to volatile object is a "side effect", so this statement must be compiled into

[read c]
[write b]
[read b]
[write a]

Introducing the temporary or reading c twice is, I believe, an error in the implementation.

When you go to assembly,

August 19, 2007 - 12:16pm
Anonymous (not verified)

When you go to assembly, then reading c will be stored in a register, effectively a temporary.

So, "read c, write b, write a" would seem most optimized, and not necessarily incorrect.

Not clear enough to explain volatile

August 19, 2007 - 12:22pm
Anonymous (not verified)

When you introduce the volatile keyword, the implementation of the statement depends on how you interpret the standard. Yes, a = b = c; is a statement that associates to the right, and the result of (b = c) is b, essentially. In my copy of the K&R book, it explains that the value of (b = c) is the value of b, but ask yourself... The value of b in the moment it was assigned or the value of b in the moment a is being written? Because the value of b can change after it has been written and before the next instruction is run. That's the magic of volatile.

The problem is that volatile means that the contents of those variables can change at any moment, without needing modifications by the program itself. Why? Because they may be mapped to device memory, and the hardware can change the contents without you explicitly changing it. So when you do a = b = c; You have to read c at least once, you have to write the contents to b, and then you probably need to read b again (the hardware may have changed the value in the mean time), and write the contents to a. But maybe you think that a = b = c; should be interpreted as assigning a and b the same value c has. In other words, you interpret that such an operation should be more or less atomic, given that if you didn't want it to be atomic you would have written "b = c; a = c;" or "b = c; a = b;". In this case you may want to store the value read from c in a temporary variable, and write that value to a and b. It all comes down to what I said previously. (b = c); is the value of b, but in what moment in time?

Moreover, sometimes standards leave the result of some operations difficult to interpret on purpose, or plainly tell you that the result of some legal operations is undefined, so people avoid undefined behaviours and use proper mechanisms. I don't know if this is the case, though.

Erm...

August 19, 2007 - 1:57pm

The way *I* understand it, you're not assigning 'b' to 'a'. You're assigning "the value of the expression (b = c)" to 'a'. The value of 'b' can change all day long, but the value of the expression (b = c) gets established the moment the CPU reads 'c'.

Clearly, that's not the only valid interpretation. The message Timothy Brownawell linked to below sums it up pretty well I think: http://gcc.gnu.org/ml/gcc/1999-10n/msg00258.html

--
Program Intellivision and play Space Patrol!

lvalue

August 20, 2007 - 12:28pm
Anonymous (not verified)

What you say is true. You are assigning 'a' the value of (b = c). But as far as I know, the value of an assignment expression is its lvalue, or 'b' in this case.

lvalues and rvalues

August 20, 2007 - 5:21pm

Well, the value of (b = c) is an rvalue, not an lvalue. (Take a look at line 1281.) You can't assign to (b = c). As someone else (not here) pointed out, it's lvalue-to-rvalue conversion that implies a read.

In fact, everything about the description of the assignment expression implies the interpretation involving an invisible temporary is indeed the most correct interpretation.

1279 An assignment operator shall have a modifiable lvalue as its left operand.

1280 An assignment operator stores a value in the object designated by the left operand.

1281 An assignment expression has the value of the left operand after the assignment, but is not an lvalue.

1282 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.

1283 The side effect of updating the stored value of the left operand shall occur between the previous and the next sequence point.

1284 The order of evaluation of the operands is unspecified.

1285 If an attempt is made to modify the result of an assignment operator or to access it after the next sequence point, the behavior is undefined.

"Has the value of the operand after the assignment" can be interpreted two ways. One interpretation is that the expression has the value that's left over after casting to the target type, and the other implies an actual read of memory. (It's muddy, because depending on the type conversion and the architecture, this cast could happen as part of the store or as a separate instruction.)

"Unless the left operand has qualified type, in which case it is the unqualified version of the type" is probably the more important detail. If the type was previously "volatile int"—a qualified type—the type of the result is simply "int". The example they give on line 1297 confirms this, if perhaps confusingly: The pointer type was qualified as "const" (meaning the pointer itself couldn't be changed to point to something else), but the pointed-to type was qualified as "volatile". The pointed-to type is irrelevant. The assignment expression changes the resulting pointer to be non-const without changing the type of the pointed-to object. Had the example been merely "const int", the resulting type would be "int".

Confused yet?

Oh, and "The side effect of updating the stored value of the left operand shall occur between the previous and the next sequence point" has its own implication. See, assignment isn't a sequence point. So even the order in which 'a' and 'b' get stored in the expression "a = b = c" is left to the implementation.

Gotta love C.

--
Program Intellivision and play Space Patrol!

Well, sometimes your

August 21, 2007 - 1:49am
Anonymous (not verified)

Well, sometimes your compiler can do the sane thing or (XOR) the standards conformant thing. I prefer a compiler doing the sane thing or having a switch to chose between either.

Hmm...

August 21, 2007 - 9:32am

In this case, I happen to think treating (b = c) as an rvalue and not reading 'b' is the sane thing, but plenty of others disagree. It actually surprised me that some people expect a read of 'b' there, since "the value of the expression (b = c)" is a very different thing than "a reference to b".

I guess it's like -fsigned-char and -funsigned-char. Where the standard gives you wiggle room and there's multiple "sane" interpretations, or where the standard is clearly insane, you want to be able to enable the set of decisions the programmer thinks is most sane on a case by case basis.

--
Program Intellivision and play Space Patrol!

confusion

August 21, 2007 - 2:33pm
Anonymous (not verified)

I'm sorry if my words confused you. I didn't mean to say that an assigment is an lvalue. It's not. The only thing I said is that, as far as I knew, and your quote has confirmed with a small exception, the value of an assignment expression is its lvalue (the lvalue appearing in the assigment expression). This is directly said in point 1281, as "has the value of the left operand after the assignment". The value of (b = c) is the value of b, which is got in this case from c. Like you said, the "volatile" qualifier leaves a lot of things in the air as how that should be translated to machine code. Do not forget I essentially agree with you. I was merely trying to point out that the guy who originally replied to you was wrong.

No worries

August 21, 2007 - 4:56pm

It's a complicated topic... all the more reason to avoid relying on volatile. In the few cases where it does get used, use it carefully, deliberately, and in a manner unlikely to result in surprising code.

--
Program Intellivision and play Space Patrol!

a=b=c is well-defined and

August 19, 2007 - 11:09am
Zeljko Vrba (not verified)

a=b=c is well-defined and there is only one way to interpret this code: operator = is right-associative, so this statement is interpreted as a = (b=c). The C99 standard that any access to volatile object is a "side effect", so this statement must be compiled into

[read c]
[write b]
[read b]
[write a]

Introducing the temporary or reading c twice is, I believe, an error in the implementation.

According to

August 19, 2007 - 12:11pm

According to http://gcc.gnu.org/ml/gcc/1999-10n/msg00258.html it sounds like all 3 are valid, but the last one only barely so.

Yep

August 19, 2007 - 1:51pm

In my discussions with the compiler folks at work, the consensus is that the "most correct" interpretation of "a = b = c" is that 'a' gets assigned "the value of the expression (b = c)," as opposed to 'a' gets assigned 'b'. In other words, there isn't an implied read of 'b' in the expression at all. Also, "the value of the expression (b = c)" does not imply a second reading of 'c' either. Reading 'c' once is sufficient to establish the value of the expression.

Clearly, as the message you linked points out, the C standard allows a surprising bit of latitude on this. That makes volatile that much less usable, since its meaning is so vague.

--
Program Intellivision and play Space Patrol!

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
speck-geostationary