C语言结构体成员是否继承编译器属性?

3

对于大多数C编译器而言,可以在结构体上指定编译器属性,以定义该结构体成员在内存中的对齐方式。例如:

typedef struct{
    char a;
    char b;
} __attribute__((aligned(2))) TwoChars;


如果 char a 最终位于地址0xA(为了简单起见),那么char b不会位于地址0xB,而是0xC,因为它被对齐到2个字节。
我的问题是:结构成员是否继承了这个属性?例如:
typedef struct{
    char a;
    char b;
} TwoChars;

typedef struct {
    TwoChars tc;
    char c;
} __attribute__((aligned(1))) ThreeChars;

这在内存中是什么样子?如果使用} __attribute__((aligned(2))) TwoChars呢?


3
可以使用您喜欢的编译器轻松测试。这些属性本来就是编译器的扩展。 - Eugene Sh.
如果它是“继承”的话,我会感到惊讶。因为两个不同的翻译单元可以分别编译,一个具有“普通”的TwoChars变量,另一个具有ThreeChars。这些应该是兼容的。但如果在第二个TU中继承了它,那么它们将不兼容。 - Eugene Sh.
是的,我想你的问题更适用于“packed”属性,如下面的答案所指出的。 - Eugene Sh.
1
我将此标记为GCC,因为它是一个非标准的编译器特定扩展。如果可以的话,请让我知道。 - Marco Bonelli
2个回答

5

注:这是一个非标准的编译器特定扩展。我在回答中提到的内容可能适用于GCC和Clang,我认为这就是你所谓的“大多数C编译器”。


可以在结构体上指定编译器属性,定义该结构体成员在内存中的对齐方式。

不是这样的,__attribute__((aligned(...)))只适用于结构体本身,而不适用于其字段。请参见相关的文档页面

如果要控制结构体字段的对齐方式,您必须在它们上面使用该属性。

这样写:

struct {
    char a;
    char b;
} __attribute__((aligned(8))) foo = { 1, 2 };

将编译成类似以下的内容:

foo:
    .byte   1
    .byte   2
    .zero   6

虽然这个:

struct {
    char a __attribute__((aligned(2)));
    char b __attribute__((aligned(2)));
} bar = { 1, 2 };

将会做你想要的事情:

bar:
    .byte   1
    .zero   1
    .byte   2
    .zero   1

因此:
结构成员不会继承此属性。
有趣的是,我们需要知道是否 packed 属性被应用在结构体上后,是否会被嵌套在该结构体内的结构体成员继承,因为 packed 属性特别用于继承结构成员。文档并未明确说明这一点,但我们可以轻松地进行检查。
以下代码:
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};

编译成:

x:
    .byte   101
    .zero   3
    .long   102
    .byte   103
    .zero   3
    .byte   104
    .long   105        

我们可以从上面看到,packed 只适用于 bar 的成员,而不会被 foo 的成员继承,而将 __attribute__((packed)) 添加到 foo 会产生以下效果:
x:
    .byte   101
    .long   102
    .byte   103
    .byte   104
    .long   105

如果你在疑惑,这同样适用于直接定义在其他结构体内部的结构体:
struct bar {
    struct {
        char a;
        int b;
        char c;
    } d;
    char e;
    int c;
} __attribute__((packed));

struct bar x = {{101, 102, 103}, 104, 105};

输出:

x:
    .byte   101
    .zero   3
    .long   102
    .byte   103
    .zero   3
    .byte   104
    .long   105        

如果将d添加__attribute__((packed)),则会产生以下结果:

x:
    .byte   101
    .long   102
    .byte   103
    .byte   104
    .long   105

@EugeneSh。疑问,packed不接受任何参数。 - Marco Bonelli
是的,不是字面意思。我的意思是,如果问到“压缩”的话,这个问题就是合法的。 - Eugene Sh.
也许 aligned 不适用于成员,但将 packed 应用于结构体也会导致成员被打包。因此,答案并不像说成员不继承自父结构体那样简单。还有其他属性是成员会继承的吗? - Eric Postpischil
@EricPostpischil 扩展了。 - Marco Bonelli

0

这样做可以使ThreeChars至少对齐到2。

你可以自己测试一下:

typedef struct{
    char a;
    char b;
} __attribute__((aligned(2))) TwoChars;


typedef struct {
    TwoChars tc;
    char c;
} __attribute__((aligned(1))) ThreeChars;

_Static_assert(_Alignof(ThreeChars) == 2,"");

https://gcc.godbolt.org/z/9bTzbs

或者您可以阅读https://gcc.gnu.org/onlinedocs/gcc-3.3/gcc/Type-Attributes.html上对齐属性的文档。

(不幸的是,标准化的_Alignas使用起来不太方便,因为使用它指定较低的对齐方式将违反约束。)

请注意,这里仅指定整个结构体的对齐方式,因此,虽然TwoChars tc;将是两字节对齐的,从而影响ThreeChars的对齐方式,但随后的成员将不会具有高于自然对齐的对齐方式。

后一事实可以通过_Static_assert验证be的偏移量为1(_Static_assert(offsetof(TwoChars,b)==1,"");),如自然对齐所示,因此它不能是2字节对齐的。


@MarcoBonelli 嗯,谢谢,但我认为只有最初的“是”的回答是误导和完全错误的(现在已删除)。其余部分,我相信是正确的。 - Petr Skocik
是的,当我看到你的编辑时,我删除了我的评论。但是如果你问我,这并不是很清楚。似乎你的第一行回答的是一个与OP提出的问题不同的问题。 - Marco Bonelli

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接