函数返回C中的指针

函数返回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);

我是C新手,我尝试创建一个返回指针的函数。我用了不同的方法:

一,

它可以编译,但似乎不起作用

二,

这似乎可行,并打印“学生年龄为24岁”,但如果我在上一个printf之前添加了一个printf语句:

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;
    }