venab42230
venab42230

Reputation: 31

How to make a hexadecimal number from bytes?

I want to compose the number 0xAAEFCDAB from individual bytes. Everything goes well up to 4 tetrads, and for some reason extra 4 bytes are added with it. What am I doing wrong?

#include <stdio.h>

int main(void) {
  unsigned long int a = 0;

  a = a | ((0xAB) << 0);
  printf("%lX\n", a);
  a = a | ((0xCD) << 8);
  printf("%lX\n", a);
  a = a | ((0xEF) << 16);
  printf("%lX\n", a);

  a = a | ((0xAA) << 24);
  printf("%lX\n", a);

  return 0;
}

Output:

image

Upvotes: 3

Views: 210

Answers (3)

msaw328
msaw328

Reputation: 1569

Constants in C are actually typed, which might not be obvious at first, and the default type for a constant is an int which is a signed 32-bit integer (it depends on the platform, but it probably is in your case).

In signed numbers, the highest bit describes the sign of the number: 1 is negative and 0 is positive (for more details you can read about two's complement).

When you perform the operation 0xAB << 24 it results in a 32-bit signed value of 0xAB000000 which is equal to 10101011 00000000 00000000 00000000 in binary. As you can see, the highest bit is set to 1, which means that the entire 32-bit signed number is actually negative.

In order to perform the | OR operation between a (which is a 64-bit unsigned number) and a 32-bit signed number, some type conversions must be performed. The size promotion is performed first, and the 32-bit signed value of 0xAB000000 is promoted to a 64-bit signed value of 0xFFFFFFFFAB000000, according to the rules of the two's complement system. This is a 64-bit signed number which has the same numerical value as the 32-bit signed one before conversion.

Afterwards, type conversion is performed from 64-bit signed to 64-bit unsigned value in order to OR the value with a. This fills the top bits with ones and results in the value you see on the screen.

In order to force your constants to be different type than 32-bit signed int you may use suffixes such as u and l, as shown in the website I linked in the beginning of my answer. In your case, a ul suffix should work best, indicating a 64-bit unsigned value. Your lines of code which OR constants with your a variable would then look similarly to this:

a = a | ((0xAAul) << 24);

Alternatively, if you want to limit yourself to 4 bytes only, a 32-bit unsigned int is enough to hold them. In that case, I suggest you change your a variable type to unsigned int and use the u suffix for your constants. Do not forget to change the printf formats to reflect the type change. The resulting code looks like this:

#include <stdio.h>

int main(void) {
  unsigned int a = 0;

  a = a | ((0xABu) << 0);
  printf("%X\n", a);
  a = a | ((0xCDu) << 8);
  printf("%X\n", a);
  a = a | ((0xEFu) << 16);
  printf("%X\n", a);

  a = a | ((0xAAu) << 24);
  printf("%X\n", a);

  return 0;
}

My last suggestion is to not use the default int and long types when portability and size in bits are important to you. These types are not guaranteed to have the same amount of bits on all platforms. Instead use types defined in the <stdint.h> header file, in your case probably either a uint64_t or uint32_t. These two are guaranteed to be unsigned integers (their signed counterparts omit the 'u': int64_t and int32_t) while being 64-bit and 32-bit in size respectively on all platforms. For Pros and Cons of using them instead of traditional int and long types I refer you to this Stack Overflow answer.

Upvotes: 4

0___________
0___________

Reputation: 67546

a = a | ((0xAA) << 24);

((0xAA) << 24) is a negative number (it is int), then it is sign extended to the size of 'unsigned long' which adds those 0xffffffff at the beginning.

You need to tell the compiler that you want an unsigned number.

a = a | ((0xAAU) << 24);
int main(void) {
  unsigned long int a = 0;

  a = a | ((0xAB) << 0);
  printf("%lX\n", a);
  a = a | ((0xCD) << 8);
  printf("%lX\n", a);
  a = a | ((0xEF) << 16);
  printf("%lX\n", a);

  a = a | ((0xAAUL) << 24);

  printf("%lX\n", a);
  printf("%d\n", ((0xAA) << 24));

  return 0;
}

https://gcc.godbolt.org/z/fjv19bKGc

Upvotes: 3

Remy Lebeau
Remy Lebeau

Reputation: 596557

0xAA gets treated as a signed value when it is scaled up during the bit shifting. Since its high bit is 1 (0xAA = 10101010b), the scaled value is sign extended to 0x...FFFFFFAA before you shift and OR it to a.

You need to cast 0xAA to an unsigned value before bit shifting it, so it gets zero extended instead.

Upvotes: 2

Related Questions