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



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

Thanks a lot

#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%dn", m->rows, m->cols);
    for(int i=0; i<m->rows; i++) {
        for (int j = 0; j<m->cols; j++)
            printf("%4.6ft",(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);
}

如果矩阵较小,则按值传递。您必须静态地定义大小,并为每个(有用的)大小准备不同的结构和函数集。这将更快(没有分配,没有指针跟踪)和更安全(没有泄漏,没有free之后的使用,没有空指针)。

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));
}

这将为您节省一些按键,但这些对象的生命周期变得非常不明显。你不能从函数中返回它们,如果你分配了很多它们(例如在循环中),或者只分配了一个大的,你可以破坏堆栈。

如果你真的想自由地传递这些指针,你可以把垃圾收集器变成c语言。

相关内容

  • 没有找到相关文章

最新更新