Sign extending unsigned number?

Go To Last Post
76 posts / 0 new

Pages

Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

Curious, with this code:

uint8_t* buf; 
uint32_t sum = *buf++ << 8; 

Even with this code:

uint8_t* buf;
sum = ((uint8_t)*buf++) << ((uint8_t)0x08);

if buff[0] == 0xC0 shouldnt sum be 0x0000C000 the result I get is 0xFFFFC000, I have to do this to fix it:  

uint32_t sum = (uint32_t)*buf++ << 8;

But why is it sign extending what should be an unsigned number?

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This works as I would expect: 

volatile uint8_t b = 0xC0;
volatile uint32_t sum = b;

Put the shift in and it breaks it:

volatile uint8_t b = 0xC0;
volatile uint32_t sum = b << 8;

Is there no unsigned bit shift or is that a bug?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Looks like a bug, I found the issue on AVR8.

 

I checked it on SAM & UC3, these both work as expected.

 

AVR8 bit shift sign extension doesn't work correctly for unsigned numbers!

Last Edited: Sun. Dec 30, 2018 - 10:31 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Are SAM and UC3 32-bit processors?  That could explain

the difference in behavior.  Since AVR8 has 16-bit ints,

the shift operator generates a 16 bit int with the top bit

set and then extends this (negative) number to 32 bits.

 

At least that's how it appears to be working.  I can't claim

to know all the intricate details of C integer promotion

rules.

 

Your fix where you cast to a uint32_t prior to shifting is

a good solution.

 

--Mike

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yep SAM and UC3 are both 32bit

 

Looks like some chips have dedicated signed and unsigned shift instructions, either way should be more than capable on 8-bit!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

avr-mike is right about it being down to 16 bit int.

Your uint8 *buf undergoes integer promotion for the shift operation, it's promoted in this case to type int. And int is signed. And 16 bits on AVR.

So the shift gives a 16 bit signed result, which is negative (0xc000 or -16384 dec), and when you then put that negative value into uint32 you would get 2^^32 - 16384 = 0xffffc000

 

Note also that doing this shift as signed 16 bit is actually undefined for the values given since the resulting value ie. 0xc0 * 2^^8 does not fit in the range of a signed 16 bit (which is why it goes negative). So you would be best to do it as unsigned int anyway.

For example I think sum = (unsigned)*buf++ << 8 would work (the cast to unisgned int means  integer promotion doesn't change it and it remains as unsigned int , so the shift is performed as unsigned). Or (uint16_t)*buf++ << 8. Or the uint32_t cast as you have found.

 

EDIT: improve clarity hopefully

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

savman wrote:
Looks like some chips have dedicated signed and unsigned shift instructions
So does the AVR.  ASR vs LSR.

 

MrKendo wrote:
avr-mike is right about it being down to 16 bit int. Your uint8 *buf undergoes integer promotion for the shift operation, it's promoted in this case to type int. And int is signed. And 16 bits on AVR.
Yup.  Just standard C.  No bug.

 

An important lesson is to understand that the compiler performs no arithmetic at widths less than that of int.  So, even though you explicitly cast to an 8-bit type with (uint8_t), that result is promoted back to int before the shift is performed.  Same for the non-cast version, since *buf is an 8-bit type i.e. it is promoted to int.

 

You could also have:

uint8_t* buf; 
uint32_t sum = (uint16_t)(*buf++) << 8; 

This has the effect of explicitly promoting to a type of the same rank as an int, namely unsigned int (because int is 16-bit for AVR GCC), so no further promotion is needed or performed.

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I dont know much about integer promotion, I get the shift of a char has to be extended to a short but surely it should remain unsigned!

 

Does putting (unsigned) purely change it to unsigned or does that default to unsigned int?

 

That fixed it but I assume thats no different to me putting (uint32_t) or (uint16_t) if thats what the compiler extended it to.

 

I still think its a bug! Should integer promotion rules be different between 8/32 processors? its a C/C++ standard right?

 

I'm using C++ GCC by the way

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

savman wrote:
I dont know much about integer promotion, I get the shift of a char has to be extended to a short but surely it should remain unsigned!
Nope.  You need to understand the rules of promotion.  Innumerable previous discussions, e.g.:

https://www.avrfreaks.net/comment/1254241#comment-1254241

savman wrote:
I still think its a bug!
You are free to think so, but you would be wrong.  It is a clear consequence of the C standard.
savman wrote:
Should integer promotion rules be different between 8/32 processors?
It's nothing to do with the hardware, and everything to do with the language specification, and a given implementation.

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So from that links quote:

First, if the corresponding real type of either operand is long double, the other operand is converted, without change of type domain, to a type whose corresponding real type is long double.

Otherwise, if the corresponding real type of either operand is double, the other operand is converted, without change of type domain, to a type whose corresponding real type is double.

Otherwise, if the corresponding real type of either operand is float, the other operand is converted, without change of type domain, to a type whose corresponding real type is float.

Otherwise, the integer promotions are performed on both operands. Then the following rules are applied to the promoted operands:

If both operands have the same type, then no further conversion is needed.

Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank is converted to the type of the operand with greater rank.

Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.

Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, then the operand with unsigned integer type is converted to the type of the operand with signed integer type.

Otherwise, both operands are converted to the unsigned integer type corresponding to the type of the operand with signed integer type.

Yep, the 2 unsigned integers should remain unsigned and extended should it not.

 

I made sure the constant was treated as unsigned by doing this:

sum = ((uint8_t)*buf++) << ((uint8_t)0x08);

Bug!?

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Those look like the 'Usual Arithmetic Conversions' but remember

 

1) this happens AFTER integer promotion. You have to accept that a uint8_t will be promoted to a signed int

2) for shift operator it's a bit different, the type is actually derived only from the promoted left operand (this came up in another thread recently)

 

There is no bug! At least not according to the rules for C. I assume C++ rules are the same in this respect but I don't know about C++.

 

Other points. 'unsigned' and 'unsigned int' are the same thing ie. unsigned on its own is just a shorter way of saying unsigned int.

If you think all this is weird, ask yourself what do you expect to happen if you

i) shift an 8 bit value 8 times

ii) put values into types that can't hold that value

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I found some C++ documentation on integer promotions:

 

https://en.cppreference.com/w/cpp/language/implicit_conversion

 

unsigned char, char8_t (since C++20) or unsigned short can be converted to int if it can hold its entire value range, and unsigned int otherwise;

Common sense would suggest the compiler would only change it to signed, if the result was signed. given it can be changed but not required to change the sign.

prvalues of small integral types (such as char) may be converted to prvalues of larger integral types (such as int). In particular, arithmetic operators do not accept types smaller than int as arguments, and integral promotions are automatically applied after lvalue-to-rvalue conversion, if applicable. This conversion always preserves the value.

The value of C0 is not being preserved as it is being changed from 0x0000C000 to 0xFFFFC000.

 

Yes it can fit in a signed number but it does not preserve its value in doing so, given the result datatype is unsigned.

If it remained unsigned it would fit and preserve its value.

 

Does not conform to the C++ standard!?

Last Edited: Mon. Dec 31, 2018 - 02:24 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Also if it is supposed to work that way, why does ARM and UC3 work properly? they are all the same C++ compiler so same standard.

It was said previously it is not hardware related.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

savman wrote:
This conversion always preserves the value
That hasn't been violated here.

 

savman wrote:
The value of C0 is not being preserved as it is being changed from 0x0000C000 to 0xFFFFC000.

The promotion is of *buf, which is uint8_t, to int.  This preserves the original value.  The value of *buf is 0xC0.  When promoted to int, it is 0x00C0.  Same value.

 

What happens to that int afterwards is a separate matter.  The shift left by 8 alters that value to 0xC000.  Since int (in AVR GCC) is a signed 16-bit value, this represents a negative number.  This is where the problem with your original code lies.

 

When the int result of the shift is subsequently assigned to the unsigned 32-bit sum, the sign extension is performed.

 

EDIT:

savman wrote:
Also if it is supposed to work that way, why does ARM and UC3 work properly? they are all the same C++ compiler so same standard.
They most certainly are not the same compiler.

 

Those compilers implement 32-bit int.  Let's look at your original code:

uint8_t* buf;
uint32_t sum = *buf++ << 8; 

So, first *buf is promoted from the 8-bit 0xC0 to a 32-bit 0x000000C0.  Then, the shift left by 8 alters that value to 0x0000C000.  That value fits comfortably on the positive side of a 32-bit int i.e. the sign of the result has not been altered.  Subsequent assignment to the 32-bit sum proceeds without sign extension.

 

Were you to try this:

uint8_t* buf;
uint64_t sum = *buf++ << 24; 

On one of the two named compilers, I'd expect similar results as in your AVR8 example.

 

The result depends upon the order of conversion performed by the compiler.  There are two steps when converting int16_t to uint32_t:  widening, and casting.  If widening is performed first, then 0xC000 becomes 0xFFFFC000 through sign extension, and that is cast to the uint32_t.  If casting is performed first, then 0xC000 (signed) becomes 0xC000 (unsigned), and then that is widened to 0x0000C000.

 

The standard doesn't explain it this way, but it is possibly useful when trying to understand why you've seen what you're seeing.

 

The standard says it like this:

C99 6.3.1.3 Signed and unsigned integers

  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.

  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.60)

  3. Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.

Basically that means that the conversion from the negative value 0xC000 (-16,384) to an unsigned 32-bit int is carried out by adding 232 (4,294,967,296) i.e. 4,294,967,296 - 16,384 = 4 294 950 912, or 0xFFFFC000.

 

Other possibly helpful references:

https://stackoverflow.com/questions/21769068/casting-negative-integer-to-larger-unsigned-integer/21769421

https://stackoverflow.com/questions/18303682/is-uint64-t-1-guaranteed-to-yield-0xffffffffffffffff

 

/EDIT

 

EDIT2:  sp/typo

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

Last Edited: Mon. Dec 31, 2018 - 05:26 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok so you agree the integral promotion is preserving the number, but it still shouldn't be changing the sign!

 

This backs me up:

 

https://en.cppreference.com/w/cpp/language/operator_arithmetic#Conversions

For the built-in operators, lhs and rhs must both have integral or unscoped enumeration type. Integral promotions are performed on both operands.

The return type is the type of the left operand after integral promotions.

For unsigned a, the value of a << b is the value of a * 2b , reduced modulo 2N
where N is the number of bits in the return type (that is, bitwise left shift is performed and the bits that get shifted out of the destination type are discarded).

So after integral promotion should have; 0x000000C0 << 0x00000008

 

a is unsigned so; 0x000000C0 * 2 ^ 0x00000008 = 0x0000C000

 

And anything overflowing gets discarded so the result should be; 0x0000C000 % 100000000 = 0x0000C000

 

That says everything right? Straight from the standard

 

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

savman wrote:
Ok so you agree the integral promotion is preserving the number, but it still shouldn't be changing the sign!
The promotion is the act, and only the act, of converting the uint8_t 0xC0 to an int as 0x00C0.  The value is preserved.

 

The problem is with the shift to the left by 8 of that preserved value.  That shift changes 0x00C0 to 0xC000.  You want 0x00C0 to represent 192 decimal.  And you want to multiply that by 256 (shifting left by 8), for a result of 49,152.  That value cannot be represented in a signed 16-bit int.

 

The shift operator is not guaranteed to preserve value.  That's rather the point, unless you're shifting by zero bits.  The trouble is that for a signed 16-bit int, 0xC000 does not represent what you want it to represent.

savman wrote:
So after integral promotion should have; 0x000000C0 << 0x00000008 a is unsigned so; 0x000000C0 * 2 ^ 0x00000008 = 0x0000C000
That's with a promotion to int on a platform with 32-bit int (as I noted in #14, but I think you replied with #15 before I completed my edit to that).

 

This is not a bug.  It is correct behaviour according to the standard, and is by careful design.  If you want to multiply 0xC0 by 256 and have it fit into a 16-bit signed integer, I'm afraid you're going to have to move to another universe ;-)

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

Last Edited: Mon. Dec 31, 2018 - 05:27 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I struggle with why you keep mentioning signed numbers, in my example there are none, and the standard says a(uint8_t) is first promoted to the return type (uint32_t) still no signed numbers.

Therefore there should be no sign extending and the number is more than capable of fitting in a uint32_t even in a uint16_t after a shift of 8 bits.

 

The fact that its a 8-bit processor should have nothing to do with the result. If it doesn't have the hardware instruction to do the math it can emulate the 32-bit instructions.

 

The purpose in standards is to standardise results regardless of implementation, I should be getting the same result as per the C++ standard regardless of AVR8, UC3, SAM or Excel or pen and paper should I not?

 

It's a bug! ;-) Agree?

 

Your last highlight I missed sounds like more sign extending...

 

I dont want it to fit in a 16bit signed integer I want it in a 32bit unsigned integer

Last Edited: Mon. Dec 31, 2018 - 05:58 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Why are you arguing? You've been told the reason. With 32bit processors, a C int is 32 bits. With the AVR an int in gcc is 16 bits. These issues are well known to those skilled in the art as we've hit them before. Thousands of programmers had the same issue when moving from 16 bit to 32 bit code on Windows.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So this is a known bug that has been ignored going back how many years?

 

I'm not arguing I'm just going through the facts, seems people are programming around these bugs rather than acknowledging and fixing them.

 

My project is using the same code between the 3 processors and its frustrating having to change the code to workaround these bugs, when I'm supposedly programming each to the same standard. ;-)

 

 

Last Edited: Mon. Dec 31, 2018 - 06:07 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

savman wrote:

So this is a known bug that has been ignored going back how many years?

Given that it violates the Principle of Least Surprise I'd call it a poor design decision, but you can't call it a bug.

 

Quote:

My project is using the same code between the 3 processors and its frustrating having to change the code to workaround these bugs, when I'm supposedly programming each to the same standard. ;-)

The code that works via the cast to uint32_t will work on any processor.

Last Edited: Mon. Dec 31, 2018 - 06:09 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Lucky we're not talking about endianism! 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Dont worry Kartman, I've been down that track! Got it sussed the UC3 is actually quite nice to program with compared with AVR8 and SAM due to Endianism… AVR8 and SAM to me are just back to front but thats just my opinion :-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

savman wrote:
I struggle with why you keep mentioning signed numbers, in my example there are none, and the standard says a(uint8_t) is first promoted to the return type (uint32_t) still no signed numbers.
Show us where, please.

 

I'll show you where is says different:

6.3.3.1

2.

.

.

.

If an int can represent all values of the original type, the value is converted to an int;
otherwise, it is converted to an unsigned int.
These are called the integer
promotions. 48) All other types are unchanged by the integer promotions.

In other words, under AVR GCC, uint8_t is promoted to int16_t, not uint16_t.

 

This is because uint8_t is a typedef of unsigned char, and under AVR GCC unsigned char is an 8-bit type.  It's range of values is 0-255.  This fits into int, which is signed, and under AVR GCC is a 16-bit type with range -32768 through +32767.  Since all values of uint8_t can be represented in int16_t, that is the way it is promoted under AVR GCC.  This follows directly from the standard.

 

Other platforms/implementations implement unsigned char differently.  For example, TI's C compiler for their TMS320C55x family of DSPs implements unsigned char as a 16-bit type with a range of 0-65535, while it's signed in and unsigned int types are also both 16 bit types.  As such, not all values of an unsigned char can be represented in a signed int, so on that platform/implementation the promotion would turn unsigned char into an unsigned int.  Again, all following the standard, but because of the details of the implementation would lead to different results.

 

To wit: although you did not explicitly specify a signed type:

uint8_t* buf;
uint32_t sum = *buf++ << 8; 

... and indeed even though you did in fact explicitly specify an unsigned type:

uint8_t* buf;
sum = ((uint8_t)*buf++) << ((uint8_t)0x08);

... the compiler will perform no arithmetic with a type narrower than int.  Again, this is spelled out in the standard.  Therefore, your uncast *buf, and even your explicitly cast (uint8_t)*buf are promoted according to the rules of integer promotion, and that has led to the implicit conversion of *buf and even (uint8_t)*buf to 16-bit signed int.  The rest (left shifting etc.) has been spelled out for you already.  There is no mystery here.

Although I'll echo @kk6gm's suggestion that it can be surprising ;-)

 

savman wrote:
So this is a known bug that has been ignored going back how many years? I'm not arguing I'm just going through the facts, seems people are programming around these bugs rather than acknowledging and fixing them. My project is using the same code between the 3 processors and its frustrating having to change the code to workaround these bugs, when I'm supposedly programming each to the same standard. ;-)
Oy.

 

It can indeed be challenging to use the the same code base for multiple platforms/implementations.  That is why libraries like <stdint.h> were created.  When creating code which will be used across multiple implementations, it's important to understand how a standard-compliant implementation will handle things like implicit conversion.  Most of these problems can be avoided by preventing implicit conversions from ever happening with careful and consistent use of explicit casts to the types in <stdint.h>.  This will avoid the apparently ambiguous, but absolutely well defined, well understood, behaviour you have seen.

 

As mentioned, there are other pitfalls when coding across implementations, like endianness.  Search out Google's protobuf for one of the ways to mitigate that particular problem.

 

You've been given the benefit of the doubt for over twenty posts, but now it seems apparent that you're trapped by your original preconception that you've stumbled across a bug rather than a hole in your own understanding.  It's OK.  We've all been there.

 

I have to admit, this whole thread reminds me a bit of this:

https://www.youtube.com/watch?v=Q7K5LSsJN90#t=0m52s

 

#NOTABUG

 

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

Last Edited: Mon. Dec 31, 2018 - 07:06 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Sorry I'm still sticking with, It's a bug! Common sense has to prevail!

 

Forget all the standard jargon, take a glimpse of my code:

 

uint8_t* buf;
uint32_t sum = *buf++ << 8; 

Is it not obvious what i'm trying to achieve? Take a uint8_t from the buffer shift(or multiply if by 256) and save it as a uint32_t, it should be that easy!

 

Following the calculation given in the standard I showed earlier I get exactly the result I was looking for.

 

Regardless of the platforms/implementations it shouldn't be hard to achieve. Seems to me people are looking for loopholes to justify not fixing something that would make every ones life easier.

 

The AVR8 can do 32-bit addition/subtraction as per the standard and its a 8-bit processor so why have they implemented that and not fully implementing 32-bit bit-shifts?

I'm not disagreeing there might be a loopholes in the standard but thats not helpful, compilers there to make your life easier :-)

 

I have to admit, this whole thread reminds me a bit of this:

https://www.youtube.com/watch?v=Q7K5LSsJN90#t=0m52s

Agreed, I'm not holding my breath for a fix :-)

 

Last Edited: Mon. Dec 31, 2018 - 08:53 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Your main problem seems to be the refusal to accept that a uint8_t will be promoted to a signed int.

Search the internet for phrase like 'value preserving vs unsigned preserving' for some of the background.

Basically there was a choice. The C gods went with value preserving.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Common sense tells me thats the last thing I want to accept!

I wouldn't have spent all day fault finding if it didn't sign extend and worked correctly like the 32-bit variants did as per the standard.

 

I will be sticking with the (uint32_t) workaround until fixed, my concern is where else this bug is sitting dormant in my code because it will only present itself when the sign bit is set...

 

And I am now aware of the problem so, I know to correct the compiler next time I come across this issue.

 

Thanks Guys, was an interesting discussion! Learnt something new :-)

Last Edited: Mon. Dec 31, 2018 - 10:19 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thinking about your 'value preserving vs unsigned preserving' comment.

you should preserve the sign, because its just bad programming if you try putting a value greater than what can be held in a variable.

And its clearly buggy changing unsigned variables to signed as it changes the value of the variable from what it should be, corrupting code.

 

 

Last Edited: Mon. Dec 31, 2018 - 10:19 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Sorry I'm still sticking with, It's a bug! Common sense has to prevail!

 Then don't use C, if the roles don't make sense to you.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

savman wrote:
I will be sticking with the (uint32_t) workaround until fixed,

Don't hold your breath because it has been fixed already:

 

MrKendo wrote:
The C gods went with value preserving.

In the original Kernighan and Ritchie specification of C the compiler went with "unsigned preserving" promotion. This however caused even more surprise and trouble than savman has experienced. The ANSI spec rewrote the promotion rules to favour "value preserving" promotion instead.

 

This stackoverflow anwser How do promotion rules work when the signedness on either side of a binary operator differ? is an interesting read (C++ related but it matters not)

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

That is interesting, while I have the page open I had a look at basic Arithmatics:

  • If both operands are signed or both are unsigned, the operand with lesser conversion rank is converted to the operand with the greater integer conversion rank
  • Otherwise, if the unsigned operand's conversion rank is greater or equal to the conversion rank of the signed operand, the signed operand is converted to the unsigned operand's type.
  • Otherwise, if the signed operand's type can represent all values of the unsigned operand, the unsigned operand is converted to the signed operand's type
  • Otherwise, both operands are converted to the unsigned counterpart of the signed operand's type.

Signed comparisons with unsigned could be another bug source! one to be aware of!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

There’s MISRA rules for that.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

savman wrote:
Signed comparisons with unsigned could be another bug source! one to be aware of!

Indeed - there is the classical example of this:

#include <stdio.h>
int main(void)
{
    if (sizeof(int) > -1)
        printf("True");
    else
        printf("False");
}

What gets printed ?

It's "False" of course.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

savman wrote:
Signed comparisons with unsigned could be another bug source! one to be aware of!

With possibly a slight hint of irony, I think that's actually one of the main reasons the 'value preserving' rule was chosen over 'unsigned preserving' :)

 

For example.

If you have a comparison betwen an int and an unsigned char, (assume char is 8 bits and int 16 bits), if you go for 'unsigned preserving', the unsigned char promotes to unsigned int. So your original comparison is now between int and unsigned int, which according to the Usual Arithmetic Conversions means the comparison will be performed as unsigned. So you get a big surprise if your original int value was negative.

If you go for 'value preserving', the unsigned char promotes to int, so your original comparison is now between int and int, hence signed, so no surprises.

Whichever way you choose you will get some surprises. I guess it is felt that, overlall,  the 'value preserving' gives fewer surprises,

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ya know, I'm just an old guy.  The situation of OP might well be very confusing, and the "answer" of the standard-compliant operation might be disheartening.  But in the end, once the cause is found find an alternate sequence of operations and move on.  *256 for <<8 comes to mind, as the operands appear to be unsigned.  On occasion I might use an ASM fragment to do a sequence that it is hard to entice from the compiler.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:
But in the end, once the cause is found find an alternate sequence of operations and move on.

Agree, but also take the time to document why your doing this, because, trust me, you will not remember why you did this a few weeks/months from now!

 

Jim

 

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274
get $5 free gold/silver https://www.onegold.com/join/713...

 

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I wouldn't have spent all day fault finding if it didn't sign extend and worked correctly like the 32-bit variants did as per the standard.

I hope you know that you would have exactly the same problem with 32-bit only at other values.

 

If you want to test on a PC then use the simulator, or a C compiler that use 16-bit int's  .

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Just to confirm: The behavior described is, in fact, correct. It may be a "bug" in that the C specification specifies behavior that is often not what we would want, but by the time standardization was happening, it was too late to change it without breaking everything.

 

Since a signed 16-bit int can hold every value of an unsigned 8-bit int, given `uint8_t u`, the expression `u` has  type "signed int". This is possibly stupid, but it's a historical practice. Thus, `u << 8` is a signed int operation left-shifting u by 8, producing the 16-bit value 0xC000, and that's correct.

 

Any compiler with 16-bit int would be expected to produce this result. For the 32-bit int compilers, try doing this with uint64_t as the final state, and `u << 24`. You'll see the same thing.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

savman wrote:
I will be sticking with the (uint32_t) workaround until fixed
That's not a workaround.  It's how to write good code, and the only way to ensure consistent behaviour from a single codebase destined for multiple implementations, each of which has a different implementation of the native types.

 

savman wrote:
my concern is where else this bug is sitting dormant in my code because it will only present itself when the sign bit is set...
The sign bit will only become (inappropriately) set if you shift left (or otherwise embiggen) a signed quantity beyond the range of values of its storage class can represent.  This is called overflow, and is a well-understood consequence of what you've done.

 

You will need to review your code to expunge any and all implicit conversions from any and all expressions.  That's what a good programmer should do anyway, even if they were not trying to develop a one-size-fits-all codebase meant to work across multiple implementations.  There are tools to help you identify where these potential problems lie.  Lint is one of them:

https://docs.oracle.com/cd/E19205-01/819-5265/bjafs/index.html:

In enhanced mode, lint can detect these problems:

  • Unused #include directives, variables, and procedures

  • Memory usage after its deallocation

  • Unused assignments

  • Usage of a variable value before its initialization

  • Deallocation of nonallocated memory

  • Usage of pointers when writing in constant data segments

  • Nonequivalent macro redefinitions

  • Unreached code

  • Conformity of the usage of value types in unions

  • Implicit casts of actual arguments.

 

savman wrote:
I wouldn't have spent all day fault finding if it didn't sign extend and worked correctly like the 32-bit variants did as per the standard.
Again, it is in fact working correctly.  It is only that your expectations are not being met.  The problem is that your expectations don't mesh with reality.  The only reality that matters here is the C standard.

 

savman wrote:
And I am now aware of the problem so, I know to correct the compiler next time I come across this issue.
You will not be correcting the compiler.  You will be correcting flaws in your own code.

 

savman wrote:
Forget all the standard jargon
Astonishing to hear such reckless advice from a professional programmer.

 

savman wrote:
Is it not obvious what i'm trying to achieve?
To whom should it be obvious?  To me?  To the hardware?  To your boss?  To K&R?  To the standards committee?  Programming is the art and science of eliminating ambiguity.  Your incomplete understanding of the tools (i.e. language rules) at your disposal have resulted in ambiguity in your own code.  You have relied instead on the fallacy that your intent 'should be obvious'.  The trouble is that what it 'obvious' to you is not 'obvious' to everyone.  In fact, what is 'obvious' to you will very likely be 'obviously not' to somebody else.  The standard is there to protect you from this.  It does so by stipulating agreed-upon invariant behaviour in the face of ambiguity from the programmer.  Your OP is a textbook example of this.

 

savman wrote:
Regardless of the platforms/implementations it shouldn't be hard to achieve.
It isn't, if you understand the rules that form the language.  You came here with a question because you couldn't solve a problem on your own.  That's what these fora are here for.  Everyone who comes here is in the same boat.  Including me, and everyone else who has responded to you in this thread.  We've all had problems we couldn't solve on our own, and we've all learned from others and from each other.  Why are you so resistant to that possibility?  Why are you so convinced of your own infallibility?

 

savman wrote:
Seems to me people are looking for loopholes to justify not fixing something that would make every ones life easier.
What are you planning on fixing?  The implementation?  Which one?  There is nothing to fix, since all the major C compilers for AVR8 (AVR GCC, IAR Embedded Workbench, CodeVisionAVR, ImageCraft) are to-the-letter compliant with the standard set out by a massively cooperative body of experts and professionals who have debated this question (and literally thousands of others) for years, and have agreed on a solution.  Would you 'fix' the standard?  By doing what?  Reverting to the unsigned preserving legacy of early, pre-standard C?  Would you be prepared to receive the wrath of millions of programmers, project managers, and vice presidents for breaking billions of lines of exiting codebase?  I doubt the committee would be.

 

If you really so strongly believe that this is an egregious flaw in the standard, make your case to the committee.  The standard, like any language, is an evolving entity, and is continuously under review.  You may have a while to wait before you see any changes accepted.  The latest revision was published this year (C17/C18).  The last one was seven years ago (C11).  When you make your case, point them to this thread.  You can contact the committee here:

https://www.iso.org/contact-iso.html

 

savman wrote:
The AVR8 can do 32-bit addition/subtraction as per the standard and its a 8-bit processor so why have they implemented that and not fully implementing 32-bit bit-shifts?
You do understand, don't you, that AVR GCC provides an implementation of C where int is 16 bits?  It seems you lack some fundamental understanding of the language and platform you have chosen to work in.  C does not stipulate the size of int.  It only stipulates the minimum size a compliant implementation must provide.  Strange that a professional programmer would not know this.  Though you're so averse to the standard which governs the language you're using, surely you've at least visited Wikipedia:

https://en.wikipedia.org/wiki/C_data_types:

Basic types

The C language provides the four basic arithmetic type specifiers char, int, float and double, and the modifiers signed, unsigned, short and long. The following table lists the permissible combinations to specify a large set of storage size-specific declarations.

Type Explanation Format specifier
char Smallest addressable unit of the machine that can contain basic character set. It is an integer type. Actual type can be either signed or unsigned. It contains CHAR_BIT bits.[3] %c
signed char Of the same size as char, but guaranteed to be signed. Capable of containing at least the [−127, +127] range;[3][4] %c (or %hhi for numerical output)
unsigned char Of the same size as char, but guaranteed to be unsigned. Contains at least the [0, 255] range.[5] %c (or %hhu for numerical output)
short
short int
signed short
signed short int
Short signed integer type. Capable of containing at least the [−32,767, +32,767] range;[3][4] thus, it is at least 16 bits in size. The negative value is −32767 (not −32768) due to the one's-complement and sign-magnitude representations allowed by the standard, though the two's-complement representation is much more common.[6] %hi
unsigned short
unsigned short int
Short unsigned integer type. Contains at least the [0, 65,535] range;[3][4] %hu
int signed
signed int
Basic signed integer type. Capable of containing at least the [−32,767, +32,767] range;[3][4] thus, it is at least 16 bits in size. %i or %d
unsigned
unsigned int
Basic unsigned integer type. Contains at least the [0, 65,535] range;[3][4] %u
long
long int
signed long
signed long int
Long signed integer type. Capable of containing at least the [−2,147,483,647, +2,147,483,647] range;[3][4] thus, it is at least 32 bits in size. %li
unsigned long
unsigned long int
Long unsigned integer type. Capable of containing at least the [0, 4,294,967,295] range;[3][4] %lu
long long
long long int
signed long long
signed long long int
Long long signed integer type. Capable of containing at least the [−9,223,372,036,854,775,807, +9,223,372,036,854,775,807] range;[3][4] thus, it is at least 64 bits in size. Specified since the C99 version of the standard. %lli
unsigned long long
unsigned long long int
Long long unsigned integer type. Contains at least the [0, +18,446,744,073,709,551,615] range;[3][4] Specified since the C99 version of the standard. %llu

And since you've chosen AVR GCC, surely you've at least done the bare minimum of research to understand what size an int is?:

https://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_reg_usage:

  • Data types:
    char is 8 bits, int is 16 bits, long is 32 bits, long long is 64 bits, float and double are 32 bits (this is the only supported floating point format), pointers are 16 bits (function pointers are word addresses, to allow addressing up to 128K program memory space). There is a -mint8 option (see Options for the C compiler avr-gcc) to make int 8 bits, but that is not supported by avr-libc and violates C standards (int must be at least 16 bits). It may be removed in a future release.

Note that the other three major C compilers for AVR8 also implement int as a 16-bit type:

 

IAR:

http://supp.iar.com/FilesPublic/UPDINFO/007051/ew/doc/EWAVR_CompilerReference.pdf

 

CodeVisionAVR:

https://www.uni-due.de/~hl271st/Lehre/SMR/cvavr_manual.pdf

 

ImageCraft AVR:

https://imagecraft.com/help/PDF/ICCAVR.pdf

 

Curious that all of these implementations, together used for decades by millions of professionals, hobbyists, and students alike, should suffer the same fatal flaw.

 

Your continuing refusal to accept that the problem is with your understanding and not with the implementation nor the language is... entertaining.  However, it belies a stance which precludes an ability to learn.  That's a poor quality in an engineer.  You have work to do.  If you're going to use a language to express your ideas, be it a human language or a computer language, you should take the time to understand the rules of the language, the rationale behind those rules, and the work of those who came before you.  A refusal to do so, by falling back on meaningless tropes like 'common sense' is nothing less than hubris.  Another dangerous quality for an engineer.

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

Last Edited: Mon. Dec 31, 2018 - 06:56 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:

But in the end, once the cause is found find an alternate sequence of operations and move on.  *256 for <<8 comes to mind...

That's actually a good example which shows you have exactly the same problem. Given a uint8 value of 0xc0 (192), the OPs code boils down to

uint8_t x = 192;
uint32_t result = x * 256;

Anyone who does a bit of C fairly quickly gets to know that the type of result does not affect in any way the type used for the multiply operation.

So you have to think a bit about what type do you want to be used for the operation, which requires knowing about integer promotion (EDIT: and a little about the Usual Arithmetic Conversions which fortunately aren't as compliacted as it might seem from the grandiose title).

So without any casts, x is promoted to int. (EDIT: should also point out that the constant 256 has type int, so you end up with int * int therefore the operation is carried out as type int).

With 32 bit int, the result (192 * 256 = 49152) fits in range of int32_t so it works.

With 16 bit int, the result doesn't fit in range of int16_t so effectively you have signed integer overflow (which is actually undefined behaviour).

But the result does fit in range of uint16_t, so probably the obvious thing you would do is

(uint16_t)x * 256;

Alternatively, knowing that int must be a minimum of 16 bits you could do

(unsigned int)x * 256;

So really there's no difference between thinking of it as multiply or as shift.

Actually there is one difference compared to shift. With the multiply, if you wanted, you could force the operation to be carried out as type unisgned int by sticking a U on the end of 256

x * 256U;

But you can't do that with the left shift ie. sticking a U on the 8 doesn't change the type used for the shift operation

x << 8U

as described here

https://www.avrfreaks.net/commen...

 

But that's a minor point. Essentially you still have the same issue with multiply. So you just have to suck it up :)

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

savman, I don't particularly like this gotcha either.  There is no question that it is unintuitive.  Maybe the "fix" would introduce even more unintuitive behaviors, I don't know.  But it seems you have two choices.

 

1) Use proper casts to force the compiler to do what you want.

-or-

2) Complain to compiler writers, asking them to fix the "bug".  Then either get ignored or told it is not a bug.  Repeat as often as you choose.  Then use proper casts to force the compiler to do what you want.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It's really interesting how you guys think...

 

Ok I need to rap my head around this:

 

So because its a 16-bit int processor:

 

0xC0(unsigned) becomes a signed int because it fits in an int 0xFFC0

 

It's sign extended and shifted by 8 0xFFFFC000

 

Thats shifted bits and set new ones?.?..

 

Back to common sense, does a sign have anything at all to do with shifting bits? Why would you shift a sign? I chose a << over * because I wanted to shift bits not multiply numbers.

I think it makes no sense what so ever to do anything with the sign except maybe sign extend a signed number before shifting, I dont have a signed number.

why not just default the << parameters to unsigned.

 

Only do that for bit shift instructions and it wont break anything else will it?

 

https://www.iso.org/contact-iso.html

I'm so tempted!.. But I'm just a hobbyist, and I'm realistic the chances of it happening is slim

Last Edited: Mon. Dec 31, 2018 - 08:29 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sparrow2 wrote:

I hope you know that you would have exactly the same problem with 32-bit only at other values.

I would be interested to see an example of that? I'm not aware of any issues on 32-bit, I would have found them by now.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

savman, you will not likely be successful in changing

the C standard.

 

Think of it this way: you have an 8-bit number which

you want to be 32 bits.  Cast it to 32 bits first, and

then shift it. Doing so will always give you the result

you want.

 

--Mike

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I know, I settled on reality many posts back.

I'm amazed by the denial of bug/bad design(whatever you want to call it) in common sense logic in the responses.

 

It's interesting I have been using excel spreadsheets to debug C++ math logic because it's more trustworthy of giving the correct results

What does that mean for C/C++?...

 

Food for thought, This thread needs to go to sleep :-)

 

Last Edited: Mon. Dec 31, 2018 - 08:55 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

savman wrote:

It's interesting I have been using excel spreadsheets to debug C++ math logic because it's more trustworthy of giving the correct results

What does that mean for C/C++?...

Likely excel does not use single bytes to represent

numbers, and probably does a lot of work to avoid

edge cases like the one you encountered.  At a cost,

of course since it'll be slower.

 

--Mike

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

savman wrote:
So because its a 16-bit int processor: 0xC0(unsigned) becomes a signed int because it fits in an int 0xFFC0

No No No

 

The compiler says to itself:

 

OK What's this expression all about ?

*buf++ << 8;

 

  1. buf  is uint8_t*  (value unimportant here)
  2. *buf is uint8_t (value = 192 {0xC0})
  3. I have to do a logical shift left by 8 bits. I'm required to do this operation with minimum width of a signed int as 1st preference or unsigned int 2nd preference
  4. 0xC0 fits into signed int just fine (Value = 192 {0x00C0})
  5. Now to shift left (Value = -16384 {0xC000}) NB: This is where the trouble begins because it's the programmer's responsibility to handle overflow conditions

 

The final bit is the assignment to uint32_t sum

The compiler cannot fit -16384 into an unsigned integer so does it's best job and preserves the value making sum = -16384 {0xFFFFC000}

 

BTW: Writing the above has triggered a memory of some code I wrote to "assemble" a 32 bit value from 4 separate bytes - I'll dig it out and add it later.

 

Last Edited: Mon. Dec 31, 2018 - 11:35 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

savman wrote:
So because its a 16-bit int processor:
C implementation, not processor.  Hardware is not especially relevant here.  Although there are none that I'm aware of, it's possible to implement a C compiler for 8-bit AVR targets with a 32-bit int.  Likely nobody has has because there isn't much value in doing so.  Similarly, a C compiler with 16-bit int could be made for x64 hardware, but it wouldn't be especially useful.

 

N.Winterbottom wrote:

savman wrote:

So because its a 16-bit int processor: 0xC0(unsigned) becomes a signed int because it fits in an int 0xFFC0

No No No

The OP is either being willfully ignorant, or has simply not bothered to read any of the responses he has solicited.

 

savman, please use your browser's search function and look for all mentions of 0xFFC0 in this thread.  Hint:  there is only one.  Yours.  Nobody has said that 0xC0 gets promoted to 0xFFC0.  Nobody.

 

Now search for 0x00C0.  You will see promotion clearly explained in no fewer than 3 posts, starting over 30 posts ago.

 

If you're not going to put in the time to read and understand the responses others have taken the time to compose, what possible motivation can there be to expend the effort to help you?

 

Good luck with your future endeavours.  With only 'common sense' as your guide, you're likely to need it.

 

No hard feelings though.  Happy New year.  You're in New Zealand, so that was what... over 14 hours ago?  Or are you in the Chatham Islands?  I'm another 3.5 hours away from 2019, but I'll probably be unconscious when we get there...

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

Last Edited: Tue. Jan 1, 2019 - 01:28 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks N.Winterbottom for the clear explanation!

 

Sorry joeymorin I must have got confused about all the sign stuff apparently required in shifting some bits around. I wont go back and read the previous posts, as his one post explains everything! ;-)

 

Yep in New Zealand we keep things simple here, common sense is our guide, Happy New Year.

 

Sounds like I had a pretty good solution in my first post... Thanks guys

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So, you identified the defect?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:

So, you identified the defect?

Don't hold your breath ;-)

 

MrKendo wrote:

Your main problem seems to be the refusal to accept that a uint8_t will be promoted to a signed int.

Search the internet for phrase like 'value preserving vs unsigned preserving' for some of the background.

Basically there was a choice. The C gods went with value preserving.

 

Readily available:

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n802.pdf

6.2 Conversions

6.2.1 Arithmetic operands

6.2.1.1 Characters and integers

 

Since the publication of K&R, a serious divergence has occurred among implementations of C in the
evolution of integral promotion rules. Implementations fall into two major camps, which may be
characterized as unsigned preserving and value preserving. The difference between these approaches
centers on the treatment of unsigned char and unsigned short , when widened by the
integral promotions, but the decision has an impact on the typing of constants as well (see §6.1.3.2).

 

The unsigned preserving approach calls for promoting the two smaller unsigned types to unsigned
int . This is a simple rule, and yields a type which is independent of execution environment.

 

The value preserving approach calls for promoting those types to signed int , if that type can
properly represent all the values of the original type, and otherwise for promoting those types to
unsigned int . Thus, if the execution environment represents short as something smaller than
int , unsigned short becomes int ; otherwise it becomes unsigned int .

 

Both schemes give the same answer in the vast majority of cases, and both give the same effective
result in even more cases in implementations with twos-complement arithmetic and quiet wraparound
on signed overflow - that is, in most current implementations. In such implementations, differences
between the two only appear when these two conditions are both true:

 

1. An expression involving an unsigned char or unsigned short produces an
int -wide result in which the sign bit is set: i.e., either a unary operation on such a
type, or a binary operation in which the other operand is an int or "narrower'' type.

 

2. The result of the preceding expression is used in a context in which its signedness is
significant:

 

• sizeof(int) < sizeof(long) and it is in a context where it must be
widened to a long type, or

 

• it is the left operand of the right-shift operator (in an implementation where this
shift is defined as arithmetic), or

 

• it is either operand of / , % , < , <= , > , or >= .

 

In such circumstances a genuine ambiguity of interpretation arises. The result must be dubbed
questionably signed, since a case can be made for either the signed or unsigned interpretation. Exactly
the same ambiguity arises whenever an unsigned int confronts a signed int across an
operator, and the signed int has a negative value. (Neither scheme does any better, or any worse,
in resolving the ambiguity of this confrontation.) Suddenly, the negative signed int becomes a very
large unsigned int , which may be surprising - or it may be exactly what is desired by a
knowledgeable programmer. Of course, all of these ambiguities can be avoided by a judicious use of
casts.

 

One of the important outcomes of exploring this problem is the understanding that high-quality
compilers might do well to look for such questionable code and offer (optional) diagnostics, and that
conscientious instructors might do well to warn programmers of the problems of implicit type
conversions.

 

The unsigned preserving rules greatly increase the number of situations where unsigned int
confronts signed int to yield a questionably signed result, whereas the value preserving rules
minimize such confrontations. Thus, the value preserving rules were considered to be safer for the
novice, or unwary, programmer. After much discussion, the Committee decided in favor of value
preserving rules, despite the fact that the UNIX C compilers had evolved in the direction of unsigned
preserving.

 

QUIET CHANGE

 

A program that depends upon unsigned preserving arithmetic conversions will behave
differently, probably without complaint. This is considered the most serious semantic
change made by the Committee to a widespread current practice.

 

The Standard clarifies that the integral promotion rules also apply to bit-fields.

 

6.2.1.2 Signed and unsigned integers

 

Precise rules are now provided for converting to and from unsigned integers. On a twos-complement
machine, the operation is still virtual (no change of representation is required), but the rules are now
stated independent of representation.

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

Last Edited: Tue. Jan 1, 2019 - 04:58 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Of course, all of these ambiguities can be avoided by a judicious use of casts.

I'd call that the takeaway for this entire thread.

Pages