函数返回C中的指针
我是C新手,我尝试创建一个返回指针的函数。我用了不同的方法: 一, 它可以编译,但似乎不起作用 二, 这似乎可行,并打印“学生年龄为24岁”,但如果我在上一个printf之前添加了一个printf语句:函数返回C中的指针,c,pointers,malloc,C,Pointers,Malloc,我是C新手,我尝试创建一个返回指针的函数。我用了不同的方法: 一, 它可以编译,但似乎不起作用 二, 这似乎可行,并打印“学生年龄为24岁”,但如果我在上一个printf之前添加了一个printf语句: int main() { student* s = create_student(); printf("This is a test\n"); printf("the student age is %d", s->age);
int main() {
student* s = create_student();
printf("This is a test\n");
printf("the student age is %d", s->age);
return 0;
}
它给了我:
这是一个测试
学生年龄为-1422892954
三,
如果我使用以下方法:
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 test\n");
printf("the student age is %d", s->age);
return 0;
}
它在这两种情况下都有效,无论是否使用已注释的printf
我只想知道1和2失败的原因是什么。为什么它对3有效?
一般来说,我们什么时候应该使用malloc,什么时候不应该
谢谢因为在案例1和案例2中,变量“age”在堆栈上的子函数create_student()的作用域中。因此,一旦子功能完成,该区域就被释放,这意味着“年龄”也被释放。所以s现在指向一个无意义的区域。如果你够幸运的话,这个区域仍然保存着“年龄”,你可以把这些信息打印出来,这就是为什么它在案例2的第一部分中起作用
但在案例3中,student*s指向一个堆区域,当该子函数完成时,堆区域将不会空闲。因此s->age仍然有效。因为在案例1和案例2中,变量“age”在堆栈上的子函数create_student()的作用域中。因此,一旦子功能完成,该区域就被释放,这意味着“年龄”也被释放。所以s现在指向一个无意义的区域。如果你够幸运的话,这个区域仍然保存着“年龄”,你可以把这些信息打印出来,这就是为什么它在案例2的第一部分中起作用 但在案例3中,student*s指向一个堆区域,当该子函数完成时,堆区域将不会空闲。所以s->age仍然有效。示例1 示例1不起作用,因为从未创建
student
对象
student*s;
这将创建一个指针s
,该指针应指向学生
,但当前指向未知内存位置,因为它是一个未初始化的变量。它肯定没有指向一个新学生,因为到目前为止还没有创建任何学生
s->age=24;
然后写入s
当前指向的未知内存位置,从而破坏进程中的内存。你现在进入了自由的领域。您的进程可能会在此时或稍后崩溃,或者它可能会做一些疯狂和意外的事情
考虑这一点之后会发生什么是没有意义的,因为你的过程现在已经注定了,需要终止
例2
你的例子2是可行的,但只是有时候,因为UB再次发挥作用
学生s2;
在这里,您将创建一个student
作为局部变量。它可能是在上创建的。在您离开函数create\u student
之前,该变量一直有效
但是,您将创建一个指向该student
对象的指针,并从函数返回它。这意味着,外部代码现在有一个指针,指向一个student
对象曾经所在的位置,但是由于您从函数返回并且它是一个局部变量,所以它不再存在!有点,就是这样。这是一个僵尸。或者,更好的解释是,这就像你删除硬盘上的一个文件——只要没有其他文件重写了它在磁盘上的位置,你仍然可以恢复它。因此,完全出于运气,即使在create\u student
返回之后,您也能够阅读它的age
。但是只要您稍微更改场景(通过插入另一个printf
),您就没有运气了,printf
调用使用它自己的局部变量覆盖堆栈上的student
对象。哎呀。这是因为使用指向不再存在的对象的指针也是未定义行为(UB)
例3
这个例子很有效。它是稳定的,没有未定义的行为(几乎-见下文)。这是因为您使用malloc
在上而不是堆栈上创建student
。这意味着它现在永远存在(或者直到您对其调用free
),并且在函数返回时不会被丢弃。因此,传递指向它的指针并从其他位置访问它是有效的
这只是一个小问题-如果malloc
失败,例如内存不足,该怎么办?那样的话,我们又有问题了。因此,您应该添加一个检查malloc
是否返回NULL
,并以某种方式处理错误。否则,s->age=24
将尝试取消对空指针的引用,而空指针将再次失效
但是,当您使用完它时,还应记得释放它,否则会导致内存泄漏。在你们这么做之后,记住现在你们的指针已经失效了,你们不能再使用它了,否则我们就回到了UB世界
至于何时使用malloc
:基本上是在您离开当前范围后需要创建继续存在的对象时,或者当对象是本地对象但必须相当大(因为堆栈空间有限)时,或者当对象必须具有可变大小时(因为您可以将所需的大小作为参数传递给malloc
)
最后要注意的是:您的示例3之所以有效,是因为您的学生
只有一个字段年龄
,您在再次阅读之前将该字段初始化为24
。这意味着所有字段(因为它只有一个)都已初始化。如果它有另一个字段(例如,名称
)如果你的代码在其他地方试图读取未初始化的名称
,那么你还没有初始化该字段,你仍然会携带一个“UB定时炸弹”。因此,始终确保所有字段都已初始化。你也可以使用calloc
而不是malloc
,在内存通过之前用零填充内存
int main() {
student* s = create_student();
printf("This is a test\n");
printf("the student age is %d", s->age);
return 0;
}
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 test\n");
printf("the student age is %d", s->age);
return 0;
}
student* s;
student* s;
student s2;
s2.age = 24;
s = &s2;
student* s = malloc(sizeof(student));
if (s == NULL) {
perror ("malloc-s");
return NULL;
}