首页 > 解决方案 > 函数在C中返回一个指针

问题描述

我是 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 test\n");
    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 test\n");
    printf("the student age is %d", s->age);
    return 0;
}

它适用于两种情况,有和没有注释的 printf

我只想知道它对 1 和 2 失败的原因是什么。为什么它对 3 有效?一般来说,什么时候应该使用 malloc,什么时候不应该?

谢谢

标签: cpointersmalloc

解决方案


示例 1

您的示例 1 不起作用,因为没有student创建任何对象。

student* s;

这将创建一个应该指向 a但当前指向未知内存位置的指针s,因为它是一个未初始化的变量。它绝对不是指向一个新学生,因为到目前为止还没有创建。student

s->age = 24;

然后,这会写入s当前指向的未知内存位置,从而破坏进程中的内存。您现在进入了未定义行为 (UB)的领域。您的进程可能会在此时或稍后崩溃,或者它可能会做一些疯狂和意想不到的事情。

考虑在此之后会发生什么是没有意义的,因为您的进程现在已经注定了,需要终止。

示例 2

您的示例 2 可以工作,但仅在某些时候有效,因为 UB 再次发挥作用。

student s2;

在这里,您将 a 创建student为局部变量。它可能是在堆栈上创建的。在您离开函数之前,该变量一直有效create_student

但是,您随后将创建一个指向该student对象的指针并从您的函数中返回它。这意味着,外部代码现在有一个指向student对象曾经所在位置的指针,但由于您从函数返回并且它是一个局部变量,它不再存在!有点,就是这样。这是一个僵尸。或者,更好的解释是,就像您删除硬盘上的文件一样——只要没有其他文件覆盖它在磁盘上的位置,您仍然可以恢复它。因此,幸运的是,您age即使在create_student返回后也能阅读其中的内容。但是,一旦您稍微更改了场景(通过插入另一个printf),您就走运了,并且该printf调用使用了自己的局部变量student堆栈上的对象。哎呀。这是因为使用指向不再存在的对象的指针也是未定义行为 (UB)。

示例 3

这个例子有效。它是稳定的,它没有未定义的行为(几乎 - 见下文)。那是因为您student而不是堆栈上创建了malloc. 这意味着它现在永远存在(或直到您调用free它),并且在您的函数返回时不会被丢弃。因此,传递一个指向它的指针并从另一个地方访问它是有效的。

只是一个小问题——如果malloc失败了怎么办,例如你的内存不足?在那种情况下,我们又遇到了一个问题。所以你应该添加一个检查是否malloc返回NULL并以某种方式处理错误。否则,s->age = 24将尝试取消引用一个再次不起作用的空指针。

但是,您也应该在使用完它时记住free它,否则您会出现内存泄漏。在你这样做之后,请记住现在你的指针确实变得无效并且你不能再使用它,否则我们又回到了 UB 世界。

至于您何时使用的问题malloc:基本上每当您需要在离开当前范围后创建可以继续存在的东西时,或者当某些东西是本地的但必须相当大(因为堆栈空间有限)时,或者当某些东西必须是时可变大小(因为您可以将所需大小作为参数传递给malloc)。

最后要注意的一件事:您的示例 3 有效,因为您student只有一个字段,并且您在再次阅读之前age将该字段初始化为。24这意味着所有字段(因为它只有一个)都已初始化。如果它有另一个字段(例如,name)并且您没有初始化那个字段,那么如果您在其他地方的代码试图读取那个 uninitialized ,那么您仍然会携带一个“UB 定时炸弹” name。因此,请始终确保所有字段都已初始化。您还可以使用calloc而不是在malloc将内存传递给您之前将其填充为零,然后您可以确定您有一个可预测的状态并且它不再是未定义的。


推荐阅读