3

I was messing about with arrays and noticed this. EG:

int32_t array[];
int16_t value = -4000;

When I tried to write the value into the top and bottom half of the int32 array value,

array[0] = (value << 16) | value;

the compiler would cast the value into a 32 bit value first before the doing the bit shift and the bitwise OR. Thus, instead of 16 bit -4000 being written in the top and bottom halves, the top value will be -1 and the bottom will be -4000.

Is there a way to OR in the 16 bit value of -4000 so both halves are -4000? It's not really a huge problem. I am just curious to know if it can be done.

Morilli
  • 23
  • 1
  • 6
  • The compiler did not "cast" the value; it **converted** it. A cast is something you write in your source code to tell the compiler to do a conversion. That's called an **explicit conversion**. The compiler can also do some conversions without the need for a cast. That's called an **implicit conversion**. – Pete Becker Jan 04 '16 at 15:09

5 Answers5

3

Left shift on signed types is defined only in some cases. From standard

6.5.7/4 [...] If E1 has a signed type and nonnegative value, and E1 × 2E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.

According to this definition, it seems what you have is undefined behaviour.

Giorgi Moniava
  • 27,046
  • 9
  • 53
  • 90
3

Sure thing, just undo the sign-extension:

array[0] = (value << 16) | (value & 0xFFFF);

Don't worry, the compiler should handle this reasonably.

To avoid shifting a negative number:

array[0] = ((value & 0xFFFF) << 16) | (value & 0xFFFF);

Fortunately that extra useless & (even more of a NOP than the one on the right) doesn't show up in the code.

harold
  • 61,398
  • 6
  • 86
  • 164
  • You should avoid the use of link shorteners: http://meta.stackoverflow.com/q/313621/1025391 – moooeeeep Jan 04 '16 at 11:25
  • @moooeeeep wow thanks meta.. fine I'll edit in huge-ass links – harold Jan 04 '16 at 11:29
  • Instead of adding more bitwise operations on signed integer types, it would be more efficient and safer to first cast the signed operand to an unsigned integer type of rank equal to or greater than `unsigned int`, e.g. `uint32_t`, avoiding common pitfalls of integer promotion & conversion by being explicit. `array[0] = (int32_t)( ((uint32_t)value << 16) | (uint32_t)value );` Since arithmetic operations can't be performed on types smaller than `int` in C, it is good practice to always be explicit with integer casts. – sendaran Jan 05 '16 at 11:15
  • @sendaran except that doesn't work. So how much safer is it really? That will still sign-extend. – harold Jan 05 '16 at 14:38
  • @harold Sorry, I got distracted by other stuff and made a mistake. Intermediate cast to unsigned type of same size is required to avoid the sign extension if the signed type is lower rank than `int`, so `array[0] = (int32_t)( ((uint32_t)(uint16_t)value << 16) | (uint32_t)(uint16_t)value );` C can get quite verbose at some seemingly simple stuff if you want to be safe and explicit, and not rely on compiler optimization. There was a related example on MISRA C forum, but I can't seem to find it now. – sendaran Jan 05 '16 at 15:29
2

use unsigned value:

const uint16_t uvalue = value
array[0] = (uvalue << 16) | uvalue;
Jarod42
  • 203,559
  • 14
  • 181
  • 302
1

Normally when faced with this kind of issue I first set the resultant value to zero, then bitwise assign the values in.

So the code would be:

int32_t array[1];
int16_t value = -4000;
array[0] = 0x0000FFFF;
array[0] &= value;
array[0] |= (value << 16);
Vality
  • 6,577
  • 3
  • 27
  • 48
  • You still have problem, as `value` is promote to `int` with the problematic `0xFFFF` – Jarod42 Jan 04 '16 at 11:26
  • @Jarod42 True, that is an issue actually. Now I think about it a bitwise and would be more sane for the latter assignment, lemme fix the code – Vality Jan 04 '16 at 11:31
  • @Jarod42 Would that fix the issue, I think so but a second pair of eyes would help – Vality Jan 04 '16 at 11:35
0

Cast the 16 too. Otherwise the int type is contagious.

array[0] = (value << (int16_t 16)) | value;

Edit: I don't have a compiler handy to test. Per comment below, this may not be right, but will get you in the right direction.

David Goldfarb
  • 1,796
  • 3
  • 18
  • 32