首页 > 解决方案 > 连续分配与顺序分配

问题描述

C11 标准第 6.2.5.20 节将数组定义为:

数组类型描述了一组连续分配的具有特定成员对象类型(称为元素类型)的非空对象。

而 struct 定义为:

结构类型描述了一个顺序分配的非空成员对象集(在某些情况下,一个不完整的数组),每个对象都有一个可选的指定名称和可能的不同类型。

6.7.2.1 部分说可以在字段之间插入填充:

结构或联合对象的每个非位域成员都以适合其类型的实现定义的方式对齐。

在结构对象中,非位域成员和位域所在的单元的地址按声明顺序递增。一个指向结构对象的指针,经过适当的转换,指向它的初始成员(或者如果该成员是位域,则指向它所在的单元),反之亦然。结构对象中可能有未命名的填充,但不是在其开头。

但这是否意味着以下对象可能具有不同的内存布局?

struct A {
    char x0;
    short x1;
};

struct B {
    struct A x0;
    struct A x1;
    struct A x2;
};

assert(sizeof(struct B) == sizeof(struct A[3]));

我创建了这个测试脚本来检查 GCC 的内存布局:

import itertools
import subprocess

src = """
#include "assert.h"

struct A {
{fields}
};

struct B {
    struct A x0;
    struct A x1;
    struct A x2;
};

int main(int argc, char** argv) {
    assert(sizeof(struct B) == sizeof(struct A[3]));
    return 0;
}
"""

def main():
    all_types = ["char", "short", "int", "long long"]

    for types in itertools.product(all_types, repeat=3):
        rendered = src.replace("{fields}", "".join([
            "        {} x{};\n".format(t, i)
            for i, t in enumerate(types)]))
        with open("main.c", "w") as f:
            f.write(rendered)
        subprocess.call(["gcc", "main.c"])
        subprocess.call(["./a.out"])

if __name__ == "__main__":
    main()

但是 GCC总是为数组和结构生成相同的内存布局。

标签: clanguage-lawyer

解决方案


不同之处在于,一个数组,两个元素必须是连续的,没有交错填充,而在结构中它们是连续的,但可以以实现定义的方式存在填充。

现在为您的问题:

当布局不同时,是否有任何现实世界的例子?

AFAIK,不适用于通用编译器。此外,大多数都有选项,程序员可以通过这些选项要求在结构中不添加填充。

将这样的结构实例转换为数组是否安全?

不,因为结构没有声明等效数组,并且单个变量只能别名为大小为 1 的数组。a单个变量*(&a + 1)也是如此,形式上是未定义的行为。

工会会不会更安全?

是的,根据其他 SO 帖子,它可以通过工会来完成。这是合法的 C:

union B {
    struct {
        struct A x0;
        struct A x1;
        struct A x2;
    };
    struct A x[3];
};

即使标准不保证这一点,普通编译器也不会在相同类型的元素之间添加填充,无论是简单类型还是派生(结构)。与第一个问题相同的原因。


推荐阅读