Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Question About Sequence Points and Interrupt/Thread Safety

38 views
Skip to first unread message

Jujitsu Lizard

unread,
Feb 24, 2009, 6:58:15 PM2/24/09
to
I've included a function below and the generated STM8 assembly-language. As
it ends up (based on the assembly-language), the function is interrupt safe
as intended.

My question is, let's assume I have this:

DI();
if (x)
x--;
EI();

where DI and EI just expand to the compiler's asm( ) feature to insert the
right machine instruction to disable and enable interrupts, ...

Is there any reason that the compiler cannot delay writing "x" back so that
I get effectively this:

DI();
cpu_register = x;
if(cpu_register)
cpu_register--;
EI();
x = cpu_register;

???

It isn't clear to me if "volatile" is required on "x" or if there is any
possibility of the write of the variable back to memory being delayed.

Thanks for any insight.

Function below.

The Lizard

----------

//--------------------------------------------------------------------------------
//DESCRIPTION
// Decrements an array of zero or more 8-bit unsigned integers, but not
// below zero. This function is intended for software timers, but may
have
// other applications as well.
//
//INPUTS
// in_arg
// Pointer to first element to be decremented. This pointer must
// be valid if in_nelem > 0.
//
// in_nelem
// Number of elements to be decremented. If this value is 0, in_nelem
// will not be dereferenced and may be NULL or otherwise invalid.
//
//INTERRUPT CONSIDERATIONS
// This function must be called only with interrupts enabled (it uses
simple
// DI/EI protocol).
//
// This function may be called from non-ISR software only.
//
// In the case of software timers, individual software timers may be
safely
// shared with interrupt service, due to the critical section protocol.
So,
// an ISR may safely set and test software timers. Note that the behavior
// of individual software timers is guaranteed by DI/EI, but the
relationship
// between timers is not, as an interrupt may occur while an array or sets
// of arrays are being decremented.
//
//MNEMONIC
// "dec" : decrement.
// "u8" : unsigned 8-bit.
// "arr" : array.
// "nbz" : not below zero.
//
//UNIT TEST HISTORY
//
//
//--------------------------------------------------------------------------------
void MF_decu8arr_nbz(UINT8 *in_arg, UINT16 in_nelem)
{
while (in_nelem)
{
DI();
if (*in_arg)
(*in_arg)--;
EI();
in_nelem--;
}
}

700 ; 289 void MF_decu8arr_nbz(UINT8 *in_arg, UINT16
in_nelem)
700 ; 290 {
701 switch .text
702 00b4 f_MF_decu8arr_nbz:
704 00b4 89 pushw x
705 00000000 OFST: set 0
708 00b5 200d jra L552
709 00b7 L352:
710 ; 293 DI();
713 00b7 9b sim
715 ; 294 if (*in_arg)
717 00b8 1e01 ldw x,(OFST+1,sp)
718 00ba f6 ld a,(x)
719 00bb 2701 jreq L162
720 ; 295 (*in_arg)--;
722 00bd 7a dec (x)
723 00be L162:
724 ; 296 EI();
727 00be 9a rim
729 ; 297 in_nelem--;
731 00bf 1e06 ldw x,(OFST+6,sp)
732 00c1 5a decw x
733 00c2 1f06 ldw (OFST+6,sp),x
734 00c4 L552:
735 ; 291 while (in_nelem)
737 00c4 1e06 ldw x,(OFST+6,sp)
738 00c6 26ef jrne L352
739 ; 299 }
742 00c8 85 popw x
743 00c9 87 retf

Richard Heathfield

unread,
Feb 24, 2009, 7:14:14 PM2/24/09
to
Jujitsu Lizard said:

<snip>



> My question is, let's assume I have this:
>
> DI();
> if (x)
> x--;
> EI();
>
> where DI and EI just expand to the compiler's asm( ) feature to
> insert the right machine instruction to disable and enable
> interrupts, ...
>
> Is there any reason that the compiler cannot delay writing "x"
> back so that I get effectively this:
>
> DI();
> cpu_register = x;
> if(cpu_register)
> cpu_register--;
> EI();
> x = cpu_register;
>
> ???

3.6: "A full expression is an expression that is not part of another
expression. Each of the following is a full expression: an
initializer; the expression in an expression statement; the
controlling expression of a selection statement ( if or switch );
the controlling expression of a while or do statement; each of the
three expressions of a for statement; the expression in a return
statement. The end of a full expression is a sequence point."

Having said that, the "as if" rule applies. If the implementation
"wants" to delay the assignment to x, it is permitted to do so
/provided/ that a strictly conforming program can't tell the
difference.

<snip>

--
Richard Heathfield <http://www.cpax.org.uk>
Email: -http://www. +rjh@
Google users: <http://www.cpax.org.uk/prg/writings/googly.php>
"Usenet is a strange place" - dmr 29 July 1999

Jujitsu Lizard

unread,
Feb 24, 2009, 7:17:52 PM2/24/09
to
"Richard Heathfield" <r...@see.sig.invalid> wrote in message
news:-q6dnZpxK-fwETnU...@bt.com...

>>
>> Is there any reason that the compiler cannot delay writing "x"
>> back so that I get effectively this:
>>
>> DI();
>> cpu_register = x;
>> if(cpu_register)
>> cpu_register--;
>> EI();
>> x = cpu_register;
>>
>> ???
>
> 3.6: "A full expression is an expression that is not part of another
> expression. Each of the following is a full expression: an
> initializer; the expression in an expression statement; the
> controlling expression of a selection statement ( if or switch );
> the controlling expression of a while or do statement; each of the
> three expressions of a for statement; the expression in a return
> statement. The end of a full expression is a sequence point."
>
> Having said that, the "as if" rule applies. If the implementation
> "wants" to delay the assignment to x, it is permitted to do so
> /provided/ that a strictly conforming program can't tell the
> difference.
>
> <snip>

"tell the difference" is a bit of an ambiguous phrase.

I think you are saying that in this case the compiler is not free to delay
the write of "x" because that would be a logical error -- a conforming ISR
could in fact "tell the difference", and it would result in logical errors
in the program.

Am I understanding your response correctly?

Thanks, The Lizard

Kaz Kylheku

unread,
Feb 24, 2009, 7:49:22 PM2/24/09
to
On 2009-02-24, Jujitsu Lizard <jujitsu...@gmail.com> wrote:
> I've included a function below and the generated STM8 assembly-language. As
> it ends up (based on the assembly-language), the function is interrupt safe
> as intended.
>
> My question is, let's assume I have this:
>
> DI();
> if (x)
> x--;
> EI();
>
> where DI and EI just expand to the compiler's asm( ) feature to insert the
> right machine instruction to disable and enable interrupts, ...

I.e. if we have delimit a region of code with some compiler-specific
inline assembly magic, are there any requirements that we actually get
a properly implemented critical region?

The answer is, that the standard C language deosn't have any requirements in
htis area. The asm feature is an extension of your compiler (and a
non-conforming one, if it is actually called asm rather than say __asm,
since a C program can use the identifier asm).

Any requirements related to asm, such as the interaction between asm blocks and
surrounding code, can only be found in your compiler's documentation.

For instance, if you are using GNU C, there are special things you must do in
and around your __asm__ constructs to ensure that code is not improperly
reordered. The GNU compiler allows inline assembly to be quite tightly
integrated into the generated code, and can even allocate registers for you.
I.e. in the inline assembly you can refer to virtual register names, rather
than concrete ones, and associate them with operands denoted by C syntax. The
compiler will find registers for those operands, and generate the loads and
stores to dovetail them into the surrounding code.

If you want to use GNU C inline assembly for things like critical regions,
where there are interactions with other threads or interrupts that are not
obvious to the compiler, you have to inform it that there are ordering and
memory issues.

> Is there any reason that the compiler cannot delay writing "x" back so that
> I get effectively this:
>
> DI();
> cpu_register = x;
> if(cpu_register)
> cpu_register--;
> EI();
> x = cpu_register;
>
> ???
>
> It isn't clear to me if "volatile" is required on "x" or if there is any
> possibility of the write of the variable back to memory being delayed.

The volatile keyword is not particularly useful for concurrency issues. It is
defined by ISO C and has a couple of uses in the standard language, in relation
to signal handlers and setjmp/longjmp.

Whether it's suitable for any other purpose is up to the implementations. So
volatile may either be too weak to prevent the reordering that you are worried
about, or, on the other extreme, it may be a blunt instrument---i.e. it may
defeat all optimization of the object that is declared volatile!

So if you use volatile, you might actually get the code

cpu_register_0 = x

if (cpu_register_0 != 0) {
cpu_register_1 = x
cpu_register_1--
x = cpu_register_1
}

I.e. since your code accesses x twice and stores once, and x is volatile,
the generated code may also access twice.

But when you have critical regions of code accessing shared data, you don't
want such unoptimized access to data. You only want to stop optimization across
the entry and exit to the critical region, not everywhere! You want correct
concurrency, but not at the cost of poor code.

This is why some of the popular multithreading interfaces, like POSIX threads
and Win32, do not require volatile qualification on shared data. They
stipulate that acquiring and releasing a proper synchronization object is
enough. e.g. if you call pthread_mutex_lock, then writes performed before the
call are settled, and no premature reads have taken place.

Ideally, you want your DI and EI macros to behave the same way. The DI macro
should provide the assurance that accesses to objects prior to DI have
completed, and accesses which happen after have not yet begun.

Your compiler's documentation must explain how to do this. If it does not, you
can try your luck in various ways, like investigating the compiler's actual
behavior.

It may be enough to put DI and EI into external functions (functions defined in
a different translation unit from everything that calls them).

Since x is a shared variable, then the compiler must supect that an external
function call may modify x --- unless it is devilishly clever and can prove
otherwise. That is to say, the assignment to x cannot be delayed until after
EI(), because EI is an external function which can interact with x.
Even if x is a block-scope static varaible, EI could conceivably recurse back
into this function:

static void local_fun()
{
extern void DI(void);
extern void EI(void);

static int shared_x;

/* ... */

DI();
shared_x++;
EI();

/* ... */
}

Without knowing anything about EI and DI, we can't prove that they don't
recurse into local_fun somehow, in which case shared_x must have the old
upon the call to DI, and the new value before the call to EI.

If you put the interrupt manipulation into external functions, there is a good
likelihood that it will work. Of course, you have to review the generated code,
and that would be a last resort, if you cannot coax the behavior out of the
inlined versions.

Richard Heathfield

unread,
Feb 24, 2009, 7:53:52 PM2/24/09
to
Jujitsu Lizard said:

Okay, I'll amplify it a bit. Imagine one strictly conforming
program, and two otherwise identical compilers, one of which delays
the assignment to x and one of which does not. Compile it with each
compiler and run it with identical input. If the two invocations of
the program (one under each compiler) produce identical output,
then the strictly conforming program failed to tell the difference,
so the delay is okay. But if they don't, then the delay is not
okay.

> I think you are saying that in this case the compiler is not free
> to delay the write of "x" because that would be a logical error --

It's allowed to delay the x write /provided/ that the same results
are produced as in an otherwise identical compiler that does not
delay the x write.

Keith Thompson

unread,
Feb 24, 2009, 7:51:24 PM2/24/09
to

And of course a program that uses DI() and EI(), assuming they're as
Jujitsu Lizard describes, them, cannot be strictly conforming.

But the as-if rule doesn't refer just to strictly conforming programs.
Though the standard itself doesn't use the term, the index entry for
"as-if rule" points to 5.1.2.3, where paragraph 3 says:

In the abstract machine, all expressions are evaluated as
specified by the semantics. An actual implementation need not
evaluate part of an expression if it can deduce that its value is
not used and that no needed side effects are produced (including
any caused by calling a function or accessing a volatile object).

And paragraph 4 may also be relevant here:

When the processing of the abstract machine is interrupted by
receipt of a signal, only the values of objects as of the previous
sequence point may be relied on. Objects that may be modified
between the previous sequence point and the next sequence point
need not have received their correct values yet.

But I think that a program that uses DI() and EI() isn't just
not-strictly-conforming; I think it's all the way into the realm of
undefined behavior (which merely means behavior that isn't defined by
the standard). I think your question can be answered only by the
documentation for your implementation.

--
Keith Thompson (The_Other_Keith) k...@mib.org <http://www.ghoti.net/~kst>
Nokia
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"

Gordon Burditt

unread,
Feb 24, 2009, 7:56:51 PM2/24/09
to
>I've included a function below and the generated STM8 assembly-language. As
>it ends up (based on the assembly-language), the function is interrupt safe
>as intended.

My interpretation: since you used asm(), all bets are off from the
point of view of the C standard. Among other things, the compiler
doesn't know that the EI or DI instructions don't clobber registers
that it's using. Even with some assembly languages and machine
code, instructions are not necessarily executed in the order they
are in the code. I suspect that your processor does not schedule
instructions like this.

If you have a good compiler, it might assume that instructions
unknown to it introduced with asm() potentially clobber almost
everything, so it has to push most of the registers and restore
them afterward. This may kill your speed but guarantee ordering.

gcc's version of asm() allows you to indicate what registers are
potentially clobbered and which aren't. Done carefully, this lets
you minimize unnecessary register-saving.

If you use threads, there's no guarantee that sequence points will
work *between* threads. I'd expect that sequence points will work OK
between some stuff and other stuff in the same thread.

>
>My question is, let's assume I have this:
>
>DI();
>if (x)
> x--;
>EI();
>
>where DI and EI just expand to the compiler's asm( ) feature to insert the
>right machine instruction to disable and enable interrupts, ...
>
>Is there any reason that the compiler cannot delay writing "x" back so that
>I get effectively this:
>
>DI();
>cpu_register = x;
>if(cpu_register)
> cpu_register--;
>EI();
>x = cpu_register;
>
>???

No. And there's no reason at the end of this that it won't hold on to
cpu_register as containing a valid copy of x for use later on even
if storing the value back to x comes before EI().

>It isn't clear to me if "volatile" is required on "x"

It wouldn't hurt, except perhaps in performance, and it might help in
the situation above where it remembers that cpu_register contains a valid
copy of x when it might not be - volatile in that situation might
eliminate the problem.

>or if there is any
>possibility of the write of the variable back to memory being delayed.

You really can't look to the C standard for this. You have to rely on
guarantees made by the compiler, which usually aren't much.

Flash Gordon

unread,
Feb 24, 2009, 7:22:41 PM2/24/09
to
Jujitsu Lizard wrote:
> I've included a function below and the generated STM8
> assembly-language. As it ends up (based on the assembly-language), the
> function is interrupt safe as intended.

None of asm, interrupts or threads are actually part of standard C, they
are extensions provided by some implementations, and how they work
differ. You have cross-posted to comp.arch.embedded where there are
rather more people who know about this sort of thing that on comp.lang.c
where most of the issues are not really topical. I've set follow-ups to
comp.arch.embedded for further discussion.

> My question is, let's assume I have this:
>
> DI();
> if (x)
> x--;
> EI();
>
> where DI and EI just expand to the compiler's asm( ) feature to insert
> the right machine instruction to disable and enable interrupts, ...

OK, so we know what these do, so we can make some educated guesses from
the standards point of view. However, it is possible that on some
systems different threads could be running on different processor cores
and so still be accessing x simultaneously even with interrupts disabled!

> Is there any reason that the compiler cannot delay writing "x" back so
> that I get effectively this:
>
> DI();
> cpu_register = x;
> if(cpu_register)
> cpu_register--;
> EI();
> x = cpu_register;
>
> ???

No reason at all, since as far as the C standard is concerned the only
thing that could occur before the next access of x is a signal, and if
it was acted on by a signal handler it would need to be "volatile
sig_atomic_t" for the behaviour to be defined.

> It isn't clear to me if "volatile" is required on "x" or if there is any
> possibility of the write of the variable back to memory being delayed.

From the standards point of view it needs to be at least volatile and
it would be best in my opinion for it to be at least "volatile sig_atomic_t"

Since you are talking about threads you should look at what facilities
the threading implementation you are using provides and what guarantees
it provides.
--
Flash Gordon

Jujitsu Lizard

unread,
Feb 24, 2009, 8:23:56 PM2/24/09
to
Addendum to my previous post ...

I did a little more dinking with the compiler, and I'm convinced now that it
can't be fully trusted.

Here is the code (just dinking around):

UINT8 in_nelem22;

void MF_decu8arr_nbz(UINT8 *in_arg, UINT16 in_nelem)
{
while (in_nelem)
{
DI();

in_nelem22 += 3;
EI();
in_nelem22 += 5;
in_nelem22 += 7;
in_nelem++;
}
}

and here is what it got me:

701 ; 291 void MF_decu8arr_nbz(UINT8 *in_arg, UINT16
in_nelem)
701 ; 292 {
702 switch .text
703 00b4 f_MF_decu8arr_nbz:
705 00b4 89 pushw x
706 00000000 OFST: set 0
709 00b5 200f jra L552
710 00b7 L352:
711 ; 295 DI();
714 00b7 9b sim
716 ; 296 in_nelem22 += 3;
718 00b8 c60000 ld a,_in_nelem22
719 00bb ab03 add a,#3
720 ; 297 EI();
723 00bd 9a rim
725 ; 298 in_nelem22 += 5;
727 00be ab0c add a,#12
728 ; 299 in_nelem22 += 7;
730 00c0 c70000 ld _in_nelem22,a
731 ; 300 in_nelem++;
733 00c3 5c incw x
734 00c4 1f06 ldw (OFST+6,sp),x
735 00c6 L552:
736 ; 293 while (in_nelem)
738 00c6 1e06 ldw x,(OFST+6,sp)
739 00c8 26ed jrne L352
740 ; 302 }
743 00ca 85 popw x
744 00cb 87 retf

Note that the EI() caused it to split up the additions into +3 and +12, but
that IT DID NOT WRITE THE VARIABLE BACK TO MEMORY UNTIL LATER (it kept the
contents in the accumulator).

This is a danger sign.

It means in a complex code sequence with DI() and EI(), it might get me!

Thanks for all the information and advice.

The Lizard

Kaz Kylheku

unread,
Feb 24, 2009, 8:49:03 PM2/24/09
to
On 2009-02-25, Jujitsu Lizard <jujitsu...@gmail.com> wrote:
> Addendum to my previous post ...
>
> I did a little more dinking with the compiler, and I'm convinced now that it
> can't be fully trusted.

What if you make DI and EI into external functions, in a separate compilation
unit? That could just be the silver bullet for this compiler; no need to
snapshot the assembly language.

The external calls are a cost (and add to the amount of time you spend with
interrupts disabled!) but maybe it's a wortwhile tradeoff, if it works.

Also, in spite of what I wrote about volatile in the other article, it may also
work under this compiler, and you can minimize the performance-hurting aspects
of volatile by using non-volatile temporaries.

I.e. suppose we want this:

DI();
if (condition(x))
x++;
EI();

If x is volatile int, the semantics is that there are two accesses to x and one
store. But suppose you have a local variable temp of the same type as x, but
not volatile:

DI();
{
int temp = x;
if (condition(temp))
x = temp + 1;
}
EI();

Now we are back to one access and one store, the minimum required. We are
hoping that the compiler can optimize away temp entirely.

It would be irksome to rewrite all critical region code this way, though.

> Note that the EI() caused it to split up the additions into +3 and +12, but
> that IT DID NOT WRITE THE VARIABLE BACK TO MEMORY UNTIL LATER (it kept the
> contents in the accumulator).

It's obvious that no memory writes at all take place in the critical region
between the ``sim'' and ``rim''.

Keith Thompson

unread,
Feb 24, 2009, 8:56:33 PM2/24/09
to
"Jujitsu Lizard" <jujitsu...@gmail.com> writes:
> Addendum to my previous post ...
>
> I did a little more dinking with the compiler, and I'm convinced now
> that it can't be fully trusted.
>
> Here is the code (just dinking around):
>
> UINT8 in_nelem22;
>
> void MF_decu8arr_nbz(UINT8 *in_arg, UINT16 in_nelem)
> {
> while (in_nelem)
> {
> DI();
> in_nelem22 += 3;
> EI();
> in_nelem22 += 5;
> in_nelem22 += 7;
> in_nelem++;
> }
> }
>
> and here is what it got me:
>
[assembly code snipped]

>
> Note that the EI() caused it to split up the additions into +3 and
> +12, but that IT DID NOT WRITE THE VARIABLE BACK TO MEMORY UNTIL LATER
> (it kept the contents in the accumulator).
>
> This is a danger sign.
>
> It means in a complex code sequence with DI() and EI(), it might get me!

What happens if you declare in_nelem22 as volatile?

Keith Thompson

unread,
Feb 24, 2009, 9:26:07 PM2/24/09
to
Kaz Kylheku <kkyl...@gmail.com> writes:
[...]

> I.e. suppose we want this:
>
> DI();
> if (condition(x))
> x++;
> EI();
>
> If x is volatile int, the semantics is that there are two accesses
> to x and one store.
[...]

Quibble 1: a store is an access. C99 3.1 defines "access" as
"<execution-time action> to read or modify the value of an object",
with a note:

Where only one of these two actions is meant, ``read'' or
``modify'' is used.

Quibble 2: C99 6.7.3p6 says:

What constitutes an access to an object that has
volatile-qualified type is implementation-defined.

This doesn't contradict your advice, though; judicious use of the
volatile qualifier just might be what the OP needs to get his program
working with his particular compiler.

(Kaz, would you mind making your lines a trifle shorter? The often go
past 80 columns when quoted with "> ". I usually format paragraphs to
70 or 72 columns.)

Vladimir Vassilevsky

unread,
Feb 24, 2009, 9:36:20 PM2/24/09
to

Jujitsu Lizard wrote:

> I've included a function below and the generated STM8
> assembly-language. As it ends up (based on the assembly-language), the
> function is interrupt safe as intended.
>
> My question is, let's assume I have this:
>
> DI();
> if (x)
> x--;
> EI();
>
> where DI and EI just expand to the compiler's asm( ) feature to insert
> the right machine instruction to disable and enable interrupts, ...

You can not make any assumptions. There is no guarantee of the correct
behavior of this piece of code. If you need to ensure the atomicity, use
the explicit critical section functions provided by OS or develop the
assembly functions of your own.


Vladimir Vassilevsky
DSP and Mixed Signal Design Consultant
http://www.abvolt.com

Jujitsu Lizard

unread,
Feb 24, 2009, 9:53:30 PM2/24/09
to
"Keith Thompson" <ks...@mib.org> wrote in message
news:lnab8bg...@nuthaus.mib.org...

> "Jujitsu Lizard" <jujitsu...@gmail.com> writes:
>> Addendum to my previous post ...
>>
>> I did a little more dinking with the compiler, and I'm convinced now
>> that it can't be fully trusted.
>>
>> Here is the code (just dinking around):
>>
>> UINT8 in_nelem22;
>>
>> void MF_decu8arr_nbz(UINT8 *in_arg, UINT16 in_nelem)
>> {
>> while (in_nelem)
>> {
>> DI();
>> in_nelem22 += 3;
>> EI();
>> in_nelem22 += 5;
>> in_nelem22 += 7;
>> in_nelem++;
>> }
>> }
>>
>> and here is what it got me:
>>
> [assembly code snipped]
>>
>> Note that the EI() caused it to split up the additions into +3 and
>> +12, but that IT DID NOT WRITE THE VARIABLE BACK TO MEMORY UNTIL LATER
>> (it kept the contents in the accumulator).
>>
>> This is a danger sign.
>>
>> It means in a complex code sequence with DI() and EI(), it might get me!
>
> What happens if you declare in_nelem22 as volatile?

Assembly language below. Looks a bit different!

BTW, the ld instructions are in the form

ld destination, source

which might help with clarity.

701 ; 291 void MF_decu8arr_nbz(UINT8 *in_arg, UINT16
in_nelem)


701 ; 292 {
702 switch .text
703 00b4 f_MF_decu8arr_nbz:
705 00b4 89 pushw x
706 00000000 OFST: set 0

709 00b5 201d jra L552


710 00b7 L352:
711 ; 295 DI();
714 00b7 9b sim
716 ; 296 in_nelem22 += 3;
718 00b8 c60000 ld a,_in_nelem22
719 00bb ab03 add a,#3

720 00bd c70000 ld _in_nelem22,a
721 ; 297 EI();
724 00c0 9a rim
726 ; 298 in_nelem22 += 5;
728 00c1 c60000 ld a,_in_nelem22
729 00c4 ab05 add a,#5
730 00c6 c70000 ld _in_nelem22,a
731 ; 299 in_nelem22 += 7;
733 00c9 c60000 ld a,_in_nelem22
734 00cc ab07 add a,#7
735 00ce c70000 ld _in_nelem22,a
736 ; 300 in_nelem++;
738 00d1 5c incw x
739 00d2 1f06 ldw (OFST+6,sp),x
740 00d4 L552:
741 ; 293 while (in_nelem)
743 00d4 1e06 ldw x,(OFST+6,sp)
744 00d6 26df jrne L352
745 ; 302 }
748 00d8 85 popw x
749 00d9 87 retf

Jack Klein

unread,
Feb 24, 2009, 10:03:25 PM2/24/09
to
On Tue, 24 Feb 2009 20:23:56 -0500, "Jujitsu Lizard"
<jujitsu...@gmail.com> wrote in comp.arch.embedded:

> Addendum to my previous post ...
>
> I did a little more dinking with the compiler, and I'm convinced now that it
> can't be fully trusted.

As others have pointed out, this is beyond the C standard and
off-topic in comp.lang.c, but definitely topical in
comp.arch.embedded.

> Here is the code (just dinking around):
>
> UINT8 in_nelem22;

You have no reason whatsoever to expect much of anything without a
real function call, unless you define the object as volatile. A macro
that expands to inline assembly language that does not include a
subroutine call does not show the compiler that some external code is
going to access the object.

> void MF_decu8arr_nbz(UINT8 *in_arg, UINT16 in_nelem)
> {
> while (in_nelem)
> {
> DI();
> in_nelem22 += 3;
> EI();
> in_nelem22 += 5;
> in_nelem22 += 7;
> in_nelem++;
> }
> }

The other problem is that it can be even worse than you think even if
you do define it as volatile.

First, there's the fact that the definition of volatile is just plain
broken in the C language and standard. It is no particular
consolation that it is even more broken in C++.

The real crime is the fact there's a paper showing a number of
compilers that actually have errors in their implementation of
volatile that can be detected by a strictly conforming program. See:

http://www.cs.utah.edu/~regehr/papers/emsoft08-preprint.pdf

Hopefully, compilers for real embedded systems processor architectures
do better than this.

--
Jack Klein
Home: http://JK-Technology.Com
FAQs for
comp.lang.c http://c-faq.com/
comp.lang.c++ http://www.parashift.com/c++-faq-lite/
alt.comp.lang.learn.c-c++
http://www.club.cc.cmu.edu/~ajo/docs/FAQ-acllc.html

Tim Wescott

unread,
Feb 24, 2009, 10:12:40 PM2/24/09
to

But there is no such thing as a "conforming ISR" in C, because ISR's are,
strictly speaking, outside of the C language's paradigm.

As are, for that matter, asm statements.

So you have one of three choices:

1: Write all your really critical stuff in assembly.
2: Write your critical stuff in C, check the compiler output, and
be prepared for bugs if you get a different compiler -- even
a new version of the same old.
3: (I hate these folks) Write your critical stuff in whatever,
and move on to a new job before your sins catch up to you.

--
http://www.wescottdesign.com

Chris M. Thomasson

unread,
Feb 24, 2009, 11:43:55 PM2/24/09
to
"Kaz Kylheku" <kkyl...@gmail.com> wrote in message
news:200903040...@gmail.com...

> On 2009-02-25, Jujitsu Lizard <jujitsu...@gmail.com> wrote:
>> Addendum to my previous post ...
>>
>> I did a little more dinking with the compiler, and I'm convinced now that
>> it
>> can't be fully trusted.
>
> What if you make DI and EI into external functions, in a separate
> compilation
> unit? That could just be the silver bullet for this compiler; no need to
> snapshot the assembly language.
>

The link-time optimizer, assuming the compiler in question has one, can
still ruin everything.

Jujitsu Lizard

unread,
Feb 25, 2009, 1:39:29 AM2/25/09
to
"Tim Wescott" <t...@seemywebsite.com> wrote in message
news:aMudnZUqkt-1KjnU...@web-ster.com...

>
> So you have one of three choices:
>
> 1: Write all your really critical stuff in assembly.
> 2: Write your critical stuff in C, check the compiler output, and
> be prepared for bugs if you get a different compiler -- even
> a new version of the same old.
> 3: (I hate these folks) Write your critical stuff in whatever,
> and move on to a new job before your sins catch up to you.

#3 shows that you are not under 30 years old.

You understand the dark side of the force.

Jujitsu Lizard

unread,
Feb 25, 2009, 1:51:43 AM2/25/09
to
"Jack Klein" <jack...@spamcop.net> wrote in message
news:gqc9q4hjd9cguqvvd...@4ax.com...

>
> The real crime is the fact there's a paper showing a number of
> compilers that actually have errors in their implementation of
> volatile that can be detected by a strictly conforming program. See:
>
> http://www.cs.utah.edu/~regehr/papers/emsoft08-preprint.pdf
>
> Hopefully, compilers for real embedded systems processor architectures
> do better than this.

That is the single most depressing paper I've read recently.

But thanks for the link : )

The Lizard

Jujitsu Lizard

unread,
Feb 25, 2009, 1:56:55 AM2/25/09
to
"Jujitsu Lizard" <jujitsu...@gmail.com> wrote in message
news:u4-dnQJnm70QdznU...@giganews.com...

Jack,

In fact, the paper you linked to was SO depressing, I just had to try to
match you ... try this one on for size:

http://sunnyday.mit.edu/papers/therac.pdf

What always amazed me about the Therac affair is the initial denial of the
existence of a problem.

The Lizard

bigbrownbeast...@googlemail.com

unread,
Feb 25, 2009, 3:48:50 AM2/25/09
to
On Feb 25, 6:56 am, "Jujitsu Lizard" <jujitsu.liz...@gmail.com> wrote:
> "Jujitsu Lizard" <jujitsu.liz...@gmail.com> wrote in message
>
> news:u4-dnQJnm70QdznU...@giganews.com...
>
>
>
> > "Jack Klein" <jackkl...@spamcop.net> wrote in message

> >news:gqc9q4hjd9cguqvvd...@4ax.com...
>
> >> The real crime is the fact there's a paper showing a number of
> >> compilers that actually have errors in their implementation of
> >> volatile that can be detected by a strictly conforming program.  See:
>
> >>http://www.cs.utah.edu/~regehr/papers/emsoft08-preprint.pdf
>
> >> Hopefully, compilers for real embedded systems processor architectures
> >> do better than this.
>
> > That is the single most depressing paper I've read recently.
>
> > But thanks for the link : )
>
> > The Lizard
>
> Jack,
>
> In fact, the paper you linked to was SO depressing, I just had to try to
> match you ... try this one on for size:
>
> http://sunnyday.mit.edu/papers/therac.pdf
>
> What always amazed me about the Therac affair is the initial denial of the
> existence of a problem.
>
> The Lizard

Get a better STM8 compiler?

Tim Rentsch

unread,
Feb 25, 2009, 3:52:07 AM2/25/09
to
"Jujitsu Lizard" <jujitsu...@gmail.com> writes:

> //DESCRIPTION
> // Decrements an array of zero or more 8-bit unsigned integers, but not
> // below zero. This function is intended for software timers, but may

> // have other applications as well.


> //
> //INPUTS
> // in_arg
> // Pointer to first element to be decremented. This pointer must
> // be valid if in_nelem > 0.
> //
> // in_nelem
> // Number of elements to be decremented. If this value is 0, in_nelem
> // will not be dereferenced and may be NULL or otherwise invalid.
> //
> //INTERRUPT CONSIDERATIONS
> // This function must be called only with interrupts enabled (it uses

> // simple DI/EI protocol).


> //
> // This function may be called from non-ISR software only.
> //
> // In the case of software timers, individual software timers may be

> // safely shared with interrupt service, due to the critical section
> // protocol. So, an ISR may safely set and test software timers. Note
> // that the behavior of individual software timers is guaranteed by DI/EI,
> // but the relationship between timers is not, as an interrupt may occur
> // while an array or sets of arrays are being decremented.


> //
> //MNEMONIC
> // "dec" : decrement.
> // "u8" : unsigned 8-bit.
> // "arr" : array.
> // "nbz" : not below zero.
> //
> //UNIT TEST HISTORY
> //
> //
> //--------------------------------------------------------------------------------
> void MF_decu8arr_nbz(UINT8 *in_arg, UINT16 in_nelem)
> {
> while (in_nelem)
> {
> DI();
> if (*in_arg)
> (*in_arg)--;
> EI();
> in_nelem--;
> }
> }
>
>

> [assembly code snipped]

Several comments.

One: I assume the timers (the counters in the 'in_arg' array)
might be read and/or modified during an interrupt routine, which
is why it's necessary to delimit the access with DI()/EI(),
plus whatever else may be necessary.

Two: The comment to the function talks about decrementing an
array of timers, but the function body shown only ever deals with
the first element of such an array. So something is amiss there.

Three: If the assumption about interrupt routines accessing the
counters holds, this case certainly is one where 'volatile'
should be used inside the DI()/EI() region to access the
counters.

Four: As some other people have noted, if 'in_arg' is changed to
be a pointer to volatile (so, 'volatile UINT8 *in_arg'), then the
'if(*in_arg) (*in_arg)--;' can access a counter three times (two
reads and a write) instead of two. Whether this actually happens
is either implementation-defined behavior or an ambiguity in the
Standard (take your pick). One way around this potential
performance hit is to use a temporary:

{ UINT8 t = *in_arg; if(t) *in_arg = t-1; }

Five: Another way around the problem is to use 'volatile'
selectively rather than changing the declaration for 'in_arg':

if( *(volatile UINT8*)in_arg )
*(volatile UINT8*)in_arg = *in_arg - 1;

Of course, there is no guarantee any particular compiler will be
smart enough to optimize away the second read, so declaring
'volatile UINT8 *in_arg' and using a temporary is probably safer.

Six: Although the Standard requires the write to the "object" to
complete in the implementation machine, whether that write has
actually made it out through the memory pipeline depends on the
memory subsystem and on the particulars of the interruption
mechanism. Some systems require a call to insert a write barrier
into the store queue so that subsequent loads are guaranteed to
do the right thing. Different kinds of write barriers can be
required depending on what accesses are necessary to guarantee
(single core, multi-core, multiprocessor).

Seven: For a single threaded, single core, single processor
system (which sounds like what you're describing), just getting
the store into the store queue (which using volatile should do)
will be enough, provided the interrupt service routines do enough
to flush the store queue before they start accessing any
counters. Check what guarantees are made regarding pending
writes by the hardware interrupt mechanism and by the interrupt
service routines themselves; the two together /can/ guarantee
that all pending writes have completed, the question is are the
ISR's written in such a way that they /do/ guarantee it.

Eight: There is a corresponding question going the other
direction, i.e., from the ISR back to the interrupted process.

Nine: The question of what the basic interrupt mechanism does
can be rendered moot by putting in suitable write barriers around
the code in ISR's that accesses the clock timers in question.
Accesses of clock timers in the interrupt routines should also
use volatile; probably not necessary in a practical sense,
but the smart choice unless the performance consequences are
too costly.

Ten: Unless the compilers are known to be rock solid, it's a
good idea to check the generated assembly code in any case.

Summary:

use volatile, with explicit temporary;
verify particulars of memory and hardware interrupt system;
verify guarantee of pending writes, either
(a) for general ISR invocation and return, or
(b) specifically surrounding counter-access region in ISR's;
also use volatile for counter access in ISR's;
sanity check of generated assembly, just in case.

David Brown

unread,
Feb 25, 2009, 4:15:24 AM2/25/09
to
Jujitsu Lizard wrote:
> I've included a function below and the generated STM8
> assembly-language. As it ends up (based on the assembly-language), the
> function is interrupt safe as intended.
>
> My question is, let's assume I have this:
>
> DI();
> if (x)
> x--;
> EI();
>
> where DI and EI just expand to the compiler's asm( ) feature to insert
> the right machine instruction to disable and enable interrupts, ...
>
> Is there any reason that the compiler cannot delay writing "x" back so
> that I get effectively this:
>
> DI();
> cpu_register = x;
> if(cpu_register)
> cpu_register--;
> EI();
> x = cpu_register;
>
> ???
>
> It isn't clear to me if "volatile" is required on "x" or if there is any
> possibility of the write of the variable back to memory being delayed.
>
> Thanks for any insight.
>
> Function below.
>

You've had quite a number of comments from others - rather than replying
to everyone, I'll add a few points here.

Someone suggested making DI() and EI() external functions. If the
compiler is calling these as external functions which it knows nothing
about, then it must assume that they could read or write any globally
accessible data, and thus it follows sequence points strictly ("x" is
not read until after "DI();", and written back before "EI();"). But
that's a big *if* - if information is available about the function
(maybe it's defined in a header, or you have some sort of intern-module
optimisation), all bets are off. It might be good enough for your tools
now, but not for future compilers.

Using "volatile" accesses can certainly help with sequencing. In
particular, the compiler will not re-organise volatile accesses with
respect to each other. However, it is perfectly free to re-organise
other accesses before, after, and around those volatile accesses. If
your compiler does not consider your DI() and EI() to act as volatile
accesses to memory, it can re-arrange them around accesses to "x" even
if "x" is declared volatile.

One point to remember about "volatile", especially when trying to write
optimal code - it is *accesses* that are volatile, not the data itself.
It is perfectly possible to have "x" as a normal "UINT8", and force
volatile accesses as and when needed by using *((volatile UINT8*) &x).
It is also possible to cast away the volatile access on a variable that
is declared volatile, but that should give you compiler warnings, and is
probably better achieved by explicitly caching to a local variable. If
you use gcc, this macro can be handy:

#define volatileAccess(v) *((volatile typeof((v)) *) &(v))

Once you have made "x" volatile (or added volatile access casts), the
next issue is to make sure that DI() and EI() are considered volatile
accesses to memory. This is highly dependent on the compiler.

For many compilers, inline assembly and assembly functions are *always*
considered volatile memory accesses. Some compilers, however, are
smarter than that, and can do a substantial amount of optimising of
inline assembly. gcc, for example, will move an "asm ("DI")"
instruction back and forth - sometimes even if it is declared "asm
volatile ("DI")" (the "volatile" will ensure that things like loop
unrolling don't duplicate the assembly). The trick is to tell gcc that
it "clobbers" memory: asm ("DI" ::: "memory").

Of course, the *real* fun comes when you are using a processor that
re-orders accesses in hardware which the compiler can't directly control...

Here's a links on the subject:

http://lkml.indiana.edu/hypermail/linux/kernel/9605.0/0214.html

Tim Rentsch

unread,
Feb 25, 2009, 6:57:57 AM2/25/09
to
"Jujitsu Lizard" <jujitsu...@gmail.com> writes:

That wasn't my reaction at all. For developers, it had
this set of suggestions:

7.3 Recommendations for application developers

We recommend that the developers of mission-critical or
safety-critical embedded software that is written in C and
that relies on volatile take one or more of the following
steps:

* Manually validate the compiler's output for functions that
rely importantly on proper compilation of accesses to
volatile variables.

* Develop specific tests to ensure that the compiler
generates proper code for the kinds of volatile accesses
found in the software of interest.

* Factor accesses to volatiles into small helper functions,
if the overhead of this can be tolerated.

* Compile functions that critically rely on volatile with
optimizations turned off, if the overhead of this can be
tolerated. Our belief (based on observations, but
unsupported by systematic data) is that code emitted by
compilers for accessing volatiles is generally correct when
optimizations are turned off. It is folklore among embedded
software researchers that in many cases, safety-critical
embedded software for applications such as commercial
avionics is always compiled without optimizations.


On top of that there was lots of other stuff about why the
problems may tend to come up, and how to go about dealing
with them (meaning, at a compiler level). The main result
is that it identifies an area of C compilers that is likely
to be weak, which isn't that surprising since volatile tends
not to be used - if more attention is put on it, the results
should get better relatively easily.

Charlton Wilbur

unread,
Feb 25, 2009, 9:17:40 AM2/25/09
to
>>>>> "JL" == Jujitsu Lizard <jujitsu...@gmail.com> writes:

JL> "Tim Wescott" <t...@seemywebsite.com> wrote in message
JL> news:aMudnZUqkt-1KjnU...@web-ster.com...

>> So you have one of three choices:

>> 3: (I hate these folks) Write your critical stuff in whatever,


>> and move on to a new job before your sins catch up to you.

JL> #3 shows that you are not under 30 years old.

Hardly; my first real programming job, part time while I was a student,
was as a maintenance programmer, and the person before me thought
nothing mattered except whether the program compiled and did mostly what
it was supposed to.

That taught me the value of clear code by the time I was 20 years old.

Charlton


--
Charlton Wilbur
cwi...@chromatico.net

Vladimir Vassilevsky

unread,
Feb 25, 2009, 10:47:51 AM2/25/09
to

Tim Wescott wrote:


> So you have one of three choices:

0: Use synchronization functions provided by OS.

> 1: Write all your really critical stuff in assembly.

> 2: Write your critical stuff in C, check the compiler output, and
> be prepared for bugs if you get a different compiler -- even
> a new version of the same old.

Yes, yes, yes. For anything more complex then "Hello world", switching
the compiler version can be painful. For that matter, keep the complete
backup of the virtual machine with the older toolchain.

> 3: (I hate these folks) Write your critical stuff in whatever,
> and move on to a new job before your sins catch up to you.

Well, messy code is a job security for some.

Phil Carmody

unread,
Feb 25, 2009, 1:47:04 PM2/25/09
to
Jack Klein <jack...@spamcop.net> writes:
> The real crime is the fact there's a paper showing a number of
> compilers that actually have errors in their implementation of
> volatile that can be detected by a strictly conforming program. See:
>
> http://www.cs.utah.edu/~regehr/papers/emsoft08-preprint.pdf

I'm feeling a bit dense currently. I'm having issues with the
following:
"""
2. WHAT DOES VOLATILE MEAN?
...
A compiler may not move accesses to volatile variables across
sequence points.[1]
...
[1.
According to Section 3.8 of the C FAQ [18], "A sequence point is a point
in time at which the dust has settled and all side effects which have been
seen so far are guaranteed to be complete. The sequence points listed in
the C standard are at the end of the evaluation of a full expression (a full
expression is an expression statement, or any other expression which is not
a subexpression within any larger expression); at the ||, &&, ?:, and comma
operators; and at a function call (after the evaluation of all the arguments,
and just before the actual call)."
]
...
No guarantees are made about the atomicity of
any given volatile access, about the ordering of multiple volatile
accesses between two consecutive sequence points, or about the
ordering of volatile and non-volatile accesses.
"""

That final clause seems to contradict the initial footnoted clause,
on the presumption that the non-volatile accesses begin with, end
with, or contain, a sequence point.

"""
For example, the
following code illustrates a common mistake in which a volatile
variable is used to signal a condition about a non-volatile data struc-
ture, perhaps to another thread:

volatile int buffer_ready;
char buffer[BUF_SIZE];
void buffer_init() {
int i;
for (i=0; i<BUF_SIZE; i++)
buffer[i] = 0;
buffer_ready = 1;
}

The for-loop does not access any volatile locations, nor does it
perform any side-effecting operations. Therefore, the compiler is
free to move the loop below the store to buffer_ready, defeating
the developer's intent.
"""

However, it contains bucket loads of sequence points. Moving the
loop below the volatile store just a different way of saying moving
the volatile store before the loop (with all its sequence points).
But "a compiler may not move accesses to volatile variables across
sequence points". Contradiction, non?

Where have I gone wrong?

TIA,
Dozy 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 /.

Kaz Kylheku

unread,
Feb 25, 2009, 2:52:08 PM2/25/09
to
["Followup-To:" header set to comp.lang.c.]

On 2009-02-25, Phil Carmody <thefatphi...@yahoo.co.uk> wrote:
> Jack Klein <jack...@spamcop.net> writes:
>> The real crime is the fact there's a paper showing a number of
>> compilers that actually have errors in their implementation of
>> volatile that can be detected by a strictly conforming program. See:
>>
>> http://www.cs.utah.edu/~regehr/papers/emsoft08-preprint.pdf
>
> I'm feeling a bit dense currently. I'm having issues with the
> following:
> """
> 2. WHAT DOES VOLATILE MEAN?
> ...
> A compiler may not move accesses to volatile variables across
> sequence points.[1]

But it may move all other accesses however it wants.

I.e. if two volatile accesses happen in the same evaluation phase, then their
order relative to each other is not well-defined, but if they are separated by
a sequence point, their order /is/ well defined.

But other accesses do not constitute side effects, and so can be eliminated or
reordered arbitrarily.

> No guarantees are made about the atomicity of
> any given volatile access, about the ordering of multiple volatile
> accesses between two consecutive sequence points, or about the
> ordering of volatile and non-volatile accesses.
> """
>
> That final clause seems to contradict the initial footnoted clause,
> on the presumption that the non-volatile accesses begin with, end
> with, or contain, a sequence point.

All accesses are contained in some evaluation phase that is delimited
by sequence points.

The final clause is about accesses that are delimited in the same evaluation
phase, whereas the footnoted sentence is about accesses that have a sequence
point between them, and are thus in separate evaluation phases.

So there is no contradiction; the texts are about different situations.

>
> """
> For example, the
> following code illustrates a common mistake in which a volatile
> variable is used to signal a condition about a non-volatile data struc-
> ture, perhaps to another thread:
>
> volatile int buffer_ready;
> char buffer[BUF_SIZE];
> void buffer_init() {
> int i;
> for (i=0; i<BUF_SIZE; i++)
> buffer[i] = 0;
> buffer_ready = 1;
> }
>
> The for-loop does not access any volatile locations, nor does it
> perform any side-effecting operations. Therefore, the compiler is
> free to move the loop below the store to buffer_ready, defeating
> the developer's intent.
> """
>
> However, it contains bucket loads of sequence points.

But sequence points do not constrain accesses.

> Moving the
> loop below the volatile store just a different way of saying moving
> the volatile store before the loop (with all its sequence points).

No it isn't, because the volatile store is not being reordered with
respect to other volatile stores.

> But "a compiler may not move accesses to volatile variables across
> sequence points". Contradiction, non?

Not really.

You can regard the division into sequence points as being a fixed medium, in
which accesses can move around. When accesses are relocated from one evaluation
phase (space between sequence points) to another, the medium itself does not
move; these moves do not drag the sequence points along.

Volatile accesses must stay in their evaluation phases, but non-volatile
accesses can be moved, coalesced or eliminated.

REH

unread,
Feb 25, 2009, 5:05:57 PM2/25/09
to
On Feb 24, 6:58 pm, "Jujitsu Lizard" <jujitsu.liz...@gmail.com> wrote:
> I've included a function below and the generated STM8 assembly-language.  As
> it ends up (based on the assembly-language), the function is interrupt safe
> as intended.
>
> My question is, let's assume I have this:
>
> DI();
> if (x)
>    x--;
> EI();
>
> where DI and EI just expand to the compiler's asm( ) feature to insert the
> right machine instruction to disable and enable interrupts, ...
>
> Is there any reason that the compiler cannot delay writing "x" back so that
> I get effectively this:
>
> DI();
> cpu_register = x;
> if(cpu_register)
>    cpu_register--;
> EI();
> x = cpu_register;
>

Hopefully, the regulars will confirm or discount this, but I believe
you can force the ordering by making wrappers for your two functions
at take x as a parameter. I think this make the compiler think they
are dependent on x's value, and not reorder the code. Something like:

DIx(x);
if (x)
x--;
EIx(x);

REH

Hans-Bernhard Bröker

unread,
Feb 25, 2009, 5:18:13 PM2/25/09
to
Jujitsu Lizard wrote:

> It isn't clear to me if "volatile" is required on "x"

Setting aside the interrupt disable, 'x' should to be qualified volatile
for exactly the same reasons that made you want to disable interrupts
around that fragment.

Whether or not it's strictly necessary depends on the semantics of those
non-standard calls DI()/EI(). If the compiler can be sure that EI()
doesn't reference x (e.g. because it's a tightly described bit of inline
asm, or a compiler intrinsic), it's allowed to delay storing 'x' in
memory unless you qualify it as volatile.

> or if there is any
> possibility of the write of the variable back to memory being delayed.

If you don't tell the compiler about it needing protection by qualifying
it as 'volatile', there is.

Ben Bacarisse

unread,
Feb 25, 2009, 6:32:08 PM2/25/09
to
REH <spam...@stny.rr.com> writes:

> On Feb 24, 6:58 pm, "Jujitsu Lizard" <jujitsu.liz...@gmail.com> wrote:

<snip>


>> Is there any reason that the compiler cannot delay writing "x" back so that
>> I get effectively this:
>>
>> DI();
>> cpu_register = x;
>> if(cpu_register)
>>    cpu_register--;
>> EI();
>> x = cpu_register;
>>
>
> Hopefully, the regulars will confirm or discount this, but I believe
> you can force the ordering by making wrappers for your two functions
> at take x as a parameter. I think this make the compiler think they
> are dependent on x's value, and not reorder the code. Something like:
>
> DIx(x);
> if (x)
> x--;
> EIx(x);

I would not rely on that. x can be passed (to EI) by copying the
register from the OP's example and the change to x could still occur
later. You might have move luck with &x because in that case the
compiler would have to do extra work to avoid the obvious method of
updating x "on time".

--
Ben.

Flash Gordon

unread,
Feb 25, 2009, 6:36:12 PM2/25/09
to

Others have already pointer out that there can be inter-function and
even inter-translation-unit (link time) optimisation which will allow
the compiler to break the code.

Using non-standard (possibly OS provided) routines is probably the best
way to go in my opinion.
--
Flash Gordon

Stephen Sprunk

unread,
Feb 25, 2009, 7:08:05 PM2/25/09
to
Phil Carmody wrote:
> I'm feeling a bit dense currently. I'm having issues with the
> following:
> """
> 2. WHAT DOES VOLATILE MEAN?
> ...
> A compiler may not move accesses to volatile variables across
> sequence points.[1]
> ...
> No guarantees are made about the atomicity of
> any given volatile access, about the ordering of multiple volatile
> accesses between two consecutive sequence points, or about the
> ordering of volatile and non-volatile accesses.
> """
>
> That final clause seems to contradict the initial footnoted clause,
> on the presumption that the non-volatile accesses begin with, end
> with, or contain, a sequence point.

The former quote says that compilers can't move volatile accesses
_across_ a sequence point; the latter quote says that the order of
volatile accesses _between_ two sequence points is undefined. The two
do not contradict each other.

For instance, consider the following:

volatile int a, b, c;
/* other code */
foo();
a += b + c;
bar();

The compiler cannot move the volatile accesses before foo() or after
bar(), but it _can_ choose in what order to read a, b, and c. However,
it can also choose to move other (non-volatile) code between foo() and
bar(), or move code before foo() to after bar() or vice versa, if it
could prove that doing so wouldn't violate the as-if rule.

S

--
Stephen Sprunk "Stupid people surround themselves with smart
CCIE #3723 people. Smart people surround themselves with
K5SSS smart people who disagree with them." --Isaac Jaffe

Hans-Bernhard Bröker

unread,
Feb 25, 2009, 7:16:58 PM2/25/09
to
Stephen Sprunk wrote:

> The former quote says that compilers can't move volatile accesses
> _across_ a sequence point;

[...]


> bar(), or move code before foo() to after bar() or vice versa, if it
> could prove that doing so wouldn't violate the as-if rule.

Ehm, no. You're overlooking that by moving code down from before foo()
to after bar(), you're also moving your volatile accesses up, across the
sequence point(s) in that code, which would violate the first statement
above.

It cuts both ways --- whenever you talking about "moving" code, what
actually happens is that operations are swapped, and that means you move
each across the other.

Jujitsu Lizard

unread,
Feb 25, 2009, 7:25:40 PM2/25/09
to
"Tim Rentsch" <t...@alumnus.caltech.edu> wrote in message
news:kfny6vu...@alumnus.caltech.edu...

>
> Two: The comment to the function talks about decrementing an
> array of timers, but the function body shown only ever deals with
> the first element of such an array. So something is amiss there.

You are correct. Actually, I hadn't unit-tested the function, and I would
have found that.

Want to be my code reviewer?

That is embarrassing. I don't do that often.

> Eight: There is a corresponding question going the other
> direction, i.e., from the ISR back to the interrupted process.

I don't see that corresponding question, at least not on my hardware. By
the time the ISR returns, the data will have been written to memory.

The STM8 is one step above toaster/dishwasher grade ...

If you're seeing a problem going the other way, please explain it ...

The Lizard

Stephen Sprunk

unread,
Feb 25, 2009, 8:43:18 PM2/25/09
to

Hmm. I think that you're right in theory, but is it possible that a
conforming program could tell the difference? Doesn't the as-if rule
still apply to the non-volatile parts of the code? Or does a single
volatile access break the as-if rule even for non-volatile operations?

I've seen compilers do what I described (from studying the asm output),
and nobody seems to complain about it. Mixing volatile and non-volatile
operations is pretty unpredictable in practice, so I have always avoided
that entirely rather than try to figure out exactly what the Standard
guarantees (which doesn't appear to be much when it comes to volatile
variables).

Phil Carmody

unread,
Feb 26, 2009, 2:57:20 AM2/26/09
to

I'm happy with that. A nice clear absolute. No wondering about 'as if's.

> but it _can_ choose in what order to read a, b, and c.

Perfectly happy with that too, wouldn't want it any other way.

> However, it can also choose to move other (non-volatile) code between
> foo() and bar(), or move code before foo() to after bar() or vice versa

What's so special about function calls? Why wouldn't a for loop,
or even simple assignments, contain sequence points that the
volatile accesses couldn't be moved past? What property do foo()
and bar() have that those other things with sequence points don't
have?

Chris Dollin

unread,
Feb 26, 2009, 3:05:02 AM2/26/09
to
Phil Carmody wrote:

> Stephen Sprunk <ste...@sprunk.org> writes:
>> volatile int a, b, c;
>> /* other code */
>> foo();
>> a += b + c;
>> bar();
>>
>> The compiler cannot move the volatile accesses before foo() or after
>> bar(),
>
> I'm happy with that. A nice clear absolute. No wondering about 'as if's.
>
>> but it _can_ choose in what order to read a, b, and c.
>
> Perfectly happy with that too, wouldn't want it any other way.
>
>> However, it can also choose to move other (non-volatile) code between
>> foo() and bar(), or move code before foo() to after bar() or vice versa
>
> What's so special about function calls? Why wouldn't a for loop,
> or even simple assignments, contain sequence points that the
> volatile accesses couldn't be moved past? What property do foo()
> and bar() have that those other things with sequence points don't
> have?

They're in the example, so they can be referred to.

--
"We are on the brink of a new era, if only --" /The Beiderbeck Affair/

Hewlett-Packard Limited registered office: Cain Road, Bracknell,
registered no: 690597 England Berks RG12 1HN

Stephen Sprunk

unread,
Feb 26, 2009, 9:54:02 AM2/26/09
to

I know it isn't strictly true, but my mental shortcut for all the rules
about volatile variables/operations is that volatile voids the as-if
rule. The problems only come when you mix volatile and non-volatile
operations together.

>> but it _can_ choose in what order to read a, b, and c.
>
> Perfectly happy with that too, wouldn't want it any other way.
>
>> However, it can also choose to move other (non-volatile) code between
>> foo() and bar(), or move code before foo() to after bar() or vice versa
>
> What's so special about function calls? Why wouldn't a for loop,
> or even simple assignments, contain sequence points that the
> volatile accesses couldn't be moved past? What property do foo()
> and bar() have that those other things with sequence points don't
> have?

Any sequence point suffices; the only magical property of function calls
in my example is that they're easy to refer to by name. It's hard to
refer to particular semicolons because they all look the same...

Phil Carmody

unread,
Feb 26, 2009, 1:38:37 PM2/26/09
to

So you think the document's wrong?

David Brown

unread,
Feb 26, 2009, 2:37:39 PM2/26/09
to

You are correct - the "as-if" rule lets the compiler re-order
non-volatile accesses as it wants. It is only volatile accesses that
must remain strictly in order. However, if the compiler does not know
the details of a function, then any function call must be ordered as
though it were a volatile access, since the compiler does not know that
it does not contain volatile accesses. So in your example, it could not
normally move the foo() or bar() calls above or below the volatile sum.

If the definitions of foo() and bar() are known to the compiler (or it
knows other details, such as if they are declared with the gcc "const"
attribute), so that the compiler knows it is safe, it can move them around.

Tim Rentsch

unread,
Feb 26, 2009, 5:55:47 PM2/26/09
to
Phil Carmody <thefatphi...@yahoo.co.uk> writes:

> Jack Klein <jack...@spamcop.net> writes:
> > The real crime is the fact there's a paper showing a number of
> > compilers that actually have errors in their implementation of
> > volatile that can be detected by a strictly conforming program. See:
> >
> > http://www.cs.utah.edu/~regehr/papers/emsoft08-preprint.pdf
>
> I'm feeling a bit dense currently. I'm having issues with the
> following:

>[snip]


> """
> For example, the
> following code illustrates a common mistake in which a volatile
> variable is used to signal a condition about a non-volatile data struc-
> ture, perhaps to another thread:
>
> volatile int buffer_ready;
> char buffer[BUF_SIZE];
> void buffer_init() {
> int i;
> for (i=0; i<BUF_SIZE; i++)
> buffer[i] = 0;
> buffer_ready = 1;
> }
>
> The for-loop does not access any volatile locations, nor does it
> perform any side-effecting operations. Therefore, the compiler is
> free to move the loop below the store to buffer_ready, defeating
> the developer's intent.
> """

This part of the paper is wrong. The loop does perform
side-effecting operations, both the increments of i and the
stores into buffer[i], and these must be done before the
(volatile) assignment into buffer_ready.

Whether the consequences of using (volatile) in this way
qualify as a mistake depends on other factors, notably the
implementation-defined rule for what constitutes an access
to a volatile-qualified variable. Perhaps what the paper
means to say is that assuming this code will always work
is a mistake, and that is in fact correct; but whether
it must work is implementation dependent, so on some
implementations it could be just fine.

More about volatile shortly...

Tim Rentsch

unread,
Feb 26, 2009, 6:44:21 PM2/26/09
to
Stephen Sprunk <ste...@sprunk.org> writes:

The semantics of volatile are confusing, or at least expressed in
a confusing way. What many people think are the rules for how
volatiles are accessed and what the Standard says about how
volatile-qualified objects must be treated are quite a bit
different from each other.

In the examples below, the variables v, v1, v2, ..., are
volatile, and other variables aren't. To keep things simple all
the arithmetic types are (unsigned int).

First simple example:

x = a + b + c + v;

The first rule for volatile is captured in a single sentence
in 6.7.3p6:

Therefore any expression referring to such an object [i.e.,
with volatile-qualified type] shall be evaluated strictly
according to the rules of the abstract machine, as described
in 5.1.2.3.

The full expression assigning to x is an expression referring to an
object with volatile-qualified type. Therefore that expression,
the /entire/ expression, must be evaluated strictly according to
the rules of the abstract machine. The sums must be formed in the
right order; even though addition for (unsigned int) commutes,
the additions must be done as (((a + b) + c) + v), and not, for
example, as ((a + (b + c)) + v). Furthermore, the sum (a+b)
must be performed, even if that value happens to be lying around
conveniently in a register somewhere. These consequences follow
because of the requirement than any volatile-referring expression
be evaluated /strictly/ according to the rules of the abstract
machine.

Now let's consider a multi-statement (and so multi-sequence-point)
example:

foo();
x = a + b + c + v;
bas();

To lend some concreteness, foo() increments a global variable
foo_count, and bas increments a global varible bas_count.
Neither foo_count nor bas_count is volatile. The functions
foo() and bas() don't either read or write x, a, b, c, or v.

Question: can any part of the assignment expression (statement)
be done before calling foo() or after calling bas()?

Answer: No.

Reason: The expression in question must be evaluated strictly
according to the rules of the abstract machine, as described in
5.1.2.3. In particular, there is 5.1.2.3 p 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,(11) 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.

The assignment expression must be evaluated strictly according to
this rule. As it follows a sequence point, all side effects of
evalutions before that sequence point (namely, the call to foo())
must be complete. It has a sequence point at its end; all side
effects of evaluations after that sequence point (namely, the call
to bas()) must not have taken place. The act of calling foo()
(or bas()) is itself a side effect, since an object is modified
in its function body.

These kinds of consequences may seem counter-intuitive. Certainly
they are not what many people expect. But the stated semantics for
volatile, coupled with the abstract machine description in 5.1.2.3p2,
impose very strict requirements for how much latitude is available
for optimizing around expressions that access volatile objects.

Note also: apparently some of the people who are confused about
volatile are compiler writers; hence, we have the results shown in

http://www.cs.utah.edu/~regehr/papers/emsoft08-preprint.pdf

Tim Rentsch

unread,
Feb 26, 2009, 6:46:32 PM2/26/09
to
David Brown <david...@hesbynett.removethisbit.no> writes:

No, this is a misreading of the semantics of volatile as
required by the description in 5.1.2.3 of how the abstract
machine operates. Please see my last response in this
thread.

Tim Rentsch

unread,
Feb 26, 2009, 7:00:16 PM2/26/09
to
Phil Carmody <thefatphi...@yahoo.co.uk> writes:

Sequence points are just a kind of convenient reference
point. Sequence points can be identified at the level
of source code, and perhaps they "exist" in some sense
in the abstract machine, but they don't have any meaning
during an actual execution. The key thing is side effects;
when starting to evaluate

a += b + c;

all the side effects of previous evaluations must be complete
(and not just as-if!), and presumably the call to foo()
qualifies because its body has a side effect of its own.
The rule about side effects also includes assignment,
and for() loops, assuming of course that there is incrementing
or something else going on in the for() loop that qualifies
as a side effect.

Tim Rentsch

unread,
Feb 26, 2009, 7:06:54 PM2/26/09
to
Phil Carmody <thefatphi...@yahoo.co.uk> writes:

> Stephen Sprunk <ste...@sprunk.org> writes:
> > Phil Carmody wrote:
> >> What's so special about function calls? Why wouldn't a for loop,
> >> or even simple assignments, contain sequence points that the
> >> volatile accesses couldn't be moved past? What property do foo()
> >> and bar() have that those other things with sequence points don't
> >> have?
> >
> > Any sequence point suffices; the only magical property of function
> > calls in my example is that they're easy to refer to by name. It's
> > hard to refer to particular semicolons because they all look the
> > same...

Minor correction: any side effect before a previous sequence
point suffices. Presuming the definitions of foo() and bas()
have side effects in their bodies, then calls to foo() or
bas() also count as side effects.

>
> So you think the document's wrong?

Yes. On a broad scale it's right, but on the specific
explanation for this case it's wrong.

Phil Carmody

unread,
Feb 27, 2009, 2:46:30 AM2/27/09
to
>> I'm feeling a bit dense currently. I'm having issues with the ...

>
> This part of the paper is wrong.

Thank you thank you thank you Tim. So I'm not going senile.
Or if I am, I'm not alone.

> More about volatile shortly...

Looking forward to it. I'll probably have to wait until Sunday
though...

Cheers,

David Brown

unread,
Feb 27, 2009, 4:54:50 AM2/27/09
to
Tim Rentsch wrote:
>

<snip to save space>

<snip to save space>


>
> These kinds of consequences may seem counter-intuitive. Certainly
> they are not what many people expect. But the stated semantics for
> volatile, coupled with the abstract machine description in 5.1.2.3p2,
> impose very strict requirements for how much latitude is available
> for optimizing around expressions that access volatile objects.
>
> Note also: apparently some of the people who are confused about
> volatile are compiler writers; hence, we have the results shown in
>
> http://www.cs.utah.edu/~regehr/papers/emsoft08-preprint.pdf
>

That is very interesting - I haven't read through the standards quite
like that. Your interpretation may well be the correct one according to
the standards (I don't know the priority of these rules and the "as-if"
rule), but I think it is slightly different from what users and compiler
writers expect, and slightly different from what they *want*. In
particular, no one wants or expects the compiler to treat the "a + b +
c" part of your expression in any special way just because there is a "+
v" added. They want and expect "v" to be read as a volatile access, but
"a + b + c" should be subject to common expression and other optimisations.

As for whether code (in particular, non-volatile loads and stores)
should be moveable around the volatile accesses, there are two camps -
those who think it should not be possible (if your interpretation is
correct, this is the standard's viewpoint), and those who think it is
possible (and arguably desirable). The fact is, real compilers *do*
move code around like that - if you want your code to run correctly, you
have to assume that such re-ordering is possible.

Just for fun, I compiled this code with a couple of versions of gcc and
different optimisation flags:

typedef unsigned char uint8_t;

extern volatile uint8_t v, v1, v2, v3;
extern uint8_t x, a, b, c;
extern uint8_t foo_count, bas_count;

void foo(void) {
foo_count++;
}

void bas(void) {
foo_count--;
}

void test1(void) {
v1 = a + b;


foo();
x = a + b + c + v;
bas();

v2 = a + b + c;
x += 1;
}


With avr-gcc 4.2 and 4.3, -Os optimisation, test1 is implemented as
though it were:

void test1(void) {
uint8_t r24, r25; // 8-bit registers
r24 = b;
r25 = a;
r24 += r25; // a + b
v1 = r24; // volatile store v1 = a + b
r25 = c;
r25 += r24; // a + b + c
r24 = v; // volatile load v
v2 = r25; // volatile store v2 = a + b + c
r24 += 1; // v + 1
r24 += r25 // v + 1 + a + b + c
x = r24; // Store x = a + b + c + v + 1
}

As you can see, calls to foo() and bas() have been eliminated, and there
is a lot of re-use of the partial calculations. This is, in my opinion,
correct code - the volatile accesses are correct, and in the correct
order, and the rest of the code runs *as if* it were a direct
translation. The generated code is also as small and fast as it
possibly could be on the AVR.


The biggest problem with "volatile" in C is that the standards are
vague, difficult to understand, and probably inconsistent. This will
always lead to misunderstandings and disagreements about "volatile".
comp.lang.c frequenters will often be interested in exactly what the
standards say, while comp.arch.embedded frequenters will be more
interested in what real-life compilers actually *do* (we are used not
not-quite-standard compilers).


Tim Rentsch

unread,
Feb 27, 2009, 1:07:13 PM2/27/09
to
David Brown <da...@westcontrol.removethisbit.com> writes:

> Tim Rentsch wrote:
> > [SNIP]
> [SNIP]

Sometime soon I'd like to post a more complete response
that doesn't ignore this snipped portion, talking about
the semantics of volatile. Right now the response will
be just to the paragraph below.


> The biggest problem with "volatile" in C is that the standards are
> vague, difficult to understand, and probably inconsistent. This will
> always lead to misunderstandings and disagreements about "volatile".
> comp.lang.c frequenters will often be interested in exactly what the
> standards say, while comp.arch.embedded frequenters will be more
> interested in what real-life compilers actually *do* (we are used not
> not-quite-standard compilers).

This makes perfect sense, given both the problems of how
volatile is described in the Standard, and what the priorities
are of the two groups. Folks in comp.arch.embedded aren't
going to care very much what compilers are supposed to do,
if most compilers don't actually do what they're supposed to
do -- and moreso if what they're supposed to do isn't clear.

However, it is in the interests of both groups -- in fact
maybe even more in the interests of comp.arch.embedded than it
is of comp.lang.c -- that what 'volatile' is supposed to do be
well-understood, and that such understanding be shared by a
broad community, in particular including lots of compiler
people who produce the different implementations, and also
including people like those in comp.arch.embedded who actually
use 'volatile' in their C code. I'm much more of a
comp.lang.c person than a comp.arch.embedded person, but the
cross-pollination that's happened in this thread has been (at
least IMO) a real boon, and I would like to see that continue
during a further discussion of the semantics of volatile.

Also, on a personal note, a "thank you" to David Brown for
taking the time to write/compile the example code that uses
volatile to see what the compilers actually do with it.

Hoped-for followup discussing the semantics of volatile: as
soon as I can get to it, but realistically that won't be
before next week.

David Brown

unread,
Feb 27, 2009, 3:45:40 PM2/27/09
to

I'll look forward to that. I too enjoy a bit of cross-posting between
these two groups - comp.lang.c is mostly a bit too theoretical for my
tastes, but areas such as "volatile" are definitely in an overlap area,
and it's good to get input from a different viewpoint.

Gil Hamilton

unread,
Feb 27, 2009, 7:01:55 PM2/27/09
to
Tim Rentsch <t...@alumnus.caltech.edu> wrote in
news:kfntz6g...@alumnus.caltech.edu:

> First simple example:
>
> x = a + b + c + v;
>
> The first rule for volatile is captured in a single sentence
> in 6.7.3p6:
>
> Therefore any expression referring to such an object [i.e.,
> with volatile-qualified type] shall be evaluated strictly
> according to the rules of the abstract machine, as described
> in 5.1.2.3.
>
> The full expression assigning to x is an expression referring to an
> object with volatile-qualified type. Therefore that expression,
> the /entire/ expression, must be evaluated strictly according to
> the rules of the abstract machine. The sums must be formed in the
> right order; even though addition for (unsigned int) commutes,
> the additions must be done as (((a + b) + c) + v), and not, for
> example, as ((a + (b + c)) + v). Furthermore, the sum (a+b)
> must be performed, even if that value happens to be lying around
> conveniently in a register somewhere. These consequences follow
> because of the requirement than any volatile-referring expression
> be evaluated /strictly/ according to the rules of the abstract
> machine.

I disagree with this analysis. I think you're ascribing too pandemic a
meaning to the phrase 'any expression referring to such an object...'.

As you say, the language syntax requires that the interpretation of the
expression is "(((a + b) + c) + v)". However, decomposing that further
shows that in the outermost expression, 'v' is being added to the result
of another expression '((a + b) + c)'. This latter (sub-)expression
references no volatile object and hence could be commuted as "(a + (b +
c))" or could use an already computed sub-expression like (a + b). Once
the '((a + b) + c)' is evaluated, the outermost expression (which *does*
reference a volatile object) can then be evaluated 'strictly according
to the rules of the abstract machine'.

In your explanation, you reference the term 'full expression'. But the
term 'full expression' is explicitly defined in the standard (6.8p4): "A
full expression is an expression that is not part of another expression
or declarator. [...]" And it seems to have exactly the meaning you are
arguing for here. But if 'full expression' was indeed what was intended
in 6.7.3p6 as you argue, then wouldn't the well-defined term have been
used there?

GH

John Regehr

unread,
Feb 28, 2009, 11:59:44 AM2/28/09
to
On Feb 26, 3:55 pm, Tim Rentsch <t...@alumnus.caltech.edu> wrote:
> This part of the paper is wrong.  The loop does perform
> side-effecting operations, both the increments of i and the
> stores into buffer[i], and these must be done before the
> (volatile) assignment into buffer_ready.

No-- accesses to global variables are not side effecting operations.

The definition of side effect in the C standard differs from the
colloquial computer science definition:

"Accessing a volatile object, modifying an object, modifying a file,
or calling a function

that does any of those operations are all side effects,11) which are


changes in the state of
the execution environment."

John Regehr

John Regehr

unread,
Feb 28, 2009, 12:10:52 PM2/28/09
to
> "Accessing a volatile object, modifying an object, modifying a file,
> or calling a function
> that does any of those operations are all side effects,11) which are
> changes in the state of
> the execution environment."

Hmm, nevermind... probably I am not reading "object" correctly!

Even so, as people have pointed out, compilers move these accesses
around pretty freely. For example:

volatile int Ready;
int Message[100];
void foo (int i)
{
Message[i / 10] = 42;
Ready = 1;
}

regehr@john-home:~$ gcc -O3 -S -o - ready.c -fomit-frame-pointer
.globl foo
.type foo, @function
foo:
movl 4(%esp), %ecx
movl $1717986919, %edx
movl $1, Ready
movl %ecx, %eax
imull %edx
sarl $31, %ecx
sarl $2, %edx
subl %ecx, %edx
movl $42, Message(,%edx,4)
ret

Here the store into the non-volatile array has been moved below the
volatile store by gcc-4.2 for x86. The latest (pre-4.4.0) version of
gcc does the same thing, as does the current Intel CC. The current
version of llvm-gcc does not, it stores to the flag last. This code
example is from Arch Robison's blog.

John Regehr

John Regehr

unread,
Feb 28, 2009, 1:18:30 PM2/28/09
to
On Feb 24, 11:51 pm, "Jujitsu Lizard" <jujitsu.liz...@gmail.com>
wrote:
> That is the single most depressing paper I've read recently.

Perhaps amusingly, when I talk to compiler developers about volatile
bugs they are not at all surprised. Of course optimizers, like any
other large and complex programs, contain bugs. Embedded developers
tend to be more surprised and depressed (my gut reaction as well).

To make things worse we have also found regular old wrong-code errors
in every compiler that we've tested, including a number of embedded
compilers. This is for integer C programs: no pointers, no FP, not
even any structs or unions.

One example: the gcc that shipped with Ubuntu Hardy for x86
miscompiled this function so that it returned 1 instead of 0:

int foo (void) {
signed char l_11 = 1;
unsigned char l_12 = -1;
return (l_11 > l_12);
}

The base version of gcc, 4.2.3, did not mess this up, but the Ubuntu
people applied about 5 MB of patches in the released compiler and one
of these broke it badly. They have since pushed out an update that
fixes this. Did this bug put any security vulnerabilities into
Ubuntu? Wish I knew how to answer that :).

Broadly speaking, embedded compilers seem to be worse than, for
example, gcc for x86, in the sense that they produce wrong code for
broader classes of inputs. It is hard to say what is going on, but I
suspect that the cross-product of compiler vendors * target platforms
results in a situation where each individual compiler cannot be tested
nearly as thoroughly as a typical gcc for x86 release.

John Regehr

Eric Eide

unread,
Feb 28, 2009, 1:47:53 PM2/28/09
to
On Feb 26, 3:55 pm, Tim Rentsch <t...@alumnus.caltech.edu> wrote:

> This part of the paper is wrong.  The loop does perform
> side-effecting operations, both the increments of i and the
> stores into buffer[i], and these must be done before the
> (volatile) assignment into buffer_ready.

Hi! I am one of the authors of the paper in question.

I think that the cited portion of the paper is not wrong.

As described in other posts in this thread, Section 5.1.2.3 paragraph
2 describes a semantics in which "modifying an object" --- any object
--- is a side-effect. Side-effects are complete at sequence points.

But it is important to remember that this is a description of the
abstract semantics, and the specification distinguishes between the
abstract semantics and the semantics that are allowed by conforming
implementations.

Paragraph 5 in the same section describes the minimum requirements for
a conforming implementation, which are essentially that only volatile
objects must be stable at sequence points. Paragraphs 8 and 9 provide
further discussion, which help to clarify that the abstract semantics
and the semantics of a conforming implementation may be different.

If I have missed some detail here, please let me know!

Thanks ---

Eric.

PS --- Please direct any email replies to ee...@cs.utah.edu, not the
addess from which this message was posted.

David Brown

unread,
Feb 28, 2009, 2:36:44 PM2/28/09
to
John Regehr wrote:
> On Feb 24, 11:51 pm, "Jujitsu Lizard" <jujitsu.liz...@gmail.com>
> wrote:
>> That is the single most depressing paper I've read recently.
>
> Perhaps amusingly, when I talk to compiler developers about volatile
> bugs they are not at all surprised. Of course optimizers, like any
> other large and complex programs, contain bugs. Embedded developers
> tend to be more surprised and depressed (my gut reaction as well).
>
> To make things worse we have also found regular old wrong-code errors
> in every compiler that we've tested, including a number of embedded
> compilers. This is for integer C programs: no pointers, no FP, not
> even any structs or unions.
>
> One example: the gcc that shipped with Ubuntu Hardy for x86
> miscompiled this function so that it returned 1 instead of 0:
>
> int foo (void) {
> signed char l_11 = 1;
> unsigned char l_12 = -1;
> return (l_11 > l_12);
> }
>
> The base version of gcc, 4.2.3, did not mess this up, but the Ubuntu
> people applied about 5 MB of patches in the released compiler and one
> of these broke it badly. They have since pushed out an update that
> fixes this. Did this bug put any security vulnerabilities into
> Ubuntu? Wish I knew how to answer that :).
>

There should not be any security vulnerabilities due to such a bug - the
C code is clearly incorrect code, even though it is legal C, and
security-critical code is often better checked than application-level
code. Of course, *should not be* does not mean the same as *are not* !
Real bugs in heavily used features of heavily used compilers are rare,
but not unknown - that's one reason you need to test software well
(especially if it is critical to security or reliability).

> Broadly speaking, embedded compilers seem to be worse than, for
> example, gcc for x86, in the sense that they produce wrong code for
> broader classes of inputs. It is hard to say what is going on, but I
> suspect that the cross-product of compiler vendors * target platforms
> results in a situation where each individual compiler cannot be tested
> nearly as thoroughly as a typical gcc for x86 release.
>

I think that is a perfectly good explanation. Tools that are heavily
used are going to have fewer bugs (at least, fewer bugs that are easily
triggered) since there are more people using them. They also tend to
have more resources in development. This is somewhat countered by the
complexity of the software, which is often higher than for smaller
tools. Certainly if you look at embedded compilers, the bugs are
generally in the target-specific backends rather than the more generic
front-ends.

Keith Thompson

unread,
Feb 28, 2009, 3:53:10 PM2/28/09
to
Gil Hamilton <gil_ha...@hotmail.com> writes:
> Tim Rentsch <t...@alumnus.caltech.edu> wrote in
> news:kfntz6g...@alumnus.caltech.edu:
>
>> First simple example:
>>
>> x = a + b + c + v;
>>
>> The first rule for volatile is captured in a single sentence
>> in 6.7.3p6:
>>
>> Therefore any expression referring to such an object [i.e.,
>> with volatile-qualified type] shall be evaluated strictly
>> according to the rules of the abstract machine, as described
>> in 5.1.2.3.
>>
>> The full expression assigning to x is an expression referring to an
>> object with volatile-qualified type. Therefore that expression,
>> the /entire/ expression, must be evaluated strictly according to
>> the rules of the abstract machine.
[...]

>
> I disagree with this analysis. I think you're ascribing too pandemic a
> meaning to the phrase 'any expression referring to such an object...'.

I don't.

> As you say, the language syntax requires that the interpretation of the
> expression is "(((a + b) + c) + v)". However, decomposing that further
> shows that in the outermost expression, 'v' is being added to the result
> of another expression '((a + b) + c)'. This latter (sub-)expression
> references no volatile object and hence could be commuted as "(a + (b +
> c))" or could use an already computed sub-expression like (a + b). Once
> the '((a + b) + c)' is evaluated, the outermost expression (which *does*
> reference a volatile object) can then be evaluated 'strictly according
> to the rules of the abstract machine'.

The standard says that "*any expression referring to such an
object shall be evaluated strictly according to the rules of the
abstract machine". v is such an object. ``x = a + b + c + v''
is an "expression referring to such an object". Therefore
``x = a + b + c + v'' must be evaluated strictly according to the
rules of the abstract machine. The statement in the standard, if
taken literally (and I don't know how else one could reasonably take
it) applies to the expressions ``v'', ``a + b + c + v'', and
``x = a + b + c + v''.

It's entirely possible that this wasn't the intent of the authors of
the standard. In some cases, where the literal wording of the
standard doesn't express the obvious intent, it can be reasonable to
use a loose interpretation, but I don't see how to construct such an
interpretation in this case, at least not in a way that everyone would
agree on.

> In your explanation, you reference the term 'full expression'. But the
> term 'full expression' is explicitly defined in the standard (6.8p4): "A
> full expression is an expression that is not part of another expression
> or declarator. [...]" And it seems to have exactly the meaning you are
> arguing for here. But if 'full expression' was indeed what was intended
> in 6.7.3p6 as you argue, then wouldn't the well-defined term have been
> used there?

It doesn't need to be. A full expression is an expression. If the
standard refers to "any expression", it must be referring to full
expressions as well as to subexpressions.

--
Keith Thompson (The_Other_Keith) k...@mib.org <http://www.ghoti.net/~kst>
Nokia
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"

Hans-Bernhard Bröker

unread,
Feb 28, 2009, 4:30:40 PM2/28/09
to
David Brown wrote:
> John Regehr wrote:

>> int foo (void) {
>> signed char l_11 = 1;
>> unsigned char l_12 = -1;
>> return (l_11 > l_12);
>> }

> There should not be any security vulnerabilities due to such a bug - the

> C code is clearly incorrect code,

No, it's not. It's questionable, but way short of "clearly incorrect".
As far as minimal examples checking for a possible compiler bug go, I
find it to be just about perfect.

David Brown

unread,
Feb 28, 2009, 7:02:53 PM2/28/09
to

Setting an "unsigned" value to a negative value is more than a little
questionable, IMHO. It's still a compiler bug, of course.

Richard Heathfield

unread,
Feb 28, 2009, 7:18:03 PM2/28/09
to
David Brown said:

I don't see why. Note 3.1.2.5 Types: "A computation involving
unsigned operands can never overflow, because a result that cannot
be represented by the resulting unsigned integer type is reduced
modulo the number that is one greater than the largest value that
can be represented by the resulting unsigned integer type."

So: unsigned char l_12 = -1; is guaranteed to set l_12 to UCHAR_MAX.
No question.

<snip>

--
Richard Heathfield <http://www.cpax.org.uk>
Email: -http://www. +rjh@
Google users: <http://www.cpax.org.uk/prg/writings/googly.php>
"Usenet is a strange place" - dmr 29 July 1999

Ike Naar

unread,
Feb 28, 2009, 7:20:38 PM2/28/09
to
In article <e_edncnOdIZnTTTU...@lyse.net>,
David Brown <david...@hesbynett.removethisbit.no> wrote:
> [about ``unsigned char l_12 = -1;'']

>Setting an "unsigned" value to a negative value is more than a little
>questionable, IMHO. It's still a compiler bug, of course.

Assigning -1 is an established trick^H^H^H^H^Hmethod to assign the
maximum possible value to an unsigned variable.

CBFalconer

unread,
Feb 28, 2009, 8:52:38 PM2/28/09
to

Setting an unsigned to -1 is a normal and standard way of setting
it to unsignedwhatever_MAX. Not in the least questionable.

--
[mail]: Chuck F (cbfalconer at maineline dot net)
[page]: <http://cbfalconer.home.att.net>
Try the download section.


Gil Hamilton

unread,
Mar 1, 2009, 1:14:07 AM3/1/09
to
Keith Thompson <ks...@mib.org> wrote in
news:lnljrq8...@nuthaus.mib.org:
> Gil Hamilton <gil_ha...@hotmail.com> writes:
>> Tim Rentsch <t...@alumnus.caltech.edu> wrote in
>> news:kfntz6g...@alumnus.caltech.edu:
>>
>>> First simple example:
>>>
>>> x = a + b + c + v;
>>>
>>> The first rule for volatile is captured in a single sentence
>>> in 6.7.3p6:
>>>
>>> Therefore any expression referring to such an object [i.e.,
>>> with volatile-qualified type] shall be evaluated strictly
>>> according to the rules of the abstract machine, as described
>>> in 5.1.2.3.
>>>
>>> The full expression assigning to x is an expression referring to an
>>> object with volatile-qualified type. Therefore that expression,
>>> the /entire/ expression, must be evaluated strictly according to
>>> the rules of the abstract machine.

>> As you say, the language syntax requires that the interpretation of


>> the expression is "(((a + b) + c) + v)". However, decomposing that
>> further shows that in the outermost expression, 'v' is being added to
>> the result of another expression '((a + b) + c)'. This latter
>> (sub-)expression references no volatile object and hence could be
>> commuted as "(a + (b + c))" or could use an already computed
>> sub-expression like (a + b). Once the '((a + b) + c)' is evaluated,
>> the outermost expression (which *does* reference a volatile object)
>> can then be evaluated 'strictly according to the rules of the
>> abstract machine'.
>
> The standard says that "*any expression referring to such an
> object shall be evaluated strictly according to the rules of the
> abstract machine". v is such an object. ``x = a + b + c + v''
> is an "expression referring to such an object".

But the point is that "x = a + b + c + v" is NOT merely *an* expression.
It is an expression that combines a tree of other expressions. The tree
for this example is easily derived from the grammar:

= Expr 1 (assignment)
/ \
'x' + Expr 2 (addition)
/ \
'v' + Expr 3 (addition)
/ \
'c' + Expr 4 (addition)
/ \
'b' 'a'

(Not including the trivial "primary expressions" that the variable
references themselves represent.) Expressions 1 and 2 "refer to" a
volatile object. Expressions 3 and 4, though their evaluation is
required as input to 2 and 1, do not themselves refer to any volatile
object.

> It's entirely possible that this wasn't the intent of the authors of
> the standard. In some cases, where the literal wording of the
> standard doesn't express the obvious intent, it can be reasonable to
> use a loose interpretation, but I don't see how to construct such an
> interpretation in this case, at least not in a way that everyone would
> agree on.

I've just shown you how to construct such an interpretation.

Indeed, were one to try to apply the intent that you consider so
obvious, there is really no consistent and reasonable way to apply it.
What if the expresssion was instead?
x = ((a + b + c) && 0) ? 1 : v;
Now, according to your interpretation, the compiler must generate code
to evaluate 'a + b + c' (in the correct order, etc.), logically OR the
sum with the constant 0, compare that result with 0, then and only then
access the volatile object 'v' assigning the resulting value to 'x'
(presumably including the code that would assign 1 in the event that 0
was somehow found not to be equal to 0).

I find it extremely difficult that anyone would find *this* a reasonable
interpretation of the standard.

On the other hand, with this expression (both v1 and v2 volatile):
x = (v1 && ((a + b + c) && 0) && v2);
it *would* be extremely reasonable to conclude that the standard
requires an "access" to v1 (however an access is defined by the
implementation), followed by an assignment of 0 to x. (But no
additions, or comparisons, or code to access v2.)


>> In your explanation, you reference the term 'full expression'. But
>> the term 'full expression' is explicitly defined in the standard
>> (6.8p4): "A full expression is an expression that is not part of
>> another expression or declarator. [...]" And it seems to have
>> exactly the meaning you are arguing for here. But if 'full
>> expression' was indeed what was intended in 6.7.3p6 as you argue,
>> then wouldn't the well-defined term have been used there?
>
> It doesn't need to be. A full expression is an expression. If the
> standard refers to "any expression", it must be referring to full
> expressions as well as to subexpressions.

Yet, use of the well-defined term would have clearly expressed what you
say is the 'obvious intent'. And without that term, there is ample
reason for a different interpretation.

GH

Gil Hamilton

unread,
Mar 1, 2009, 1:57:51 AM3/1/09
to
Richard Heathfield <r...@see.sig.invalid> wrote in
news:y_OdneDZd7jETjTU...@bt.com:

>> Setting an "unsigned" value to a negative value is more than a
>> little questionable, IMHO.
>
> I don't see why. Note 3.1.2.5 Types: "A computation involving
> unsigned operands can never overflow, because a result that cannot
> be represented by the resulting unsigned integer type is reduced
> modulo the number that is one greater than the largest value that
> can be represented by the resulting unsigned integer type."
>
> So: unsigned char l_12 = -1; is guaranteed to set l_12 to UCHAR_MAX.
> No question.

It is not guaranteed. The standard does not require twos' complement
representation to be used.

Indeed, this is the primary reason for having UCHAR_MAX in the first place:
so that you don't have to make the assumption that the maximum unsigned
value has the same representation as signed -1.

GH

Jon Kirwan

unread,
Mar 1, 2009, 2:13:36 AM3/1/09
to
On Sun, 1 Mar 2009 06:57:51 +0000 (UTC), Gil Hamilton
<gil_ha...@hotmail.com> wrote:

>Richard Heathfield <r...@see.sig.invalid> wrote in
>news:y_OdneDZd7jETjTU...@bt.com:
>>> Setting an "unsigned" value to a negative value is more than a
>>> little questionable, IMHO.
>>
>> I don't see why. Note 3.1.2.5 Types: "A computation involving
>> unsigned operands can never overflow, because a result that cannot
>> be represented by the resulting unsigned integer type is reduced
>> modulo the number that is one greater than the largest value that
>> can be represented by the resulting unsigned integer type."
>>
>> So: unsigned char l_12 = -1; is guaranteed to set l_12 to UCHAR_MAX.
>> No question.
>
>It is not guaranteed. The standard does not require twos' complement
>representation to be used.

Agreed. The standard explicitly mentions one's complement, for
example.

>Indeed, this is the primary reason for having UCHAR_MAX in the first place:
>so that you don't have to make the assumption that the maximum unsigned
>value has the same representation as signed -1.

Isn't it the case that the value -1, cast to unsigned and regardless
of representation, must become whatever represents -1 MODULO
(UCHAR_MAX+1)? In other words, UCHAR_MAX? Or am I forgetting my
modulos?

Jon

Nate Eldredge

unread,
Mar 1, 2009, 2:53:32 AM3/1/09
to
Gil Hamilton <gil_ha...@hotmail.com> writes:

> Richard Heathfield <r...@see.sig.invalid> wrote in
> news:y_OdneDZd7jETjTU...@bt.com:
>>> Setting an "unsigned" value to a negative value is more than a
>>> little questionable, IMHO.
>>
>> I don't see why. Note 3.1.2.5 Types: "A computation involving
>> unsigned operands can never overflow, because a result that cannot
>> be represented by the resulting unsigned integer type is reduced
>> modulo the number that is one greater than the largest value that
>> can be represented by the resulting unsigned integer type."
>>
>> So: unsigned char l_12 = -1; is guaranteed to set l_12 to UCHAR_MAX.
>> No question.
>
> It is not guaranteed. The standard does not require twos' complement
> representation to be used.

Please see 6.3.1.3 (1-2) of the C99 standard.

(1) When a value with integer type is converted to another integer type
other than _Bool, if the value can be represented by the new type, it is
unchanged. [Doesn't apply here, included for context.]

(2) Otherwise, if the new type is unsigned, the value is converted by
repeatedly adding or subtracting one more than the maximum value that
can be represented in the new type until the value is in the range of
the new type.

So although the standard does not require two's complement
representation to be used internally, conversion of negative values to
unsigned types is required to behave as though they were two's
complement, as far as I can tell. This might require extra code to be
generated if the representations are actually different.

Thus, given

int main(void) {
signed char s = -1;
unsigned char u1, u2;
u1 = s;
u2 = *(unsigned char *)&s;
printf("%u %u\n", u1, u2);
return 0;
}

I believe the first value output must be equal to UCHAR_MAX. The second
value output examines the representation of `s', and need not equal
UCHAR_MAX. On a one's complement machine, it would presumably be
UCHAR_MAX - 1. On a sign-magnitude machine, it might be 129.

> Indeed, this is the primary reason for having UCHAR_MAX in the first place:
> so that you don't have to make the assumption that the maximum unsigned
> value has the same representation as signed -1.

They needn't have the same *representation*, but the latter is *converted*
to the former. UCHAR_MAX looks nicer than (unsigned char)-1, and also
provides consistency with CHAR_MAX, etc, but I don't think it could
actually have a different value.

Richard Heathfield

unread,
Mar 1, 2009, 4:28:42 AM3/1/09
to
Gil Hamilton said:

> Richard Heathfield <r...@see.sig.invalid> wrote in
> news:y_OdneDZd7jETjTU...@bt.com:
>>> Setting an "unsigned" value to a negative value is more than a
>>> little questionable, IMHO.
>>
>> I don't see why. Note 3.1.2.5 Types: "A computation involving
>> unsigned operands can never overflow, because a result that
>> cannot be represented by the resulting unsigned integer type is
>> reduced modulo the number that is one greater than the largest
>> value that can be represented by the resulting unsigned integer
>> type."
>>
>> So: unsigned char l_12 = -1; is guaranteed to set l_12 to
>> UCHAR_MAX. No question.
>
> It is not guaranteed. The standard does not require twos'
> complement representation to be used.

Er, so what? This is about values, not about representation.

> Indeed, this is the primary reason for having UCHAR_MAX in the
> first place: so that you don't have to make the assumption that
> the maximum unsigned value has the same representation as signed
> -1.

This isn't about representation.

When you try to assign to an unsigned integer type a value that is
not within the range it can store, the value is "reduced" (which
might mean increasing it) into the appropriate range. Thus, if you
do this: unsigned char uc = -17; then what you actually get in uc
is the value (UCHAR_MAX + 1 - 17). If UCHAR_MAX is 255, then you
get 256 - 17, or 239. Representation has nothing to do with it. I
refer you again to the above citation from the Standard.

Hans-Bernhard Bröker

unread,
Mar 1, 2009, 6:36:00 AM3/1/09
to
Gil Hamilton wrote:
> Richard Heathfield <r...@see.sig.invalid> wrote in
> news:y_OdneDZd7jETjTU...@bt.com:

>> So: unsigned char l_12 = -1; is guaranteed to set l_12 to UCHAR_MAX.
>> No question.

> It is not guaranteed. The standard does not require twos' complement
> representation to be used.

Nonsense. Representation has nothing to do with it. The result is
defined by value, not by representation.

David Brown

unread,
Mar 1, 2009, 7:20:28 AM3/1/09
to

The code has let to several questions in this thread - ergo, it is more
than a little questionable.

I write embedded software for a living. To do that well, I aim to write
code that makes sense logically, that is as clear in its meaning and
self-explanatory as possible, and that is portable to different *real*
embedded compilers where practical (but taking advantage of better tools
if it makes sense). The C standards are not a Bible to me - *real*
embedded processors and *real* embedded development tools are what count.

So for me, the line "unsigned char l_12 = -1;" has three glaring faults,
and I consider it incorrect code regardless of whether it works or not.

First and most obviously, the name "l_12" is unreadable and
uninformative - even something like "x_12" would be better, since it
avoids using a lonely small "l" that looks like a "1".

Secondly, assigning a negative value to an unsigned type is mathematical
and logical nonsense. I don't give a *beep* what the standards say, or
even whether the code works or not - it doesn't make sense, so the code
is wrong.

Thirdly, a "char" holds a character - something like 'A'. If you want
to hold a number, use a number type. That's called "good typing", and
is part of "good programming". It is one of C's many unfortunate design
flaws that there are no fundamental types for smaller numbers, and that
instead you must abuse "char". Fortunately there is "typedef", and
especially <stdint.h>, which allows you to write "uint8_t".

So for an embedded programmer, correct code will be more like:

"uint8_t x_12 = 0xff;"

If you are working with code for a wider range of systems, including
those that can't handle 8-bit data, and you are really writing code that
is portable across such systems, you might want something like:

"uint_fast8_t x_12 = UINT_FAST8_MAX;"

Of course, if you are writing such portable code, then lines like this
are the least of your worries - a great many uses of "UCHAR_MAX" and
related constants are a waste of time since the rest of the code would
not work if it were anything other than 0xff.

Keith Thompson

unread,
Mar 1, 2009, 7:56:55 AM3/1/09
to
Gil Hamilton <gil_ha...@hotmail.com> writes:
> Keith Thompson <ks...@mib.org> wrote in
> news:lnljrq8...@nuthaus.mib.org:
>> Gil Hamilton <gil_ha...@hotmail.com> writes:
>>> Tim Rentsch <t...@alumnus.caltech.edu> wrote in
>>> news:kfntz6g...@alumnus.caltech.edu:
>>>
>>>> First simple example:
>>>>
>>>> x = a + b + c + v;
>>>>
>>>> The first rule for volatile is captured in a single sentence
>>>> in 6.7.3p6:
>>>>
>>>> Therefore any expression referring to such an object [i.e.,
>>>> in 5.1.2.3.
>>>>
>>>> The full expression assigning to x is an expression referring to an
>>>> object with volatile-qualified type. Therefore that expression,
>>>> the /entire/ expression, must be evaluated strictly according to
>>>> the rules of the abstract machine.
>
>>> As you say, the language syntax requires that the interpretation of
>>> the expression is "(((a + b) + c) + v)". However, decomposing that
>>> further shows that in the outermost expression, 'v' is being added to
>>> the result of another expression '((a + b) + c)'. This latter
>>> (sub-)expression references no volatile object and hence could be
>>> commuted as "(a + (b + c))" or could use an already computed
>>> sub-expression like (a + b). Once the '((a + b) + c)' is evaluated,
>>> the outermost expression (which *does* reference a volatile object)
>>> can then be evaluated 'strictly according to the rules of the
>>> abstract machine'.
>>
>> The standard says that "*any expression referring to such an
>> object shall be evaluated strictly according to the rules of the
>> abstract machine". v is such an object. ``x = a + b + c + v''
>> is an "expression referring to such an object".
>
> But the point is that "x = a + b + c + v" is NOT merely *an* expression.
> It is an expression that combines a tree of other expressions.

Yes, but I fail to see how that contradicts what I wrote. The
standard says "any expression". It does not say, or imply, "any
expression that doesn't combine a tree of other expressions".

> The tree
> for this example is easily derived from the grammar:
>
> = Expr 1 (assignment)
> / \
> 'x' + Expr 2 (addition)
> / \
> 'v' + Expr 3 (addition)
> / \
> 'c' + Expr 4 (addition)
> / \
> 'b' 'a'

You've reversed the order of the operands of the "+" operators. That
shouldn't matter, since addition is commutative, but I think it would
be clearer to write it as:

= Expr 1 (assignment)
/ \
'x' + Expr 2 (addition)
/ \

+ 'v' Expr 3 (addition)
/ \
+ 'c' Expr 4 (addition)
/ \
'a' 'b'

> (Not including the trivial "primary expressions" that the variable
> references themselves represent.) Expressions 1 and 2 "refer to" a
> volatile object. Expressions 3 and 4, though their evaluation is
> required as input to 2 and 1, do not themselves refer to any volatile
> object.

Expressions 1 and 2 refer to a volatile object, therefore expressions
1 and 2 must be evaluated strictly according to the rules of the
abstract machine. To evaluate an expression strictly according to
the rules of the abstract machine, you must evaluate any of its
subexpressions strictly according to the rules of the abstract
machine.

>> It's entirely possible that this wasn't the intent of the authors of


>> the standard. In some cases, where the literal wording of the
>> standard doesn't express the obvious intent, it can be reasonable to
>> use a loose interpretation, but I don't see how to construct such an
>> interpretation in this case, at least not in a way that everyone would
>> agree on.
>
> I've just shown you how to construct such an interpretation.

And I disagree with your reasoning.

> Indeed, were one to try to apply the intent that you consider so
> obvious, there is really no consistent and reasonable way to apply it.
> What if the expresssion was instead?
> x = ((a + b + c) && 0) ? 1 : v;
> Now, according to your interpretation, the compiler must generate code
> to evaluate 'a + b + c' (in the correct order, etc.), logically OR the
> sum with the constant 0, compare that result with 0, then and only then
> access the volatile object 'v' assigning the resulting value to 'x'
> (presumably including the code that would assign 1 in the event that 0
> was somehow found not to be equal to 0).

Yes.

> I find it extremely difficult that anyone would find *this* a reasonable
> interpretation of the standard.

I agree that it's not a reasonable *requirement*, but I think it's the
only possible interpretation of what the standard actually says.

[...]

>>> In your explanation, you reference the term 'full expression'. But
>>> the term 'full expression' is explicitly defined in the standard
>>> (6.8p4): "A full expression is an expression that is not part of
>>> another expression or declarator. [...]" And it seems to have
>>> exactly the meaning you are arguing for here. But if 'full
>>> expression' was indeed what was intended in 6.7.3p6 as you argue,
>>> then wouldn't the well-defined term have been used there?
>>
>> It doesn't need to be. A full expression is an expression. If the
>> standard refers to "any expression", it must be referring to full
>> expressions as well as to subexpressions.
>
> Yet, use of the well-defined term would have clearly expressed what you
> say is the 'obvious intent'. And without that term, there is ample
> reason for a different interpretation.

I didn't use the phrase "obvious intent" in this context; I used it
only to refer other cases where the standard's wording is flawed.
(For example, the standard's definition of "expression" implies that
42 is not an expression, but the obvious intent is that it is.
Similarly, the standard's definition of "lvalue" implies that,
assuming 42 is an expression, 42 is an lvalue, but the obvious intent
is that it is not.)

Personally, I see no obvious intent here; I honestly don't know what
the standard *should* say. (I've never really used "volatile", so I
don't have much to say about how it should work.)

If you're dissatisfied with what the standard currently says about
"volatile" (and it seems to me that you should be), then the first
step in fixing it is to figure out what it *should* say; the next step
is to express that in standardese.

Ben Bacarisse

unread,
Mar 1, 2009, 9:47:47 AM3/1/09
to
David Brown <david...@hesbynett.removethisbit.no> writes:

That is not useful criterion -- you would not be able to write a
single lien of C if you avoided every construct that has let to
questions here!

> I write embedded software for a living. To do that well, I aim to
> write code that makes sense logically, that is as clear in its meaning
> and self-explanatory as possible, and that is portable to different
> *real* embedded compilers where practical (but taking advantage of
> better tools if it makes sense). The C standards are not a Bible to
> me - *real* embedded processors and *real* embedded development tools
> are what count.
>
> So for me, the line "unsigned char l_12 = -1;" has three glaring
> faults, and I consider it incorrect code regardless of whether it
> works or not.
>
> First and most obviously, the name "l_12" is unreadable and
> uninformative - even something like "x_12" would be better, since it
> avoids using a lonely small "l" that looks like a "1".
>
> Secondly, assigning a negative value to an unsigned type is
> mathematical and logical nonsense. I don't give a *beep* what the
> standards say, or even whether the code works or not - it doesn't make
> sense, so the code is wrong.

I think you are putting too much emphasis on the constant expression.
If you write code that subtracts from an unsigned integer, the result
can be negative and C's "reduce modulo max + 1" rule kicks in. In
fact it kicks in when you add as well if the result is larger the
maximum value for the type. In other words assigning -1 is no
different than x -= 1 when x is zero. I think all embedded
programmers will know that this "wraps round".

My point is that I think it helps to know what is really happening:
that the right hand side of x = x - 1; is often a negative number
being assigned back to an unsigned variable. In that context x = -1;
is not surprising.

I know that I stand little chance of persuading someone who has used
"real" (in bold) three times (and who has bleeped out an expletive)
but I throw it out there as food for thought!

One other minor point:

<snip>


> So for an embedded programmer, correct code will be more like:
>
> "uint8_t x_12 = 0xff;"

The trouble with this (and it is only a small style matter) is that
the type information "leaks" over to the right hand side. It is very
handy that the zero of all arithmetic types is written the same way
(0) and I like the fact that the maximum value of all unsigned types
is similarly available (-1). (OK, I know -1 is no really such a
maximum, but I hope you see what I am getting at).

Outside of the embedded world it can be useful to write "width"
neutral code so that simply by changing a typedef the code can work on
a different range or values. I've even see it used like this:

typedef struct { unsigned value:WIDTH; } integer;

if (x.value < (integer){-1}.value) ...

although I would agree if you accused this of being a bit too
"tricksy".

--
Ben.

Phil Carmody

unread,
Mar 1, 2009, 9:55:17 AM3/1/09
to
Gil Hamilton <gil_ha...@hotmail.com> writes:
> Richard Heathfield <r...@see.sig.invalid> wrote in
> news:y_OdneDZd7jETjTU...@bt.com:
>>> Setting an "unsigned" value to a negative value is more than a
>>> little questionable, IMHO.
>>
>> I don't see why. Note 3.1.2.5 Types: "A computation involving
>> unsigned operands can never overflow, because a result that cannot
>> be represented by the resulting unsigned integer type is reduced
>> modulo the number that is one greater than the largest value that
>> can be represented by the resulting unsigned integer type."
>>
>> So: unsigned char l_12 = -1; is guaranteed to set l_12 to UCHAR_MAX.
>> No question.
>
> It is not guaranteed. The standard does not require twos' complement
> representation to be used.

Representation is not being questioned, value is. However, 6.2.6.1
clause 3 implies that what applies to one applies to both. The
value of the expression ``-1'' is the value to which 0 will be
yielded if 1 is added to it, by the rules of modulo arithmetic.
UCHAR_MAX uniquely has that property.

> Indeed, this is the primary reason for having UCHAR_MAX in the first place:
> so that you don't have to make the assumption that the maximum unsigned
> value has the same representation as signed -1.

Are you sure - do you have any evidence to back up that claim?

David Brown

unread,
Mar 1, 2009, 2:32:12 PM3/1/09
to

Yes, embedded programmers know that "all" processors and C compilers use
twos-complement wrapping arithmetic (except when they use saturating
arithmetic...).

> My point is that I think it helps to know what is really happening:
> that the right hand side of x = x - 1; is often a negative number
> being assigned back to an unsigned variable. In that context x = -1;
> is not surprising.
>

I am not doubting that in this context "x = -1" and "x = 0xff" have
identical effects, nor do I doubt that (as others have pointed out) "x =
-1" is commonly written in C programs even when "x" is unsigned. But it
is illogical and non-sensical when read or written by a person, and it
will be flagged as an error by more stringent tools such as lint because
it is assigning a value outside the range expressible by the type.
Other programming languages that have stronger compile-time checking
(such as Pascal or Ada) would reject it outright.

Just because the C compiler accepts the code, and C programmers write
such code, and (baring compiler bugs) the generated code does what the
programmer expects, does not mean it is good code!

> I know that I stand little chance of persuading someone who has used
> "real" (in bold) three times (and who has bleeped out an expletive)
> but I throw it out there as food for thought!
>

These bolds look a little more dramatic than the asterisks when I wrote
it...

> One other minor point:
>
> <snip>
>> So for an embedded programmer, correct code will be more like:
>>
>> "uint8_t x_12 = 0xff;"
>
> The trouble with this (and it is only a small style matter) is that
> the type information "leaks" over to the right hand side. It is very
> handy that the zero of all arithmetic types is written the same way
> (0) and I like the fact that the maximum value of all unsigned types
> is similarly available (-1). (OK, I know -1 is no really such a
> maximum, but I hope you see what I am getting at).
>

I see your point, and I understand why people use -1 in this way. I
don't see a problem with the type information "leaking" in this way -
you should be aware of the type of both the left hand side and right
hand side of any assignment.

> Outside of the embedded world it can be useful to write "width"
> neutral code so that simply by changing a typedef the code can work on
> a different range or values. I've even see it used like this:
>
> typedef struct { unsigned value:WIDTH; } integer;
>
> if (x.value < (integer){-1}.value) ...
>
> although I would agree if you accused this of being a bit too
> "tricksy".
>

"Tricksy" code is often useful, but where possible such code should be
hidden through macros, constants, inline functions and the like, so that
the main code remains clear. You could write something like:

#define maxUInt -1

hidden away in a header file, and in the main code write:

void foo(void) {
uint8_t x = maxUInt;
}

That would have an identical effect as far as the compiler is concerned
(and still annoy tools like lint), but at least it would make sense to
human readers.

I agree that it is sometimes useful to write could with types whose size
is easily changed - that is perhaps more important in embedded systems,
when you often need to use as small types as possible for efficiency
(for example, a buffer index on an AVR would be 8-bit if possible, but
16-bit if necessary). But it is seldom a hardship to define things like
maximum values as symbolic constants of some kind at the same place as
the actual concrete width is defined.

Ben Bacarisse

unread,
Mar 1, 2009, 3:40:19 PM3/1/09
to
David Brown <david...@hesbynett.removethisbit.no> writes:

I am a bit stumped by this. My sarcasm detector went off, but I can't
see your point. If your compiler does not do standard (as in the C
standard) arithmetic, then you are not writing in C but something a bit
like it. Are such non-conforming compilers common in the embedded
world?

>> My point is that I think it helps to know what is really happening:
>> that the right hand side of x = x - 1; is often a negative number
>> being assigned back to an unsigned variable. In that context x = -1;
>> is not surprising.
>>
>
> I am not doubting that in this context "x = -1" and "x = 0xff" have
> identical effects, nor do I doubt that (as others have pointed out) "x
> = -1" is commonly written in C programs even when "x" is unsigned.
> But it is illogical and non-sensical when read or written by a person,
> and it will be flagged as an error by more stringent tools such as
> lint because it is assigning a value outside the range expressible by
> the type. Other programming languages that have stronger compile-time
> checking (such as Pascal or Ada) would reject it outright.
>
> Just because the C compiler accepts the code, and C programmers write
> such code, and (baring compiler bugs) the generated code does what the
> programmer expects, does not mean it is good code!

Agreed. I was arguing that it is good. I am happy that we just
disagree about that.

<snip>


>> One other minor point:
>>
>> <snip>
>>> So for an embedded programmer, correct code will be more like:
>>>
>>> "uint8_t x_12 = 0xff;"
>>
>> The trouble with this (and it is only a small style matter) is that
>> the type information "leaks" over to the right hand side. It is very
>> handy that the zero of all arithmetic types is written the same way
>> (0) and I like the fact that the maximum value of all unsigned types
>> is similarly available (-1). (OK, I know -1 is no really such a
>> maximum, but I hope you see what I am getting at).
>>
>
> I see your point, and I understand why people use -1 in this way. I
> don't see a problem with the type information "leaking" in this way -
> you should be aware of the type of both the left hand side and right
> hand side of any assignment.

I expressed it badly. You have duplication of information. The
correct constant is tied to the type. Often this does not matter,
but if the type might change it does matter a bit more.

>> Outside of the embedded world it can be useful to write "width"
>> neutral code so that simply by changing a typedef the code can work on
>> a different range or values. I've even see it used like this:
>>
>> typedef struct { unsigned value:WIDTH; } integer;
>>
>> if (x.value < (integer){-1}.value) ...
>>
>> although I would agree if you accused this of being a bit too
>> "tricksy".
>>
>
> "Tricksy" code is often useful, but where possible such code should be
> hidden through macros, constants, inline functions and the like, so
> that the main code remains clear. You could write something like:
>
> #define maxUInt -1
>
> hidden away in a header file, and in the main code write:
>
> void foo(void) {
> uint8_t x = maxUInt;
> }

I'd rather not have to check that the macros is correct. If I see -1
being assigned, I know what is happening. I'd have no objection to a
comment:

uint8_t x = -1; /* max value for that unsigned type */

> That would have an identical effect as far as the compiler is
> concerned (and still annoy tools like lint), but at least it would
> make sense to human readers.
>
> I agree that it is sometimes useful to write could with types whose
> size is easily changed - that is perhaps more important in embedded
> systems, when you often need to use as small types as possible for
> efficiency (for example, a buffer index on an AVR would be 8-bit if
> possible, but 16-bit if necessary). But it is seldom a hardship to
> define things like maximum values as symbolic constants of some kind
> at the same place as the actual concrete width is defined.

--
Ben.

Phil Carmody

unread,
Mar 1, 2009, 4:11:12 PM3/1/09
to
David Brown <david...@hesbynett.removethisbit.no> writes:

>>>>>>>> unsigned char l_12 = -1;

> I am not doubting that in this context "x = -1" and "x = 0xff" have


> identical effects, nor do I doubt that (as others have pointed out) "x
> = -1" is commonly written in C programs even when "x" is unsigned.
> But it is illogical and non-sensical when read or written by a person,
> and it will be flagged as an error by more stringent tools such as
> lint because it is assigning a value outside the range expressible by
> the type.

Do you feel the same way about

unsigned char uc = -1u;

Ike Naar

unread,
Mar 1, 2009, 4:46:02 PM3/1/09
to
In article <874oydk...@nonospaz.fatphil.org>,

Phil Carmody <thefatphi...@yahoo.co.uk> wrote:
>David Brown <david...@hesbynett.removethisbit.no> writes:
>
>>>>>>>>> unsigned char l_12 = -1;
>
>> I am not doubting that in this context "x = -1" and "x = 0xff" have
>> identical effects, nor do I doubt that (as others have pointed out) "x
>> = -1" is commonly written in C programs even when "x" is unsigned.
>> But it is illogical and non-sensical when read or written by a person,
>> and it will be flagged as an error by more stringent tools such as
>> lint because it is assigning a value outside the range expressible by
>> the type.
>
>Do you feel the same way about
>
> unsigned char uc = -1u;
>
>?

-1u is parsed as -(1u), not as (-1)u .
So, adding the ``u'' does not change anything to the fact that
an unsigned variable is initialised with a negative number.

Niklas Holsti

unread,
Mar 1, 2009, 5:14:00 PM3/1/09
to
David Brown wrote:

>>> ...


>>> So for me, the line "unsigned char l_12 = -1;" has three glaring
>>> faults, and I consider it incorrect code regardless of whether it
>>> works or not.
>>>

>>> ...


>>>
>>> Secondly, assigning a negative value to an unsigned type is
>>> mathematical and logical nonsense. I don't give a *beep* what the
>>> standards say, or even whether the code works or not - it doesn't make
>>> sense, so the code is wrong.

>>. ..


>
> I am not doubting that in this context "x = -1" and "x = 0xff" have
> identical effects, nor do I doubt that (as others have pointed out) "x =
> -1" is commonly written in C programs even when "x" is unsigned. But it
> is illogical and non-sensical when read or written by a person, and it
> will be flagged as an error by more stringent tools such as lint because
> it is assigning a value outside the range expressible by the type. Other
> programming languages that have stronger compile-time checking (such as
> Pascal or Ada) would reject it outright.

Yes and no; for Ada it depends on the type definition. Ada has both
unsigned types with modular arithmetic, similar to the C unsigned,
and integer types with range restrictions but with non-modular
arithmetic. For example, here is a small Ada program:

with Ada.Text_IO;

procedure Uns
is
type Unsigned_T is mod 256;
-- Unsigned type with arithmetic modulo 256.
-- The range is 0 .. 255.

Minus_1 : constant Unsigned_T := -1;
-- Correct; the result is 255, as in C, because
-- the unary "-" operation is taken mod 256.

Non_Negative : Natural;
-- A variable of a (predefined) non-modular type
-- with the range constraint 0 .. Integer'Last.

begin

Ada.Text_IO.Put_Line (Unsigned_T'Image (Minus_1));
-- Outputs 255.

Non_Negative := -1;
-- Compiler warns that the value -1 is not in range.
-- Exception Constraint_Error is raised at run-time.

Ada.Text_IO.Put_Line (Natural'Image (Non_Negative));

end Uns;

So for modular integer types Ada has made the same choice as C: the
expression -1 is acceptable and yields the largest value of the type.


--
Niklas Holsti
Tidorum Ltd
niklas holsti tidorum fi
. @ .

Ike Naar

unread,
Mar 1, 2009, 5:13:14 PM3/1/09
to
In article <12359439...@proxy00.news.clara.net>,

Let me choose my words more carefully:
Adding the ``u'' does not change anything to the fact that
an unsigned variable is initialised with something that has the
appearance of a negative number (effectively, -(1u) is unsigned).

David Brown

unread,
Mar 1, 2009, 5:38:06 PM3/1/09
to
Ben Bacarisse wrote:
> David Brown <david...@hesbynett.removethisbit.no> writes:
>
>> Ben Bacarisse wrote:
>>> David Brown <david...@hesbynett.removethisbit.no> writes:
<snip>

>>> I think you are putting too much emphasis on the constant expression.
>>> If you write code that subtracts from an unsigned integer, the result
>>> can be negative and C's "reduce modulo max + 1" rule kicks in. In
>>> fact it kicks in when you add as well if the result is larger the
>>> maximum value for the type. In other words assigning -1 is no
>>> different than x -= 1 when x is zero. I think all embedded
>>> programmers will know that this "wraps round".
>>>
>> Yes, embedded programmers know that "all" processors and C compilers
>> use twos-complement wrapping arithmetic (except when they use
>> saturating arithmetic...).
>
> I am a bit stumped by this. My sarcasm detector went off, but I can't
> see your point. If your compiler does not do standard (as in the C
> standard) arithmetic, then you are not writing in C but something a bit
> like it. Are such non-conforming compilers common in the embedded
> world?
>

Sometimes saturating arithmetic is used in embedded processors,
especially in DSPs (or DSP extensions to conventional processors).

It will not matter if the constant used is a symbolic constant which is
also tied to the type in its definition. You write something like
"indexType i = indexTypeMax", and make sure that indexTypeMax is
appropriately defined depending on the size of indexType.

>>> Outside of the embedded world it can be useful to write "width"
>>> neutral code so that simply by changing a typedef the code can work on
>>> a different range or values. I've even see it used like this:
>>>
>>> typedef struct { unsigned value:WIDTH; } integer;
>>>
>>> if (x.value < (integer){-1}.value) ...
>>>
>>> although I would agree if you accused this of being a bit too
>>> "tricksy".
>>>
>> "Tricksy" code is often useful, but where possible such code should be
>> hidden through macros, constants, inline functions and the like, so
>> that the main code remains clear. You could write something like:
>>
>> #define maxUInt -1
>>
>> hidden away in a header file, and in the main code write:
>>
>> void foo(void) {
>> uint8_t x = maxUInt;
>> }
>
> I'd rather not have to check that the macros is correct. If I see -1
> being assigned, I know what is happening. I'd have no objection to a
> comment:
>
> uint8_t x = -1; /* max value for that unsigned type */
>

Never write something in a comment if it can be expressed equally well
in the language itself.

You don't have to check that the macro is correct any more or less than
you have to check that the type definition is correct - and the
definitions should be in the same place.

CBFalconer

unread,
Mar 1, 2009, 8:03:42 PM3/1/09
to
David Brown wrote:
>
... snip ...

>
> So for an embedded programmer, correct code will be more like:
>
> "uint8_t x_12 = 0xff;"
>
> If you are working with code for a wider range of systems,
> including those that can't handle 8-bit data, and you are really
> writing code that is portable across such systems, you might want
> something like:
>
> "uint_fast8_t x_12 = UINT_FAST8_MAX;"
>
> Of course, if you are writing such portable code, then lines like
> this are the least of your worries - a great many uses of
> "UCHAR_MAX" and related constants are a waste of time since the
> rest of the code would not work if it were anything other than
> 0xff.

And it may not work if it is 0xff. Nothing forces a byte to be 8
bits. Examine the value of CHAR_BIT. The use of -1 will reliably
set the U*_MAX value.

CBFalconer

unread,
Mar 1, 2009, 8:16:37 PM3/1/09
to
Ike Naar wrote:
> Phil Carmody <thefatphi...@yahoo.co.uk> wrote:
>> David Brown <david...@hesbynett.removethisbit.no> writes:
>>
>>>>>>>>>> unsigned char l_12 = -1;
>>
>>> I am not doubting that in this context "x = -1" and "x = 0xff"
>>> have identical effects, nor do I doubt that (as others have
>>> pointed out) "x = -1" is commonly written in C programs even
>>> when "x" is unsigned. But it is illogical and non-sensical when
>>> read or written by a person, and it will be flagged as an error
>>> by more stringent tools such as lint because it is assigning a
>>> value outside the range expressible by the type.
>>
>> Do you feel the same way about
>>
>> unsigned char uc = -1u;
>
> -1u is parsed as -(1u), not as (-1)u .
> So, adding the ``u'' does not change anything to the fact that
> an unsigned variable is initialised with a negative number.

I have my doubts that this works. -(1u) produces an int, value
UINT_MAX. Assigning that to uc reduces that modulo (UCHAR_MAX +
1). I am not bothering to work out whether this is the same value
(because I wouldn't use it), but I suspect it is influenced by such
things as 1's complement or sign-mag representation, besides the
different values of the various U*_MAX.

Ben Bacarisse

unread,
Mar 1, 2009, 10:05:46 PM3/1/09
to
CBFalconer <cbfal...@yahoo.com> writes:

> Ike Naar wrote:
>> Phil Carmody <thefatphi...@yahoo.co.uk> wrote:
>>> David Brown <david...@hesbynett.removethisbit.no> writes:
>>>
>>>>>>>>>>> unsigned char l_12 = -1;
>>>
>>>> I am not doubting that in this context "x = -1" and "x = 0xff"
>>>> have identical effects, nor do I doubt that (as others have
>>>> pointed out) "x = -1" is commonly written in C programs even
>>>> when "x" is unsigned. But it is illogical and non-sensical when
>>>> read or written by a person, and it will be flagged as an error
>>>> by more stringent tools such as lint because it is assigning a
>>>> value outside the range expressible by the type.
>>>
>>> Do you feel the same way about
>>>
>>> unsigned char uc = -1u;
>>
>> -1u is parsed as -(1u), not as (-1)u .
>> So, adding the ``u'' does not change anything to the fact that
>> an unsigned variable is initialised with a negative number.
>
> I have my doubts that this works. -(1u) produces an int, value
> UINT_MAX.

Nit-pick: I don't think so. It produces (on most implementation) an
unsigned int. The type will be int only where int can represent all
the values of unsigned int (possible but rare). On such a system the
value is then -1. So it is never an int with value UINT_MAX. It is
either an int with value -1 or a unsigned int with value UINT_MAX.

> Assigning that to uc reduces that modulo (UCHAR_MAX +
> 1). I am not bothering to work out whether this is the same value
> (because I wouldn't use it), but I suspect it is influenced by such
> things as 1's complement or sign-mag representation, besides the
> different values of the various U*_MAX.

I can't see how the representation of negative numbers can affect the
result. UCHAR_MAX + 1 is of the form 2^k for some k >= 8 [1].
UINT_MAX is of the form 2^n - 1 for n >= k. I.e. it is 2^(n-k) * 2^k
- 1 with n-k >= 0. This is congruent to 2^k - 1 mod 2^k, i.e. the
result is UCHAR_MAX.

(Of course, if the expression is of type int with value -1 you also
get UCHAR_MAX.)

[1] Please don't tell me I've used XOR -- I am trying to write
mathematics in a restricted character set.

--
Ben.

Ben Bacarisse

unread,
Mar 1, 2009, 10:18:52 PM3/1/09
to
David Brown <david...@hesbynett.removethisbit.no> writes:

> Ben Bacarisse wrote:
>> David Brown <david...@hesbynett.removethisbit.no> writes:
<snip>

>>> Yes, embedded programmers know that "all" processors and C compilers
>>> use twos-complement wrapping arithmetic (except when they use
>>> saturating arithmetic...).
>>
>> I am a bit stumped by this. My sarcasm detector went off, but I can't
>> see your point. If your compiler does not do standard (as in the C
>> standard) arithmetic, then you are not writing in C but something a bit
>> like it. Are such non-conforming compilers common in the embedded
>> world?
>
> Sometimes saturating arithmetic is used in embedded processors,
> especially in DSPs (or DSP extensions to conventional processors).

Yes, I knew that was what you were saying but it does not answer my
question. What do some/all/most C compiler do about this? They have
lots of options but the least appealing (to me) would be to abandon
conformance.

<snip>
>>>> One other minor point:
>>>>
>>>> <snip>
>>>>> So for an embedded programmer, correct code will be more like:
>>>>>
>>>>> "uint8_t x_12 = 0xff;"
>>>> The trouble with this (and it is only a small style matter) is that
>>>> the type information "leaks" over to the right hand side. It is very
>>>> handy that the zero of all arithmetic types is written the same way
>>>> (0) and I like the fact that the maximum value of all unsigned types
>>>> is similarly available (-1). (OK, I know -1 is no really such a
>>>> maximum, but I hope you see what I am getting at).
>>>>
>>> I see your point, and I understand why people use -1 in this way. I
>>> don't see a problem with the type information "leaking" in this way
>>> -
>>> you should be aware of the type of both the left hand side and right
>>> hand side of any assignment.
>>
>> I expressed it badly. You have duplication of information. The
>> correct constant is tied to the type. Often this does not matter,
>> but if the type might change it does matter a bit more.
>>
>
> It will not matter if the constant used is a symbolic constant which
> is also tied to the type in its definition. You write something like
> "indexType i = indexTypeMax", and make sure that indexTypeMax is
> appropriately defined depending on the size of indexType.

I was commenting on your original suggestion where the type was tied
to 0xff with no textual link. Where I need type-neutral code in C I
would do it that way (though templates in <whisper>that other language
</whisper> do it better).

>>>> Outside of the embedded world it can be useful to write "width"
>>>> neutral code so that simply by changing a typedef the code can work on
>>>> a different range or values. I've even see it used like this:
>>>>
>>>> typedef struct { unsigned value:WIDTH; } integer;
>>>>
>>>> if (x.value < (integer){-1}.value) ...
>>>>
>>>> although I would agree if you accused this of being a bit too
>>>> "tricksy".
>>>>
>>> "Tricksy" code is often useful, but where possible such code should be
>>> hidden through macros, constants, inline functions and the like, so
>>> that the main code remains clear. You could write something like:
>>>
>>> #define maxUInt -1
>>>
>>> hidden away in a header file, and in the main code write:
>>>
>>> void foo(void) {
>>> uint8_t x = maxUInt;
>>> }
>>
>> I'd rather not have to check that the macros is correct. If I see -1
>> being assigned, I know what is happening. I'd have no objection to a
>> comment:
>>
>> uint8_t x = -1; /* max value for that unsigned type */
>>
>
> Never write something in a comment if it can be expressed equally well
> in the language itself.

Then don't comment it! I thought your point was that the assignment
of -1 did not properly express what was happening.

> You don't have to check that the macro is correct any more or less
> than you have to check that the type definition is correct - and the
> definitions should be in the same place.

It was not clear from your example that maxUInt would be used with
only one type. (Yes, there was only one type, but the name is very
general and it is not linked to the name of the type unlike your later
example.) That is why I said one might have to check. If every type
has it's own defined max that works fine for me, though I think it is
overkill for unsigned types (that will always be unsigned).

--
Ben.

Phil Carmody

unread,
Mar 2, 2009, 2:13:29 AM3/2/09
to
CBFalconer <cbfal...@yahoo.com> writes:
> Ike Naar wrote:
>> Phil Carmody <thefatphi...@yahoo.co.uk> wrote:
>>> David Brown <david...@hesbynett.removethisbit.no> writes:
>>>
>>>>>>>>>>> unsigned char l_12 = -1;
>>>
>>>> I am not doubting that in this context "x = -1" and "x = 0xff"
>>>> have identical effects, nor do I doubt that (as others have
>>>> pointed out) "x = -1" is commonly written in C programs even
>>>> when "x" is unsigned. But it is illogical and non-sensical when
>>>> read or written by a person, and it will be flagged as an error
>>>> by more stringent tools such as lint because it is assigning a
>>>> value outside the range expressible by the type.
>>>
>>> Do you feel the same way about
>>>
>>> unsigned char uc = -1u;
>>
>> -1u is parsed as -(1u), not as (-1)u .
>> So, adding the ``u'' does not change anything to the fact that
>> an unsigned variable is initialised with a negative number.

I think I saw a retraction of that elsethread.

> I have my doubts that this works. -(1u) produces an int,

Can you C&V that please? Precisely which promotion and/or conversion
do you think is taking place? I see an integer constant with type
unsigned int and a unary minus operator acting on it to produce
an unsigned int.

> [an int,] value
> UINT_MAX.

While that's not impossible, it's highly unlikely. (I think I
used a DSP with effectively no unsigned types once, all unsigned
stuff was just done within the positive values of signed int.)

> Assigning that to uc reduces that modulo (UCHAR_MAX +
> 1). I am not bothering to work out whether this is the same value
> (because I wouldn't use it), but I suspect it is influenced by such
> things as 1's complement or sign-mag representation, besides the
> different values of the various U*_MAX.

As above, at what point do you believe signed types, and their
representation, become involved in any way, and why?

David Brown

unread,
Mar 2, 2009, 3:26:01 AM3/2/09
to
Ben Bacarisse wrote:
> David Brown <david...@hesbynett.removethisbit.no> writes:
>
>> Ben Bacarisse wrote:
>>> David Brown <david...@hesbynett.removethisbit.no> writes:
> <snip>
>>>> Yes, embedded programmers know that "all" processors and C compilers
>>>> use twos-complement wrapping arithmetic (except when they use
>>>> saturating arithmetic...).
>>> I am a bit stumped by this. My sarcasm detector went off, but I can't
>>> see your point. If your compiler does not do standard (as in the C
>>> standard) arithmetic, then you are not writing in C but something a bit
>>> like it. Are such non-conforming compilers common in the embedded
>>> world?
>> Sometimes saturating arithmetic is used in embedded processors,
>> especially in DSPs (or DSP extensions to conventional processors).
>
> Yes, I knew that was what you were saying but it does not answer my
> question. What do some/all/most C compiler do about this? They have
> lots of options but the least appealing (to me) would be to abandon
> conformance.
>

I don't know about some/all/most DSP C compilers, as I've only had the
dubious pleasure of using a couple of them (one was okay, the other was
a pain). Very often on DSPs, you get normal wrapping twos-compliment
behaviour for standard integer operations, and only see saturating
behaviour on DSP-specific functions (such as MAC operations). The
saturation modes are typically controllable by flags or by using
specific instruction variants.

I just mentioned them because sometimes on DSPs you have to deal with
awkwardnesses that you don't get in other microcontroller programming.
Other big pains you can encounter are minimal addressable sizes bigger
than 8-bit (so "char" might be 16-bit), memory blocks that are 24-bits
wide, multiple separate memory blocks that have different pointer types,
etc. Trying to write code that is portable between such processors and
"normal" processors is not an easy job!

> <snip>


>> It will not matter if the constant used is a symbolic constant which
>> is also tied to the type in its definition. You write something like
>> "indexType i = indexTypeMax", and make sure that indexTypeMax is
>> appropriately defined depending on the size of indexType.
>
> I was commenting on your original suggestion where the type was tied
> to 0xff with no textual link. Where I need type-neutral code in C I
> would do it that way (though templates in <whisper>that other language
> </whisper> do it better).
>

There was no textual link in my earlier suggestion because the type I
used was fixed - uint8_t. That is guaranteed to be 8-bit (compilers
which can't implement an 8-bit value should not have an uint8_t defined,
thus giving a compile-time error).

I agree that templates in the language-which-shall-not-be-named can be a
more elegant way to handle such variable-type code (that's kind of what
templates are for...)

>>>>> Outside of the embedded world it can be useful to write "width"
>>>>> neutral code so that simply by changing a typedef the code can work on
>>>>> a different range or values. I've even see it used like this:
>>>>>
>>>>> typedef struct { unsigned value:WIDTH; } integer;
>>>>>
>>>>> if (x.value < (integer){-1}.value) ...
>>>>>
>>>>> although I would agree if you accused this of being a bit too
>>>>> "tricksy".
>>>>>
>>>> "Tricksy" code is often useful, but where possible such code should be
>>>> hidden through macros, constants, inline functions and the like, so
>>>> that the main code remains clear. You could write something like:
>>>>
>>>> #define maxUInt -1
>>>>
>>>> hidden away in a header file, and in the main code write:
>>>>
>>>> void foo(void) {
>>>> uint8_t x = maxUInt;
>>>> }
>>> I'd rather not have to check that the macros is correct. If I see -1
>>> being assigned, I know what is happening. I'd have no objection to a
>>> comment:
>>>
>>> uint8_t x = -1; /* max value for that unsigned type */
>>>
>> Never write something in a comment if it can be expressed equally well
>> in the language itself.
>
> Then don't comment it! I thought your point was that the assignment
> of -1 did not properly express what was happening.
>

-1 does not properly express what is happening. But "x = maxUInt"
expresses it at least as clearly as "x = -1" with a comment - it is
generally better to use the language rather than a comment.

>> You don't have to check that the macro is correct any more or less
>> than you have to check that the type definition is correct - and the
>> definitions should be in the same place.
>
> It was not clear from your example that maxUInt would be used with
> only one type. (Yes, there was only one type, but the name is very
> general and it is not linked to the name of the type unlike your later
> example.) That is why I said one might have to check. If every type
> has it's own defined max that works fine for me, though I think it is
> overkill for unsigned types (that will always be unsigned).
>

I had in fact #define'd maxUInt to be -1, so that it *would* work with
any unsigned type (it would still irritate lint, but the application
source code is clear to the reader and writer, and that is the most
important factor). If I had intended to use a width-specific max value,
I'd have called it maxUInt8, or perhaps just used the <stdint.h>
definition UINT8_MAX.

mvh.,

David

David Brown

unread,
Mar 2, 2009, 3:31:34 AM3/2/09
to

Thanks for that clarification (my Ada knowledge is basically "I read a
book once").

There is a difference here, however. For your type Unsigned_T, you are
explicitly defining a modular type - thus -1 makes sense. When using
type that is for unsigned integers, -1 does not make sense (and Ada
rejects it).


Gil Hamilton

unread,
Mar 2, 2009, 8:33:07 AM3/2/09
to
Nate Eldredge <na...@vulcan.lan> wrote in news:86eixho...@vulcan.lan:

> Gil Hamilton <gil_ha...@hotmail.com> writes:
>
>> Richard Heathfield <r...@see.sig.invalid> wrote in
>> news:y_OdneDZd7jETjTU...@bt.com:

>>> So: unsigned char l_12 = -1; is guaranteed to set l_12 to UCHAR_MAX.


>>> No question.
>>
>> It is not guaranteed. The standard does not require twos' complement
>> representation to be used.
>
> Please see 6.3.1.3 (1-2) of the C99 standard.

I stand corrected. Sorry all and thanks for the education.

GH

Joe Wright

unread,
Mar 2, 2009, 11:37:18 AM3/2/09
to
CBFalconer wrote:
> David Brown wrote:
> ... snip ...
>> So for an embedded programmer, correct code will be more like:
>>
>> "uint8_t x_12 = 0xff;"
>>
>> If you are working with code for a wider range of systems,
>> including those that can't handle 8-bit data, and you are really
>> writing code that is portable across such systems, you might want
>> something like:
>>
>> "uint_fast8_t x_12 = UINT_FAST8_MAX;"
>>
>> Of course, if you are writing such portable code, then lines like
>> this are the least of your worries - a great many uses of
>> "UCHAR_MAX" and related constants are a waste of time since the
>> rest of the code would not work if it were anything other than
>> 0xff.
>
> And it may not work if it is 0xff. Nothing forces a byte to be 8
> bits. Examine the value of CHAR_BIT. The use of -1 will reliably
> set the U*_MAX value.
>
If I remember correctly, in ones-complement, -1 would be 11111110 in
eight bits. Not UCHAR_MAX. Signed-magnitude is even stranger, 10000001 I
think. Generalities are hard to deal with, generally. I have always
lived in the twos-complement world where things work right. :-)

--
Joe Wright
"Memory is the second thing to go. I forget what the first is."

jameskuyper

unread,
Mar 2, 2009, 12:13:43 PM3/2/09
to
Joe Wright wrote:
> CBFalconer wrote:
> > David Brown wrote:
> > ... snip ...
> >> So for an embedded programmer, correct code will be more like:
> >>
> >> "uint8_t x_12 = 0xff;"
...

> > And it may not work if it is 0xff. Nothing forces a byte to be 8
> > bits. Examine the value of CHAR_BIT. The use of -1 will reliably
> > set the U*_MAX value.
> >
> If I remember correctly, in ones-complement, -1 would be 11111110 in
> eight bits. Not UCHAR_MAX.

Yes, but as has been repeatedly ben pointed out, the representation is
irrelevant; the standard defines the result of the conversion in terms
of the value, not the representation.

Keith Thompson

unread,
Mar 2, 2009, 12:32:14 PM3/2/09
to

Whether it works for unsigned char or not, it can definitely fail for
unsigned types wider than unsigned int:

#include <stdio.h>
#include <limits.h>
int main(void)
{
unsigned long long x = -1;
unsigned long long y = -1u;
printf("UINT_MAX = 0x%x\n", UINT_MAX);
printf("ULLONG_MAX = 0x%llx\n", ULLONG_MAX);
printf("x = 0x%llx\n", x);
printf("y = 0x%llx\n", y);
return 0;
}

UINT_MAX = 0xffffffff
ULLONG_MAX = 0xffffffffffffffff
x = 0xffffffffffffffff
y = 0xffffffff

The correct initialization of x depends on the conversion from a
signed type to an unsigned type. In the initialization of y, since
the initializer is already unsigned, its value is not affected by the
type of y; the initializer has type unsigned int and value UINT_MAX,
which can be converted to unsigned long long with no wraparound.

Keith Thompson

unread,
Mar 2, 2009, 12:33:57 PM3/2/09
to
[snip]

>>
>> So for modular integer types Ada has made the same choice as C: the
>> expression -1 is acceptable and yields the largest value of the type.
>>
>
> Thanks for that clarification (my Ada knowledge is basically "I read a
> book once").
>
> There is a difference here, however. For your type Unsigned_T, you
> are explicitly defining a modular type - thus -1 makes sense. When
> using type that is for unsigned integers, -1 does not make sense (and
> Ada rejects it).

Ah, but by using a C unsigned type, you *are* explicitly using a
modular type.

Mark Wooding

unread,
Mar 2, 2009, 1:14:39 PM3/2/09
to
Joe Wright <joeww...@comcast.net> writes:

> CBFalconer wrote:
>> And it may not work if it is 0xff. Nothing forces a byte to be 8
>> bits. Examine the value of CHAR_BIT. The use of -1 will reliably
>> set the U*_MAX value.
>>
> If I remember correctly, in ones-complement, -1 would be 11111110 in
> eight bits. Not UCHAR_MAX. Signed-magnitude is even stranger, 10000001
> I think.

Doesn't matter. The value is reduced modulo 2^CHAR_BIT on conversion to
unsigned char, regardless of the representation used for negative signed
chars: 6.3.1.3p2.

-- [mdw]

Albert van der Horst

unread,
Mar 2, 2009, 5:29:08 PM3/2/09
to
In article <49ab99e5$0$14900$8404...@news.wineasy.se>,

David Brown <da...@westcontrol.removethisbit.com> wrote:
>
>There is a difference here, however. For your type Unsigned_T, you are
>explicitly defining a modular type - thus -1 makes sense. When using
>type that is for unsigned integers, -1 does not make sense (and Ada
>rejects it).

So the problem is that the inventors of c didn't called them
"modular integers".
(Because the only difference is the name.)


Groetjes Albert

--
--
Albert van der Horst, UTRECHT,THE NETHERLANDS
Economic growth -- like all pyramid schemes -- ultimately falters.
albert@spe&ar&c.xs4all.nl &=n http://home.hccnet.nl/a.w.m.van.der.horst

CBFalconer

unread,
Mar 2, 2009, 7:19:00 PM3/2/09
to
David Brown wrote:
>
... snip ...
>
> There was no textual link in my earlier suggestion because the
> type I used was fixed - uint8_t. That is guaranteed to be 8-bit
> (compilers which can't implement an 8-bit value should not have
> an uint8_t defined, thus giving a compile-time error).

So why not use char, with whatever signage you want. You can check
8 bittedness in just one place with:

#if (8 != CHAR_BIT)
error("need 8 bit system");
#endif

CBFalconer

unread,
Mar 2, 2009, 7:32:02 PM3/2/09
to
Joe Wright wrote:
> CBFalconer wrote:
>
... snip ...

>
>> And it may not work if it is 0xff. Nothing forces a byte to be
>> 8 bits. Examine the value of CHAR_BIT. The use of -1 will
>> reliably set the U*_MAX value.
>
> If I remember correctly, in ones-complement, -1 would be 11111110
> in eight bits. Not UCHAR_MAX. Signed-magnitude is even stranger,
> 10000001 I think. Generalities are hard to deal with, generally.
> I have always lived in the twos-complement world where things work
> right. :-)

So what? The value used is controlled by the overflow
characteristics of unsigned, not by the representation. Read the
standard.

Some useful references about C:
<http://www.ungerhu.com/jxh/clc.welcome.txt>
<http://c-faq.com/> (C-faq)
<http://benpfaff.org/writings/clc/off-topic.html>
<http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf> (C99)
<http://cbfalconer.home.att.net/download/n869_txt.bz2> (pre-C99)
<http://www.dinkumware.com/c99.aspx> (C-library}
<http://gcc.gnu.org/onlinedocs/> (GNU docs)
<http://clc-wiki.net/wiki/C_community:comp.lang.c:Introduction>
<http://clc-wiki.net/wiki/Introduction_to_comp.lang.c>

CBFalconer

unread,
Mar 2, 2009, 7:26:01 PM3/2/09
to
Phil Carmody wrote:
> CBFalconer <cbfal...@yahoo.com> writes:
>
... snip ...

>
>> I have my doubts that this works. -(1u) produces an int,
>
> Can you C&V that please? Precisely which promotion and/or
> conversion do you think is taking place? I see an integer
> constant with type unsigned int and a unary minus operator
> acting on it to produce an unsigned int.

No, which is why I used 'doubt'. It would require following the
parsing of the expression, and ensuring that the compiler designer
did likewise. That is why I would simply avoid it.

Kaz Kylheku

unread,
Mar 2, 2009, 8:35:20 PM3/2/09
to
On 2009-03-03, CBFalconer <cbfal...@yahoo.com> wrote another Chucky special:
> #if (8 != CHAR_BIT)
> error("need 8 bit system");
> #endif

And of course error is, for instance, a function prototyped as

void error(int);

so that your compile time test actually results in some kind
of compile-time error.

Keith Thompson

unread,
Mar 2, 2009, 8:48:46 PM3/2/09
to
CBFalconer <cbfal...@yahoo.com> writes:
> Phil Carmody wrote:
>> CBFalconer <cbfal...@yahoo.com> writes:
>>
> ... snip ...
>>
>>> I have my doubts that this works. -(1u) produces an int,
>>
>> Can you C&V that please? Precisely which promotion and/or
>> conversion do you think is taking place? I see an integer
>> constant with type unsigned int and a unary minus operator
>> acting on it to produce an unsigned int.
>
> No, which is why I used 'doubt'. It would require following the
> parsing of the expression, and ensuring that the compiler designer
> did likewise. That is why I would simply avoid it.

The odds that a compiler designer would get the parsing of -(1u) wrong
are somewhere between slim and none -- closer to the latter.

Keith Thompson

unread,
Mar 2, 2009, 9:00:25 PM3/2/09
to
CBFalconer <cbfal...@yahoo.com> writes:
> David Brown wrote:
>>
> ... snip ...
>>
>> There was no textual link in my earlier suggestion because the
>> type I used was fixed - uint8_t. That is guaranteed to be 8-bit
>> (compilers which can't implement an 8-bit value should not have
>> an uint8_t defined, thus giving a compile-time error).
>
> So why not use char, with whatever signage you want. You can check
> 8 bittedness in just one place with:
>
> #if (8 != CHAR_BIT)
> error("need 8 bit system");
> #endif

Presumably you mean:

#error "need 8 bit system"

And the parentheses on the condition are unnecessary. I'd write this
as:

#if CHAR_BIT != 8
#error "CHAR_BIT != 8"
#endif

Incidentally, this will trigger the #error if the
"#include <limits.h>" is missing; the identifier CHAR_BIT will be
replaced by 0. You could add an #ifdef to check for this -- but if
you remember the #ifdef, you'll probably remember the #include.

This can get interesting if you're testing a macro whose expansion
is expected to be 0. The best example I can think of from the
standard library is:

#if EXIT_SUCCESS != 0
#error "EXIT_SUCCESS != 0"
#endif

The error message will not be triggered either if EXIT_SUCCESS is
defined as 0, or if you've forgotten the "#include <stdlib.h>". One
solution is:

#if ! defined EXIT_SUCCESS || EXIT_SUCCESS != 0
#error "EXIT_SUCCESS is undefined or non-zero"
#endif

Richard Tobin

unread,
Mar 3, 2009, 3:52:15 AM3/3/09
to
In article <49AC7919...@yahoo.com>,
CBFalconer <cbfal...@maineline.net> wrote:

>No, which is why I used 'doubt'. It would require following the
>parsing of the expression, and ensuring that the compiler designer
>did likewise. That is why I would simply avoid it.

One of the main reasons for having a C standard is so that you *don't*
have to check all these things for each compiler.

-- Richard
--
Please remember to mention me / in tapes you leave behind.

It is loading more messages.
0 new messages