将函数返回的指针作为输入传递给其他函数时如何避免内存泄漏?

How to avoid memory leak when passing function-returned pointer as input to other function?

我正在尝试编写一个简单的 C 矩阵库,它动态分配所需的内存并允许 "nest" 函数调用(如 fun1(fun2(x)))以避免声明太多临时变量执行长时间操作时。但是,我无法摆脱因从未释放函数内部创建的结构而导致的内存泄漏。下面的例子清楚地表明了这一点。关于如何在不声明其他临时变量的情况下解决该问题的任何建议?

非常感谢

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

struct _MatrixF{
    uint8_t rows;
    uint8_t cols;
    float* mat;
};
typedef struct _MatrixF* Matrix;

Matrix newMatrix(uint8_t rows, uint8_t cols){
    Matrix matrix = malloc(sizeof(struct _MatrixF));
    matrix->rows = rows;
    matrix->cols = cols;
    matrix->mat = malloc(rows * cols * sizeof(float));
    return matrix;
}

Matrix matIdentity(uint8_t rows, uint8_t cols){
    Matrix matrix = newMatrix(rows, cols);
    uint16_t ii;
    for (ii = 0; ii < (cols * rows); ii++)
        matrix->mat[ii] = (((ii / cols) == (ii % cols))? 1.0f : 0.0f);
    return matrix;
}


Matrix matAdd(Matrix lhs, Matrix rhs){
    Matrix m = newMatrix(lhs->rows, lhs->cols);
    uint16_t ii;
    for (ii = 0; ii < (lhs->cols * lhs->rows); ii++) {
        m->mat[ii] = lhs->mat[ii] + rhs->mat[ii];
    }
    return m;
}

Matrix matMult(Matrix lhs, Matrix rhs){
    uint8_t i, j, k;
    Matrix result = newMatrix(lhs->rows, rhs->cols);
    for (i=0; i < lhs->rows; i++)
        for (j=0; j < rhs->cols; j++)
            for (k=0; k < lhs->cols; k++)
                MAT(result, i, j) += MAT(lhs, i, k) * MAT(rhs, k, j);
    return result;
}

void mprint(Matrix m){
    printf("%dx%d\n", m->rows, m->cols);
    for(int i=0; i<m->rows; i++) {
        for (int j = 0; j<m->cols; j++)
            printf("%4.6f\t",(float) m->mat[(i) * m->cols + (j)]);
        printf("\n");
    }
}

int main(int argc, const char * argv[]) {
    Matrix A = matIdentity(4, 3);
    Matrix B = matIdentity(3, 2);
    Matrix C = matIdentity(2, 4);
    Matrix D = matIdentity(4, 4);
    uint16_t len = 64000;
    while (len--){
        Matrix E = matAdd(D, matAdd(D, matMult(matMult(A, B),C)));
        mprint(matMult(A, B));
        mprint(A);
        mprint(E);
    }
    return 0;
}

C 在自动处理这个问题上没有提供太多帮助。您可以存储指向临时对象的指针,并在每次迭代结束时释放它们:

while (len--){
    Matrix t1, t2, t3, t4;
    Matrix E = matAdd(D, t1=matAdd(D, t2=matMult(t3=matMult(A, B),C)));
    mprint(t4=matMult(A, B));
    mprint(A);
    mprint(E);
    matFree(t1);
    matFree(t2);
    matFree(t3);
    matFree(t4);
}

如果矩阵很小,按值传递它们。您将必须静态定义大小,并为每个(有用的)大小准备不同的结构和功能集。这将更快(没有分配,没有指针追逐)和更安全(没有泄漏,释放后没有使用,没有空指针)。

struct mat4 {
    float mat[4*4];
};

struct mat4 matIdentity4(void) {
    struct mat4 identity = {
        1,0,0,0,
        0,1,0,0,
        0,0,1,0,
        0,0,0,1,
    };
    return identity;
}

...

struct mat3 {
    float mat[3*3];
};

...

如果大小变化太大但仍然很小,您仍然可以按值传递它们。您只需要选择宽度和高度的上限:

enum {
    MAX_MATRIX_SIZE = 16,
};

struct Matrix {
    int rows;
    int cols;
    float mat[MAX_MATRIX_SIZE*MAX_MATRIX_SIZE];
};

如果矩阵很大,你不想随意分配和复制它们。意外的临时矩阵可能会使您超出可用内存限制。让用户显式分配和释放它们。您可能还希望您的操作改变它们的参数之一(到安全内存、安全缓存和简化清理):

struct Matrix *result = matNew(10,10);
struct Matrix *tmp = matNew(10,10);
matSetToIdentity(result);
fun1(tmp);
matAdd(result, tmp);
fun2(tmp);
matAdd(result, tmp);
matPrint(result);
matFree(tmp);
matFree(result);

如果内存不是问题,您可以保留对已分配矩阵的引用,并在不再需要它们时立即释放它们:

struct Matrix{
    struct Matrix* next; // List embedded in elements.
                         // See: https://isis.poly.edu/kulesh/stuff/src/klist/
    int rows;
    int cols;
    float mat[];
};

struct MatrixPool {
    struct Matrix* last;
};

struct Matrix *matIdentity(struct MatrixPool *pool, int rows, int cols) {
    struct Matrix* matrix = malloc(sizeof(struct Matrix)
                                    +sizeof(float)*rows*cols);
    matrix->next = pool->last;
    matrix->rows = rows;
    matrix->cols = cols;
    matrix->mat = ...;
    pool->last = matrix;
    return matrix;
}

void matrixPoolFree(struct MatrixPool *pool) {
    while(pool->last != NULL) {
        struct Matrix* matrix = pool->last;
        pool->last = matrix->next;
        free(matrix);
    }
}

int main() {
    struct MatrixPool pool = {0};
    struct Matrix* x = matIdentity(&pool, 10, 10);
    struct Matrix* y = fun1(&pool, fun2(&pool, x));
    mprint(y);
    matrixPoolFree(&pool);
}

接下来的两个选项仅供参考。请不要在实际代码中使用它们。使用带有垃圾收集器(Java、Python)或 RAII(C++、Rust)的语言会更明智。

如果您不想在任何地方定义和传递 pool,您可以使用宏和 alloca(或者可能是可变长度数组)来实现自动化:

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

struct Matrix{
    int rows;
    int cols;
    float mat[];
};

#define allocaMatrix(w, h) alloca(sizeof(struct Matrix)+w*h*sizeof(float))

struct Matrix *aux_matIdentity(struct Matrix *mat, int w, int h) {
    mat->rows = w;
    mat->cols = h;
    for(int y=0;y<mat->cols;y++) {
        for(int x=0;x<mat->rows;x++) {
            mat->mat[y*mat->rows+x] = x==y;
        }
    }
    return mat;
}

#define matIdentity(w,h) aux_matIdentity(allocaMatrix(w,h),w,h)

struct Matrix *aux_matAdd(struct Matrix *result,struct Matrix *left, struct Matrix *right) {
    result->rows = left->rows;
    result->cols = left->cols;
    for(int y=0;y<result->cols;y++) {
        for(int x=0;x<result->rows;x++) {
            result->mat[y*result->rows+x] = left->mat[y*result->rows+x] + right->mat[y*result->rows+x];
        }
    }
    return result;
}

#define matAdd(left,right) (aux_matAdd(allocaMatrix(left->rows,left->cols), left, right))

void matPrint(struct Matrix *mat) {
    for(int y=0;y<mat->cols;y++) {
        for(int x=0;x<mat->rows;x++) {
            printf("%.1f%c",mat->mat[y*mat->rows+x], x==mat->rows-1?'\n':' ');
        }
    }
}

int main() {
    int n = 16;
    struct Matrix *mat1 = matIdentity(n,n);
    struct Matrix *mat2 = matIdentity(n,n);
    matPrint(matAdd(mat1, mat2));
}

这将为您节省一些击键次数,但这些对象的生命周期变得非常不明显。你不能 return then 从一个函数,如果你分配了很多(例如在一个循环中)或者只是一个大的,你可以粉碎堆栈。

如果你想真正自由地传递这些指针,你可以bastardize garbage collector into C