Question About Sequence Points and Interrupt/Thread Safety

Tim Rentsch wrote in news: snipped-for-privacy@alumnus.caltech.edu:

[snip]

I'll make one more attempt at explaining why I don't think that's a natural reading of the text. If you read and parse the tokens of the statement "x = a + b + c + d;" against the grammar given in section A.2 of the standard, they are matched against the rules specified in the grammar. The full list of the productions matched for this expression is given below (I've tried to use indentation to show the production boundaries). There are a large number of expressions that must be "reduced" in parsing this one statement.

01 statement 02 expression-statement 03 expression v 04 assignment-expression v 05 unary-expression 06 postfix-expression 07 primary-expression 08 identifier('x') 09 assignment-operator 10 '=' 11 assignment-expression v 12 logical-OR-expression v 13 logical-AND-expression v 14 inclusive-OR-expression v 15 exclusive-OR-expression v 16 AND-expression v 17 equality-expression v 18 relational-expression v 19 shift-expression v 20 additive-expression v 21 additive-expression 22 additive-expression 23 additive-expression 24 multiplicative-expression 25 cast-expression 26 unary-expression 27 postfix-expression 28 primary-expression 29 identifier('a') 30 '+' 31 multiplicative-expression 32 cast-expression 33 unary-expression 34 postfix-expression 35 primary-expression 36 identifier('b') 37 '+' 38 multiplicative-expression 39 cast-expression 40 unary-expression 41 postfix-expression 42 primary-expression 43 identifier('c') 44 '+' 45 multiplicative-expression v 46 cast-expression v 47 unary-expression v 48 postfix-expression v 49 primary-expression v 50 identifier('v')=> *volatile*

The "volatile object" is introduced via the evaluation of the primary- expression at lines 49-50 and flows up to other encompassing expressions from there; that is, the primary expression at 49 refers to a volatile object and, by extension, anything that depends on its evaluation also refers to a volatile object. By the same token, expressions on other non-dependent branches of the parse tree cannot be said to refer to a volatile object. In the list above, I've appended a "v" to the line for each expression that "refers to" a volatile object.

Now, the 'additive-expression' beginning at line 21 and running through line 43 represents the sub-expression "((a + b) + c)". None of the expressions within this range "refer to" a volatile object.

(Nor, interestingly, does the unary-expression representing the left- hand side of the assignment at lines 5-8, though the assignment- expression itself still does.)

I believe this interpretation is completely consistent with the standard's text "any expression referring to..."

'x = ' is an "assignment expression". See section 6.5.16.

I concede that would not be the right thing (since "full expression" is explicitly defined as *not* being part of a declarator). However, I wasn't arguing that they *should have* used the term "full expression"; my real point was that the breadth implied by "full expression" cannot be assumed.

GH

Reply to
Gil Hamilton
Loading thread data ...

Wouldn;t it be impy defined?

It's pretty stinky, but perhaps falls short of devastating.

Phil

--
I tried the Vista speech recognition by running the tutorial. I was 
amazed, it was awesome, recognised every word I said. Then I said the 
wrong word ... and it typed the right one. It was actually just 
detecting a sound and printing the expected word! -- pbhj on /.
Reply to
Phil Carmody

It would be a challenge, but it is a very unusual situation. Have you ever come across it in practise?

I am sure that is right, but it is the more common cases that I worry about -- probably because I've been tripped up by them. For example, given 8-bit chars and 32-bit ints:

unsigned char *buf; unsigned int l; ... l = (buf[i]

Reply to
Ben Bacarisse

I will only say that C90 has the same semantics in these cases.

--
Ben.
Reply to
Ben Bacarisse

No, it doesn't. 6.2.1.1:

: A |char|, a |short int|, or an |int| bit field, or their signed or : unsigned varieties, or an enumeration type, may be used in an : expression wherever an |int| or |unsigned int| may be used. If an : |int| can represent all values of the original type, the value is : converted to an |int|; otherwise, it is converted to an |unsigned : int|. These are called the /integral promotions/.[27] All other : arithmetic types are unchanged by the integral promotions.

(Copy-typed by hand.) An |unsigned int| is not a |char|, or a |short int|, and is certainly not an |int| bit field; therefore it falls under `other arithmetic types' and is unchanged by the integral promotions.

The corresponding text from C99 is 6.3.1.1p2:

: The following may be used in an expression wherever an |int| or : |unsigned int| may be used: : : -- An object or expression with an integer type whose integer : conversion rank is less than the rank of |int| and |unsigned int|. : : -- A bit-field of type |_Bool|, |int|, |signed int|, or |unsigned : int|. : : If an |int| can represent all values of the original type, the value is : converted to an |int|; otherwise, it is converted to an |unsigned : int|. These are called the integer promotions.[48] All other types are : unchanged by the integer promotions.

Which is still as it should be: the conversion rank of |unsigned int| is certainly not less than the conversion rank of |unsigned int|.

However, in n1256, we get the extra text

: -- An object or expression with an integer type whose integer : conversion rank is less than *or equal to* the rank of |int| and : |unsigned int|.

I'm now rather interested to know where this change came from, and what it's for. Silently degrading from signed to unsigned arithmetic, i.e., from an arithmetic with well-specified and predictable behaviour to an arithmetic with implementation-defined[1] aspects, is a pretty serious change to make without a very good reason.

[1] Not undefined: thank you, Phil Carmody, for the correction.

-- [mdw]

Reply to
Mark Wooding

OK, I misread that. Thanks for making me read it more carefully. On the systems in question (where int can represent all of the values of unsigned int) we still have this odd behaviour for full-width bit-fields. In other words an unsigned 32-bit int and an unsigned

32-bit bit field are to behave differently. I point this only because I think it helps explain your question:

Maybe because it tidies up an odd corner case. If integer promotion is to be value-preserving where possible, why exclude unsigned int in those rare cases where value preservation is possible? I am not saying it is correct, but it is consistent. It does have an odd consequence -- on these machines unsigned arithmetic is only possible in higher-ranked unsigned types. I can only imagine this was deliberate. Maybe there was an example of such a machine and this is what it did? Have you ever seen such an implementation?

--
Ben.
Reply to
Ben Bacarisse

Another question I would be interested in the answer to is: what does the standard guarantee about the interaction of volatile and bitfields?

Reply to
John Regehr

Mark Wooding writes: [...]

The change appeared in TC 2, in response to DR #230 . It was intended to deal with enumerated types with a rank equal to that of int. It appears that the effect on unsigned int (on systems where the range of int includes all values of type unsigned int) was unintended. I'll bring this up in comp.std.c.

--
Keith Thompson (The_Other_Keith) kst@mib.org  
Nokia
"We must do something.  This is something.  Therefore, we must do this."
    -- Antony Jay and Jonathan Lynn, "Yes Minister"
Reply to
Keith Thompson

fair point. I think it belongs in the same class as return (0);

(0) is an expression of type int

Reply to
nick_keighley_nospam

I think someone should post to comp.std.c about this. It seems a very strange addition, and one wonders whether the Committee spotted all implications of it when they introduced it. More specifically, the only integer types with a rank equal to int or unsigned int are int and unsigned int themselves, AFAICT. Applying the rule in question to any extended type, or to any short or char types which happen to have the same width as int, is perfectly reasonable, and is what the rule originally said. The only thing the addition does is to pull int and unsigned int into the rule, with weird and IMO undesirable results when unsigned int has the same width as int. True, that's unusual, and one should expect unusual things to happen on such systems; even so, one would still expect unsignedness to be conserved, which it now must not be. This is a clear bug - again, IMO.

Ok, never mind the "someone should". I'll take the blame if they _did_ think of this - I'll crosspost it myself. And follow-ups set, as well.

Richard

Reply to
Richard Bos
[...]
[snip]

I posted to comp.std.c yesterday. See the thread "Unintended side effect of DR 230?".

--
Keith Thompson (The_Other_Keith) kst@mib.org  
Nokia
"We must do something.  This is something.  Therefore, we must do this."
    -- Antony Jay and Jonathan Lynn, "Yes Minister"
Reply to
Keith Thompson

Undefined behavior. If UINT_MAX == INT_MAX, then the addition is done as (int), resulting in overflow, which is the canonical example of undefined behavior.

Reply to
Tim Rentsch

I assume you meant to say from unsigned to signed, which is what happens if UINT_MAX == INT_MAX.

Actually undefined. If INT_MAX == UINT_MAX, then doing "unsigned" additions as (int) can result in overflow; 3.4.3p3:

EXAMPLE An example of undefined behavior is the behavior on integer overflow.

Reply to
Tim Rentsch

Seen from this perspective, it's not unreasonable to say the expression 'x = + v' is an expression referring to a volatile object. However, it's just as reasonable to say that the expression 'x = a + b + c + v' is an expression referring to a volatile object. The Standard says /any/ expression, and the larger expression qualifies, so the larger expression is covered under the "strictly according" clause.

Yes, I understood your position. However, the argument you gave depended on the term "full expression" being a convenient substitute term for "any expression". Since "full expression" doesn't fill the bill, that significantly weakens the argument.

Reply to
Tim Rentsch

Thanks for the correction. Until about 10 minutes before I posted, I thought it was undefined, but alas I saw too many seemingly relevant words in the completely irrelevant 6.3.1.3 (3) and got confused. ("Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.)

Oops.

Phil

--
I tried the Vista speech recognition by running the tutorial. I was 
amazed, it was awesome, recognised every word I said. Then I said the 
wrong word ... and it typed the right one. It was actually just 
detecting a sound and printing the expected word! -- pbhj on /.
Reply to
Phil Carmody

It's an easy enough mistake to make. For an operation yielding a signed result, overflow invokes undefined behavior in most cases, but an implementation-defined result (or an implementation-defined signal) if the operation happens to be a conversion. It seems like an arbitrary distinction.

--
Keith Thompson (The_Other_Keith) kst@mib.org  
Nokia
"We must do something.  This is something.  Therefore, we must do this."
    -- Antony Jay and Jonathan Lynn, "Yes Minister"
Reply to
Keith Thompson

I think the point you're missing, that a lot of people who don't work in these kind of environments miss is this:

In certain types of programming, such as embedded programming, you are exposed to components of the system (the example here is DSPs) which do not believe in the C standard. Working with this level of device requires a certain discipline in what you expect to happen and they way you treat instructions & registers and that discipline naturally bleeds into all of your coding, even when it's not strictly required by the C standard.

These rules that David has are very important when dealing with stuff that is non-C native, such as various bits of low-level hardware. Strong discipline in how you treat your types is crucial if you don't want that stuff to bite you.

I basically came to the same way of thinking when I was working on system interfaces in various OSes in the '80s with C. These OSes were on several different architectures with different word/byte sizes and none of them were C platform based. register & system call argument size was important and and lazy programming "the compiler will fix it" was a guarantee for disaster and many hours debugging. Bruce

Reply to
Bruce Cook

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.