假设我有以下C代码:
#include <stdio.h>
#include <stdlib.h>
#define NUM_PEOPLE 24
typedef struct {
char **name;
int age;
} person_t;
void get_person_info(person_t *person);
int main(int argc, char **argv) {
for (int i = 0; i < NUM_PEOPLE; i++) {
person_t new_person;
get_person_info(&new_person);
}
return 0;
}
,其中get_person_info()
只是填充指针传递到的person_t
结构体。main()
中的new_person
是否需要malloc()
内存?也就是说,如果
person_t new_person;
相反,person_t *new_person = (person_t *) malloc(sizeof(person_t));
然后更改get_person_info()
以接受person_t **
而不是person_t *
?
对不起,如果这个问题令人困惑——我不确定这是否是一个有必要保留内存的情况,因为指向该内存的指针被传递到get_person_info()
,以避免引起分段错误。
都是正确的,这取决于你想在哪里使用person_info
。在堆栈上分配:
for (int i = 0; i < NUM_PEOPLE; i++) {
person_t new_person;
get_person_info(&new_person);
}
在堆栈上创建一个person_t
对象,并用数据填充new_person
对象,因为循环只这样做,在下一个循环迭代时,该对象将超出作用域,并且数据丢失。
使用malloc:
for (int i = 0; i < NUM_PEOPLE; i++) {
person_t *new_person = malloc(sizeof(person_t));
get_person_info(new_person);
}
在堆上创建person_t
对象并填充数据,因为它在堆上分配new_person
对象将超过循环作用域,这目前意味着您正在泄漏内存,因为您没有指针指向前一个循环周期的person_t
对象的数据。
两种方法都是正确的!
person_t *new_person = (person_t *) malloc(sizeof(person_t));
然后改变get_person_info()接受一个person_t **而不是一个person_t *?
你不需要改变函数的参数- void get_person_infperson_t *person);
.只需传递指针到main
像这样-
get_person_info(new_person);
但是在之前没有分配内存的方法中,你将无法在块之外使用它,而如果你的程序依赖于它的生命周期,你可以在堆上为它分配内存。
在你的代码中,new_person
只在loop
内部使用,所以如果你不打算使用外部循环,你可能不需要动态分配。
但是如果你想在loop
之外使用它,你也应该使用动态分配。但是不要忘记free
。
不确定是否为一个结构体
malloc
内存?
简短的回答是:在您的情况下不需要这样做。如果你想在for
循环之外使用你的对象,你可以通过动态分配内存来实现,即:
person_t *new_person = malloc(sizeof(person_t));
,然后用:
get_person_info(new_person);
在你的例子中,对象是在循环中使用的,因此不需要这样做。
注意:
当你使用动态分配的内存时,你应该总是在最后释放它,以避免内存泄漏。
编辑:
正如@Johann Gerell所指出的,在C中删除了malloc返回类型的强制转换的冗余之后,分配将看起来像:
person_t *new_person = malloc(sizeof(person_t));
malloc
返回一个void指针(void *
),这表明它是一个指向未知数据类型区域的指针。由于强类型系统,c++中需要使用强制类型转换,而C中则不是这样。
您的困惑源于没有很好地理解对象存储持续时间和指针。让我们分别看一下,看得更清楚一些。
存储时间
对象可以有自动或动态的存储时间。
自动Automatic,顾名思义,将由编译器为您管理。你只需要定义一个变量,使用它,当它超出作用域时,对象就会自动销毁。一个简单的例子:
if (flag) {
int i = 0;
/* some calc. involving i */
}
// i is dead here; it cannot be accessed and its storage is reclaimed
当控件进入if
的作用域时,将自动分配足够容纳int
的内存,并将其赋值为0
。一旦您结束了对i
的使用,当控件退出作用域时,名称i
将超出作用域,因此程序将不再可以访问它,并且它为您自动分配的存储区域将被回收。
动态
假设你想要动态分配对象,也就是说,你想要管理存储,从而在没有作用域或编译器的情况下管理对象的生命周期,那么你将继续使用malloc
从平台请求存储空间
malloc(sizeof(int));
请注意,我们没有像你习惯看到的那样将malloc
的返回值赋给任何指针。我们稍后会讲到指针,现在让我们结束动态对象。在这里,足够容纳int
的空间由malloc
交给你。当你用完它时,它由你来free
。因此,这个未命名的int
对象的生存期掌握在您的手中,它的生存期将超出创建它的代码的范围。只有在显式调用free
时才会结束。如果没有匹配的free
调用被调用,你就会有臭名昭著的内存泄漏。
指针顾名思义就是一个可以引用另一个对象的对象。指针永远不是它所指向的对象(pointee)。指针是一个对象,它的指针是另一个独立的对象。你可以让一个指针指向另一个已命名对象,未命名对象,或者什么都不指向(NULL
)。
int i = 0;
int *ptr1 = &i; // ptr1 points to the automatic int object i
int *ptr2 = malloc(sizeof(int)); // ptr2 points to some unnamed int object
int *ptr3 = NULL; // ptr3 points to nothing
因此,大多数人将指针与动态分配的指针混淆的原因是:这里的指针没有名称,因此它们总是通过指针被引用;有些人把两者混淆了。
函数接口采用指针的函数在这里是合适的,因为从调用者的角度来看,它是一个灵活的函数:它既可以采用自动对象,也可以采用动态对象。我可以创建一个自动变量并传入,或者我也可以传入一个动态变量:
void get_person_info(person_t *person);
person_t o { };
get_person_info(&a);
person_t *p = malloc(sizeof(person_t));
get_person_info(p);
free(p);
new_person within main()
需要malloc()
内存吗?
。您可以定义一个自动变量并将其传递给函数。实际上,建议您尽量减少动态对象的使用,并从
开始使用自动对象。- 它最小化了代码中内存泄漏的可能性。即使是经验丰富的程序员也会错过调用匹配的
free
到malloc
,从而引入内存泄漏。 - 动态对象分配/释放远比自动变量分配/释放慢。 大量的动态分配释放导致内存碎片。
然而,自动变量通常是在堆栈中分配的,因此你可以在堆栈上创建的数量和大小的上限相对低于你可以动态分配的(通常是从堆中)。
更改
get_person_info()
以接受person_t **
而不是person_t *
?
不,如果你这样做,传递自动变量的选项仍然是可能的,但很麻烦:
void foo(int **o);
int i = 0;
int *p = &i; // p is redundant
foo(&p);
int *p = malloc(sizeof(int));
foo(&p);
相对于更简单的
void bar(int *o);
int i = 0;
bar(&i);
int *p = malloc(sizeof(int));
bar(p);