The following code is intended to infer the alignment of double
.
My questions include:
Is there a better to do this? Either by refining the code or using an entirely different approach.
Under what (reasonable) circumstances could this not work?
Under what (plausible) circumstances could this not work? See below.
What is the actual or practical complete list of types I would need to do this with to determine universal alignment?
I think it's
short
,int
,long
,long long
,float
,double
,long double
,void*
. Specificallychar
can't be have a alignment constraint (or c-strings are broken!),unsigned
can't be different from signed (or type-aliasing is broken). Is there any reason whyvoid*
can't stand in for all pointer alignments?
#include <stdio.h>
// *********************************************************************** //
//WARNING: Do not allow this translation unit to be compiled under the
//#pragma pack directive or similar unless whole application is compiled the same way.
// *********************************************************************** //
typedef struct {
char mWedge;//Wedges the member off alignment with the start of the struct.
double mMember;//Member with type we're going to get alignment of.
} DoubleAligned;
const size_t getDoubleAlignment(void){
//A bit of pointer shennanigans to count the padding between mWedge & mMember.
//See text.
const DoubleAligned lSpecimen;
const size_t lOffset= (char*)&(lSpecimen.mMember)-(char*)&(lSpecimen.mWedge);
return lOffset;
}
int main(void) {
printf("Alignment : %zu\n",getDoubleAlignment());
return 0;
}
Expected output on platform where double
has 4-byte alignment:
Alignment : 4
Expected output on platform where double
has no alignment (AKA 1-byte alignment:
Alignment : 1
NB: If the compiler 'packs' structures by default and just 'takes the hit' on slicing and shuffling to get them into hardware alignment, I don't mind getting an output of 'Alignment : 1'. I'm interested in how the platform (programming environment presented to me) is aligned not trying to infer underlying hardware design.
Reasoning
The C specification (C99 to be specific) makes the following commitments:
malloc(.)
returns a universally aligned address.Structures can pad between members and at the end but specifically not the beginning.
Arrays of adjacent structures must be aligned in a way compatible with their members.
Members will be laid out in the order they are declared.
So the compiler has to place mMember
in an aligned position after mWedge
itself aligned to the start of DoubleAligned
. The C specification doesn't further constrain why or how structures are padded but there appears to be no normal reason to pad them other than minimally to satisfy 2, 3 & 4 above.
Hence my question about 'plausible' circumstances. Formally the compiler can do what it likes including adding arbitrary space into structures (but not at the beginning) for reasons that have nothing to do with alignment - maybe debug info or overwrite detection. Does that actually happen?
I'm not entirely sure about void*
standing in for all pointer types. void*
must have the lowest-common-factor alignment of all the pointer types (not the types pointed to!).
Bonus Note:
On many platforms I can get away with:
const size_t getDoubleAlignment(void){
//A bit of pointer shennanigans to count the padding between mWedge & mMember.
//See text.
const DoubleAligned* lSpecimen=NULL;
const size_t lOffset= (char*)&(lSpecimen->mMember)-(char*)&(lSpecimen->mWedge);
return lOffset;
}
But I believe this is formally undefined because it performs arithmetic on NULL
.
Final Note: Why do you need to know? (someone will ask) Suppose I need a portable alternative malloc(.)
implementation that uses pre- and post- 'overhead' areas. I could implement it around the native malloc(.)
but would need to know what "universal alignment" meant to comply with the C standard for malloc(.)
to carve out a universally aligned return value.