Bob5421
Bob5421

Reputation: 9173

C pointer conversions

Let's suppose I have a buffer of bytes:

char buffer[] = {  0x11, 0x00, 0x00, 0x02, .... };

What I want to do is to take a qword from a specific offset. The offset will be calculated in bytes. Here is what I have done:

unsigned long long *ptr = &buffer[16];
unsigned long long myqword = *ptr;

It seems to works for me. I am skipping 16 bytes from the beginning and then taking 8 bytes.

My question is why do I get this warning message:

warning: initialization of ‘long long unsigned int *’ from incompatible pointer type ‘char *’ [-Wincompatible-pointer-types]
   unsigned long long *ptr = &buffer[16];

Upvotes: 4

Views: 376

Answers (3)

Lundin
Lundin

Reputation: 215115

There's 4 major problems here:

  • unsigned long long* is not compatible with char*. This is the cause for the compiler error.
  • Reading a bunch of char through a unsigned long long* would violate the strict aliasing rule, invoking undefined behavior bugs.
  • Reading a bunch of char through a unsigned long long* may also cause misaligned access, invoking undefined behavior bugs.
  • Using char to store integer values is a very bad idea, since char has implementation-defined signedness and could be signed on some computers. Use uint8_t instead.

What you should do instead is to use memcpy:

size_t n = sizeof(unsigned long long);
memcpy(&myqword, &buffer[i * n], n);

If you actually need to access the contents of a specific memory location without copying it, you have no other option but to make some new type, like a union:

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

typedef union
{
  uint8_t  byte [8];
  uint64_t qword;
} qword_t;

int main (void)
{
  qword_t q = {.byte = {0x11, 0x00, ...} };
  printf("%"PRIu64, q.qword);
}

But please note that this code depends on CPU endianess and is non-portable.

Upvotes: 4

nos
nos

Reputation: 229334

You would piece together the bytes into an integer yourself.

If you are on a big endian host and your current code did get the proper integer value, use this one:

/** De-serialize a uint64_t from a byte array in big endian format.
 * @param r The byte array to containing the integer in. Must be at least of size 4.
 * @param return The deserialized integer from the byte array
 */
static inline uint64_t uc_unpack_64_be(const uint8_t *r)
{
    uint64_t v;

    v  = (uint64_t)r[0] << 56;
    v |= (uint64_t)r[1] << 48;
    v |= (uint64_t)r[2] << 40;
    v |= (uint64_t)r[3] << 32;
    v |= (uint32_t)r[4] << 24;
    v |= r[5] << 16;
    v |= r[6] << 8;
    v |= r[7];

    return v;
}

If you are currently on a little endian machine, use this one:

/** De-serialize a uint64_t from a byte array in little endian format.

 * @param r The byte array to containing the integer in. Must be at least of size 8.
 * @param return The deserialized integer from the byte array
 */
static inline uint64_t uc_unpack_64_le(const uint8_t *r)
{
    uint64_t v;

    v  = r[0];
    v |= r[1] << 8;
    v |= r[2] << 16;
    v |= (uint32_t)r[3] << 24;
    v |= (uint64_t)r[4] << 32;
    v |= (uint64_t)r[5] << 40;
    v |= (uint64_t)r[6] << 48;
    v |= (uint64_t)r[7] << 56;

    return v;
}

Use it e.g. as uint64_t myqword = uc_unpack_64_le(&buffer[16]);

Note that whether you use one of uint64_t uc_unpack_64_le or uint64_t uc_unpack_64_le functions depends on whether you formatted the data in your buffer as little or big endian, not if your code currently runs on a little or big endian machine.

If you insist on using your current long long and char types, change the code accordingly, but I encourage you to use the uint16_t and uint64_t types from the <stdint.h> header instead.

Upvotes: 3

bobah
bobah

Reputation: 18864

Apart from the strict aliasing rule violation (covered well in the comments to the questions), another reason this warning is worth paying attention to is because you may end up accessing unaligned data.

On some architectures it would just be slower, on others your app may not survive it.

If you cast it explicitly it will shut up. But I would also tell compiler to align the char array to the size of unsigned long long (and also I would switch to uint8_t and uint64_t for clarity.

Upvotes: 1

Related Questions