【问题标题】:How to free memory allocated on double pointer struct如何释放在双指针结构上分配的内存
【发布时间】:2021-08-07 09:04:14
【问题描述】:

如何释放在这个结构中分配的内存

struct image_t {
    char type[3];
    int **ptr;
    int width;
    int height;
};

在第一个函数中我做了这些分配:

    struct image_t *struktura = (struct image_t *)malloc(sizeof(struct image_t));
    int **ptr = (int**)malloc(struktura->height * sizeof(int*));

    for (i = 0; i < struktura->height; i++) {
        *(ptr + i) = (int *)malloc(struktura->width * sizeof(int));
        if (*(ptr + i) == NULL) {
            break;
        }
    }

在第二个函数中,我必须释放分配的内存,所以我尝试释放类似这样的内存,但它不起作用

void destroy_image(struct image_t **m) {
    if (m != NULL) {
        if (*m != NULL) {
            if ((*m)->ptr != NULL) {
                for (int i = 0; i < (*m)->height; i++) {
                    free(*((*m)->ptr + i));
                }
                free((*m)->ptr);
                free(*m);
            }
        }
    }
}

我不能更改销毁函数的声明,所以结构上必须有双指针。

【问题讨论】:

  • 为了创建任何东西的**(模拟二维数组),这是一个两步过程(1)分配一个rows的指针块,(2)循环rows 次分配一个包含 cols 编号的块,您需要将地址依次分配给每个指针。要释放所有内存,只需反转该过程,例如(2) 循环rows 释放分配给每个指针的每个块的次数,然后 (1) 进行一次调用以释放指针。 (您可以将heightwidth 替换为rowscols
  • 在C中,malloc的返回不需要强制转换,没有必要。见:Do I cast the result of malloc?
  • 建议:如果您的ptr 是一个像素数据数组,我建议将其设为一个连续的数据数组,而不是二维数组。

标签: c struct allocation


【解决方案1】:

从...开始

  1. 不要转换malloc返回的值

  2. 不要使用*(ptr + i) 使用等效但更易读的版本,即ptr[i]

这样做会将您的分配更改为:

struct image_t *struktura = malloc(sizeof(struct image_t));
int **ptr = malloc(struktura->height * sizeof(int*));

for (i = 0; i < struktura->height; i++) {
    ptr[i] = malloc(struktura->width * sizeof(int));
    if (ptr[i] == NULL) {
        break;
    }
}

这是第一个问题......你永远不会将ptr 分配给任何东西。在代码块的最后需要添加:

struktura->ptr = ptr;

另一个问题是struktura-&gt;heightstruktura-&gt;width 在使用时都未初始化。必须在使用前为其赋值。

释放分配的内存:您当前的代码太复杂了。像 free(*((*m)-&gt;ptr + i)); 这样的语句包含 3 个指针取消引用!!。这很难读。我建议您使用一些局部变量来简化代码。

void destroy_image(struct image_t **m) {
    if (m == NULL) return;
    if (*m == NULL) return;
    struct image_t *t = *m;
    int **ptr = t->ptr;
    if (ptr != NULL)
    {
        for (int i = 0; i < t->height; i++) 
        {
            free(ptr[i]);
        }
        free(ptr);
    }
    free(t);
    *m = NULL;  // The only reason to pass a double pointer to this
                // function is to be able to change *m. I guess
                // the prototype authoe wants the function to set
                // *m to NULL
}

通过使用这些局部变量,代码比free(*((*m)-&gt;ptr + i));之类的代码更容易阅读

对于分配代码中的break...

在前两个malloc 之后没有检查NULL,然后在循环中执行,这有点奇怪(一个错误?)。此外,使用break 有点奇怪,因为它会使其余的指针未初始化。

无论如何 - 如果你真的想在分配代码中使用 break,你还需要在释放内存时考虑到这一点。比如:

void destroy_image(struct image_t **m) {
    if (m == NULL) return;
    if (*m == NULL) return;
    struct image_t *t = *m;
    int **ptr = t->ptr;
    if (ptr != NULL)
    {
        for (int i = 0; i < t->height && ptr[i] != NULL; i++) 
        {
            free(ptr[i]);
        }
        free(ptr);
    }
    free(t);
    *m = NULL;
}

【讨论】:

    【解决方案2】:

    为了使您的销毁函数正常工作,指针数组中的所有指针必须是有效的或空的。 malloc() 返回的内存未初始化,因此在分配函数中跳出循环会使指针数组的其余部分未初始化,因此不应传递给 free()

    还要注意你应该测试结构指针和指针数组的分配失败。此外,新分配结构的 widthheight 成员未初始化:您应该使用函数参数来初始化它们。

    destroy_image 函数应该在释放后将*m 设置为NULL,并且必须free(*m);,即使(*m)-&gt;ptr 是空指针。

    以下是此问题的解决方案:

    • 使用calloc() 分配数组(在所有情况下都是一个好主意)或
    • height 设置为成功分配的指针数。
    • 将数组中剩余的指针显式设置为NULL
    • 在分配失败时释放分配的块并返回NULL

    这是修改后的版本:

    #include <stdlib.h>
    
    struct image_t {
        char type[3];
        int **ptr;
        int width;
        int height;
    };
    
    struct image_t *allocate_image(int width, int height) {
        struct image_t *struktura = calloc(1, sizeof(*struktura));
    
        if (struktura == NULL)
            return NULL;
    
        // should initialize struktura->type too
        struktura->width = width;
        struktura->height = height
        struktura->ptr = calloc(height, sizeof(*struktura->ptr));
        if (struktura->ptr == NULL) {
            free(struktura);
            return NULL;
        }
    
        for (int i = 0; i < height; i++) {
            struktura->ptr[i] = calloc(sizeof(*struktura->ptr[i]), width);
            if (struktura->ptr[i] == NULL) {
                // Iterate downwards on index values of allocated rows
                // (i --> 0) is parsed as (i-- > 0)
                // this test works on signed and unsigned index types, unlike (--i >= 0)
                while (i --> 0) {
                    free(struktura->ptr[i]);
                }
                free(struktura->ptr);
                free(struktura);
                return NULL;
            }
        }
        return struktura;
    }
    
    void destroy_image(struct image_t **m) {
        if (m != NULL) {
            struct image_t *p = *m;
            
            if (p != NULL) {
                if (p->ptr != NULL) {
                    for (int i = 0; i < p->height; i++) {
                        free(p->ptr[i]);
                    }
                    free(p->ptr);
                }
                free(p);
                *m = NULL;
            }
        }
    }
    

    【讨论】:

    • 我真的不同意这些在 malloc 失败时过于迂腐的清理工作。如果您的堆内存不足,您的程序可能会吐司,唯一明智的做法是尽可能优雅地停止它。在这种情况下,手动清理堆并不重要。
    • @DavidC.Rankin: *m*m = NULL; 之前被释放。 *m = NULL; 的真正目的是清除调用者空间中的悬空指针。不是一个完美的解决方案,因为这个指针可能在其他地方被复制,但是一个合理的解决方案。
    • @Lundin:如果结构不能完全分配,清理并返回NULL。在某些情况下,使用失败时中止的包装器很好,但在其他情况下,这是一种危及生命的方法。
    • @chqrlie - 是的,p 的使用以及与原始版本的比较引发了这个问题。谢谢。
    • @TruthSeeker:感谢您的编辑,但您的测试修复不正确,我碰巧喜欢优雅的downto operator
    【解决方案3】:

    这是不必要的复杂和低效。看看Correctly allocating multi-dimensional arrays

    你可以像这样改变结构:

    struct image_t {
        char type[3];
        int* ptr; // placeholder pointer
        int width;
        int height;
    };
    

    然后这样 malloc:

    struct image_t* s = malloc(sizeof *s);
    s->width  = width;
    s->height = height;
    s->ptr    = malloc( sizeof(int[width][height]) );
    

    (记得检查每个malloc的结果,如果返回NULL则停止程序。)

    然后像这样使用它:

    int (*arr)[s->height] = (void*) s->ptr; // convert to a temporary 2D array pointer
    ...
    arr[i][j] = whatever; // this enables 2D array access syntax
    

    然后像这样释放它:

    free(s->ptr);
    free(s);
    

    【讨论】:

    • 或者你可以使用旧样式的“mangled arrays”s-&gt;ptr = malloc(width*height*sizeof(int)); ... (s-&gt;ptr)[i*s-&gt;width + j],但这不太可读。
    • 使用真正的 2D 数组是个好建议,但具有两个维度变量的 2D 数组是 C11 可选功能,不能指定为结构成员。此外,我怀疑 OP 可以更改结构定义或分配策略:分配 2D 数组并初始化指针数组以指向其每一行似乎更有效,但如果其他代码重新分配单个行,则会失败。
    • @chqrlie 不,这也没有意义。在这种情况下,他们需要将各个尺寸存储在某处,而不是使用称为高度和宽度的两个成员。您描述的结构听起来像一个结构数组,每个结构都存储数据和大小。
    • @DavidC.Rankin 我不明白您所说的“违反技术界限”是什么意思。
    • @Lundin:通过重新分配,我的意思是改变行指针。即使没有调用free,仅仅交换第一个和第二个指针也会使二维数组不再可用,因为它的基地址不再已知。
    猜你喜欢
    • 2016-04-01
    • 2014-02-15
    • 2021-11-26
    • 2017-05-07
    • 1970-01-01
    • 2021-12-11
    • 2017-08-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多