Hi!
I tried to post this to comp.lang.c.mod erated but it didn't
seem to go through.
I've got a question about volatiles in assignment expressions.
I found the following code snippet in an embedded development
forum:
----
volatile char a;
...
if (++a == 1)
{
...
----
This code snippet was then followed by the output from two
compilers, one which was gcc. Gcc's code looked like something
like this (in RTL like form where Rn denotes a register):
R1 <- *a
R1 <- R1 + 1
*a <- R1
R2 <- *a
IF R2 == 1
Now to my question! The code above contains two read accesses
and one write access to a. I would have assumed that only one
read access should be performed. Does the standard allow the
second read operation?
I am adding a lot of standard texts that I've used when trying
to deduce the answer. Please help if you can!
Sorry for the long posting!
Regards
/Daniel W
P.s. Remove underscores from e-mail address if replying via
e-mail!
------------------------------------------------------------
Reading from the standard ISO/IEC [9899:1999] I find the
following passages related to this issue (A denotes the
rationale and B denotes the standard):
B.5.1.2.3 - 2 "Accessing a volatile object, modifying an object,
modifying a file, or calling a function that does any of those
operations are all side effects, which are changes in the state
of the execution environment. Evaluation of an expression may
produce side effects. At certain specified points in the execution
sequence called sequence points, all side effects of previous
evaluations shall be complete and no side effects of subsequent
evaluations shall have taken place." ...
B.6.5.16 - 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."
B.6.5.16.2 - 3 "A compound assignment of the form E1 op= E2
differs from the simple assignment expression E1 = E1 op E2 only
in that the lvalue E1 is evaluated only once."
B.6.3.2.1 - 2 "Except when it is the operand of the sizeof operator,
the unary & operator, the ++ operator, the -- operator, or the left
operand of the . operator or an assignment operator, an lvalue that
does not have array type is converted to the value stored in the
designated object (and is no longer an lvalue). If the lvalue have
qualified type, the value has the unqualified version of the type
of the lvalue; otherwise, the value has the type of the lvalue. If
the lvalue has an incomplete type and does not have array type, the
behavior is undefined."
B.6.5.3.1 - 2 "The value of the operand of the prefix ++ operator
is incremented. The result is the new value of the operand after
incrementation. The expression ++E is equivalent to (E+=1). See
discussions of additive operators and compound assignment for
information on constraints, types, side effects, and conversions
and the effects of operations on pointers."
B.6.7.3 - 3 "The properties associated with qualified types are
meaningfull only for expressions that are lvalues."
B.6.7.3 - 6 "An object that has volatile-qualified type may be
modified in ways unknown to the implementation or have other
unknown side effects. Therefore any expression refering to such
an object shall be evaulated strictly according to the rules of
the abstract machine, as described in 5.1.2.3. Furthermore, at
every sequence point the value stored in the object shall agree
with that prescribed by the abstract machine, except as modified
by the unknown factors mentioned previously. What constitutes
an access to an object that has volatile-qualified type is
implementetion-defined."
Annex C in the standard places a sequnce point after the controlling
expression of a selection statement.
A.6.5.2.4 "The C89 Committee did not endorse the practice in some
implementations of considering post-increment and post-decrement
operator expressions to be lvalues."
A.6.5.3.1 "See 6.5.2.4."
A.6.5.16 "The optimization rules for factoring out assignments can
also be stated. Let X(i,S) be an expression which contains no
impure functions or sequenced operators, and suppose that X
contains a storage S(i) to i which sets i to Snew(i) and returns
Sval(i). The possible expressions are
S(i): Sval(i): Snew(i):
++i i+1 i+1
i++ i i+1
--i i-1 i-1
i-- i i-1
y y y
i op= y i op y i op y
Then X(i,S) can be replaced by either
(T = i, i = Snew(i), X(T,Sval))
or
(T = X(i,Sval), i = Snew(i), T)
provided that neither i nor y have side effects themselves."
Mar 7 '06
13 1953
"Chris Torek" <no****@torek.n et> wrote in message
news:du*******@ news4.newsguy.c om... On 2006-03-07, Daniel W <_R*********@ho tmail.com> wrote: [given] volatile char a; ... if (++a == 1)
[gcc chooses to load, add, store, load again, and then compare, i.e., re-read "a" after writing it in case the computed "old a plus 1" is not still stored in "a".]
In article <sl************ ***********@ran dom.yi.org> Jordan Abel <ra*******@gmai l.com> wrote:That's because for some reason gcc chooses to read a again. However, the C code only includes ONE read access - to read the original value which is incremented.
This is not clear, or at least, not to me. (It happens to be what I would *prefer*, but the model in the C standards is a bit vague.) I believe the most important sentence, however, is this one from 6.5.3 of a C99 draft:
What constitutes an access to an object that has volatile-qualified type is implementation-defined.
In other words, gcc can do anything at all here, provided what it does is documented.
Hmm. The value of an object is the last value stored in it (ignoring
type-punning issues here). So, if we have that to hand, we can take the
value of the object without actually accessing the object, even in the
abstract machine.
But we're told that with a volatile object, the last store need not be
explicit in the program (C99 6.2.4 p2 footnote 26 -- the standard regards a
volatile object as one which does retain its last stored value, but which
may have values stored inexplicitly between one program access and the
next).
So, the abstract machine, if it needs to take the value of a volatile
object, cannot have the last stored value handy, and must fetch the value
from the object.
And with volatiles around, the real machine must forget about as-if rewrites
and do what the abstract machine does.
There's still the bit about "what constitutes an access ... is
implementation-defined". But if the latitude given to the implementor
allows him to define read access to a volatile object as "don't bother to
look at the object, just use the last value we wrote to it", then the whole
purpose of volatile is defeated and it's all nonsense. The scope given to
the implementor must be limited by the requirement to actually obtain the
last (inexplicitly) stored value.
(This means to say: I don't know what it is exactly that's
implementation-defined, but I can't see any room within the defined
semantics for it to make a difference)
When we come to the assignment expression, that unfortunate phrase "value of
the left operand" leads me to the conclusion that (with a volatile operand)
the read access (a la gcc) is not just optional but mandatory, on the
wording as it stands.
But there's a possible get-out in the tag "after the assignment". This has
two possible interpretations :
(A) immediately after the assignment, before anything else can happen
(B) after the assignment, and possibly after further writes to the object
Is it possible that the authors, if they'd been a bit more verbose, would
have written (B)? Hard to believe. The "further writes" can't be explicit
writes, the leeway could only extend to inexplicit writes to volatile
objects. What rationale could the committee have had for wanting to include
such a proviso, which basically would conflict with the normally understood
intention of assignments being expressions with values, as represented by
expressions like a = b = c = d?
On the other hand, (A) seems to conflict with footnote 26 mentioned above,
which apparently intends to say that the last stored value of a volatile
object can never be taken as known. But maybe not, on a closer argument.
We've been given the difficult example of a hardware register that takes a
command code and returns a status code. Since 6.2.4 p2 says that all C
objects retain their last-stored values, the only way we can reconcile this
beastie with C semantics is to say that the explicit write is immediately
followed by an inexplicit write: but since the two writes are separate
sequential events, there must logically be a conceptual interval during
which the last stored value of the C object (which is an abstraction, not
actually a piece of hardware) is indeed the value we just wrote,
notwithstanding that there is effectively no corresponding hardware state.
Of course we can't access this value by readback, but the C semantics don't
require that, since we know what it is by definition anyway.
With this argument, I claim that option (A) above is possible, i.e. not in
conflict with anything else, and obviously preferable to (B). Can I get
away with this?
--
RSH
Robin Haigh wrote: "Chris Torek" <no****@torek.n et> wrote in message news:du*******@ news4.newsguy.c om...
On 2006-03-07, Daniel W <_R*********@ho tmail.com> wrote: [given]
volatile char a; ... if (++a == 1)
[gcc chooses to load, add, store, load again, and then compare, i.e., re-read "a" after writing it in case the computed "old a plus 1" is not still stored in "a".]
In article <sl************ ***********@ran dom.yi.org> Jordan Abel <ra*******@gmai l.com> wrote:
That's because for some reason gcc chooses to read a again. However, the C code only includes ONE read access - to read the original value which is incremented.
This is not clear, or at least, not to me. (It happens to be what I would *prefer*, but the model in the C standards is a bit vague.) I believe the most important sentence, however, is this one from 6.5.3 of a C99 draft:
What constitutes an access to an object that has volatile-qualified type is implementation-defined.
In other words, gcc can do anything at all here, provided what it does is documented.
Hmm. The value of an object is the last value stored in it (ignoring type-punning issues here). So, if we have that to hand, we can take the value of the object without actually accessing the object, even in the abstract machine.
Yes, but the value of an assignment expression is not an alias
for the object assigned, i.e. the value of the assignment expression
should not be subject to any volatileness of the lvalue object? (C99
Rationale A.6.5.3.1).
The standard also states that it is the lvalue to rvalue conversion
that constitutes a read (B.6.3.2.1 p2), and if the value of an
assignment expression is not an lvalue, then no lvalue-to-rvalue
conversion can occur.
But we're told that with a volatile object, the last store need not be explicit in the program (C99 6.2.4 p2 footnote 26 -- the standard regards a volatile object as one which does retain its last stored value, but which may have values stored inexplicitly between one program access and the next).
So, the abstract machine, if it needs to take the value of a volatile object, cannot have the last stored value handy, and must fetch the value from the object.
And with volatiles around, the real machine must forget about as-if rewrites and do what the abstract machine does.
There's still the bit about "what constitutes an access ... is implementation-defined". But if the latitude given to the implementor allows him to define read access to a volatile object as "don't bother to look at the object, just use the last value we wrote to it", then the whole purpose of volatile is defeated and it's all nonsense. The scope given to the implementor must be limited by the requirement to actually obtain the last (inexplicitly) stored value.
(This means to say: I don't know what it is exactly that's implementation-defined, but I can't see any room within the defined semantics for it to make a difference)
When we come to the assignment expression, that unfortunate phrase "value of the left operand" leads me to the conclusion that (with a volatile operand) the read access (a la gcc) is not just optional but mandatory, on the wording as it stands.
Yet again, the C99 Rationale (A.6.5.16) gives only two possible
optimizations which, if taken as guideline on how things should
work in the non-optimized case, clearly indicates that the
intended functionality does not allow the second read. The only
possible optimization for the pre-increment case is:
(T = a + 1, a = T, T)
which do not contain the second read operation.
But there's a possible get-out in the tag "after the assignment". This has two possible interpretations :
(A) immediately after the assignment, before anything else can happen (B) after the assignment, and possibly after further writes to the object
Is it possible that the authors, if they'd been a bit more verbose, would have written (B)? Hard to believe. The "further writes" can't be explicit writes, the leeway could only extend to inexplicit writes to volatile objects. What rationale could the committee have had for wanting to include such a proviso, which basically would conflict with the normally understood intention of assignments being expressions with values, as represented by expressions like a = b = c = d?
On the other hand, (A) seems to conflict with footnote 26 mentioned above, which apparently intends to say that the last stored value of a volatile object can never be taken as known. But maybe not, on a closer argument.
It could also be that they wanted to say
(C) The value of an assignmend expression is the properly type
casted value assigned to the left operand
We've been given the difficult example of a hardware register that takes a command code and returns a status code. Since 6.2.4 p2 says that all C objects retain their last-stored values, the only way we can reconcile this beastie with C semantics is to say that the explicit write is immediately followed by an inexplicit write: but since the two writes are separate sequential events, there must logically be a conceptual interval during which the last stored value of the C object (which is an abstraction, not actually a piece of hardware) is indeed the value we just wrote, notwithstanding that there is effectively no corresponding hardware state. Of course we can't access this value by readback, but the C semantics don't require that, since we know what it is by definition anyway.
Again, I think that the rationale (A.6.5.16) gives a strong
hint that this is just the interpretation that they commitee
intended, i.e. the value *just* after passing it along the
data bus to the destination :-)
With this argument, I claim that option (A) above is possible, i.e. not in conflict with anything else, and obviously preferable to (B). Can I get away with this?
I feel inclined to say that (A) falls closer to what I would
like the standard to say. I would prefere even more if the
wording was more like (C).
/Daniel
(who likes his theories nice and concrete)
"Daniel W" <_R*********@ho tmail.com> wrote in message
news:44******** *************** @news3.bahnhof. se... Robin Haigh wrote: "Chris Torek" <no****@torek.n et> wrote in message news:du*******@ news4.newsguy.c om...
On 2006-03-07, Daniel W <_R*********@ho tmail.com> wrote:
[given]
> volatile char a; > ... > if (++a == 1)
[gcc chooses to load, add, store, load again, and then compare, i.e., re-read "a" after writing it in case the computed "old a plus 1" is not still stored in "a".]
In article <sl************ ***********@ran dom.yi.org> Jordan Abel <ra*******@gmai l.com> wrote:
That's because for some reason gcc chooses to read a again. However,
theC code only includes ONE read access - to read the original value which is incremented.
This is not clear, or at least, not to me. (It happens to be what I would *prefer*, but the model in the C standards is a bit vague.) I believe the most important sentence, however, is this one from 6.5.3 of a C99 draft:
What constitutes an access to an object that has volatile-qualified type is implementation-defined.
In other words, gcc can do anything at all here, provided what it does is documented.
Hmm. The value of an object is the last value stored in it (ignoring type-punning issues here). So, if we have that to hand, we can take the value of the object without actually accessing the object, even in the abstract machine.
Yes, but the value of an assignment expression is not an alias for the object assigned, i.e. the value of the assignment expression should not be subject to any volatileness of the lvalue object? (C99 Rationale A.6.5.3.1).
The standard also states that it is the lvalue to rvalue conversion that constitutes a read (B.6.3.2.1 p2), and if the value of an assignment expression is not an lvalue, then no lvalue-to-rvalue conversion can occur.
But we're told that with a volatile object, the last store need not be explicit in the program (C99 6.2.4 p2 footnote 26 -- the standard
regards a volatile object as one which does retain its last stored value, but
which may have values stored inexplicitly between one program access and the next).
So, the abstract machine, if it needs to take the value of a volatile object, cannot have the last stored value handy, and must fetch the
value from the object.
And with volatiles around, the real machine must forget about as-if
rewrites and do what the abstract machine does.
There's still the bit about "what constitutes an access ... is implementation-defined". But if the latitude given to the implementor allows him to define read access to a volatile object as "don't bother
to look at the object, just use the last value we wrote to it", then the
whole purpose of volatile is defeated and it's all nonsense. The scope given
to the implementor must be limited by the requirement to actually obtain
the last (inexplicitly) stored value.
(This means to say: I don't know what it is exactly that's implementation-defined, but I can't see any room within the defined semantics for it to make a difference)
When we come to the assignment expression, that unfortunate phrase
"value of the left operand" leads me to the conclusion that (with a volatile
operand) the read access (a la gcc) is not just optional but mandatory, on the wording as it stands.
Yet again, the C99 Rationale (A.6.5.16) gives only two possible optimizations which, if taken as guideline on how things should work in the non-optimized case, clearly indicates that the intended functionality does not allow the second read. The only possible optimization for the pre-increment case is:
(T = a + 1, a = T, T)
which do not contain the second read operation.
But there's a possible get-out in the tag "after the assignment". This
has two possible interpretations :
(A) immediately after the assignment, before anything else can happen (B) after the assignment, and possibly after further writes to the
object Is it possible that the authors, if they'd been a bit more verbose,
would have written (B)? Hard to believe. The "further writes" can't be
explicit writes, the leeway could only extend to inexplicit writes to volatile objects. What rationale could the committee have had for wanting to
include such a proviso, which basically would conflict with the normally
understood intention of assignments being expressions with values, as represented
by expressions like a = b = c = d?
On the other hand, (A) seems to conflict with footnote 26 mentioned
above, which apparently intends to say that the last stored value of a volatile object can never be taken as known. But maybe not, on a closer
argument. It could also be that they wanted to say
(C) The value of an assignmend expression is the properly type casted value assigned to the left operand
We've been given the difficult example of a hardware register that takes
a command code and returns a status code. Since 6.2.4 p2 says that all C objects retain their last-stored values, the only way we can reconcile
this beastie with C semantics is to say that the explicit write is
immediately followed by an inexplicit write: but since the two writes are separate sequential events, there must logically be a conceptual interval during which the last stored value of the C object (which is an abstraction,
not actually a piece of hardware) is indeed the value we just wrote, notwithstanding that there is effectively no corresponding hardware
state. Of course we can't access this value by readback, but the C semantics
don't require that, since we know what it is by definition anyway.
Again, I think that the rationale (A.6.5.16) gives a strong hint that this is just the interpretation that they commitee intended, i.e. the value *just* after passing it along the data bus to the destination :-)
With this argument, I claim that option (A) above is possible, i.e. not
in conflict with anything else, and obviously preferable to (B). Can I get away with this?
I feel inclined to say that (A) falls closer to what I would like the standard to say. I would prefere even more if the wording was more like (C).
And there's another argument. Presumably it's not the intention to imply a
sequence point here. But if the wording is read as requiring two reads of a
modified object between sequence points, this is exactly analogous to a
situation that would normally produce undefined behaviour, because the
timing of the assignment is unspecified.
And if the ordering of the steps in the normal case is unspecified, then the
requirement that we must emulate the abstract machine in the presence of a
volatile object doesn't say much. As-if rewrites (where the abstract
semantics are specified, but an alternative is externally equivalent) are
banned, but any indeterminacy in the ordering of steps between sequence
points at the abstract machine level is still there.
--
RSH
Robin Haigh wrote: <big snip>
And there's another argument. Presumably it's not the intention to imply a sequence point here. But if the wording is read as requiring two reads of a modified object between sequence points, this is exactly analogous to a situation that would normally produce undefined behaviour, because the timing of the assignment is unspecified.
And if the ordering of the steps in the normal case is unspecified, then the requirement that we must emulate the abstract machine in the presence of a volatile object doesn't say much. As-if rewrites (where the abstract semantics are specified, but an alternative is externally equivalent) are banned, but any indeterminacy in the ordering of steps between sequence points at the abstract machine level is still there.
So what it boils down to is that we cannot use the value
of an assignment expression to a volatile object if we
want to guarantee the one-read-one-write scenario. Either
implementation (one read or two reads) are both within
the (fuzzy) limits of the standard.
So, if I want to be sure of the sequencing of, and number of
read operations in, the generated code, I must rely on a
temporary in the code, i.e.
t = a + 1;
a = t;
if (t /* or a */ == 1)
{
...
Many thanks
/Daniel
P.s. It's when I read texts like yours that I truly
comprehend the difference between beeing good at english
as a second language and beeing really good at english
as the primary language :-) This thread has been closed and replies have been disabled. Please start a new discussion. Similar topics |
by: Paul Rubin |
last post by:
OK, I want to scan a file for lines matching a certain regexp. I'd
like to use an assignment expression, like
for line in file:
if (g := re.match(pat, line)):
croggle(g.group(1))
Since there are no assignment expressions in Python, I have to use a
temp var. That's a little more messy, but bearable:
|
by: Andrew Koenig |
last post by:
It has been pointed out to me that various C++ books disagree about
the relative precedence of ?: and the assignment operators.
In order to satisfy myself about the matter once and for all,
I looked at the grammar in the C++ standard. The relative fragments
are as follows:
conditional-expression:
logical-or-expression
logical-or-expression ? expression : assignment-expression
|
by: Neil Zanella |
last post by:
Hello,
I would like to know whether the following C fragment is legal in
standard C and behaves as intended under conforming implementations...
union foo {
char c;
double d;
};
|
by: berns |
last post by:
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...
|
by: Lauri Alanko |
last post by:
The following code crashes on Solaris 10 when compiled without
optimization:
typedef struct Node Node;
struct Node {
int val;
Node *next;
};
| |
by: TimeHorse |
last post by:
I would like to gauge interest in the following proposal:
Problem:
Assignment statements cannot be used as expressions.
Performing a list of mutually exclusive checks that require data
processing can cause excessive tabification. For example, consider
the following python snipet...
|
by: sturlamolden |
last post by:
Python allows the binding behaviour to be defined for descriptors,
using the __set__ and __get__ methods. I think it would be a major
advantage if this could be generalized to any object, by allowing the
assignment operator (=) to be overloaded.
One particular use for this would be to implement "lazy evaluation".
For example it would allow us to get rid of all the temporary arrays
produced by NumPy.
For example, consider the...
|
by: boblatest |
last post by:
Hello,
I have an if-elif chain in which I'd like to match a string against
several regular expressions. Also I'd like to use the match groups
within the respective elif... block. The C-like idiom that I would
like to use is this:
if (match = my_re1.match(line):
# use match
elsif (match = my_re2.match(line)):
|
by: donbock |
last post by:
The paper Volatiles are Miscompiled, and What to Do about It by Eide and Regehr reports that mishandling of volatiles is a common category of compiler bugs. You should read it if you use volatiles for memory-mapped I/O or thread synchronization. Almost in passing they describe a common programmer error with volatiles:
Inserting a "side-effecting operation" into the loop would also prevent the undesired transformation.
Would inserting any...
|
by: Hystou |
last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can effortlessly switch the default language on Windows 10 without reinstalling. I'll walk you through it.
First, let's disable language synchronization. With a Microsoft account, language settings sync across devices. To prevent any complications,...
|
by: Hystou |
last post by:
Overview:
Windows 11 and 10 have less user interface control over operating system update behaviour than previous versions of Windows. In Windows 11 and 10, there is no way to turn off the Windows Update option using the Control Panel or Settings app; it automatically checks for updates and installs any it finds, whether you like it or not. For most users, this new feature is actually very convenient. If you want to control the update process,...
| |
by: tracyyun |
last post by:
Dear forum friends,
With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each protocol has its own unique characteristics and advantages, but as a user who is planning to build a smart home system, I am a bit confused by the choice of these technologies. I'm particularly interested in Zigbee because I've heard it does some...
|
by: agi2029 |
last post by:
Let's talk about the concept of autonomous AI software engineers and no-code agents. These AIs are designed to manage the entire lifecycle of a software development project—planning, coding, testing, and deployment—without human intervention. Imagine an AI that can take a project description, break it down, write the code, debug it, and then launch it, all on its own....
Now, this would greatly impact the work of software developers. The idea...
|
by: isladogs |
last post by:
The next Access Europe User Group meeting will be on Wednesday 1 May 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM).
In this session, we are pleased to welcome a new presenter, Adolph Dupré who will be discussing some powerful techniques for using class modules.
He will explain when you may want to use classes instead of User Defined Types (UDT). For example, to manage the data in unbound forms.
Adolph will...
|
by: TSSRALBI |
last post by:
Hello
I'm a network technician in training and I need your help.
I am currently learning how to create and manage the different types of VPNs and I have a question about LAN-to-LAN VPNs.
The last exercise I practiced was to create a LAN-to-LAN VPN between two Pfsense firewalls, by using IPSEC protocols.
I succeeded, with both firewalls in the same network. But I'm wondering if it's possible to do the same thing, with 2 Pfsense firewalls...
|
by: adsilva |
last post by:
A Windows Forms form does not have the event Unload, like VB6. What one acts like?
|
by: 6302768590 |
last post by:
Hai team
i want code for transfer the data from one system to another through IP address by using C# our system has to for every 5mins then we have to update the data what the data is updated we have to send another system
| |
by: muto222 |
last post by:
How can i add a mobile payment intergratation into php mysql website.
| |