2

The following derives from this question but is distinct

Consider the following code sample;

uint32_t *value = malloc(sizeof(uint32_t));
*value = 0xAAAABBBB;

int16_t *subset = (int16_t*) (value);

uint32_t test = 0x00000000;
uint32_t test2 = 0x00000000;
// IMPORTANT!!!!
// *subset vs *subset & 0xFFFF
test = (*subset << 16) | *subset & 0xFFFF;
test2 = (*subset << 16) | *subset;

// Test: 0xbbbb bbbb
// Test2: 0xffff bbbb

As you can see, subset is only 16 bits wide. As such (and as commenters on the previous post stated_, & 0xFFFF is redundant since the type is only 16 bit.

This is incorrect and when tested on my compiler (gcc 15.2.1 on Linux AND gcc for noabi arm), the & operator acts outside the bitlength of *subset to the bit length of the literal 0xFFFF (which I believe from the answers to the previous question is 32), thus acting on bits which are -16 from the address of subset.

Is this a bug in GCC or is this within spec? Also why are first 16 bits FFFF?

Note: My system is little endian

3
  • 2
    Pretty sure subset gets promoted to a 32-bit int in the shift left operation. I'm going to stand by . . . an expert should be along shortly. :-) Commented Oct 1 at 2:30
  • Why don't you actually read the answers posted to your last question. This has the same bugs once more, except you have now added even more bugs on top. Commented Oct 1 at 6:48
  • I'll close this as a duplicate to the previous question then link an additional duplicate FAQ for the new bugs added. You can also check out Implicit type promotion rules Commented Oct 1 at 6:51

3 Answers 3

8

You've got multiple instances of undefined behavior here.

First, value points to an object of type uint32_t. You then point subset to it which has type int16_t and subsequently dereference that pointer. Doing so is a strict aliasing violation which causes undefined behavior.

But supposed you fixed that part and did this instead:

int16_t v2 = 0xbbbb;
int16_t *subset = &v2;

Then you still have a problem with this:

*subset << 16

Which is the same problem as your last question, namely the value in *subset is negative and subsequently left shifted, and left shifting a negative value triggers undefined behavior.

And again, changing subset to a pointer to uint16_t isn't enough either because the value is first converted to int before the shift, and a left shift of the signed value 0x0000bbbb shifts into the sign bit which again triggers undefined behavior.

As before an explicit cast is required to an unsigned 32 bit type for the program to have well defined behavior.

uint16_t v2 = 0xbbbb;
uint16_t *subset = &v2;
uint32_t test = 0x00000000;
test = ((uint32_t)*subset << 16) | *subset;

Taking a step back, if you were to add the explicit cast but not change the type of subset:

int16_t v2 = 0xbbbb;
int16_t *subset = &v2;
uint32_t test = 0x00000000;
test = ((uint32_t)*subset << 16) | *subset;

The behavior is well defined, but test ends up with the value 0xffffbbbb. This is because of the promotion of the negative int16_t value of 0xbbbb to the same value with type int, which is represented as 0xffffbbbb. This explains the output you were seeing, and explains why *subset & 0xffff fixed this issue.

Sign up to request clarification or add additional context in comments.

1 Comment

And to appease OP, the & 0xFFFF on the final line really is redundant in this case.
4

*subset is expanded to an int when combined with integer constants like 16 and 0xFFFF. Since the leading bit is 1, it is expanded as a negative number (on a twos complement computer): 0xFFFFBBBB in your case. The results are within spec.

However, the result depends on the endianess of the computer - and while all commercial CPUs that I am aware of are 2s complement, there is a mix of Big Endian and Little Endian, and most load/store RISC cpus can load/store either way. So your results end in 0xbbbb on Little Endian (e.g. Intel) and 0xaaaa on Big Endian (e.g. PPC Big Endian mode).

3 Comments

*** hopes to play with balanced trinary hardware
"*subset is expanded to an int when combined with integer constants" No, in the first case it is promoted to an int because a shift operator was used. And the shift operators are a special case since after integer promotion, they do not use "balancing" (aka the usual arithmetic operations) between the operands.
From a C language perspective, the result does not depend on endianness, so much as it produces undefined behavior as a result of strict aliasing violations associated with evaluating *subset. It is possible to observe endianness effects without engaging UB, but in this case, the endianness effect you describe is directly linked to UB. (And so it could, in principle, not behave as you describe.)
2

Consider this code:

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


int main(void)
{
    int16_t  a = 0xBBBB;
    uint32_t b = 0 | a; // Using trivial OR with zero to demonstrate promotion. (See below.)
    printf("b = 0x%" PRIx32 ".\n", b);
}

This outputs:

b = 0xffffbbbb.

That is because:

  • In int16_t a = 0xBBBB;, BBBB16 (48,05910) is converted to int16_t. This conversion produces −17,477 (48,059 − 216), which is represented in 16-bit two’s complement with the bits BBBB16. These bits are stored in a. (The conversion is implementation-defined, but this is the most common behavior.)
  • In uint32_t b = 0 | a;, a is automatically promoted to int. Promotion never changes a value; it remains −17,477. However, in a 32-bit two’s complement int, −17,477 is represented with the bits FFFFBBBB16. Then, to initialize b, this value is converted to uint32_t. This produces the value 4,294,949,819 (−17,477 + 232), which is represented with the bits FFFFBBBB16.

The printf shows that value of b.

This is what is happening in your code test2 = (*subset << 16) | *subset;. The value −17,477 in *subset is promoted, and the bits FFFFBBBB16 are used in the | operation.

There is, as others have noted, undefined behavior in your code. But, as the defined code above shows, this behavior stems from the promotion to int.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.