我是C的新手,我试图创建一个返回指针的函数。我用了不同的方法:
1.
typedef struct student{
int age;
}student;
student *create_student(){
student* s;
s-> age= 24;
return s;
}
int main() {
student* s = create_student();
printf("the student age is %d", s->age);
return 0;
}
它可以编译,但似乎不起作用。
2.
typedef struct student{
int age;
}student;
student *create_student(){
student* s;
student s2;
s2.age = 24;
s = &s2;
return s;
}
int main() {
student* s = create_student();
printf("the student age is %d", s->age);
return 0;
}
它似乎起作用,并打印";学生年龄为24〃;,但是如果我在前一个printf:之前添加了一个printf语句
int main() {
student* s = create_student();
printf("This is a testn");
printf("the student age is %d", s->age);
return 0;
}
它给了我:
这是一个测试
学生年龄为-1422892954
3.
如果我使用以下方式:
typedef struct student{
int age;
}student;
student *create_student(){
student* s = malloc(sizeof(student));
s -> age = 24;
return s;
}
int main() {
student* s = create_student();
// printf("This is a testn");
printf("the student age is %d", s->age);
return 0;
}
它在这两种情况下都有效,有和没有评论打印的
我只想知道1和2失败的原因是什么。为什么它适用于3?一般来说,我们什么时候应该使用malloc,什么时候不应该?
感谢
示例1
示例1不起作用,因为从未创建过student
对象。
student* s;
这会创建一个指针s
,该指针应该指向student
,但当前指向未知内存位置,因为它是一个未初始化的变量。它绝对没有指向一个新的学生,因为到目前为止还没有创建。
s->age = 24;
然后,这会写入s
当前指向的未知内存位置,从而损坏进程中的内存。您现在进入了未定义行为(UB)的领域。你的过程可能会在此时此刻或稍后崩溃,也可能会发生一些疯狂和意想不到的事情。
思考这一点之后会发生什么是没有意义的,因为你的过程现在已经注定了,需要终止。
示例2
你的例子2有点奏效,但只是有时,因为UB再次发挥作用。
student s2;
在这里,您正在创建一个student
作为局部变量。它可能是在堆栈上创建的。变量在您离开函数create_student
之前一直有效。
但是,您将创建一个指向student
对象的指针,并从函数中返回它。这意味着,外部代码现在有一个指向student
对象曾经所在位置的指针,但由于您从函数返回,并且它是一个局部变量,因此它不再存在!有点像是僵尸。或者,更好的解释是,这就像当你删除硬盘上的一个文件时-只要没有其他文件覆盖它在磁盘上的位置,你仍然可以恢复它。因此,幸运的是,即使在create_student
返回后,你也可以读取它的age
。但是,只要稍微改变一下场景(通过插入另一个printf
),您就没有运气了,printf
调用就会使用它自己的局部变量来覆盖堆栈上的student
对象。哎呀。这是因为使用指向不再存在的对象的指针也是未定义的行为(UB)。
示例3
这个例子很有效。它是稳定的,没有未定义的行为(几乎见下文)。这是因为您使用malloc
在堆上而不是堆栈上创建student
。这意味着它现在将永远存在(或者直到您对它调用free
),并且在函数返回时不会被丢弃。因此,将指针传递到周围并从另一个位置访问它是有效的。
这只是一个小问题——如果malloc
失败了,例如内存不足,该怎么办?在这种情况下,我们又遇到了问题。因此,您应该添加一个检查malloc
是否返回NULL
,并以某种方式处理错误。否则,s->age = 24
将尝试取消引用一个空指针,该指针再次无效。
但是,当您使用完它时,还应该记住free
,否则会出现内存泄漏。在你这样做之后,请记住,现在你的指针确实无效,你不能再使用它,否则我们将回到UB世界。
至于您的问题,何时使用malloc
:基本上,无论何时您需要创建在离开当前范围后仍然存在的东西,或者当某个东西是本地的但必须非常大时(因为堆栈空间有限),或者当某些东西必须是可变大小时(因为您可以将所需的大小作为参数传递给malloc
)。
不过,最后需要注意的一点是:您的示例3之所以有效,是因为您的student
只有一个字段age
,并且在再次读取之前将该字段初始化为24
。这意味着所有字段(因为它只有那个字段)都被初始化了。如果它有另一个字段(比如name
),而您没有初始化该字段,那么您仍然会携带一个";UB定时炸弹";如果您在其他地方的代码试图读取未初始化的CCD_ 28。因此,请始终确保所有字段都已初始化。您也可以使用calloc
而不是malloc
来在内存传递给您之前让它充满零,这样您就可以确保您有一个可预测的状态,并且它不再是未定义的。
因为在案例1和2中,变量"年龄;在子函数create_student()的作用域中,在堆栈上。因此,一旦子功能完成,该区域就被释放,这意味着";年龄;也发布了。所以s现在指向一个无意义的区域。如果你足够幸运,该区域仍然存储";年龄";,你可以把这些信息打印出来,这就是为什么它在案例2的第一部分中有效。
但在案例3中,student指向一个堆区域,当该子函数完成时,该堆区域将不会空闲。所以s->年龄仍然有效。
从注释中继续,您可以这样想:
案例1.
student* s;
未初始化指针中的s
,该指针不指向任何可以有效使用的内存。它的值是不确定的。
案例2.
student* s;
student s2;
s2.age = 24;
s = &s2;
s
是一个未初始化的指针(如1所示),s2
声明了一个有效的结构,s2.age
被有效初始化。s = &s2;
将s2
的地址(声明为函数的本地地址)分配给s
。当返回s
时,s2
无效——其寿命仅限于函数,因此返回到main()
的地址无效。
案例3.
student* s = malloc(sizeof(student));
是的!!s
现在将起始地址保存到具有分配的存储持续时间的有效内存块(有利于程序的使用寿命或直到释放为止)。唯一的问题是您需要验证分配是否成功:
if (s == NULL) {
perror ("malloc-s");
return NULL;
}
现在,您可以确信返回的地址是有效地址,或者是指示分配失败的NULL
。
何时使用
对于你的最后一个问题,当你不知道需要多少东西时,你会动态分配,所以你会声明一些数量的东西,记录你使用了多少,当最初分配的块被填满时,会再分配realloc
。或者,您需要的内容比程序堆栈中的内容更多,您可以分配或声明为static
(或全局声明)。否则,如果您事先知道需要多少,并且这将适合堆栈,那么只需声明它们的数组,就完成了。
如果您还有其他问题,请告诉我。