13

So, I'm trying to convert an array of unsigned chars into an uint32_t, but keep getting different results each time:

unsigned char buffer[] = {0x80, 0x00, 0x00, 0x00};;
uint32_t num = (uint32_t*)&buffer;

Now, I keep getting this warning:

warning: initialization makes integer from pointer without a cast

When I change num to *num i don't get that warning, but that's not actually the real problem (UPDATE: well, those might be related now that I think of it.), because every time I run the code there is different results. Secondly the num, once it's cast properly, should be 128, but If I need to change the endianness of the buffer I could manage to do that myself, I think.

Thanks!

omninonsense
  • 6,644
  • 9
  • 45
  • 66
  • the above example you are setting num to the address of the buffer array. You should use `uint32_t num = *(uint32_t*)buffer; ` to get to the number. – Neil Jul 29 '20 at 16:27

6 Answers6

23

Did you try this ?

num = (uint32_t)buffer[0] << 24 |
      (uint32_t)buffer[1] << 16 |
      (uint32_t)buffer[2] << 8  |
      (uint32_t)buffer[3];

This way you control endianness and whatnot.

It's really not safe to cast a char pointer and interpret it as anything bigger. Some machines expect pointers to integers to be aligned.

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
cnicutar
  • 178,505
  • 25
  • 365
  • 392
  • +1, this is the sane and portable way of doing this. Assuming `buffer` holds a `uint32_t` in big-endian format, of course, but the OP didn't specify that. – Fred Foo Aug 14 '11 at 19:57
  • Won't this cause problems if you are casting buffer[i], an 8-bit quantity, to a uint32_t? – Foo Bah Aug 14 '11 at 20:07
  • 1
    @Foo Bah: No, there is nothing wrong with converting an `unsigned char` to `uint32_t` - the latter type must necessarily be at least as large as the former, so the value will be unchanged. – caf Aug 15 '11 at 04:16
10

cnicutar's answer is the best assuming you want a particular fixed endianness. If you want host endian, try:

uint32_t num;
memcpy(&num, buffer, 4);

or apply ntohl to cnicutar's answer. Any method based on type punning is wrong and dangerous.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • thanks a lot, this is exactly what I was looking for and trying to do with cast operator! – MaxC Jun 15 '20 at 16:27
3

First, you want to say num = *(uint32_t *)&buffer

To change endianness, you can use a call like bswap_32 (in linux, byteswap.h) or OSSwapInt64 (in osx, libkern/OSByteOrder.h)

Foo Bah
  • 25,660
  • 5
  • 55
  • 79
  • 1
    ... which yields undefined behavior due to alignment problems; on some RISC processors, this may crash the program. – Fred Foo Aug 14 '11 at 19:56
  • 1
    @larsmans without explicitly forcing a single-character packing scheme, won't the static array be correctly aligned? – Foo Bah Aug 14 '11 at 20:01
  • 2
    That's not guaranteed by the C standard. In fact, I believe your example violates the aliasing rule as well, meaning that it may break due to optimizations even when the buffer is correctly aligned. – Fred Foo Aug 14 '11 at 20:04
  • 1
    This is the traditional way to do it, but it's invalid C. – R.. GitHub STOP HELPING ICE Aug 14 '11 at 20:06
1

The warning was issued because &buffer yields a pointer to pointer. Even without the reference operator & the warning wouldn't have disappeared because the casting changes only the pointer type. The pointer is further converted to integer and therefore the warning.

If endianness is not important, the obvious solution seems to me

unsigned char buffer[] = {0x80, 0x00, 0x00, 0x00};
uint32_t num = *(uint32_t *)buffer;

which means dereferencing the casted pointer to the char array.

Else
  • 11
  • 1
  • 1
  • This is doubly-broken. First, given `unsigned char buffer[]`, `uint32_t num = *(uint32_t *)buffer;` [violates strict aliasing](https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule) and is thus undefined behavior. It also can violate any alignment restrictions of [**6.3.2.3 Pointers**, paragraph 7](https://port70.net/~nsz/c/c11/n1570.html#6.3.2.3p7): "A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined." – Andrew Henle Jul 29 '20 at 18:23
  • @AndrewHenle can you please further elaborate on why this violates strict aliasing? I am having trouble understanding aliasing. Aren't uint8_t and uint32_t compatible types? – earthling Jun 14 '23 at 08:58
  • @earthling Strict aliasing in C or C++ (they're very close if not actually identical in the way this is interpreted) is pretty simple - you can't refer to a memory location as a type different from what it actually is - except that you can refer to anything as an array of `[[un]signed] char`. Breaking that rule - as done here by referring to an `unsigned char` array as a `uin32_t` - violates strict aliasing as the arrays is not a `uint32_t`, so it invokes UB. See [**What is the strict aliasing rule?**](https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule) for a LOT more – Andrew Henle Jun 14 '23 at 22:50
  • (cont) The portable solution is to use `memcpy()` - as in `memcpy(&num, buffer, sizeof(num));` Any decent optimizing compiler will optimize out the `memcpy()` and replace it with a suitable direct assignment that won't break things. The *compiler* is allowed to do that, if your code does that it invokes undefined behavior. The compiler (especially at high optimization levels) might then compile your code into a binary that doesn't do what you want. You can think of it as the compiler first optimizes your code, then replaces `memcpy()` with an assignment. – Andrew Henle Jun 14 '23 at 22:55
  • @AndrewHenle Thank you for writing out this answer! Very much appreciated. It helped me understand it better. BR – earthling Jun 19 '23 at 06:35
0

Borrowing from @Mr. R. above, my approach for converting a 3 byte big endian unsigned char array within a structure to a little endian unsigned int...

struct mystruct {
  int stuff;
  int stuff2;
  unsigned char x[3]    // big endian
} 
mystruct z;
unsigned int y  // little endian

memcpy(&y, z->x, 3);
y=be32toh(y<<8);`
chb
  • 1,727
  • 7
  • 25
  • 47
makermarc
  • 1
  • 2
0

Assuming it's the same endianess a union would the best option.

union
{
    uint32_t u32;
    float flt;  
    uin8_T bytes[4];

} converter;

// Use of the above union

converter.bytes = your_byte_array;
uint32_t u32_output = converter.u32;
float float_output = converter.flt;
Neil
  • 1,036
  • 9
  • 18
Jesse
  • 1