-1

I'm a newbie to C. I had extended the question from the previous question: Strange behavior when returning "string" with C (Thanks for all who answered or commented that question, by the way.)

Pretty straight forward:

Why can this work:

#include <stdio.h>

int func() {
    int i = 5;
    return i;
}

int main() {
    printf("%d",func());
}

But not this:

#include <stdio.h>

char * func() {
    char c[] = "Hey there!";
    return c;
}

int main() {
    printf("%s",func());
}

From the previous question, logically the int i should not exist too because the function has returned, but why can it still be returned while char c[] cannot?

(It seems to be duplicated from "Pointers and memory scope" but I would like to know more about what is the difference between returning an int and a char *.)

Clifford
  • 88,407
  • 13
  • 85
  • 165
  • 1
    Functions return by value, which is why the *'logically the int i should not exist too'* bit is irrelevant for a scalar integer value. Returning a pointer value for something allocated on the function call stack is a very different situation. – Eli Korvigo Dec 24 '18 at 14:17
  • 1
    Possible duplicate of [Pointers and memory scope](https://stackoverflow.com/questions/5428360/pointers-and-memory-scope) – Jongware Dec 24 '18 at 14:18
  • 1
    You *can* return a `char *` from a function. You can even return a pointer to an element of an automatically-allocated array, such as your second example shows. There's nothing inherently wrong with a function returning such values. **The issue is with what you may do with the return value**. You may read / print / whatever the pointer itself, just as you may do the `int`, but you *may not* attempt to access the object to which it once pointed, because that object no longer exists. – John Bollinger Dec 24 '18 at 14:52
  • 2
    @JohnBollinger: Re “You may read / print / whatever the pointer itself”: In C, a pointer to an object becomes invalid when the lifetime of the object ends. The C standard does not define the behavior of reading, printing, or whatevering the invalid pointer. (This rule likely exists to allow C implementations in which pointers are semi-active objects, not static addresses, and the tables or mappings they need may vanish when a lifetime ends. Then the fact the rule exists allows other C implementations to take advantage of it when optimizing. So all C programmers must keep it in mind.) – Eric Postpischil Dec 24 '18 at 15:30
  • SO is not a discussion forum - if you have comments about specific answers, comment on the answer. If you appreciate an answer, accept and/or upvote it; do not add thanks in your question, or even in comments on answers. – Clifford Dec 24 '18 at 15:47
  • Yeah, that's not a discussion forum but I'm just showing politeness. Also, I'm new to this platform so I don't have enough reputations. I can't upvote nor downvote anything. Sorry but I don't see anything wrong for giving "thanks" to others. Anyway, thanks for your tip. –  Dec 24 '18 at 16:37
  • Gee... I don't mean to "thank"... –  Dec 24 '18 at 16:43
  • It clutters the question. Questions stand as a community resource, long after you have moved on; they should not be personalised. This is the expected behaviour on SO and no one will think you rude for omitting unnecessary content such as this. The "policy" is clearly stated in the help pages. You will probably gain reputation faster if you confirm to local convention. – Clifford Dec 26 '18 at 08:20
  • See https://stackoverflow.com/help/someone-answers – Clifford Dec 26 '18 at 08:37
  • ... How is "thank" related to "socializing"? And that will be a bit self contradicted if you `should not thank` others but `using "up-voting" as "thanks"`. By the way, I have only got 11 reputation up till now. I think I'll use your suggestion later if I'd gain more. (It's really hard to not type "thanks" afterwards to me...) –  Dec 26 '18 at 13:00
  • I've made up my mind. Since it is an `EXPECTED BEHAVIOR`and this is REALLY important to programmers, I think I'll simply follow. –  Dec 26 '18 at 13:04

6 Answers6

6

Problem is not returning char *, it is returning something that is allocated on stack.

If you allocate memory for your string rather than pointing to function stack, there will be no problem. Something like this:

char * func() {
    char c[] = "Hey there!";
    return strdup(c);
}

int main() {
    char* str = func();
    printf("%s", str);
    free(str);
}

It is important to mention that in both cases, you are copying a value and in both cases copied value is correct, but the meaning of copied value differs.

In first case, your are copying an int value and after your return from function, you are using that int value which will be valid. But in 2nd case, even though you have a valid pointer value, it refers to an invalid address of memory which is stack of called function.

Based on suggestions in comment, I decided to add another better practice in memory allocating for this code:

#define NULL (void*)0

int func(char *buf, int len) {
    char c[] = "Hey there!";
    int size = strlen(c) + 1;

    if (len >= size) {
        strcpy(buf, c);
    }

    return size;
}

int main() {
    int size = func(NULL, 0);
    char *buf = calloc(size, sizeof(*buf));
    func(buf, size);
    printf("%s", buf);
    free(buf);
    return 0;
}

Similar approach is used in a lot of windows API functions. This approach is better, because owner of pointer is more obvious (main in here).

Afshin
  • 8,839
  • 1
  • 18
  • 53
  • The OP seems to understand that his second example is incorrect. The question is about why his *first* example is not also incorrect. – John Bollinger Dec 24 '18 at 14:44
  • @JohnBollinger I will edit and add some comment about it too. – Afshin Dec 24 '18 at 14:49
  • Thanks for your detailed explanation and the codes given. It does helps me to understand. –  Dec 24 '18 at 15:11
  • While this approach works, it requires that the caller know how the string data was allocated and that it is responsible for its destruction. Not generally good practice and a recipe for memory leaks and maintenance issues. If `func()` were reimplemented to make `c[]` `static` for example; the calling code will be rendered invalid. In general I would advise against returning a pointer to dynamically allocated memory. – Clifford Dec 24 '18 at 15:54
  • @Clifford I just made a small change to make his code valid. Regarding returning allocated memory from function and returning, you are probably right in this case. But I think with proper design, it can work (like a lot of APIs in windows), but it is normally better to allocate memory outside and pass it into function. But I don't get it why if `c` was `static`, this code will fail. I think this code should work with both static and non-static `c` if we keep `strdup` in both cases. – Afshin Dec 24 '18 at 16:02
  • @Clifford I will add another better example based on passing alloced memory from outside too. – Afshin Dec 24 '18 at 16:03
2

In the first example the return value is copied. In your second example you're returning a pointer, which will point to a memory location which no longer exists.

najayaz
  • 194
  • 3
  • 11
2

In the first case, you return the int value 5 from the function. You can then print that value.

In the second case however, you return a value of type char *. That value points to an array that is local to the function func. After that function returns the array goes out of scope, so the pointer points to invalid memory.

The difference between these two cases is a value that you use directly, versus a pointer value that no longer points to valid memory. Had you returned a pointer to memory allocated by malloc, then the pointer would point to valid memory.

dbush
  • 205,898
  • 23
  • 218
  • 273
2

You are trying to return pointer to local array, which is very bad. If you want to return a pointer to array, allocate it dynamically using malloc inside your func();

Then you must call free() on caller side to free up memory you allocated when you no longer need it

Rez Trentnor
  • 114
  • 2
  • That seems not to be the thing the OP is asking about. He wants to know why his first example is not equally as incorrect as his second. – John Bollinger Dec 24 '18 at 14:46
0

In the first example, you return an int, and the second you return a pointer to a char. They both return in exactly the same manner, it is just a matter of understanding the stack and how values are returned.

Even though i was declared in the function and is allocated on the stack, when the function returns it returns the value of i (which is basically copied, so when i falls off the stack the value of i is still returned.)

This is the exact same thing that happens to the char * in the second example. It will still be a pointer to a char, and it returns the 'copied' value of c. However, since it was allocated on the stack, the address it points to is effectively invalid. The pointer value itself has not changed, but what it points to has.

You would have to dynamically allocate this to avoid this situation.

user2827048
  • 539
  • 6
  • 18
0

The return value of function is returned by copy. In the first example, you get a copy of the integer variable from the function. In the second you get a copy of the char pointer, not a copy of the string.

The pointer references the string data that has automatic storage, so is no longer valid after the function returns. The space becomes available for use by other code and many be modified - any attempt to access it has undefined behaviour.

The point is, it is a pointer that is returned, not a string; in C a strings (and more generally arrays) are not a first-class data types.

Depending on your needs there are a number of valid ways of returning the string data; for example the following is valid:

char* func() 
{
    static char c[] = "Hey there!";
    return c;
}

because here although the local variable goes out of scope the static data is not destroyed or de-allocated, and any reference to it remains valid.

Another alternative is to embed the string in a struct which is a first-class data type:

typedef struct
{
    char content[256] ;
} sString ;

sString func() 
{
    sString c = {"Hey there!"};
    return c;
}

Or more conventionally to copy the data to a caller buffer:

char* func( char* buffer ) 
{
    char c[] = "Hey there!";

    strcpy( buffer, c ) ;

    return buffer ;
}

I have omitted code to mitigate the possibility of buffer overrun above for clarity in this last example, such code is advised.

Clifford
  • 88,407
  • 13
  • 85
  • 165