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

C Comparison Question

62 views
Skip to first unread message

David T. Ashley

unread,
May 17, 2012, 5:48:02 PM5/17/12
to
I had the following code on an old ARM compiler:

unsigned char *a;
unsigned char *b;

...

if (*a != (~(*b)))

and when I looked at the assembly-language, it was putting *b into a
32-bit register, complementing the entire register, then comparing
against *a which was in a second 32-bit register.

The upper 24 bits of *b were always set to 1 because they started as
0.

This wouldn't work as I intended it, because even if *a and *b were
complements, the test would fail.

Question: in the comparison above, is the compiler allowed to extend
*b to 32 bits, complement the 32 bits, extend *a to 32 bits, then
compare the two 32-bit values?

Or should the compiler be comparing only 8 bits?

Thanks, DTA

Lanarcam

unread,
May 17, 2012, 5:57:39 PM5/17/12
to
I remember having asked a question about that a long
time ago and the answer was that operations in C are
done by extending arithetic types to int first.

One solution is to write:

if ( (uint8_t) *a != (uint8_t) (~(*b)))

>
> Or should the compiler be comparing only 8 bits?
>
Not unless you specify it, IMO.

James Kuyper

unread,
May 17, 2012, 6:15:12 PM5/17/12
to
*b is subject to the standard integer promotions before evaluating
~(*b). If UCHAR_MAX > INT_MAX (which cannot happen if unsigned char is
8 bits on your system) that means that *b will be converted to an
unsigned int. Otherwise it will be converted to int. The same conversion
will also be applied to *a. All of the rest of the operations will be
evaluated in that same type. Either way, if int is 32 bits, then the
code you've written is being implemented correctly.

You could mask off the bits you're interested in to get the desired result:

if((0XFF & *a) != (0XFF & ~*b))

Barry Schwarz

unread,
May 17, 2012, 6:21:05 PM5/17/12
to
If I'm reading 6.3.1.1-2 properly, any operand with rank less than int
must undergo integer promotion to either int or unsigned int. This
appears to match what you have described. It appears that not only is
the compiler allowed to do this, it is REQUIRED to.

--
Remove del for email

Kaz Kylheku

unread,
May 17, 2012, 7:44:02 PM5/17/12
to
On 2012-05-17, David T Ashley <das...@gmail.com> wrote:
> I had the following code on an old ARM compiler:
>
> unsigned char *a;
> unsigned char *b;
>
> ...
>
> if (*a != (~(*b)))
>
> and when I looked at the assembly-language, it was putting *b into a
> 32-bit register, complementing the entire register, then comparing
> against *a which was in a second 32-bit register.

This is correct behavior and it follows from the C "default promotion"
rules. You cannot actually do arithmetic on an unsigned char type in C;
it gets convert to int before you can touch it, so to speak.
(Or, rarely, unsigned int, on compilers where sizeof int == 1,
and so int cannot represent all of the values of unsigned char).

The proper way to complement the bits in a value which came from an
unsigned char is to XOR with UCHAR_MAX:

*a != (*b ^ UCHAR_MAX)

--
If you ever need any coding done, I'm your goto man!

Kaz Kylheku

unread,
May 17, 2012, 7:46:23 PM5/17/12
to
Or, if you are really interested in just working with 8 bits, and not the
full width of the unsigned char, use 0xFF rather than UCHAR_MAX.

Eric Sosman

unread,
May 17, 2012, 9:01:08 PM5/17/12
to
The operands of `!=' and the operand of `~' are subject to the
"usual arithmetic conversions" (section 6.3.1.8 of the Standard).
When no floating-point is involved, these conversions involve the
"integer promotions" (6.3.1.1). It's the latter that you're seeing:
On your machine an `unsigned char' value promotes to some 32-bit
type (most likely an `int'), and the operations actually take place
on the promoted type.

Okay, this may seem niggly and arbitrary and obfuscatory, but
the idea is to define C arithmetic for lots of types and type
combinations that the hardware can't manage natively. For example,
few machines have hardware that can compare a `double' to an `int'
directly: You can't compare 42.0 and 42 as they stand, but must
begin by reconciling their differing types. This inability extends
to integer types, too: Few machines have instructions that do
arithmetic on five-bit integer bit-fields, so C does not require
them to do so. Instead, the ungainly types are promoted to a form
the CPU finds more convenient, the arithmetic is done in the
promoted type, and then (maybe) the result is down-converted to
a five-bit field again.

If the machine can do arithmetic on oddball types, the compiler
is permitted to use its special capabilities -- as long as the final
result is the same as would have been achieved by going through the
whole conversion/promotion rigamarole. In your case, I think a
sufficiently smart compiler might have eliminated the test altogether,
knowing that the high-order zeroes of `*a' could not possibly equal
the high-order ones of `~*b', so the outcome is foreordained: The
`!=' comparison will always yield true.

For the case at hand, I suggest you try some variant on

if ((*a ^ *b) != 0)

... which (1) works as you desire even in the face of promotions,
and (2) may give the compiler more opportunity to take shortcuts
if it feels they are warranted.


--
Eric Sosman
eso...@ieee-dot-org.invalid

Heinrich Wolf

unread,
May 18, 2012, 5:04:16 AM5/18/12
to

"David T. Ashley" <das...@gmail.com> schrieb im Newsbeitrag
news:h8sar7l63tdelnkso...@4ax.com...
I tested with my very old Turbo C 2.0 and with my Borland C++Builder 5.

File NotCmp.c (no C++)

#include <stdio.h>

int main(void)
{
unsigned char ba = 0x0f;
unsigned char bb = 0xf0;
unsigned char *a = &ba;
unsigned char *b = &bb;
char sba = 0x0f;
char sbb = 0xf0;
char *sa = &sba;
char *sb = &sbb;
char s[2];

if (*a != (~(*b)))
puts("unsigned true");
else
puts("unsigned false");
if (*sa != (~(*sb)))
puts("signed true");
else
puts("signed false");
fgets(s, sizeof(s), stdin);
return 0;
}

Turbo C 2.0 prints
unsigned false
signed false

Borland C++Builder 5 prints
unsigned true
signed false

So Turbo C 2.0 does not seem to make a promotion to integer bit width. And
using signed char preserves the complement when promoting.

David T. Ashley

unread,
May 18, 2012, 8:44:49 AM5/18/12
to
On Thu, 17 May 2012 21:01:08 -0400, Eric Sosman
<eso...@ieee-dot-org.invalid> wrote:
> If the machine can do arithmetic on oddball types, the compiler
>is permitted to use its special capabilities -- as long as the final
>result is the same as would have been achieved by going through the
>whole conversion/promotion rigamarole. In your case, I think a
>sufficiently smart compiler might have eliminated the test altogether,
>knowing that the high-order zeroes of `*a' could not possibly equal
>the high-order ones of `~*b', so the outcome is foreordained: The
>`!=' comparison will always yield true.

That caught my eye as well. I did notice that the test can't be false
for any value of the arguments.

> For the case at hand, I suggest you try some variant on
>
> if ((*a ^ *b) != 0)
>
>... which (1) works as you desire even in the face of promotions,
>and (2) may give the compiler more opportunity to take shortcuts
>if it feels they are warranted.

That is ingenious and may be the best solution.

With the ARM, the same instruction that plucks the bytes *a or *b from
memory into a 32-bit register also guarantees that the upper 24 bits
are clear. So the compiler doesn't need to insert any additional
adjustment on the load.

I assume that the ARM has an XOR instruction, so it would just XOR the
bytes loaded into 32-bit registers.

Here are the results.

EXPERIMENT #1

;;;723 if ((unsigned char)(*a) != (unsigned char)(~(*b)))

0001b2 7828 LDRB r0,[r5,#0] ;723
0001b4 7827 LDRB r7,[r4,#0] ;723
0001b6 43ff MVNS r7,r7 ;723
0001b8 b2ff UXTB r7,r7 ;723
0001ba 42b8 CMP r0,r7 ;723
0001bc d001 BEQ |L1.450|

EXPERIMENT #2

;;;715 if (*a != ((~(*b)) & 0x000000FF))

0001b2 7828 LDRB r0,[r5,#0] ;715
0001b4 7827 LDRB r7,[r4,#0] ;715
0001b6 43ff MVNS r7,r7 ;715
0001b8 b2ff UXTB r7,r7 ;715
0001ba 42b8 CMP r0,r7 ;715
0001bc d001 BEQ |L1.450|

EXPERIMENT #3

;;;715 if (*a ^ *b)

0001b2 7820 LDRB r0,[r4,#0] ;715
0001b4 782f LDRB r7,[r5,#0] ;715
0001b6 4078 EORS r0,r0,r7 ;715
0001b8 d001 BEQ |L1.446|

Your method saved two instructions.

Clearly, you've had training in the teachings of Mr. Boole and Mr.
Karnaugh ...

Thanks for the clarification and implementation suggestion.

Dave A.

David T. Ashley

unread,
May 18, 2012, 9:58:10 AM5/18/12
to
On Fri, 18 May 2012 08:44:49 -0400, David T. Ashley
<das...@gmail.com> wrote:
>
>EXPERIMENT #2
>
>;;;715 if (*a != ((~(*b)) & 0x000000FF))
>
>0001b2 7828 LDRB r0,[r5,#0] ;715
>0001b4 7827 LDRB r7,[r4,#0] ;715
>0001b6 43ff MVNS r7,r7 ;715
>0001b8 b2ff UXTB r7,r7 ;715
>0001ba 42b8 CMP r0,r7 ;715
>0001bc d001 BEQ |L1.450|
>
>EXPERIMENT #3
>
>;;;715 if (*a ^ *b)
>
>0001b2 7820 LDRB r0,[r4,#0] ;715
>0001b4 782f LDRB r7,[r5,#0] ;715
>0001b6 4078 EORS r0,r0,r7 ;715
>0001b8 d001 BEQ |L1.446|

Oops, I had a moment of weak thinking.

The original test,

if (*a != (~(*b)))

is really equivalent to

if ((*a ^ *b) != (unsigned)-1)

which leads to:

0001b2 7820 LDRB r0,[r4,#0] ;715
0001b4 782f LDRB r7,[r5,#0] ;715
0001b6 4078 EORS r0,r0,r7 ;715
0001b8 1c40 ADDS r0,r0,#1 ;715
0001ba d001 BEQ |L1.448|

So, an increment is still required there, for a savings of one
instruction.

I welcome any further suggestions ...

DTA

David T. Ashley

unread,
May 18, 2012, 10:02:09 AM5/18/12
to
On Fri, 18 May 2012 09:58:10 -0400, David T. Ashley
Oops, I'm really losing it mentally.

Make that:

if ((*a ^ *b) != 0xFF)

0001b2 7820 LDRB r0,[r4,#0] ;715
0001b4 782f LDRB r7,[r5,#0] ;715
0001b6 4078 EORS r0,r0,r7 ;715
0001b8 28ff CMP r0,#0xff ;715
0001ba d001 BEQ |L1.448|

Still respectable.

I'm 49 and the neurons are really misfiring today.

DTA

Ike Naar

unread,
May 18, 2012, 10:18:29 AM5/18/12
to
On 2012-05-18, David T Ashley <das...@gmail.com> wrote:
> The original test,
>
> if (*a != (~(*b)))
>
> is really equivalent to
>
> if ((*a ^ *b) != (unsigned)-1)

For unsigned char a and b, shouldn't that be

if ((*a ^ *b) != (unsigned char) -1)

?

David T. Ashley

unread,
May 18, 2012, 10:31:26 AM5/18/12
to
Thanks, you are correct.

We had a midair collision of our posts ... I caught that at about the
same time you did.

I'm too old. I need to retire from the software game.

I think it was G.H. Hardy who wrote in "A Mathematician's Apology"
that "mathematics is primarily a young man's game". Perhaps the same
is true of software.

http://en.wikipedia.org/wiki/A_Mathematician's_Apology#Summary

(Last paragraph of the summary alludes to that.)

DTA

Tim Rentsch

unread,
May 18, 2012, 2:26:55 PM5/18/12
to
David T. Ashley <das...@gmail.com> writes:

> I had the following code on an old ARM compiler:
>
> unsigned char *a;
> unsigned char *b;
>
> ...
>
> if (*a != (~(*b)))
>
> and when I looked at the assembly-language, it was putting *b into a
> 32-bit register, complementing the entire register, then comparing
> against *a which was in a second 32-bit register. [snip]

You've gotten answers already on why this code is compiled the
way it is.

The comparison I think you want to make is one that has the same
semantics as

if( *a != (~*b & UCHAR_MAX) )

or equivalently

if( *a != (~*b & (unsigned char)-1) )

ie, you want to compare *a and ~*b, but limited to the width of
the unsigned char datatype.

Assuming that is so, you could instead write any of these:

if( *a != (unsigned char) ~*b )

if( *a != (*b ^ UCHAR_MAX) )

if( (*a ^ *b) != UCHAR_MAX )

if( *a != UCHAR_MAX - *b )

if( *a + *b != UCHAR_MAX )

if( UCHAR_MAX - *a - *b != 0 )

if( UCHAR_MAX - (*a + *b) != 0 )

if( (UCHAR_MAX ^ *a ^ *b) != 0 )

if( UCHAR_MAX ^ *a ^ *b )

and get the same result. (Note: theoretically it is possible
for the expression '*a + *b' to provoke signed integer overflow,
ie, undefined behavior, but in practical terms there shouldn't
be any concern about it. All the others are safe.)

Hopefully one of these will do the trick for you.
0 new messages