简体   繁体   中英

Function return a pointer in C

I am new to C and I try to create a function return a pointer. I used different ways to do it:

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.

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:

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

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

I just want to know what are the reasons it fails for 1 and 2. And why it works for 3? And generally speaking when should we use malloc and when we shouldn't?

Thanks

Example 1

Your example 1 doesn't work because no student object is ever created.

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. 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. You now entered the realm of undefined behavior (UB) . 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

Your example 2 sort-of works but only sometimes, because yet again UB comes into play.

student s2;

Here you are creating a student as a local variable. It is probably created on the stack . The variable is valid until you leave the function create_student .

However, you are then creating a pointer to that student object and returning it from your 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. 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. 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. Oops. That is because using a pointer to an object that no longer exists is also undefined behavior (UB).

Example 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 . That means it now exists for eternity (or until you call free on it), and won't get discarded when your function returns. 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? 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. Otherwise, s->age = 24 will attempt to dereference a null pointer which again won't work.

However, you should also remember to free it when you are done using it, otherwise you have a memory leak. 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.

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 ).

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. 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 . 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.

Because in case1 and 2, the variable "age" is in the sub-function create_student()'s scope, on a stack. 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. 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.

But in case3, student* s points to a heap area and when that sub-function is finished that heap area won't be free. So s->age still works.

Continuing from the comments, you can think of it this way:

Case 1.

    student* s;

s in an uninitialized pointer that does not point to any memory you can validly use. It's value is indeterminate.

Case 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 = &s2; assigns the address of s2 (declared local to the function) to s . When s is returned, s2 is invalidated -- its life limited to the function, so the address return to main() is invalid.

Case 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). 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.

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. Or, you need more of something than will fit on the program stack, you can allocate or declare as static (or declare globally). 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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