C中struct中可变长度数组的奇怪行为

时间:2016-11-06 18:22:54

标签: c struct

我遇到了一个人们称之为" Struct Hack"我们可以在结构中声明一个指针变量,如下所示:

struct myStruct{
    int data;
    int *array;
};

以及稍后当我们在struct myStruct函数中使用mallocmain()分配内存时,我们可以在同一步骤中同时为int *array指针分配内存,如这样:

struct myStruct *p = malloc(sizeof(struct myStruct) + 100 * sizeof(int));

p->array = p+1;

而不是

struct myStruct *p = malloc(sizeof(struct myStruct));

p->array = malloc(100 * sizeof(int));

假设我们想要一个大小为100的数组。

据说第一个选项更好,因为我们可以获得连续的内存块,我们可以在后一种情况下通过一次调用free()和2次调用来释放整个块。

试验,我写了这个:

#include<stdio.h>
#include<stdlib.h>

struct myStruct{
    int i;
    int *array;
};

int main(){
    /* I ask for only 40 more bytes (10 * sizeof(int)) */

    struct myStruct *p = malloc(sizeof(struct myStruct) + 10 * sizeof(int)); 

    p->array = p+1; 

    /* I assign values way beyond the initial allocation*/
    for (int i = 0; i < 804; i++){
        p->array[i] = i;
    }

    /* printing*/
    for (int i = 0; i < 804; i++){
        printf("%d\n",p->array[i]);
    }

    return 0;
}

我可以毫无问题地执行它,没有任何分段错误。看起来很奇怪。

我也知道C99有一条规定说,我们可以int *array而不是在结构中声明int array[],我只使用malloc()执行此操作结构,如

struct myStruct *p = malloc(sizeof(struct myStruct));

并像这样初始化array []

p->array[10] = 0; /* I hope this sets the array size to 10 
                    and also initialises array entries to 0 */

但是再一次这种奇怪的地方,我能够访问并分配超出数组大小的数组索引,并打印条目:

for(int i = 0; i < 296; i++){ // first loop
    p->array[i] = i;
}

for(int i = 0; i < 296; i++){ // second loop
    printf("%d\n",p->array[i]);
}

打印p->array[i]i = 296后,它会给我一个细分错误,但显然除了i = 9之外没有问题。 (如果我在上面的第一个for循环中增加&#39; i&#39;直到300,我立即得到分段错误,程序不会打印任何值。)

关于发生了什么的任何线索?它是未定义的行为还是什么?

编辑:当我使用命令

编译第一个片段时
cc -Wall -g -std=c11 -O    struct3.c   -o struct3

我收到了这个警告:

 warning: incompatible pointer types assigning to 'int *' from
  'struct str *' [-Wincompatible-pointer-types]
    p->array = p+1;

2 个答案:

答案 0 :(得分:3)

是的,你在这里看到的是一个未定义行为的例子。

写入超出已分配数组的末尾(也就是缓冲区溢出)是未定义行为的一个很好的例子:它经常会出现在#34;正常工作&#34;,而有时它会崩溃(例如&#34;分段错误&#34;)。

一个低级别的解释:内存中的控制结构与您分配的对象相距一定距离。如果你的程序有很大的缓冲区溢出,那么它将更有可能损坏这些控制结构,而对于更温和的溢出,它会损坏一些未使用的数据(例如填充)。但是,在任何情况下,缓冲区溢出都会调用未定义的行为。

&#34; struct hack&#34;在你的第一种形式中也会调用未定义的行为(如警告所示),但是在一些特殊类型中 - 它几乎可以保证在大多数编译器中它总能正常工作。但是,它仍然是未定义的行为,因此不建议使用。为了制裁它的使用,C委员会发明了这个&#34;灵活的阵列成员&#34;语法(你的第二种语法),保证可以工作。

只是为了说清楚 - 对数组元素的赋值永远不会为该元素分配空间(至少在C中)。在C中,当分配给一个元素时,它应该已经被分配,即使该数组是&#34;灵活的&#34;。您的代码应该知道在分配内存时要分配多少。如果您不知道要分配多少,请使用以下技术之一:

  • 分配上限: struct myStruct{ int data; int array[100]; // you will never need more than 100 numbers };
  • 使用realloc
  • 使用链接列表(或任何其他复杂的数据结构)

答案 1 :(得分:0)

你所描述的&#34; Struct Hack&#34;确实是一个黑客。这不值得IMO。

p->array = p+1;

会在许多需要显式转换的编译器上给你带来问题:

p->array = (int *) (p+1);

  

我可以毫无问题地执行它,没有任何分段错误。看起来很奇怪。

这是未定义的行为。您正在访问堆上的内存,许多编译器和操作系统不会阻止您这样做。但使用它是非常糟糕的做法。