I'm not saying you should use the following (because its still encouraging pointer casts), but it does mitigate the underlying issue on GCC.
GCC assumes that a pointer of MyStruct refers to a storage that is aligned as the object is statically allocated. If its stored in a byte buffer that assumption is invalid. Copying to local variable/struct fixes this allocation by relying on memcpy() dealing with this misalignment.
I've found GCC has the "__builtin_assume_aligned", which can be used as a minimum alignment (which doesn't help, as we need to annotate a maximum alignment of up to 4 bytes as thats the maximum width that can be loaded with unaligned pointers). However, there is also a variant where you can say which byte alignment the struct has on a certain modulo of bytes.
So:
void DoTest(unsigned char *src)
{
MyStruct* pS = (MyStruct*) (src);
pS = __builtin_assume_aligned(pS, 8, 1);
MyFn(pS->a, pS->b); //No Surprise!
}
"""Fixes""" this issue by telling GCC that for sure the pointer is aligned to 1 byte offset out of 8.
However, I'm not sure why Clang ignores this annotation and will still go for the ldrd or ldm instruction even though this code is explicitly saying that will cause problems. So it does not seem to be a very stable 'fix', and honestly, I personally wouldn't want to go around my code and telling the compiler which pointers might be crooked.
So I agree with nctnico and bruce. Just use memcpy to make sure the object allocation is sound.
Unfortunately, GCC seem to blindly copy the whole object with memcpy.
Interestingly, when I use in GCC 13.x:
typedef struct
{
int junk[1000];
int a, b;
} /* __attribute__((packed)) */ MyStruct;
void DoTest(unsigned char *src)
{
MyStruct s = *(MyStruct*)src;
MyFn(s.a, s.b); //Surprise!
}
I get:
DoTest:
ldr r1, [r0, #4004]
ldr r0, [r0, #4000]
b MyFn
Or have atleast 1 other 'int' before a,b.
But with any preceding field removed, it uses LDM again.
Same behaviour on clang.