1

I'm using a char* array to store different data types, like in the next example:

int main()
{
    char* arr = new char[8];
    *reinterpret_cast<uint32_t*>(&arr[1]) = 1u;
    return 0;
}

Compiling and running with clang UndefinedBehaviorSanitizer will report the following error:

runtime error: store to misaligned address 0x602000000011 for type 'uint32_t' (aka 'unsigned int'), which requires 4 byte alignment

I suppose I could do it another way, but why is this undefined behavior? What concepts are involved here?

Kelno
  • 667
  • 6
  • 7
  • 3
    `uint32_t*` corresponds to addresses which are a multipliers of 4, e.g. 0, 4, 8, etc. And now you are asking to convert address, say, 5, to `uint32_t*`. What should happen? –  May 30 '19 at 22:45
  • 3
    uint32_t pointer needs to be aligned – Raxvan May 30 '19 at 22:47
  • 2
    Possible duplicate of [How to align pointer](https://stackoverflow.com/questions/6320264/how-to-align-pointer) – Borgleader May 30 '19 at 22:51

1 Answers1

5

You cannot cast an arbitrary char* to uint32_t*, even if it points to an array large enough to hold a uint32_t

There are a couple reasons why.

The practical answer:

uint32_t generally likes 4-byte alignment: its address should be a multiple of 4.

char does not have such a restriction. It can live at any address.

That means that an arbitrary char* is unlikely to be aligned properly for a uint32_t.

The Language Lawyer answer:

Aside from the alignment issue, your code exhibits undefined behavior because you're violating the strict aliasing rules. No uint32_t object exists at the address you're writing to, but you're treating it as if there is one there.

In general, while char* may be used to point to any object and read its byte representation, a T* for any given type T, cannot be used to point at an array of bytes and write the byte-representation of the object into it.


No matter the reason for the error, the way to fix it is the same:

If you don't care about treating the bytes as a uint32_t and are just serializing them (to send over a network, or write to disk, for example), then you can std::copy the bytes into the buffer:

char buffer[BUFFER_SIZE] = {};
char* buffer_pointer = buffer;
uint32_t foo = 123;
char* pfoo = reinterpret_cast<char*>(&foo);
std::copy(pfoo, pfoo + sizeof(foo), buffer_pointer);
buffer_pointer += sizeof(foo);
uint32_t bar = 234;
char* pbar = reinterpret_cast<char*>(&bar);
std::copy(pbar, pbar + sizeof(bar), buffer_pointer);
buffer_pointer += sizeof(bar);
// repeat as needed

If you do want to treat those bytes as a uint32_t (if you're implementing a std::vector-like data structure, for example) then you will need to ensure the buffer is properly-aligned, and use placement-new:

std::aligned_storage_t<sizeof(uint32_t), alignof(uint32_t)> buffer[BUFFER_SIZE];
uint32_t foo = 123;
uint32_t* new_uint = new (&buffer[0]) uint32_t(foo);
uint32_t bar = 234;
uint32_t* another_new_uint = new (&buffer[1]) uint32_t(foo);
// repeat as needed
Community
  • 1
  • 1
Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
  • That's a very comprehensive and well explained answer, thanks! – Kelno May 30 '19 at 23:48
  • To be pedantic, you can make the cast ; the strict aliasing rule is not violated until you try to read or write via dereferencing the result of the cast. If the alignment is incorrect then the result of the cast is unspecified. If the alignment is correct you could make the cast and then use `memcpy` with 32-bit units, for example. – M.M May 31 '19 at 01:14