什么时候应该使用动态内存分配函数与直接变量声明?
When should one use dynamic memory allocation function versus direct variable declaration?
下面是直接声明变量的例子。
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;
}
如果我有选择的话,我会一直使用直接变量声明,因为代码看起来更具可读性。什么情况下动态内存分配更合适?
When are circumstances when dynamic memory allocation is more suitable?
当编译时不知道分配大小时,我们需要使用动态内存分配。
除了上面的情况,还有一些其他的场景,比如
如果我们想要一个在运行时可调整大小的数据结构,我们需要进行动态内存分配。
动态分配内存的生命周期仍然有效,除非它是 free()
d。有时,当从函数调用返回某个变量的地址时,它会派上用场,否则,对于 auto
变量,该地址将超出范围。
通常堆栈大小会受到适度限制。如果要创建和使用 huge 数组,最好使用动态内存分配。这将从堆中分配内存。
当您打算将数据传输出本地范围(例如函数)时,需要动态内存分配。
还有,当你无法事先知道你需要多少内存时(例如用户输入)。
最后,当您确实知道所需的内存量但它溢出堆栈时。
否则,由于可读性、运行时开销和安全性,您不应该使用动态内存分配。
使用 malloc 的动态内存分配将内存放在堆上,因此在离开函数时不会被销毁。
稍后您需要手动释放内存。
直接声明落在堆栈上,并在离开函数时被删除。 return 语句中发生的事情是在变量被销毁之前创建了一个副本。
考虑这个例子:
在堆上
void createPeople():
struct person *p = makePerson();
addToOffice(p);
addToFamily(p);
比。在堆栈上
void createPeople():
struct person p = makePerson();
addToOffice(p);
addToFamily(p);
在第一种情况下,只创建了一个人并将其添加到 office 和 family。现在删除了这个人,在office和family都失效了,而且他的数据改了,两个也都改了。
在第二种情况下,为办公室和家庭创建了此人的副本。现在可能会发生您更改办公室副本的数据而家庭副本保持不变的情况。
所以基本上如果你想让多方访问同一个对象,它应该在堆栈上。
"If I had a choice, I will use direct variable declaration all the time"
你也应该这样做。除非需要,否则不要使用堆内存。这显然引出了一个问题:我什么时候需要动态内存?
- 堆栈 space 是有限的,如果你需要更多 space,你必须自己分配它(想想大数组,比如
struct huge_struct array[10000]
)。了解堆栈有多大 see this page。请注意,实际堆栈大小可能不同。
- C 传递参数,returns 按值传递值。如果你想 return 一个数组,它衰减成一个指针,你最终会 return 指向一个超出范围(无效)的数组的指针,导致 UB。像这样的函数应该分配内存和 return 指向它的指针。
- 当您需要更改某些内容的大小时 (
realloc
),或者您不知道需要多少内存来存储某些内容。您在堆栈上声明的数组的大小是固定的,可以重新分配指向内存块的指针(malloc
新块 >= 当前块大小 + memcpy
+ free
原始指针基本上就是 realloc
所做的)
- 当某块内存需要在各种函数调用中保持有效时。在某些情况下,全局变量不会做(想想线程)。此外:全局变量在几乎所有情况下都被视为不好的做法。
- 共享库一般使用堆内存。这是因为他们的作者不能假设他们的代码会有大量可用的堆栈 space。如果你想写一个共享库,你可能会发现自己写了很多内存管理代码
所以,一些例子来澄清:
//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);
正在返回 "arrays"
//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 *
和一个指向 return 无效函数的函数指针, 并接受一个 int 和一个 void *
作为参数。
我们需要做的就是将 void *
转换为正确的类型,然后我们就可以对其进行操作了。另请注意 some_func
return 是指向不透明指针的指针,因此我们可以在任何需要的地方使用它:
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;
}
下面是直接声明变量的例子。
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;
}
如果我有选择的话,我会一直使用直接变量声明,因为代码看起来更具可读性。什么情况下动态内存分配更合适?
When are circumstances when dynamic memory allocation is more suitable?
当编译时不知道分配大小时,我们需要使用动态内存分配。
除了上面的情况,还有一些其他的场景,比如
如果我们想要一个在运行时可调整大小的数据结构,我们需要进行动态内存分配。
动态分配内存的生命周期仍然有效,除非它是
free()
d。有时,当从函数调用返回某个变量的地址时,它会派上用场,否则,对于auto
变量,该地址将超出范围。通常堆栈大小会受到适度限制。如果要创建和使用 huge 数组,最好使用动态内存分配。这将从堆中分配内存。
当您打算将数据传输出本地范围(例如函数)时,需要动态内存分配。
还有,当你无法事先知道你需要多少内存时(例如用户输入)。
最后,当您确实知道所需的内存量但它溢出堆栈时。
否则,由于可读性、运行时开销和安全性,您不应该使用动态内存分配。
使用 malloc 的动态内存分配将内存放在堆上,因此在离开函数时不会被销毁。
稍后您需要手动释放内存。
直接声明落在堆栈上,并在离开函数时被删除。 return 语句中发生的事情是在变量被销毁之前创建了一个副本。
考虑这个例子:
在堆上
void createPeople():
struct person *p = makePerson();
addToOffice(p);
addToFamily(p);
比。在堆栈上
void createPeople():
struct person p = makePerson();
addToOffice(p);
addToFamily(p);
在第一种情况下,只创建了一个人并将其添加到 office 和 family。现在删除了这个人,在office和family都失效了,而且他的数据改了,两个也都改了。
在第二种情况下,为办公室和家庭创建了此人的副本。现在可能会发生您更改办公室副本的数据而家庭副本保持不变的情况。
所以基本上如果你想让多方访问同一个对象,它应该在堆栈上。
"If I had a choice, I will use direct variable declaration all the time"
你也应该这样做。除非需要,否则不要使用堆内存。这显然引出了一个问题:我什么时候需要动态内存?
- 堆栈 space 是有限的,如果你需要更多 space,你必须自己分配它(想想大数组,比如
struct huge_struct array[10000]
)。了解堆栈有多大 see this page。请注意,实际堆栈大小可能不同。 - C 传递参数,returns 按值传递值。如果你想 return 一个数组,它衰减成一个指针,你最终会 return 指向一个超出范围(无效)的数组的指针,导致 UB。像这样的函数应该分配内存和 return 指向它的指针。
- 当您需要更改某些内容的大小时 (
realloc
),或者您不知道需要多少内存来存储某些内容。您在堆栈上声明的数组的大小是固定的,可以重新分配指向内存块的指针(malloc
新块 >= 当前块大小 +memcpy
+free
原始指针基本上就是realloc
所做的) - 当某块内存需要在各种函数调用中保持有效时。在某些情况下,全局变量不会做(想想线程)。此外:全局变量在几乎所有情况下都被视为不好的做法。
- 共享库一般使用堆内存。这是因为他们的作者不能假设他们的代码会有大量可用的堆栈 space。如果你想写一个共享库,你可能会发现自己写了很多内存管理代码
所以,一些例子来澄清:
//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);
正在返回 "arrays"
//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 *
和一个指向 return 无效函数的函数指针, 并接受一个 int 和一个 void *
作为参数。
我们需要做的就是将 void *
转换为正确的类型,然后我们就可以对其进行操作了。另请注意 some_func
return 是指向不透明指针的指针,因此我们可以在任何需要的地方使用它:
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;
}