为什么有时不会将变长数组声明为指针?

时间:2010-09-30 17:12:48

标签: c++ c arrays

我有时会在代码中看到这一点:

struct S
{
    int count; // length of array in data
    int data[1];
};

如果S的存储空间大于sizeof(S),那么数据可以为其数组提供更多空间。然后使用它:

S *s;

// allocation

s->data[3] = 1337;

我的问题是,为什么data不是指针?为什么长度为1的数组?

6 个答案:

答案 0 :(得分:9)

如果将data声明为指针,则必须为data数组分配一个单独的内存块,即您必须进行两次分配而不是一次。虽然实际功能没有太大差异,但仍可能会对性能产生负面影响。它可能会增加内存碎片。这可能导致结构内存被“远离”data数组内存分配,导致数据结构的缓存行为不佳。如果您使用自己的内存管理例程,例如池化分配器,则必须设置两个分配器:一个用于结构,一个用于数组。

通过使用上述技术(称为“struct hack”),您可以在一个块中为整个结构(包括data数组)分配内存,只需一次调用malloc(或者您自己的分配器)。这就是它的用途。除此之外,它确保结构存储器尽可能靠近数组存储器(即它只是一个连续的块),因此数据结构的缓存行为是最佳的。

答案 1 :(得分:6)

Raymond Chen写了一篇很好的文章,关于为什么变长结构选择这种模式而不是其他许多模式(包括指针)。

他没有直接评论为什么在数组上选择指针,但Steve Dispensa在评论部分提供了一些见解。

来自Steve

typedef struct _TOKEN_GROUPS { 
DWORD GroupCount; 
SID_AND_ATTRIBUTES *Groups; 
} TOKEN_GROUPS, *PTOKEN_GROUPS; 
  

这仍然会强制组指针对齐,但是当你想到参数编组时,它就不那么方便了。

     

在驱动程序开发中,开发人员有时会遇到通过METHOD_BUFFERED IOCTL从用户模式向内核模式发送参数的问题。像这样的嵌入式指针的结构代表了从等待发生的安全缺陷到简单的PITA的任何事物。

答案 2 :(得分:1)

这样做可以更容易地管理数组在内存中的顺序(在结构内)。否则,在memalloc大于sizeof(S)之后,你必须将'data'指向下一个内存地址。

答案 3 :(得分:1)

因为它允许您使用代码执行此操作:

struct S
{
    int count; // length of array in data
    int data[1];
};

    struct S * foo;

    foo = malloc(sizeof(struct S) + ((len - 1)*sizeof(int)) );
    strcpy(foo->data, buf);

只需要一次调用malloc,一次调用free。

这很常见,C99标准允许您甚至不指定数组的长度。它被称为灵活的 数组 成员

来自ISO / IEC 9899:1999,Section 6.7.2.1,第16段:“作为一种特殊情况,具有多个命名成员的结构的最后一个元素可能具有不完整的数组类型;这称为灵活的数组成员。” 称为灵活的阵列成员。“

struct S
{
    int count; // length of array in data
    int data[];
};

gcc允许0个长度数组成员作为结构的最后一个成员作为一段时间的扩展。

答案 4 :(得分:0)

由于复制语义不同。如果它是一个指针,那么内容必须明确复制。如果里面是C风格的数组,则副本是自动的。

答案 5 :(得分:0)

顺便说一下,我认为没有任何保证使用长度为一的数组作为更长的时间才能起作用。编译器可以自由地生成有效地址代码,该代码依赖于下标不大于指定的边界(例如,如果将数组绑定指定为1,则编译器可以生成始终访问第一个元素的代码,如果它是两个在某些平台上,优化编译器可能会将[i]转换为((i& 1)?a [1]:a [0])。请注意,虽然我不知道任何实际执行该转换的编译器,我知道平台比计算数组下标更有效。

我认为符合标准的方法是将数组声明为[MAX_SIZE]并分配sizeof(struct S) - (MAX_SIZE-len)* sizeof(int)bytes。