下面是一个直接变量声明的例子:
double multiplyByTwo (double input) {
double twice = input * 2.0;
return twice;
}
下面是一个动态内存分配的例子:
double *multiplyByTwo (double *input) {
double *twice = malloc(sizeof(double));
*twice = *input * 2.0;
return twice;
}
如果我有选择的话,我会一直使用直接变量声明,因为代码看起来更可读。在什么情况下动态内存分配更合适?
在什么情况下动态内存分配更合适?
当编译时不知道分配大小时,我们需要使用动态内存分配。
除了上述情况,还有其他一些情况,如
-
如果我们想要一个在运行时重新调整大小的数据结构,我们需要使用动态内存分配
-
动态分配的内存的生命周期保持有效,除非它是
free()
d。有时,当从函数调用返回某个变量的地址时,它很方便,否则,与auto
变量一起,将超出作用域。 -
通常堆栈大小会受到适度限制。如果您想要创建和使用一个巨大的数组,最好使用动态内存分配。
使用malloc的动态内存分配将内存放在堆上,因此它在离开函数时不会被销毁。
稍后您需要手动释放内存。
直接声明位于堆栈上,并在离开函数时删除。return语句中所发生的是,在销毁变量之前创建一个变量的副本。
考虑这个例子:
堆void createPeople():
struct person *p = makePerson();
addToOffice(p);
addToFamily(p);
Vs。在堆栈
void createPeople():
struct person p = makePerson();
addToOffice(p);
addToFamily(p);
在第一种情况下,只创建一个人并将其添加到office和family。现在,如果这个人被删除了,他在办公室和家庭中都是无效的,而且,如果他的数据被改变了,它在两者中也都被改变了。
在第二种情况下,为办公室和家庭创建该人的副本。现在可能发生的情况是,您更改了办公室副本的数据,而家庭副本保持不变。
所以基本上如果你想让多方访问同一个对象,它应该在堆栈上。
"如果我有选择的话,我会一直使用直接变量声明"
你也应该这么做。除非需要,否则不要使用堆内存。这显然引出了一个问题:我什么时候需要动态内存?
- 堆栈空间是有限的,如果你需要更多的空间,你必须自己分配它(想想大数组,像
struct huge_struct array[10000]
)。要了解堆栈有多大,请参阅此页。注意,实际的堆栈大小可能不同。 - C传递参数,并逐个返回值。如果您想返回一个数组,它会衰变成一个指针,那么您最终将返回一个指向超出作用域(无效)的数组的指针,从而导致UB。像这样的函数应该分配内存并返回指向它的指针。
- 当你需要改变一些东西的大小(
realloc
),或者你不知道你需要多少内存来存储一些东西。你在堆栈上声明的数组是固定大小的,指向内存块的指针可以重新分配(malloc
新块>=当前块大小+memcpy
+free
原始指针基本上是realloc
所做的) - 当某块内存需要在各种函数调用中保持有效时。在某些情况下,全局变量是不行的(想想线程)。此外,在几乎所有情况下,全局变量都被认为是不好的做法。 共享库通常使用堆内存。这是因为它们的作者不能假设他们的代码将有大量可用的堆栈空间。如果您想编写一个共享库,您可能会发现自己编写了大量内存管理代码
所以,一些例子来澄清:
//perfectly fine
double sum(double a, double b)
{
return a + b;
}
//call:
double result = sum(double_a, double_b);
//or to reassign:
double_a = (double_a, double_b);
//valid, but silly
double *sum_into(double *target, double b)
{
if (target == NULL)
target = calloc(1, sizeof *target);
*target = b;
return target;
}
//call
sum_into(&double_a, double_b);//pass pointer to stack var
//or allocate new pointer, set to value double_b
double *double_a = sum_into(NULL, double_b);
//or pass double pointer (heap)
sum_into(ptr_a, double_b);
返回"数组"
//Illegal
double[] get_double_values(double *vals, double factor, size_t count)
{
double return_val[count];//VLA if C99
for (int i=0;i<count;++i)
return_val[i] = vals[i] * factor;
return return_val;
}
//valid
double *get_double_values(const double *vals, double factor, size_t count)
{
double *return_val = malloc(count * sizeof *return_val);
if (return_val == NULL)
exit( EXIT_FAILURE );
for (int i=0;i<count;++i)
return_val[i] = vals[i] * factor;
return return_val;
}
必须调整对象的大小:
double * double_vals = get_double_values(
my_array,
2,
sizeof my_array/ sizeof *my_array
);
//store the current size of double_vals here
size_t current_size = sizeof my_array/ sizeof *my_array;
//some code here
//then:
double_vals = realloc(
double_vals,
current_size + 1
);
if (double_vals == NULL)
exit( EXIT_FAILURE );
double_vals[current_size] = 0.0;
++current_size;
需要在作用域中停留更长时间的变量:
struct callback_params * some_func( void )
{
struct callback_params *foo = malloc(sizeof *foo);//allocate memory
foo->lib_sum = 0;
call_some_lib_func(foo, callback_func);
}
void callback_func(int lib_param, void *opaque)
{
struct callback_params * foo = (struct callback_params *) opaque;
foo->lib_sum += lib_param;
}
在这个场景中,我们的代码调用了一些异步处理的库函数。我们可以传递一个回调函数来处理库的结果。该库还为我们提供了一种通过void *opaque
将一些数据传递给该回调的方法。
call_some_lib_func
的签名如下:
void call_some_lib_func(void *, void (*)(int, void *))
或者用更容易读懂的格式:
void call_some_lib_func(void *opaque, void (*callback)(int, void *))
所以它是一个叫做call_some_lib_func
的函数,它有两个参数:一个叫做opaque
的void *
,一个指向返回void的函数的函数指针,并接受int和void *
作为参数。
所需要做的就是将void *
强制转换为正确的类型,然后就可以对其进行操作了。还需要注意的是,some_func
返回一个指向不透明指针的指针,因此我们可以在任何需要的地方使用它:
int main ( void )
{
struct callback_params *params = some_func();
while (params->lib_sum < 100)
printf("Waiting for something: %d%%r", params->lib_sum);
puts("Done!");
free(params);//free the memory, we're done with it
//do other stuff
return 0;
}
当您打算将数据传输到局部作用域外(例如函数)时,需要动态内存分配。
此外,当您无法事先知道需要多少内存时(例如用户输入)。
最后,当你知道需要的内存量但是它溢出了堆栈。否则,由于可读性、运行时开销和安全性的原因,不应该使用动态内存分配。