//compile with -mcpu=cortex-m3 -O2/-O3/-Os
extern void MyFn(int a, int b);
//Disable for now
//#define WITH_PACK
#ifdef WITH_PACK
#define PACKED __attribute__((packed))
#else
#define PACKED
#endif
typedef struct
{
int a, b;
} PACKED MyStruct;
void DoTest(unsigned char *src)
{
MyStruct *ptr = (MyStruct *)src;
MyFn(ptr->a, 1); //OK
MyFn(4, ptr->b); //OK
MyFn(ptr->a, ptr->b); //Surprise!
}
- everything looks fine, the core should handle int field accesses even if the src pointer is unaligned, but in reality it would UsageFault on the 3rd MyFn() call void DoTest(unsigned char *src)
{
MyStruct s;
memcpy(&s, src, sizeof(s));
MyFn(s.a, s.b); // No surprise!
}
DoTest:
mov r2, r0
sub sp, sp, #8
mov r3, sp
ldr r0, [r0] @ unaligned
ldr r1, [r2, #4] @ unaligned
stmia r3!, {r0, r1}
add sp, sp, #8
b MyFn
DoTest:
ldr r2, [r0]
ldr r1, [r0, #4]
mov r0, r2
b MyFn
Does anyone know a better solution?
Never, ever casts structs onto pointers which are of a different type (especially void or byte sized pointers). That is bad coding practise and begging for a world of pain on your bare knees.
Has godbolt.org been REALLY sluggish for anyone else recently?
Never, ever casts structs onto pointers which are of a different type (especially void or byte sized pointers). That is bad coding practise and begging for a world of pain on your bare knees.
How else would you do "type erasure", for instance when you call qsort() in order to sort an array of structs?
Or how can you memcpy() a struct w/o casting the struct pointer to void* ?
Never, ever casts structs onto pointers which are of a different type (especially void or byte sized pointers). That is bad coding practise and begging for a world of pain on your bare knees.
How else would you do "type erasure", for instance when you call qsort() in order to sort an array of structs?
Or how can you memcpy() a struct w/o casting the struct pointer to void* ?
void sortByBs(MyStruct *p, int n){
qsort(p, sizeof(*p), n,
[](const void *x, const void *y) -> int {
return (*(MyStruct*)x).b < (*(MyStruct*)y).b;
}
);
}
The question is how to tell the compiler "this pointer is unaligned, do the best you can on this arch" with a minimal overhead.
More fun, GCC's __attribute__((packed)) seems to be inapplicable to a non-struct pointer type at all: https://godbolt.org/z/MzY6o7vbT
all combinations resulted in ldm r0, {r0, r1, r2}. I should just switch back to Keil, which allows things like uint32_t __packed *ptr
More fun, GCC's __attribute__((packed)) seems to be inapplicable to a non-struct pointer type at all: https://godbolt.org/z/MzY6o7vbT
all combinations resulted in ldm r0, {r0, r1, r2}. I should just switch back to Keil, which allows things like uint32_t __packed *ptrIIRC the packed attribute can only be assigned to variables, not to types.
If the intent is to force the compiler to do what it takes to handle a potentially unaligned access, I don't think you can outside the context of a struct.
Although the memcpy approach proposed above solves the alignment problem, it is a trick relying on implicit things, possibly leading to serious overhead (imagine a half-KB sized struct from which we are pulling 3 last ints instead of my shorter example).
typedef struct
{
int junk[1000];
int a, b;
} MyStruct;
void DoTest(unsigned char *src)
{
MyStruct s;
memcpy(&s, src, sizeof(s));
MyFn(s.a, s.b); //Surprise!
}
DoTest:
ldr.w r2, [r0, #4000]
ldr.w r1, [r0, #4004]
mov r0, r2
b MyFn
void DoTest(unsigned char *src)
{
int a, b;
MyStruct *s = (MyStruct*)src;
memcpy(&a, &s->a, sizeof(a));
memcpy(&b, &s->b, sizeof(b));
MyFn(a, b);
}
DoTest:
ldr r1, [r0, #4004] @ unaligned
ldr r0, [r0, #4000] @ unaligned
b MyFn
the memcpy approach
A valid pointer value must always have the same alignment as the the type to which the pointer points
QuoteA valid pointer value must always have the same alignment as the the type to which the pointer pointsSurely not? A pointer to a 96byte struct doesn't have to have 96 (or 128) byte alignment...
typedef struct { int i; double d; } S1;
typedef struct { ...whatsoever... } __attribute__((packed)) S2;
S1* ptr1;
_Alignof(S2) == 1 // packed
_Alignof(S1) == max(_Alignof(int),_Alignof(double))
sizeof(S1) % _Alignof(S1) == 0
ptr1 != NULL && (uintptr_t)ptr1 % _Alignof(S1) == 0
void DoTest(unsigned char *src)
{
MyStruct* pS = (MyStruct*) (src);
pS = __builtin_assume_aligned(pS, 8, 1);
MyFn(pS->a, pS->b); //No Surprise!
}
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.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.
Alignment of a struct is not a matter of its size, but is the maximum of the alignments of its components. The size of a struct is padded to an integral multiple of its alignment in order that each element is still properly aligned in an array of structs. The declarationCode: [Select]typedef struct { int i; double d; } S1;
typedef struct { ...whatsoever... } __attribute__((packed)) S2;
S1* ptr1;
has the following implicationsCode: [Select]_Alignof(S2) == 1 // packed
Code: [Select]_Alignof(S1) == max(_Alignof(int),_Alignof(double))
Code: [Select]sizeof(S1) % _Alignof(S1) == 0
and the following condition must be met in order that dereferencing ptr1 is not UBCode: [Select]ptr1 != NULL && (uintptr_t)ptr1 % _Alignof(S1) == 0