C 自由动态结构数组 - 为什么它们没有传染性

C Free Dynamic Array of Structs - Why are they not contigous

我正在努力处理一个必须包含较小结构的动态数组的 C 结构:

typedef struct issueStruct {
    int data;
} issue;

typedef struct volumeStruct {
    issue* collection;
    size_t elements;
} volume;

我可以在卷结构数组中动态创建任意数量的问题结构。我还可以遍历该数组:

int main(){
    volume* TimeMagazine = (volume*)malloc(sizeof(volume));
    TimeMagazine->collection = (issue*)malloc(4 * sizeof(issue));
    TimeMagazine->elements = 4;

    issue* ptr = TimeMagazine->collection;
    int i;

    // Populate & iterate through array:
    i = 0;
    while(i < TimeMagazine->elements){
            ptr->data = 100*i;
            printf("%d)  %d\n", i, ptr->data);
            i++;
            ptr = ptr+i;       // Advance ptr
    }
    return 0;
}

OUTPUT:
[Linux]$ gcc -Wall magazines.c
[Linux]$ ./a.out
0)  0
1)  100
2)  200
3)  300
[Linux]$

到目前为止,还不错。当我在 GDB 中逐步执行上述操作时,一切看起来都很好,尽管我注意到问题结构似乎没有连续的内存地址。这是我看到的内存地址:

issue 0)  0x602030
issue 1)  0x602034
issue 2)  0x60203c
issue 3)  0x602048

这让我有些犹豫;我会假设所有问题都相隔 4 个字节,如 sizeof(issue) = 4。更严重的是,当我修改 "iterate through" 代码以释放数组元素时,我的代码出现段错误。具体来说,它在尝试释放第二个问题时出错。这是代码:

    i = 0;
    ptr = TimeMagazine->collection;
    issue* ptr2 = ptr;
    while(i< TimeMagazine->elements){
            printf("freeing %d...\n", i);
            i++;
            free(ptr2);           // free ptr2
            ptr2 = ptr = ptr+i;   // advance ptr & ptr2
    }

这是错误(Linux 上的 GCC):

*** Error in `./a.out': free(): invalid pointer: 0x000000000137c034 ***

所以我确定我在这里遗漏了一些东西,但不确定是什么。有人可以推荐一种有效的方法来释放()数组元素吗?

非常感谢!

-皮特

PS - 有很多 "freeing structs in array" 帖子,但 none 似乎与我正在做的完全吻合。所以我发布这个是希望我的这个问题的版本是独一无二的。

while(i < TimeMagazine->elements){
        ptr->data = 100*i;
        printf("%d)  %d\n", i, ptr->data);
        i++;
        ptr = ptr+i;       // Advance ptr
}

您在 ptr = ptr+i 中使用了错误的指针算法,应该是 ptr = ptr+1 或者您访问了边界之外。 free 部分相同。

正如@kaylum 在评论中指出的那样:您在循环中调用 free,这也是错误的,您可以立即 free(TimeMagazine->collection); 因为您正在保留 space对于同一块中的 4 个元素。

这是关于连续内存和包含动态数组的结构的旁注。实际答案参考.

如前所述,一个malloc ==一个free

但是,未提及的事实是,出于缓存方面的考虑,连续内存通常性能更好。

这意味着如果使用相同的 malloc 调用分配内存和动态数组,您的 struct volumeStruct 会表现得更好。

有两种常见的方法可以实现这一点。

第一,使用与您当前相同的结构(我将您的循环固定为 ptr = ptr + 1,因此我们不会越界):

int main(){
    volume* TimeMagazine = (volume*)malloc(sizeof(volume) + (4 * sizeof(issue)) );
    TimeMagazine->collection = TimeMagazine + 1; // pointer arithmetics
    TimeMagazine->elements = 4;

    issue* ptr = TimeMagazine->collection;
    int i;

    // Populate & iterate through array:
    i = 0;
    while(i < TimeMagazine->elements){
            ptr->data = 100*i;
            printf("%d)  %d\n", i, ptr->data);
            i++;
            ptr = ptr+1;       // Advance ptr
    }

    free(TimeMagazine);

    return 0;
}

另一个选项(我认为这是在 C99 中引入的)是在结构的末尾添加一个可变长度数组。这为您节省了 collection 指针所需的 8(或 4)个字节。

即:

typedef struct issueStruct {
    int data;
} issue;

typedef struct volumeStruct {
    size_t elements;
    issue collection[];
} volume;

int main(){
    volume* TimeMagazine = (volume*)malloc(sizeof(volume) + (4 * sizeof(issue)) );
    TimeMagazine->elements = 4;
    // no need to assign a value for TimeMagazine->collection

    issue* ptr = TimeMagazine->collection;
    int i;

    // Populate & iterate through array:
    i = 0;
    while(i < TimeMagazine->elements){
            ptr->data = 100*i;
            printf("%d)  %d\n", i, ptr->data);
            i++;
            ptr = ptr+1;       // Advance ptr
    }

    free(TimeMagazine);

    return 0;
}

最大的好处是 CPU 内存缓存和更简单的代码。在大多数情况下,我们为每个对象保存两个系统调用(一个 malloc 和一个 free)这一事实是无关紧要的。