Note: this is a non-standard compiler-specific extension. What I mention in my answer may be applied to GCC and Clang, which I assume is what you mean by "most C compilers".
one can specify compiler attributes on a struct that define how the members of that struct are aligned in memory
No, that's not how __attribute__((aligned(...))) works. The aligned attribute only applies to the structure itself, not its fields. See the relative documentation page.
If you want to control the alignment of structure fields, you will have to use the attribute on them.
This:
struct {
char a;
char b;
} __attribute__((aligned(8))) foo = { 1, 2 };
Will compile to something like:
foo:
.byte 1
.byte 2
.zero 6
While this:
struct {
char a __attribute__((aligned(2)));
char b __attribute__((aligned(2)));
} bar = { 1, 2 };
Will do what you want:
bar:
.byte 1
.zero 1
.byte 2
.zero 1
Therefore:
is this property inherited by struct members?
No. Alignment is not something that is inherited by structure members.
An interesting thing to know would be if the packed attribute is inherited by members of nested structures inside the structure on which it's applied, since packed is specifically meant to be inherited by structure members. Documentation does not explicitly state whether this is the case or not, but we can check this easily.
The following code:
struct foo {
char a;
int b;
char c;
};
struct bar {
struct foo d;
char e;
int f;
} __attribute__((packed));
struct bar x = {{101, 102, 103}, 104, 105};
Compiles to:
x:
.byte 101
.zero 3
.long 102
.byte 103
.zero 3
.byte 104
.long 105
We can see from the above that packed only applies to the members of bar and is not inherited by the members of foo, whereas adding __attribute__((packed)) to foo produces the following:
x:
.byte 101
.long 102
.byte 103
.byte 104
.long 105
In case you were wondering, this also applies to structures defined right inside other structures:
struct bar {
struct {
char a;
int b;
char c;
} d;
char e;
int c;
} __attribute__((packed));
struct bar x = {{101, 102, 103}, 104, 105};
Produces:
x:
.byte 101
.zero 3
.long 102
.byte 103
.zero 3
.byte 104
.long 105
Whereas adding __attribute__((packed)) to d produces:
x:
.byte 101
.long 102
.byte 103
.byte 104
.long 105