函数在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 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(或全局声明)。否则,如果您事先知道需要多少,并且这将适合堆栈,那么只需声明它们的数组,就完成了。

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

相关内容

  • 没有找到相关文章

最新更新