繁体   English   中英

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

[英]Weird behaviour with variable length arrays in struct in C

我遇到了一个被某些人称为“ Struct Hack”的概念,我们可以在结构内部声明一个指针变量,如下所示:

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

之后,当我们在main()函数中使用mallocstruct myStruct分配内存时,我们可以在同一步骤中同时为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()释放整个块,而在第二种情况下可以进行两次调用。

做实验时,我这样写:

#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循环中将“ i”增加到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;

是的,您在此处看到的是一个未定义行为的示例。

写超出已分配数组的末尾(aka缓冲区溢出)是未定义行为的一个很好的例子:它通常看起来像是“正常工作”,而其他时候它会崩溃(例如,“分段错误”)。

低级解释:内存中有一些控制结构,这些结构与您分配的对象相距一定距离。 如果您的程序发生较大的缓冲区溢出,则很有可能损坏这些控制结构,而对于较小的溢出,则将损坏一些未使用的数据(例如,填充)。 但是,无论如何,缓冲区溢出会调用未定义的行为。

第一种形式的“结构hack”也会调用未定义的行为(如警告所示),但是它是一种特殊的行为-几乎可以保证,在大多数编译器中,它始终可以正常工作。 但是,它仍然是未定义的行为,因此不建议使用。 为了批准其使用,C委员会发明了这种“灵活数组成员”语法(您的第二种语法),该语法可以保证正常工作。

为了明确起见-分配给数组的元素永远不会为该元素分配空间(至少不在C中)。 在C语言中,分配给元素时,即使数组是“ flexible”的,也应该已经分配了它。 您的代码应该知道分配内存时要分配多少。 如果您不知道要分配多少,请使用以下技术之一:

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

您所说的“结构黑客”确实是一种黑客。 这是不值得的IMO。

p->array = p+1;

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

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

我能够执行它而没有任何问题,没有任何分割错误。 对我来说看起来很奇怪。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM