【问题标题】:Proper way of freeing pointers when using method chaining使用方法链接时释放指针的正确方法
【发布时间】:2019-09-23 08:21:35
【问题描述】:

我目前正在用 c 编写多种不同的矩阵乘法实现。所以我写了矩阵加减的实用函数,结构如下:

int** m_sub(int** a, int** b, int size) {
    int **result = get_matrix(size);
    // calculate addition of a & b
    return result;

}

get_matrix():

int** get_matrix(int size){
    int** m = malloc(size*sizeof(int));
    for (int i=0;i<size;i++){
        m[i] = malloc(size*sizeof(int));
    }
    return m;

}

我设计了所有可以使用并返回 int** 的函数,因为据我了解,您不能在 c 中返回可变大小的二维数组。

现在程序可以正常工作,但是通过运行内存检查器(Valgrind),我发现调用的所有返回值,例如m_add 导致内存泄漏,因为我在方法链接中直接使用返回值,而我没有 free() 它们,例如:

m_copy(c_11,m_add(matmul_recursive(n/2, a,e),matmul_recursive(n/2, b, g), n/2), n/2);

我知道我可以通过将每个返回的 int** 分配给一个变量并随后手动释放它来解决此问题。但是,这将导致大量临时分配的变量,因此(可以说)代码的可读性降低。

现在我想知道在 c 中是否有“正确”的方式来做到这一点?还是我返回矩阵的 int** 指针这一事实已经是一个糟糕的设计选择?

【问题讨论】:

  • 你不能反过来做吗?循环释放m[i] 指针,最后释放**m?
  • (@Inian 表示循环中的free (m[i]);,然后是free (m);
  • 是的,我知道如何通过循环释放从get_matrix() 获得的矩阵。问题更多的是“即使我没有将返回的int** 分配给变量而是直接将其用作另一个函数的参数,我是否可以释放它们?”
  • int** m = malloc(size*sizeof(int)); 是非常错误的,它假定指针的大小是整数的大小。它应该是int **m = malloc(size * sizeof *m);,即sizeof (int *),但不重复类型,并对相关变量进行一点“锁定”。

标签: c memory-management free


【解决方案1】:

问题在于 C 没有自动析构函数调用的概念,就像 C++ 一样。因此,AFAIK,作为程序员,您有责任释放已分配的任何内容。

当然,您可以想象一个库,它保留了它已分配的任何内容的引用,并提供了一种在您不再需要它时释放它的方法。例如,您可以实现一个动态堆栈来存储每个新分配的内存块,然后使用该堆栈来回收整个内存。

只是我懒得写它的实现......但如果你想这样做但不能从一开始就实现它,请随时在评论中联系我。

【讨论】:

  • 我一直在考虑类似的事情。因此,您会建议在计算中使用某种“全局”堆栈并将所有临时变量推送到该堆栈上,对吧?
  • 没错。它可能是编译单元中包含分配和释放例程的静态成员 - 请注意,不要忘记显式初始化它,因为 C 没有自动构造函数调用。或者,它可以是分配/解除分配例程的参数。
  • 您可以将at_exit 用作“穷人的析构函数”,但它确实很差,因为它只能用于在程序终止时释放对象,而不是在不再使用对象时。跨度>
  • +1 表示堆栈的想法。对内存负责并不意味着你不能编写抽象来帮助你。
  • @Lundin 我认为那时唯一真正的好处就是让valgrind开心。
【解决方案2】:

实际上手动释放每个指针是在 C 中执行此操作的正确方法,因为正如 Serge 所写,在 C 中没有自动析构函数调用。但是,您可以使其手动且美观,而无需求助于动态堆栈或垃圾收集器之类的解决方案在我看来,这通常是一个巨大的矫枉过正。所以不用多说。

  1. 命名创建函数,以便清楚地分配内存,如 create... 或 allocate... 或 new... 而不是 get。让我们在这里假设创建/删除命名法。所以:

    int* create_matrix(int size);

注意我使用的是int*,而不是int**。更方便,不需要你做所有的第二维分配,你只需分配一个大小为n*n的数组。在内存中这是相同的空间。但是,如果您愿意,可以坚持使用 2 维。

  1. 接下来是析构函数

    void delete_matrix(int* matrix);

  2. 现在我们有了一些漂亮的语法,我们修改您的操作原型。不是采用两个数组并返回一个新分配的数组,而是对第一个参数进行操作。您分配数据的乘法函数并不好,因为假设乘法分配新数据而不是按值返回它是不直观的。所以:

    int* matmul_recursive(int* left, const int* right, int size);

请注意,我们不会修改 right,因此是 const。我们将修改left,因此没有常量。现在写下leftright 的乘法,并将结果放入left。没有额外的分配意味着没有泄漏。该函数应返回 left 以方便链式调用。

请注意,您可以添加 int* create_matrix_copy(const int* source, int size); 来克隆矩阵。

另请注意,您通常不需要操作后的操作参数。您将对生成的累积矩阵感兴趣。请参阅此示例(使用您的函数名称):

int* arg1 = create_matrix(n);
int* arg2 = create_matrix(n);
int* product1 = create_matrix(n);
int* product2 = create_matrix(n);
int* result = m_add(matmul_recursive(product1, arg1, n), matmul_recursive(product2, arg2, n), n);
delete_matrix(arg1);
delete_matrix(arg2);
delete_matrix(product2);
printf("%d", product1[0]);
printf("%d", result[0]); // result == product1
delete_matrix(product1);

还可以在适用的情况下使用const,即您不更改参数的值,这有助于避免错误。还可以考虑使用unsigned int 作为尺寸。

你可能会更频繁地对一个矩阵进行许多操作,因此这将更常见(如 3D 转换):

int* result = create_matrix(n);
int* rotate = create_matrix(n);
int* translate = create_matrix(n);
int* scale = create_matrix(n);
matmul_recursive(matmul_recursive(matmul_recursive(result, rotate, n), translate, n), scale, n);
printf("%d", result[0]);
// Remember to delete all arguments.

【讨论】:

  • 感谢您的深入回答,我想我明白了。但是有一个问题:我不能像您在我的案例中提出的那样使用就地修改,因为对于我正在实施的算法,我可能需要int **c1 = m_add(a, b),然后,例如int** c2 = m_sub(c, a)。所以我不能在第一条语句中修改a。你会怎么做呢?
【解决方案3】:

C 中你没有C++ 中的奢侈品,所以内存释放只留给你。如果您真的想避免一一取消分配矩阵,您可以执行以下操作:

创建一个struct,负责存储创建的矩阵的所有地址(我们称之为容器)。

  1. 因此,您的函数还将采用指向此矩阵容器对象的指针。当 get_matrix 被调用时,新分配的矩阵的地址被添加到容器中。

  2. 在计算结束时您需要做的就是正确释放容器。

类似下面的东西(下面的伪代码):

int** get_matrix(int size, struct MatricesContainer* cnt){ //takes the container
    int** m = malloc(size*sizeof(int));
    for (int i=0;i<size;i++){
        m[i] = malloc(size*sizeof(int));
    }
    vector_push_back(m, cnt); //push_the address in the container
    return m;
}

int** m_sub(int** a, int** b, int size, struct MatricesContainer* cnt) {
    int **result = get_matrix(size, cnt); //address of result is stored in cnt
    // calculate addition of a & b
    return result;
}

正常使用,然后在函数中销毁

int heavy_calculation(...)
{
struct MatricesContainer* cnt
   .....
   m_copy(c_11,m_add(matmul_recursive(n/2, a,e, cnt),matmul_recursive(n/2, b, g,cnt),   n/2), n/2,cnt); 
   ...
   destroy_container(cnt);
}

destroy_container 循环遍历矩阵向量并一一释放。

请注意get_matrix 中的评论有问题。 int** m = malloc(size*sizeof(int)); 应该是 int** m = malloc(size*sizeof(int*));,因为您需要为指针分配空间,而不是为 ints。

【讨论】:

    猜你喜欢
    • 2020-05-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-16
    • 1970-01-01
    • 1970-01-01
    • 2022-01-13
    相关资源
    最近更新 更多