4

People say it's not good to trust reinterpret_cast to convert from raw data (like char*) to a structure. For example, for the structure

struct A
{
    unsigned int a;
    unsigned int b;
    unsigned char c;
    unsigned int d;
};

sizeof(A) = 16 and __alignof(A) = 4, exactly as expected.

Suppose I do this:

char *data = new char[sizeof(A) + 1];
A *ptr = reinterpret_cast<A*>(data + 1); // +1 is to ensure it doesn't points to 4-byte aligned data

Then copy some data to ptr:

memcpy_s(sh, sizeof(A),
         "\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00", sizeof(A));

Then ptr->a is 1, ptr->b is 2, ptr->c is 3 and ptr->d is 4.
Okay, seems to work. Exactly what I was expecting.

But the data pointed by ptr is not 4-byte aligned like A should be. What problems this may cause in a x86 or x64 platform? Performance issues?

LHLaurini
  • 1,737
  • 17
  • 31

2 Answers2

3

For one thing, your initialization string assumes that the underlying integers are stored in little endian format. But another architecture might use big endian, in which case your string will produce garbage. (Some huge numbers.) The correct string for that architecture would be

"\x00\x00\x00\x01\x00\x00\x00\x02\x03\x00\x00\x00\x00\x00\x00\x04".

Then, of course, there is the issue of alignment.

Certain architectures won't even allow you to assign the address of data + 1 to a non-character pointer, they will issue a memory alignment trap.

But even architectures which will allow this (like x86) will perform miserably, having to perform two memory accesses for each integer in the structure. (For more information, see this excellent answer: https://stackoverflow.com/a/381368/773113)

Finally, I am not completely sure about this, but I think that C and C++ do not even guarantee to you that an array of characters will contain characters packed in bytes. (I hope someone who knows more might clarify this.) Conceivably, there can be architectures which are completely incapable of addressing non-word-aligned data, so in such architectures each character would have to occupy an entire word. This would mean that it would be valid to take the address of data + 1, because it would still be aligned, but your initialization string would be unsuitable for the intended job, as the first 4 characters in it would cover your entire structure, producing a=1, b=0, c=0 and d=0.

Community
  • 1
  • 1
Mike Nakis
  • 56,297
  • 11
  • 110
  • 142
  • Thanks. (Would be `"\x00\x00\x00\x01\x00\x00\x00\x02\x03\x00\x00\x00\x00\x00\x00\x04"` because the third variable is a `char`). Yes, I know this. What I would like to know is if the not-aligned data may cause some problem. – LHLaurini Mar 12 '15 at 21:07
  • Thanks. That's what I wanted to know. – LHLaurini Mar 12 '15 at 21:20
  • 1
    From what I know, reading misaligned continuous data gives you one or two more memory read(s) and a lot of shifting. I'm quite sure you don't need to read every word twice. – Zdeněk Jelínek Mar 12 '15 at 21:21
  • @Alegnem you are right, my wording was bad. I wrote "execute memory access operations" while I meant "perform memory accesses". Of course, it is the CPU that does it, not the programmer. I fixed it. – Mike Nakis Mar 12 '15 at 21:29
  • @LHLaurini oh, I just now noticed that `c` is a char. – Mike Nakis Mar 12 '15 at 21:32
  • Well, then I think it's all right because my code isn't supposed to run in any other processor architecture than x86 and maybe later x64. – LHLaurini Mar 12 '15 at 21:47
  • 1
    IIRC 'byte' means 'smallest addressable unit' in C/C++, and doesn't have to be 8 bits. – Cubic Mar 12 '15 at 21:53
  • @cubic depending on how you interpret limits.h - C99 does require types to be a multiple of 8bits – Martin Beckett Mar 12 '15 at 23:07
  • @MartinBeckett yes, but the question here is whether a char can be as long as a word. My suspicion is that it probably can, though I do not know how to verify this suspicion. – Mike Nakis Mar 13 '15 at 00:31
0

The problem is that you can not be sure if this code will run on another platform, with the next version of Visual Studio, etc. When running on another processor, it may cause a hardware exception.

There was a time when you could read out arbitrary memory locations, but all those programs crash with an "access violation" exception nowadays. Something similar could happen to this program in the future.

However, what you can do, and what any compiler that calls itself "C++ standard compliant" must compile correctly, is this:
You can reinterpret_cast a pointer to something else, and then back to the original type. The value of the type, when read before and after, must stay the same.

I don't know what exactly you want to do, but you might get away with, for example

  • allocating a struct A
  • reinterpret_casting it to chars
  • saving the memory content to a file

and restore everything later:

  • allocate a struct A
  • reinterpret_cast it to chars
  • load the content to memory
  • reinterpret_cast it back to a struct A
alain
  • 11,939
  • 2
  • 31
  • 51
  • Thanks. Nowadays, with x86 or x64 processors, is it a problem to access unaligned data? – LHLaurini Mar 12 '15 at 21:12
  • (mis)Alignment only reduces performance (on x86/64). The true problem is the memory access mode and the concrete memory model of different CPU architectures that may not support your idea. – Zdeněk Jelínek Mar 12 '15 at 21:14
  • Well, then I think it's all right because my code isn't supposed to run in any other processor architecture than x86 and maybe later x64. – LHLaurini Mar 12 '15 at 21:47
  • That's exactly what I was trying to do when I got this doubt: trying to read a struct from a file. But I didn't get "`reinterpret_cast` it to `char`s" when restoring... – LHLaurini Mar 12 '15 at 22:55
  • @LHLaurini No need to cast back from `char *` to `A*`. (1) Unless the `char *` was cast from an aligned `A`, this is UB. (2) Here, it is, but it's pointless! The way to load a file to `A` - if it's trivially copyable, which is essential - is to allocate `A a` & copy the file over it as a series of `char`s - by reading to `reinterpret_cast(&a)` or a separate buffer then `memcpy`, etc (the Standard guarantees TC types are serialisable to/from `char` buffers). Then read the `a` you already have - no point casting back. In suitable cases, copies can be optimised to unaligned casts _anyway_ – underscore_d Aug 14 '16 at 10:43