【问题标题】:Function return a pointer in C函数在C中返回一个指针
【发布时间】:2020-07-19 02:16:41
【问题描述】:

我是 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,什么时候不应该使用?

谢谢

【问题讨论】:

  • 1. student* s; s 指向什么有效内存? (它的值是什么地址) 2. s2 是函数本地的,在函数返回时被销毁(无效)。回到 1. student *s = malloc (sizeof *s); 然后验证分配成功 if (!s) { /* handle error */ } 3. 因为malloc 将起始地址返回到分配的内存块,然后将其存储为s 的值(即s 指向分配的块),您可以使用它直到它被释放(或程序结束)。
  • 对于你的最后一个问题,当你不知道你需要多少东西时,你动态分配,所以你声明了一些,跟踪你用了多少,@987654333 @more 当最初分配的块被填满时。或者,您需要的东西超出了程序堆栈的容量,您可以分配或声明为static(或全局声明)。否则,如果您事先知道需要多少,并且可以放入堆栈,只需声明它们的数组即可。
  • 关于:student *create_student(){ student* s; 此指针未初始化为指向应用程序拥有的内存。因此,写入指针指向的任何字段都是未定义的行为,并可能导致段错误事件。建议改成:student* s = malloc( sizeof( student );

标签: c pointers malloc


【解决方案1】:

示例 1

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

student* s;

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

s->age = 24;

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

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

示例 2

您的示例 2 可以工作,但只是有时,因为 UB 再次发挥作用。

student s2;

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

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

示例 3

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

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

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

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

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

【讨论】:

    【解决方案2】:

    因为在 case1 和 2 中,变量“age”在子函数 create_student() 的范围内,在堆栈上。所以一旦子功能完成,那个区域就被释放了,也就意味着“年龄”也被释放了。所以 s 现在指向一个无意义的区域。如果你足够幸运,该区域仍然存储“年龄”,你可以打印出该信息,这就是它在 case2 的第一部分中起作用的原因。

    但在 case3 中,student* s 指向一个堆区域,当该子函数完成时,该堆区域将不再空闲。所以 s->age 仍然有效。

    【讨论】:

      【解决方案3】:

      继续从cmets,你可以这样想:

      案例 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(或全局声明)。否则,如果您事先知道需要多少,并且可以放入堆栈,只需声明它们的数组即可。

      如果您还有其他问题,请告诉我。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-05-25
        • 2020-01-12
        • 1970-01-01
        • 2015-08-05
        • 1970-01-01
        • 2014-06-28
        • 2010-12-25
        • 1970-01-01
        相关资源
        最近更新 更多