简体   繁体   English

Function 返回 C 中的指针

[英]Function return a pointer in C

I am new to C and I try to create a function return a pointer.我是 C 的新手,我尝试创建一个 function 返回一个指针。 I used different ways to do it:我使用了不同的方法来做到这一点:

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

It compiles but doesn't seem to work.它编译但似乎不起作用。

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

It seems to work, and print "the student age is 24", but if I added one printf statement before previous printf:它似乎有效,并打印“学生年龄为 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;
}

It gives me:它给了我:

This is a test这是一个测验

the student age is -1422892954学生年龄是-1422892954

3. 3.

If I use following ways:如果我使用以下方式:

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

It works in both cases, with and without the commented printf它适用于两种情况,有和没有注释的 printf

I just want to know what are the reasons it fails for 1 and 2. And why it works for 3?我只想知道它对 1 和 2 失败的原因是什么。为什么它对 3 有效? And generally speaking when should we use malloc and when we shouldn't?一般来说,我们什么时候应该使用 malloc 什么时候不应该使用?

Thanks谢谢

Example 1示例 1

Your example 1 doesn't work because no student object is ever created.您的示例 1 不起作用,因为没有创建student object。

student* s;

This creates a pointer s that is supposed to point to a student but currently points to an unknown memory location because it is an uninitialized variable.这将创建一个指针s ,它应该指向一个student ,但当前指向一个未知的 memory 位置,因为它是一个未初始化的变量。 It definitely doesn't point to a new student, since none was created so far.它绝对不是指向一个新学生,因为到目前为止还没有创建。

s->age = 24;

This then writes to the unknown memory location that s is currently pointing to, corrupting memory in the process.然后,这将写入s当前指向的未知 memory 位置,在此过程中破坏 memory。 You now entered the realm of undefined behavior (UB) .您现在输入了未定义行为 (UB)的 realm 。 Your process may crash at this very moment, or later on, or it may do something crazy and unexpected.您的进程可能会在此时或稍后崩溃,或者它可能会做一些疯狂和意想不到的事情。

It doesn't make sense to think about what happens after this point, because your process is already doomed by now and needs to be terminated.考虑在此之后会发生什么是没有意义的,因为您的进程现在已经注定了,需要终止。

Example 2示例 2

Your example 2 sort-of works but only sometimes, because yet again UB comes into play.您的示例 2 可以工作,但仅在某些时候有效,因为 UB 再次发挥作用。

student s2;

Here you are creating a student as a local variable.在这里,您正在创建一个student作为局部变量。 It is probably created on the stack .它可能是在堆栈上创建的。 The variable is valid until you leave the function create_student .在您离开 function create_student之前,该变量一直有效。

However, you are then creating a pointer to that student object and returning it from your function .但是,您将创建一个指向该student object 的指针,并从您的 function 返回它 That means, the outer code now has a pointer to a place where a student object once was, but since you returned from the function and it was a local variable, it no longer exists, Sort of.这意味着,外部代码现在有一个指向student object 曾经所在的位置的指针,但是由于您从 function 返回并且它是一个局部变量,它不再存在,排序。 that is.那是。 It's a zombie, Or, better explained, it's like when you delete a file on your harddisk - as long as no other file overwrote its location on the disk.这是一个僵尸,或者,更好的解释是,就像您删除硬盘上的一个文件 - 只要没有其他文件覆盖它在磁盘上的位置。 you may still restore it, And therefore, out of sheer luck, you are able to read the age of it even after create_student had returned.您仍然可以恢复它,因此,幸运的是,即使在create_student返回之后,您也可以读取它的age But as soon as you change the scenario a bit (by inserting another printf ), you ran out of luck, and the printf call uses its own local variables which overwrite your student object on the stack.但是,一旦您稍微更改场景(通过插入另一个printf ),您就会运气不佳,并且printf调用使用其自己的局部变量,这些变量会覆盖堆栈上的student object。 Oops.哎呀。 That is because using a pointer to an object that no longer exists is also undefined behavior (UB).这是因为使用指向不再存在的 object 的指针也是未定义行为 (UB)。

Example 3示例 3

This example works.这个例子有效。 And it is stable, it doesn't have undefined behavior (almost - see below).它是稳定的,它没有未定义的行为(几乎 - 见下文)。 That is because you create the student on the heap instead of the stack, with malloc .那是因为您使用malloc而不是堆栈上创建student That means it now exists for eternity (or until you call free on it), and won't get discarded when your function returns.这意味着它现在永远存在(或者直到你free调用它),并且当你的 function 返回时不会被丢弃。 Therefore it is valid to pass a pointer to it around and access it from another place.因此,传递一个指向它的指针并从另一个地方访问它是有效的。

Just one small issue with that - what if malloc failed, for example you ran out of memory?只是一个小问题——如果malloc失败了怎么办,例如你用完了 memory? In that case we have a problem yet again.在那种情况下,我们又遇到了问题。 So you should add a check for whether malloc returned NULL and handle the error somehow.因此,您应该检查malloc是否返回NULL并以某种方式处理错误。 Otherwise, s->age = 24 will attempt to dereference a null pointer which again won't work.否则, s->age = 24将尝试取消引用 null 指针,该指针再次不起作用。

However, you should also remember to free it when you are done using it, otherwise you have a memory leak.但是,您还应该记住在使用完毕后将其free ,否则您会出现 memory 泄漏。 And after you did that, remember that now your pointer did become invalid and you can no longer use it, otherwise we are back in UB world.在你这样做之后,请记住现在你的指针确实变得无效并且你不能再使用它,否则我们又回到了 UB 世界。

As for your question when to use malloc : Basically whenever you need to create something that lives on after you leave your current scope, or when something is local but has to be quite big (because stack space is limited), or when something has to be of variable size (because you can pass the desired size as argument to malloc ).至于您何时使用malloc的问题:基本上每当您需要在离开当前 scope 后创建可以继续存在的东西时,或者当某些东西是本地的但必须很大(因为堆栈空间有限)时,或者当某些东西必须大小可变(因为您可以将所需的大小作为参数传递给malloc )。

One last thing to note though: Your example 3 worked because your student only had one field age and you initialized that field to 24 before you read it again.最后要注意的一件事:您的示例 3 有效,因为您的student只有一个字段age ,并且您在再次阅读之前将该字段初始化为24 This means all the fields (since it had only that one) got initialized.这意味着所有字段(因为它只有一个)都已初始化。 If it had had another field (say, name ) and you hadn't initialized that one, you would still have carried an "UB time bomb" if your code elsewhere would have attempted to read that uninitialized name .如果它有另一个字段(例如name )并且您没有初始化那个字段,如果您在其他地方的代码试图读取那个未初始化的name ,您仍然会携带一个“UB 定时炸弹”。 So, always make sure all fields get initialized.因此,请始终确保所有字段都已初始化。 You can also use calloc instead of malloc to get the memory filled with zeroes before it's passed to you, then you can be sure that you have a predictable state and it is no longer undefined.您还可以使用calloc而不是malloc来让 memory 在传递给您之前用零填充,然后您可以确定您有一个可预测的 state 并且它不再未定义。

Because in case1 and 2, the variable "age" is in the sub-function create_student()'s scope, on a stack.因为在 case1 和 2 中,变量“age”在子函数 create_student() 的 scope 中,在堆栈上。 So once the sub-function is finished, that area is released, which means that the "age" is released also.所以一旦子功能完成,那个区域就被释放了,也就意味着“年龄”也被释放了。 So s now points to a non-sense area.所以 s 现在指向一个无意义的区域。 If you are lucky enough, that area still stores the "age", you can print that info out, that's why it works in case2's first part.如果你足够幸运,该区域仍然存储“年龄”,你可以打印出该信息,这就是它在 case2 的第一部分工作的原因。

But in case3, student* s points to a heap area and when that sub-function is finished that heap area won't be free.但在 case3 中,student* s 指向一个堆区域,当该子功能完成时,该堆区域将不再空闲。 So s->age still works.所以 s->age 仍然有效。

Continuing from the comments, you can think of it this way:继续评论,你可以这样想:

Case 1.情况1。

    student* s;

s in an uninitialized pointer that does not point to any memory you can validly use. s在未初始化的指针中,它不指向您可以有效使用的任何 memory。 It's value is indeterminate.它的价值是不确定的。

Case 2.案例 2。

    student* s;
    student s2;
    s2.age = 24;
    s = &s2;

s is an uninitialized pointer (as in 1.), s2 declares a valid struct and s2.age is validly initialized. s是一个未初始化的指针(如 1.), s2声明一个有效的结构并且s2.age被有效地初始化。 s = &s2; assigns the address of s2 (declared local to the function) to s .s2的地址(声明为函数的本地)分配给s When s is returned, s2 is invalidated -- its life limited to the function, so the address return to main() is invalid.返回s时, s2无效——它的寿命仅限于 function,因此返回到main()的地址无效。

Case 3.案例 3。

    student* s = malloc(sizeof(student));

Yes!!是的!! s now holds the starting address to a valid block of memory with allocated storage duration (good for the life of the program or until freed). s现在保存 memory 的有效块的起始地址,该块具有分配的存储持续时间(有利于程序的生命周期或直到被释放)。 The only problem is you need to validate the allocation succeeds:唯一的问题是您需要验证分配是否成功:

    if (s == NULL) {
        perror ("malloc-s");
        return NULL;
    }

Now you can have confidence that the return is either a valid address or NULL indicating allocation failed.现在您可以确信返回是有效地址或NULL指示分配失败。

When to use何时使用

To your last question, you dynamically allocate when you don't know how many of something you will need, so you declare some number of them, keep track of how many you have used, and realloc more when the initially allocated block is filled.对于最后一个问题,当您不知道需要多少东西时,您会动态分配,因此您声明其中的一些,跟踪您使用了多少,并在最初分配的块被填充时realloc更多。 Or, you need more of something than will fit on the program stack, you can allocate or declare as static (or declare globally).或者,您需要比程序堆栈更多的东西,您可以分配或声明为static (或全局声明)。 Otherwise, if you know how many you need beforehand, and that will fit on the stack, just declare an array of them and you are done.否则,如果您事先知道需要多少,并且可以放入堆栈,只需声明它们的数组即可。

Let me know if you have further questions.如果您还有其他问题,请告诉我。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM