7

I would like to understand the difference between the following two C programs.

First program:

void main()
{
    int *a;
    {
        int b = 10;
        a=&b;
    }
    printf("%d\n", *a);
}

Second program:

void main()
{
    int *a;
    a = foo();
    printf("%d\n", *a);
}

int* foo()
{
    int b = 10;
    return &b;
}

In both cases, the address of a local variable (b) is returned to and assigned to a. I know that the memory a is pointing should not be accessed when b goes out of scope. However, when compiling the above two programs, I receive the following warning for the second program only:

warning C4172: returning address of local variable or temporary

Why do I not get a similar warning for the first program?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Kumar
  • 616
  • 1
  • 18
  • 39
  • 1
    In the first, you're not returning. The compiler doesn't know that the pointer is going out of scope (it is not testing that case.) – Alexis Wilke Mar 20 '14 at 08:57
  • Undefined Behaviour affects the compiler too (not only the executable). – pmg Mar 20 '14 at 08:57
  • 3
    Both are cases of undefined behavior. You can't have a pointer to a variable that has gone out of scope. How and when it happens doesn't matter. – Some programmer dude Mar 20 '14 at 08:57
  • Possible duplicate of this [SO question](http://stackoverflow.com/questions/7632120/scope-vs-life-of-variable-in-c) – Jabberwocky Mar 20 '14 at 08:59
  • I think that compiler should give warning in the first case. Code generated by compiler allocates stack space for all local variables (including nested) in the beginning of the function, and releases it in the end. However, constructors/destructors are called exactly by definition: when execution enters/exits a nested block. So, in your case, b still exists, but if it is class instance, its destructor is already called. – Alex F Mar 20 '14 at 09:01
  • 2
    Those are C++, not C, programs. You can't just use "a little C++" and still have a C program. – unwind Mar 20 '14 at 09:04
  • Note that `void main()` is only valid on Windows. Elsewhere, it is `int main()`. – Jonathan Leffler Mar 20 '14 at 09:06
  • `int main` is correct on all windows C implementations, too. – CB Bailey Mar 20 '14 at 09:24

6 Answers6

5

As you already know that b goes out of scope in each instance, and accessing that memory is illegal, I am only dumping my thoughts on why only one case throws the warning and other doesn't.

In the second case, you're returning the address of a variable stored on Stack memory. Thus, the compiler detects the issue and warns you about it.

The first case, however skips the compiler checking because the compiler sees that a valid initialized address is assigned to a. The compilers depends in many cases on the intellect of the coder.

Similar examples for depicting your first case could be,

char temp[3] ;
strcpy( temp, "abc" ) ;

The compiler sees that the temp have a memory space but it depends on the coder intellect on how many chars, they are going to copy in that memory region.

Abhineet
  • 5,320
  • 1
  • 25
  • 43
2

your foo() function has undefined behavior since it returns a pointer to a part of stack memory that is not used anymore and that will be overwritten soon on next function call or something

it is called "b is gone out of scope". Sure the memory still exists and probably have not changed so far but this is not guaranteed.

The same applies to your first code since also the scope of b ends with the closing bracket of the block there b is declared.

Edit: you did not get the warning in first code because you did not return anything. The warning explicitly refers to return. And since the compiler may allocate the stack space of the complete function at once and including all sub-blocks it may guarantee that the value will not be overwritten. but nevertheless it is undefined behavior.

may be you get additional warnings if you use a higher warning level.

vlad_tepesch
  • 6,681
  • 1
  • 38
  • 80
0

In the first code snippet even though you explicitly add brackets the stack space you are using is in the same region; there are no jumps or returns in the code so the code still uses consecutive memory addresses from the stack. Several things happen:

  1. The compiler will not push additional variables on the stack even if you take out the code block.
  2. You are only restricting the visibility of variable b to that code-block; which is more or less the same as if you would declare it at the beginning and only use it once in the exact same place, but without the { ... }
  3. The value for b is most likely saved in a register which so there would be no problem to print it later - but this is speculative.

For the second code snippet, the function call means a jump and a return which means:

  1. pushing the current stack pointer and the context on the stack
  2. push the relevant values for the function call on the stack
  3. jump to the function code
  4. execute the function code
  5. restore the stack pointer to it's value before the function call

Because the stack pointer has been restored, anything that is on the stack is not lost (yet) but any operations on the stack will be likely to override those values.

I think it is easy to see why you get the warning in only one case and what the expected behavior can be...

Pandrei
  • 4,843
  • 3
  • 27
  • 44
0

Maybe it is related with the implementation of a compiler. In the second program,the compiler can identify that return call is a warning because the program return a variable out of scope. I think it is easy to identify using information about ebp register. But in the first program our compiler needs to do more work for achieving it.

Abhineet
  • 5,320
  • 1
  • 25
  • 43
Thinking
  • 13
  • 2
0

Your both programs invoke undefined behaviour. Statements grouped together within curly braces is called a block or a compound statement. Any variable defined in a block has scope in that block only. Once you go out of the block scope, that variable ceases to exist and it is illegal to access it.

int main(void) {
    int *a;
    {   // block scope starts

        int b = 10;  // b exists in this block only
        a = &b;

    }   // block scope ends

    // *a dereferences memory which is no longer in scope
    // this invokes undefined behaviour

    printf("%d\n", *a);  
}

Likewise, the automatic variables defined in a function have function scope. Once the function returns, the variables which are allocated on the stack are no longer accessible. That explains the warning you get for your second program. If you want to return a variable from a function, then you should allocate it dynamically.

int main(void) {
    int *a;
    a = foo();
    printf("%d\n", *a);
}

int *foo(void) {
    int b = 10;  // local variable

    // returning the address of b which no longer exists
    // after the function foo returns

    return &b;  
} 

Also, the signature of main should be one of the following -

int main(void);
int main(int argc, char *argv[]);
ajay
  • 9,402
  • 8
  • 44
  • 71
-1

In your first program-

The variable b is a block level variable and the visibility is inside that block only. But the lifetime of b is lifetime of the function so it lives upto the exit of main function. Since the b is still allocated space, *a prints the value stored in b ,since a points b.

Enkesh Gupta
  • 37
  • 2
  • 9
  • Wrong. https://en.cppreference.com/w/c/language/storage_duration : Every object has a property called *storage duration*, which limits the object *lifetime*. There are four kinds of storage duration in C: *automatic storage duration.* The storage is allocated when the block in which the object was declared is entered and deallocated when it is exited by any means (goto, return, reaching the end). One exception is the VLAs... – imz -- Ivan Zakharyaschev Jul 02 '19 at 14:25