1

I have 2 uint16_t's that i want to combine into 1 32 bit number:

uint16_t var1 = 255; // 0000 0000 1111 1111
uint16_t var2 = 255; // 0000 0000 1111 1111

uint32_t var3 = (var1 << 16) +  var2;

I expect var3 to be 0000 0000 1111 1111 0000 0000 1111 1111... so 16711935 in decimal but i get 255 ( 0000 0000 0000 0000 0000 0000 1111 1111).

Any ideas?

Thanks

2 Answers2

3

When an integer type is being shifted, both operands are promoted first and the resulting integer has the same type of the promoted left operand.

Compiler will first try to promote uint16_t to int and if an int can't hold the value (i.e. the value is larger than INT_MAX) it will be promoted to unsigned int. Now if your system uses 16-bit ints the result will still be 16 bits and hence your most significant bits in the case of a left shift will be lost as long as your shift value is less than 16, from which point the behavior is undefined by standard.

In that kind of a system, you need to first cast to a wider integer type like uint32_t or uint64_t and then left shift. And to be on the safe side, we can always cast the left operand of a shift to the expected type so that our code is not affected by implementation decisions made by compiler designer such as bit widths.

m0h4mm4d
  • 400
  • 4
  • 12
  • Close. Shifting a 16bit value by more than 15 places is actually *undefined* :) –  Jul 27 '17 at 15:44
  • 1
    That would explain the observed result, but it's simply not true. The standard says, "The integer promotions are performed on each of the operands. The type of the result is that of the promoted left operand." ([C2011 6.5.7/3](http://port70.net/~nsz/c/c11/n1570.html#6.5.7p3)). – John Bollinger Jul 27 '17 at 15:44
  • 2
    @JohnBollinger That seems to imply a 16 bit `int` on OP's platform. – dbush Jul 27 '17 at 15:45
  • @JohnBollinger the promotion occurs after both left shift and addition. – m0h4mm4d Jul 27 '17 at 15:46
  • Yes, @dbush. I was just about to expand my comment to say the same thing. – John Bollinger Jul 27 '17 at 15:46
  • 1
    No, @m0h4mm4d. The standard is pretty clear here. The integer promotions are performed *on the operands*, before the operation is performed. This is the case for most arithmetic operations on integer types, and it is a subset of "the usual arithmetic conversions" that are performed on the operands of arithmetic operators that accept both integer and real types. – John Bollinger Jul 27 '17 at 15:47
  • @JohnBollinger there are 3 operations involved. first a left shift is performed and since the operand is `uint16_t` the result in `uint16_t`. then an addition, and since both operands are `uint16_t` again no promotion is required and the result is still `uint16_t` and finally an assignment but since the destination is `uint32_t` the previous `uint16_t` result will be promoted to `uin32_t`. – m0h4mm4d Jul 27 '17 at 15:51
  • 2
    This answer isn't "*wrong enough*" to deserve a downvote, IMHO. (But wrong enough to prevent me from upvoting). Yes, OP must have 16bit `int` for this effect. And then, all bits zero after `<< 16` is just *one* possible result. –  Jul 27 '17 at 15:52
  • @m0h4mm4d your comments are **very** wrong, promotions are done on the operands of any (sub-)expression. –  Jul 27 '17 at 15:54
  • @JohnBollinger From standard, the one you linked to, about shift it says, "The type of the result is that of the promoted left operand." – m0h4mm4d Jul 27 '17 at 15:55
  • 2
    @m0h4mm4d, your assertions directly contradict the behavior specified by the C standard, which I have both quoted and linked. The operands of the left shift operator are subject to the integer promotions; the operation is performed on the promoted values; and the result of the operation has the type of the promoted left operand. I am not disputing that the promoted type may indeed be `uint16_t` in the OP's case; rather, I am objecting to your blanket statement that the result has the same type as the left-hand operand. – John Bollinger Jul 27 '17 at 15:57
  • @JohnBollinger I get it now. You are right. You were saying that the resut is not of the same type as the left operand but rather of the type that corresponds to its promoted value. – m0h4mm4d Jul 27 '17 at 15:58
  • It is better to have an unnecessary cast instead of not having the important one. (the only reason I can imagine that one cant afford a new keyboard and is saving the one he has) – 0___________ Jul 27 '17 at 15:59
  • 1
    @PeterJ applied in general, this would be a very bad rule. Casts hide compiler warnings. Always **avoid** them where not needed. –  Jul 27 '17 at 16:11
  • "When an integer type is being shifted, the resulting integer has the same type as the original integer …" - That's plain wrong. – too honest for this site Jul 27 '17 at 16:29
  • @Olaf Yeah, I already updated the answer, but thanks for the comment. – m0h4mm4d Jul 27 '17 at 16:30
  • 1
    @m0h4mm4d: you are free (and even encouraged) to edit your answer to be correct. The comments are there to point out what's wrong, that's all. Make sure to include that this behavior can be explained when `int` has 16 bits and that in this case, the shift is *undefined behavior* (but *might* yield a 0 result). –  Jul 27 '17 at 16:31
  • @FelixPalmen "Shifting a 16bit value by more than 15 places is actually undefined" . Not if `int` or `unsigned` have more bits than the shift-count. – too honest for this site Jul 27 '17 at 16:31
  • @Olaf we've been through this, this is all assuming 16bit `int` (because it's somewhat obvious from what OP observed). But welcome to the discussion. –  Jul 27 '17 at 16:31
  • @Olaf and then, my statement is even correct in general ... if the value is first promoted to e.g. 32 bits, you no longer "*shift a 16bit value*". –  Jul 27 '17 at 16:32
  • @FelixPalmen: Literally, you are right (and as some also meaning it literally I should have noticed indeed). But beginners tend to missunderstand this as m0h4mm4d seems to. – too honest for this site Jul 27 '17 at 16:34
  • 1
    @m0h4mm4d Please read the **complete** paragraph of the standard: 6.5.7p3: "The integer promotions are performed on **each of the operands**. The type of the result is that of the promoted left operand. " For other operators, the usual arithmetic/integer _conversions_ are performed to find the common type; this is not necessary for shifts, as the count is not directly combined with the left operator. – too honest for this site Jul 27 '17 at 16:37
  • @m0h4mm4d upvoted, but "*your most significant bits in the case of a left shift will be lost.*" still isn't entirely correct. shifting an `n` bit value by more than `n-1` places is *undefined behavior*. Some implementations will yield the original value (like no shift is done at all), some will yield `0` (as observed here) and there could be anything else happening as well. –  Jul 27 '17 at 16:46
  • @m0h4mm4d It still is not correct (luckly, because then even smaller shifts would invoke UB). There also is no casting to `unit64_t` necessary if a 32 bit result is required. And on a 32 bits system (i.e. 32 bit `int`), the cast is necessary, too for exactly the reason to avoid UB for certain values. – too honest for this site Jul 27 '17 at 16:47
  • 1
    @m0h4mm4d and then, there's no need to hint about the edit. An answer on SO should be just that: an answer. Comments to help you correct errors in your answer aren't uncommon ;) –  Jul 27 '17 at 16:49
  • @FelixPalmen I didn't mean all of them. Or am I misunderstanding you, if you left shift say by 1, then the most significant bit will not be lost on a system where `int`s are 16 bit? – m0h4mm4d Jul 27 '17 at 16:50
  • @m0h4mm4d this is true (the MSB is shifted out for a shift by one), but as soon as you "shift out all the bits", the C standard says this is undefined. So there are implementations that just do *nothing at all* when shifting an `n` bit value by `n` places. For *undefined behavior*, every result is "fine", because it's undefined. –  Jul 27 '17 at 16:51
  • @FelixPalmen Yeah, I kinda got that from reading the standard, but I didn't mean and didn't mention all of them, yet I'll try to make it more clear. Thanks. – m0h4mm4d Jul 27 '17 at 16:53
  • 1
    @m0h4mm4d it looks correct and exhaustive now and should IMHO have a higher score. Well, that's how fast it can happen that you need to explain things like *integer promotion* to *really* explain a "seemingly simple" effect in C ;) –  Jul 27 '17 at 17:06
  • @FelixPalmen You are absolutely right. Thanks for all the help and info. – m0h4mm4d Jul 27 '17 at 17:08
  • 1
    @m0h4mm4d: 1) On the assumed system, `uint16_t` is **not** promoted to `int`. 2) Shifting an `int` left will invoke undefgined behaviour for certain values/shift counts. It seems like you eventually got it right now. I'll remove the DV. You should correct the part about casting, though. In general, the left operand should be casted to the expected result type. This is independent of the internal bitwidth. – too honest for this site Jul 27 '17 at 17:16
3

To some extent, this is platform-dependent. On my nearest system, we get the expected results, which can be demonstrated with a short program:

#include <stdint.h>
#include <inttypes.h>
#include <stdio.h>

int main()
{
    uint16_t var1 = 255; // 0000 0000 1111 1111
    uint16_t var2 = 255; // 0000 0000 1111 1111

    uint32_t var3 = (var1 << 16) +  var2;

    printf("%#"PRIx32"\n", var3);
}

Output is 0xff00ff.

However, your var1 and var2 undergo the normal integer promotions before any arithmetic. If the promoted types can't hold the intermediate result, part of the calculation can be lost, as you see.

You can avoid the problem by explicitly widening var1 before the arithmetic:

    uint32_t var3 = ((uint32_t)var1 << 16) +  var2;

The equivalent failing program on my system is:

#include <stdint.h>
#include <inttypes.h>
#include <stdio.h>

int main()
{
    uint16_t var1 = 255; // 00ff
    uint16_t var2 = 255; // 00ff

    uint64_t var3 = ((uint64_t)var1 << 32) +  var2;

    printf("%#"PRIx64"\n", var3);
}

This produces 0x1fe instead of 0xff000000ff if I don't widen var1 with the cast as shown (because on this system, <<32 happens to be a no-op with 32-bit unsigned types).

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
  • many thanks for this, however; how might this be achieved when the destination type is a malloc'd piece of memory `uint32_t * buff; buff = malloc(232); *buff = (( ? )var1 << 16 + var2;` Would this involve creating a custom type? –  Jul 28 '17 at 10:18
  • Makes no difference - `*buff` is a `uint32_t` just as `var3` was, so you'll want to widen `var1` to `uint32_t` before you shift. And did you really `malloc` a literal number, rather than `buff = malloc(count * sizeof *buff)`? – Toby Speight Jul 28 '17 at 10:20
  • ok, but this would not be possible if i wanted to fill all 232 bytes with data, or anything beyond 32 bits for that matter: `*buff = ((uint32_t)var1 << 64 +( var1 << 48) + (var1 << 32) + (var1 << 16) + var1;` –  Jul 28 '17 at 10:28
  • That sounds like the beginnings of a whole new question! – Toby Speight Jul 28 '17 at 12:05