0

below program does not run as we are returning local int value.

#include<stdio.h>

int *fun1()
{
    int i = 10;
    return &i;       // local i hence will not get printed in main
}

main()
{
    int *x = fun1();

    printf("%d", *x);
}

However, at the same time below program runs, even though we are returning local string base address. Why the concept of local char* does not apply here?

char *fun()
{
    char *p = "ram";
    return p;             //char* is local, even though in main it gets printed. why?
}

int main()
{
    char *x = fun();
    printf("%s", x);

    return 0;
}
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
Ravi
  • 173
  • 1
  • 10
  • 1
    just a bit of clarification, your assertion that "local `i` hence will not get printed in main" is not necessarily correct. Returning the address of a local and dereferencing it in the caller invokes undefined behavior, where [anything can happen](https://stackoverflow.com/questions/6441218/can-a-local-variables-memory-be-accessed-outside-its-scope/6445794#6445794), including `i` getting printed "correctly". – yano Sep 01 '21 at 18:28
  • Change 'char *p' to 'char p[]' and see what happens – tstanisl Sep 01 '21 at 18:38
  • @tstsnisl surprisingly I get same err as in case of first program when I change char *p to char p[] . Can you please explain a bit why? – Ravi Sep 01 '21 at 18:43
  • What do you mean it "won't run" - it does run - it may crash or do any number of things, even appear to work. – Clifford Sep 01 '21 at 18:49
  • 1
    The second "works" for the reason stated in the answers, it is still unsafe code. To be "safe" however, `fun()` should return `const char*`, and for good form `p` should also be a `const char*`. – Clifford Sep 01 '21 at 18:54

3 Answers3

8
char *fun()
{
    char *p = "ram";
    return p;             //char* is local, even though in main it gets printed. why?
}

When you write code like this, "ram" does not get placed on the stack the same way that int i = 10 does - it gets placed in the .rodata (read only data) section of the executable. What gets placed on the stack is a pointer to this location in memory.

Imagine if "ram" wasn't three characters, but a million characters - if the string itself were to be pushed onto the stack, a million bytes would have to be pushed to the stack every time the function was called! Even though the string is constant.


Since the location of the string in memory is constant, it is valid to return a pointer to that location.

Daniel Kleinstein
  • 5,262
  • 1
  • 22
  • 39
2

In this function

int *fun1()
{
    int i = 10;
    return &i;       // local i hence will not get printed in main
}

the block scope variable i has automatic storage duration. It means that after exiting the function the variable will not be alive and the returned pointer to the variable will be invalid.

In this function

char *fun()
{
    char *p = "ram";
    return p;             //char* is local, even though in main it gets printed. why?
}

the string literal "ram" having the type char[4] has static storage duration. It means that it is will be alive after exiting the function and the returned pointer to the literal will be valid.

From the C Standard (6.4.5 String literals)

6 In translation phase 7, a byte or code of value zero is appended to each multibyte character sequence that results from a string literal or literals.78) The multibyte character sequence is then used to initialize an array of static storage duration and length just sufficient to contain the sequence.

Pay attention to that the function returns a copy of the pointer p but the pointed object (the string literal) has static storage duration. So the returned pointer has a valid value.

You can achieve the same effect in the first function by declaring the variable i as having static storage duration. For example

int *fun1()
{
    static int i = 10;
    return &i;       // local i hence will not get printed in main
}

As for your question written by you as an answer

And why below does not work? when i changed from char *p = "ram" to char p[]="ram" ?

char *fun()
{
    char p[] = "ram";
    return p;             
}

int main()
{
    char *x = fun();
    printf("%s", x);

    return 0;
}

then the array p is a local array with automatic storage duration of the function. Its elements are initialized by elements of the string literal "ram". But this does change the array storage duration. In fact this declaration of the array is similar to

char p[] = { 'r', 'a', 'm', '\0' };

So this return statement

    return 0;

returns a pointer to the first element of the array that (array) will not be alive after exiting the function. So the returned pointer will be invalid.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • hi @Vlad how come char *p = "ram" is a static variable? Is it not a local variable? – Ravi Sep 01 '21 at 18:49
  • @Ravi See my updated post. The function returns a copy of the value of the pointer p. But this value is a valid pointer value because it is the address of the string literal that has static storage duration. The problem is not that the returned pointer p is local. The function returns a copy of its value. The problem is where the returned pointer points to that is whether to a valid alive object or not. – Vlad from Moscow Sep 01 '21 at 18:51
  • char *fun() { char p[] = "ram"; return p; } int main() { char *x = fun(); printf("%s", x); return 0; } – Ravi Sep 01 '21 at 19:00
  • @Ravi In this code snippet you are returning a pointer to a local array p that has automatic storage duration. Array elements are initialized by elements of the string literal but this does not mean that an initialized such a way array itself has static storage duration. You could write for example static char p[] = "ram";. In this case the array will have static storage duration. – Vlad from Moscow Sep 01 '21 at 19:03
  • and what about if I replace char *p = "ram" ; with malloc as in below code ? char *fun() { char *p = (char*)malloc(4*sizeof(char)); strcpy(p, "ram"); return p; } int main() { char *x = fun(); printf("%s", x); return 0; } – Ravi Sep 01 '21 at 19:25
  • @Ravi From the C Standard "The lifetime of an allocated object extends from the allocation until the deallocation." So the returned pointer will be valid because the dynamically allocated array was not deallocated. – Vlad from Moscow Sep 01 '21 at 19:32
  • thanks, @Vlad now i have a better picture about the program and its behavior – Ravi Sep 01 '21 at 19:35
1

In the code

char *fun()
{
    char *p = "ram";
    return p;             //char* is local, even though in main it gets printed. why?
}

The variable p is local to the function, and its lifetime ends with the function. However, you are returning p's value, not its address, so that doesn't really matter here.

p's value is the address of the string literal "ram". String literals are stored in such a way that they are available over the lifetime of the entire program, from startup to termination - the literal "ram" does not cease to exist when fun exits, so its address stays valid.

And why below does not work? when i changed from char *p = "ram" to char p[]="ram" ?

Now you've changed how and where the string gets stored. Instead of p being a pointer that stores the address of the string literal, it is now an array that stores the contents of the string itself. The lifetime of the string is now the same as the lifetime of the function, so once the function exits, the array that stored that string no longer exists and the return value is not valid.

I took your code and added a utility1 to display the address and contents of various items in memory. First we start with the first version where p is a pointer:

#include <stdio.h>
#include "dumper.h"

char *fun( void )
{
  char *p = "ram";

  char *names[] = { "p", "\"ram\"" };
  void *addrs[] = { &p, "ram" };
  size_t sizes[] = { sizeof p, sizeof "ram" };

  puts( "In fun: ");

  dumper( names, addrs, sizes, 2, stdout );

  return p;
}

int main( void )
{
  char *x;
  
  char *names[] = { "x", "\"ram\"" };
  void *addrs[] = { &x, "ram" };
  size_t sizes[] = { sizeof x, sizeof "ram" };

  puts( "Before call to fun:" );
  dumper( names, addrs, sizes, 2, stdout );

  x = fun();

  puts( "After call to fun:" );
  dumper( names, addrs, sizes, 2, stdout );

  return 0;
}

And here's the output:

Before call to fun:
           Item         Address   00   01   02   03
           ----         -------   --   --   --   --
              x  0x7ffee2451a40   a0   1a   45   e2    ..E.
                 0x7ffee2451a44   fe   7f   00   00    ....

          "ram"     0x10d7aef04   72   61   6d   00    ram.

In fun: 
           Item         Address   00   01   02   03
           ----         -------   --   --   --   --
              p  0x7ffee24519b8   04   ef   7a   0d    ..z.
                 0x7ffee24519bc   01   00   00   00    ....

          "ram"     0x10d7aef04   72   61   6d   00    ram.

After call to fun:
           Item         Address   00   01   02   03
           ----         -------   --   --   --   --
              x  0x7ffee2451a40   04   ef   7a   0d    ..z.
                 0x7ffee2451a44   01   00   00   00    ....

          "ram"     0x10d7aef04   72   61   6d   00    ram.

First, notice that the string literal "ram" has the same address in both fun and main. Again, string literals are allocated such that they are available over the lifetime of the program. You'll notice its address is much lower than the other items, indicating it's in a very different region of memory.

Again, notice that p only stores the address of the string, not its contents. So even though p ceases to exist, its value continues to be valid.

Now, we change that code so that p is an array, not a pointer:

#include <stdio.h>
#include "dumper.h"

char *fun( void )
{
  char p[] = "ram";

  char *names[] = { "p", "\"ram\"" };
  void *addrs[] = { &p, "ram" };
  size_t sizes[] = { sizeof p, sizeof "ram" };

  puts( "In fun: ");

  dumper( names, addrs, sizes, 2, stdout );

  return p;
}

int main( void )
{
  char *x;
  
  char *names[] = { "x", "\"ram\"" };
  void *addrs[] = { &x, "ram" };
  size_t sizes[] = { sizeof x, sizeof "ram" };

  puts( "Before call to fun:" );
  dumper( names, addrs, sizes, 2, stdout );

  x = fun();

  puts( "After call to fun:" );
  dumper( names, addrs, sizes, 2, stdout );

  return 0;
}

Now our output looks like this:

Before call to fun:
           Item         Address   00   01   02   03
           ----         -------   --   --   --   --
              x  0x7ffee059ea40   98   ea   59   e0    ..Y.
                 0x7ffee059ea44   fe   7f   00   00    ....

          "ram"     0x10f661efc   72   61   6d   00    ram.

In fun: 
           Item         Address   00   01   02   03
           ----         -------   --   --   --   --
              p  0x7ffee059e9bc   72   61   6d   00    ram.

          "ram"     0x10f661efc   72   61   6d   00    ram.

After call to fun:
           Item         Address   00   01   02   03
           ----         -------   --   --   --   --
              x  0x7ffee059ea40   bc   e9   59   e0    ..Y.
                 0x7ffee059ea44   fe   7f   00   00    ....

          "ram"     0x10f661efc   72   61   6d   00    ram.

Instead of storing the address of the string literal, p now stores the contents of the string itself. Once fun exits, p (and by extension the string it contains) ceases to exist.


  1. Available here
John Bode
  • 119,563
  • 19
  • 122
  • 198