连续分配与顺序分配

时间:2019-02-27 14:52:48

标签: c language-lawyer

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 总是会为数组和结构产生相同的内存布局。

  • 当布局不同时,是否有现实世界的例子?
  • 将这样的结构实例强制转换为数组是否安全?
  • 工会会更安全吗?

1 个答案:

答案 0 :(得分:2)

区别在于,一个数组,两个元素必须是连续的,没有交织的填充,而在结构中它们是顺序的,但是以实现定义的方式存在填充 。 / p>

现在提出您的问题:

  

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

AFAIK,不适用于常见的编译器。此外,大多数工具都具有可供程序员选择不要求在结构中添加填充的选项。

  

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

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

  

工会会更安全吗?

是的,根据that other SO post,可以通过并集来完成。这是合法的C:

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

即使标准不能保证,普通编译器也不会在相同类型(简单类型或派生类型)的元素之间添加填充。与第一个问题相同。