我想知道为与结构相关的所有数据分配内存是否是一个好的做法。因此,假设我们有以下结构,其目的是存储浮点数组的数组:
#include <stdlib.h>
typedef struct Arrays {
int n;
int* l;
double** data;
} Arrays;
可以像这样初始化一个示例:
Arrays* UsualInitialization(int n) {
// Constructs an array A of length n containing the arrays A[i] = [1,2,...,i]
Arrays* A = (Arrays*) malloc(sizeof(Arrays*));
A->n = n;
A->l = (int*) malloc(n * sizeof(int));
A->data = (double**) malloc(n * sizeof(double*));
for (int i = 0; i < n; i++) {
A->l[i] = i + 1;
A->data[i] = (double*) malloc((i + 1) * sizeof(double));
for (int j = 0; j < i + 1; j++)
A->data[i][j] = j + 1;
}
return A;
}
每个指针都有一个对应的 malloc 调用。但我们也可以这样做:
Arrays* BlockInitialization(int n) {
// Constructs an array A of length n containing the arrays A[i] = [1,2,...,i]
Arrays* A = (Arrays*) malloc(sizeof(Arrays) +
n * sizeof(int) +
n * sizeof(double*) +
(n * (n+1) / 2) * sizeof(double)
);
A->n = n;
A->l = (int*) (A + 1);
A->data = (double**) (A->l + n);
A->data[0] = (double*) (A->data + n);
for (int i = 0; i < n; i++) {
A->l[i] = i + 1;
for (int j = 0; j < i + 1; j++)
A->data[i][j] = j + 1;
if (i < n - 1)
A->data[i+1] = A->data[i] + i + 1;
}
return A;
}
这里有一个 malloc 调用,所有其他指针都是通过指针算术和转换从第一个调用获得的。我认为这在某些情况下可能会更有效,因为所有分配的内存都在一个块中。
那么,我想问:这种做法是否会导致未定义的行为?它会影响性能吗(在某些情况下)?是不是让代码变得更加晦涩难懂?
我在下面留下了一个 print 方法和一个 main 方法,表明两种初始化给出了相同的结果(至少在我的机器上)。
void PrintArrays(Arrays* A) {
printf("Variable contains %d arrays: \n", A->n);
for (int i = 0; i < A->n; i++) {
printf("Array %d (of length %d): [", i, A->l[i]);
for (int j = 0; j < A->l[i]; j++) {
printf("%f", A->data[i][j]);
if (j < A->l[i] - 1) printf(", ");
}
printf("] \n");
}
}
int main() {
Arrays* A = BlockInitialization(10);
PrintArrays(A);
Arrays* B = UsualInitialization(10);
PrintArrays(B);
return 0;
}
那么,我想问:这种做法是否会导致未定义的行为?
如果你做得正确就不会。
这里唯一明显的问题是,对于奇数
n
,最终的指针和双打可能会错位。您需要使用 alignof
来计算连续成员之间需要多少填充。
它会影响性能吗(在某些情况下)?
是的,它可以在受益于缓存友好的内存布局的平台上使性能更好。
如果您丢失所有指针,展平二维数组并提供简单的访问器来获取/索引数组,可能会更好,因为即使看起来冗余的偏移量计算通常也比多个指针取消引用更快。
这会让代码变得更加晦涩吗?
是的,但是如果晦涩的位被封装在两个函数(分配器和释放器)中,你可以很好地注释它,彻底测试它,然后忘记它。