C 为一个块中的不同类型分配内存
我对C语言中的动态内存分配有一个疑问。 我目前正在用C语言进行人工神经网络的实现。 我发现了一个名为的现有项目,我注意到了一种我并不熟悉的内存分配方法 考虑一个结构:C 为一个块中的不同类型分配内存,c,dynamic-memory-allocation,C,Dynamic Memory Allocation,我对C语言中的动态内存分配有一个疑问。 我目前正在用C语言进行人工神经网络的实现。 我发现了一个名为的现有项目,我注意到了一种我并不熟悉的内存分配方法 考虑一个结构: typedef struct { int an, bn; double *a, *b } foo; 和一个init函数: foo *foo_init(int an, int bn) { foo *f; f = malloc(sizeof(*f)); f->an = an;
typedef struct
{
int an, bn;
double *a, *b
} foo;
和一个init函数:
foo *foo_init(int an, int bn)
{
foo *f;
f = malloc(sizeof(*f));
f->an = an;
f->bn = bn;
f->a = malloc(an*sizeof(*f->a));
f->b = malloc(bn*sizeof(*f->b));
return f;
}
这是一个不同的init函数,与上述项目类似:
foo *foo_init(int an, int bn)
{
foo *f;
f = malloc(sizeof(*f) + an*sizeof(*f->a) + bn*sizeof(*f->b));
f->an = an;
f->bn = bn;
f->a = (double*)((char*)f + sizeof(*f)); // Could be f->a = (double*)(f + 1); ?
f->b = f->a + an;
return f;
}
所以我在想这两种方法的区别是什么。我能想到的第二种方法的唯一优点是,我只需要分配和释放一个内存块,但由于它只是一个init函数,可能只调用一次,因此性能差异应该是微不足道的。另一方面,不同类型的指针指向同一个memeory块,但我认为这并不违反严格的别名规则,因为它们指向块中的不同内存(?)。调整大小可能很困难,因为它不能像第一个init函数那样使用简单的realloc来完成。例如,如果我想将a和b缩小一倍,并重新分配整个内存块,最后两个b值将丢失(而不是一个a,一个b)
我的结论是,最好用第一种方式分配内存,因为第二种方式几乎只有缺点。init函数中有一个是错误的做法吗?我是否遗漏了一些可以使第二个函数更好的功能?也许他们在项目中使用第二个是有特殊原因的
提前感谢。如果您分配并免费使用这些结构,那么节省下来的成本可能会非常可观 它使用的空间少了一点,因为每个分配的块都有一些记录其大小的簿记。它还可以减少内存碎片 此外,这样做可以确保
a
和b
数组在内存中靠得很近。如果它们经常一起使用,这可以提高缓存命中率
库实现者经常进行这些微优化,因为他们无法预测库将如何使用,并且他们希望在所有情况下都尽可能好地工作。当您编写自己的应用程序时,您可以更好地了解哪些代码将位于对性能调整非常重要的内部循环中。如果您分配并释放了大量这些结构,那么节省下来的成本将是非常重要的 它使用的空间少了一点,因为每个分配的块都有一些记录其大小的簿记。它还可以减少内存碎片 此外,这样做可以确保
a
和b
数组在内存中靠得很近。如果它们经常一起使用,这可以提高缓存命中率
库实现者经常进行这些微优化,因为他们无法预测库将如何使用,并且他们希望在所有情况下都尽可能好地工作。当您编写自己的应用程序时,您可以更好地了解哪些代码将位于对性能调整有重要意义的内部循环中。。由于对齐,以下选项可能会失败
f->a = (double*)((char*)f + sizeof(*f));
malloc()
返回的指针对所有对象指针都有效。计算的((char*)f+sizeof(*f))
可能不符合对齐要求。在这种情况下,事情很可能会成功,但对于其他typedef
s就不是这样了
自C99以来,代码可以使用灵活的数组成员来获取单个分配的最大收益,而不必冒UB风险。语法也更简单
typedef struct {
int an, bn;
double *a, *b;
// Padding will be added here as needed to meet `data[]` alignment needs
double data[];
} foo;
foo *foo_init(int an, int bn) {
// `sizeof *f` includes space for `an,bn,a,b`
// and optional alignment padding up to `data`, but not `data`.
foo *f = malloc(sizeof *f + (an + bn) * sizeof *(f->data));
if (f) {
f->an = an;
f->bn = bn;
f->a = (double*) (f + 1); // Guaranteed to align to f->data
f->b = f->a + an;
}
return f;
}
考虑
size\u t
而不是intan,bn
在C99之前的版本中,可以使用
union
来实现这一点。差不多
typedef union {
foo f;
double data;
} both;
foo *foo_init(int an, int bn) {
both *p = malloc(sizeof(both) + (an + bn) * sizeof *(f->data));
foo *f = (foo *) p;
if (f) {
f->an = an;
f->bn = bn;
// v--- Note p
f->a = (double*) (p + 1); // Guaranteed to align to p->data type
f->b = f->a + an;
}
return f;
}
第二个方法问题。由于对齐,以下选项可能会失败
f->a = (double*)((char*)f + sizeof(*f));
malloc()
返回的指针对所有对象指针都有效。计算的((char*)f+sizeof(*f))
可能不符合对齐要求。在这种情况下,事情很可能会成功,但对于其他typedef
s就不是这样了
自C99以来,代码可以使用灵活的数组成员来获取单个分配的最大收益,而不必冒UB风险。语法也更简单
typedef struct {
int an, bn;
double *a, *b;
// Padding will be added here as needed to meet `data[]` alignment needs
double data[];
} foo;
foo *foo_init(int an, int bn) {
// `sizeof *f` includes space for `an,bn,a,b`
// and optional alignment padding up to `data`, but not `data`.
foo *f = malloc(sizeof *f + (an + bn) * sizeof *(f->data));
if (f) {
f->an = an;
f->bn = bn;
f->a = (double*) (f + 1); // Guaranteed to align to f->data
f->b = f->a + an;
}
return f;
}
考虑
size\u t
而不是intan,bn
在C99之前的版本中,可以使用
union
来实现这一点。差不多
typedef union {
foo f;
double data;
} both;
foo *foo_init(int an, int bn) {
both *p = malloc(sizeof(both) + (an + bn) * sizeof *(f->data));
foo *f = (foo *) p;
if (f) {
f->an = an;
f->bn = bn;
// v--- Note p
f->a = (double*) (p + 1); // Guaranteed to align to p->data type
f->b = f->a + an;
}
return f;
}
@阿查尔我不这么认为。@achal:不应该是
*(f->a)
,但既然->
绑定更强,我就不需要括号了。@achal我不这么认为。@achal:不应该是*(f->a)
但是既然
绑定更强,我就不需要括号了。是的。它们在一个内存分配块(而不是三个单独分配的块)中分配单个foo
结构所需的所有存储,然后将指针成员设置为指向该块的部分。这节省了空间和分配时间,也使结构的解除分配更容易、更快。@DavidRTribble:但它如何节省空间?@Osiris它节省了malloc()
用来记录每个分配块大小的簿记空间。@Barmar:好的,我知道我会保存分配和释放命令。你的意思是,如果我在一个块中分配所有内容,并且经常访问a和b组合的元素,那么这个值已经在缓存中的概率很高,因为CPU通常会加载其中的整个或部分块,如果a和b位于完全不同的内存位置,这是不会发生的?是的,这就是我的意思。这就是所谓的“地方性”。是的。它们在一个内存分配块(而不是三个单独分配的块)中分配单个foo
结构所需的所有存储,然后将指针成员设置为指向该块的部分。这节省了空间和分配时间,也使结构的解除分配变得更容易、更快。@DavidRTribble:但它如何节省空间呢?@Osiris它节省了malloc()
用来记录每个分配的大小的簿记空间