我正在尝试编写一个简单的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语言。