7

I was trying to understand the exact meaning of scope in C. What I could understand is that scope is limited to compile time only. For example, in case you access the local variable from some other function. This will result in a compile time error. On the other hand, the following program works fine. This means that the C has a flat memory model and anything can be accessed at run time. C books associate scope with lifetime and variable visibility, I found it quite confusing. I think all these terms makes sense only for the compile time. Can someone please throw light on it?

#include "stdio.h"

int *ptr;

int func(void)
{
  /** abc is a local variable **/
  int abc = 132;
  ptr = &abc;
  return 0;
}

int func1(void)
{

  /** although scope of abc is over still I can change the value in the address  of abc **/
  *ptr = 200;
  printf("the value of abc=%d\r\n",*ptr);

}

int main(void)
{
   func();
   func1();
   return 0;
}

Results: the value of abc=200

In the simpler words, what is meant by scope? Does it come into the picture at run time or compile time? As we can see, we can access anything at the run-time. But, if we don't follow the rules, then we will get the compilation error. For example, local variable reference in an another function. The compiler will throw an error saying, "variable not defined...".

Can I say the following about variables?

1) Scope attribute comes under compile time.
2) Lifetime attribute comes under run-time.
3) Visibility attribute comes under compile-time
Jens
  • 69,818
  • 15
  • 125
  • 179
dexterous
  • 6,422
  • 12
  • 51
  • 99
  • 3
    scope has only meaning in the context of variable name visibility, therefore its a compile time thing – Serve Laurijssen Sep 04 '13 at 11:41
  • I'd recommend you to read [Scope vs life of variable in C](http://stackoverflow.com/questions/7632120/scope-vs-life-of-variable-in-c) or [this](http://msdn.microsoft.com/en-us/library/592xa3sk.aspx) for a more formal treatment. – legends2k Sep 04 '13 at 12:06

7 Answers7

8

Yes, C's memory model allows you to access anything easily so you can actually do things like the above and see "interesting" results.

However, what you did here is specified as undefined behavior (UB) by the C standard. That means literally anything can happen; that might be what you expect, or it might not.

Note that you did not access "the local variable" because by the time you make the access func has already returned, so the lifetime of its local variables has expired. What you did access was a memory region that "just happened" to have an interesting value. If you called func1 from inside func then the behavior would be well-defined.

A few more notes:

Scope is definitely a compile-time-only concept; the scope of a name (variable, identifier, etc) is the subset of the program code where that name is recognized by the compiler.

This is very different than the lifetime of variables, which is independent of scope in the general case, and conflating the two is a common mistake. The lifetime and scope of local variables are indeed intertwined, but that's not true of everything.

Community
  • 1
  • 1
Jon
  • 428,835
  • 81
  • 738
  • 806
  • Thank you for the UB reference. I would like to add that although in such a trivial program you were apparently able to write and read from a variable that went out of scope, in practice the memory location where `abc` was stored would likely have been used by something else, so assigning to `*ptr` could override arbitrary memory, which could conceivably cause anything to happen. – Nicu Stiurca Sep 04 '13 at 12:10
  • If he would have used `static int abc` in `func()`, then the behavior would be the same as if he had called `func1()` from within `func()` since the lifetime of the variable would be for the duration of the program. – David M. Syzdek Sep 04 '13 at 16:35
  • @SchighSchagh - while well outside of guarantees and not the same on every machine, it's actually fairly simple to see why a typical implementation would *not* have re-used that memory yet when running this program. – Chris Stratton Sep 12 '13 at 22:30
1

what is meant by scope?

The scope of a variable is the portion of the text in which the variable can be referenced. A local variable has block scope: it is visible from its point of declaration to the end of the enclosing function body.
It has nothing to do with lifetime of a variable. It is the storage duration which tells about lifetime of a variable.

Does it come into the picture at run time or compile time?

It comes into the picture at compile time and linking time. When the program will try to access a local variable outside of its block, compiler will give you an error about that undeclared variable(which is local to its block).
This example will explain it better:

#include <stdio.h>

void userlocal(void);

int main()
{
    int a= 2;

    printf("local a in outer scope of main is %d\n",a);

    userlocal();

    printf("local a in scope of userlocal is %d\n",b); // This will give error at compile time
    return 0;

}

void userlocal(void)
{
    int b = 20;
    printf("local a in scope of userlocal is %d\n",b);
}

Output:

[Error] 'b' undeclared (first use in this function)  

Can I say the following about variables?

Yes you can say.

haccks
  • 104,019
  • 25
  • 176
  • 264
1

While in theory it's "just UB", in practice it is asking to actually fail. The location of abc is (in every implementation I know of) somewhere on the stack. Since you've left the initial block and then entered some other block, it is quite likely that something else will occupy that location of memory. Which you are going to overwrite.

aragaer
  • 17,238
  • 6
  • 47
  • 49
  • While unreliable, it's not necessarily true that the value will have been overwitten. The abandoned value that is trying to be read was at the highest level of stack utilization which has occurred (and since it's address was accessed, it won't be in a register). It typically would not be overwritten unless there's been an intervening usage of a greater amount of stack, and at least on a system where the arguments to printf() are passed in registers, that's likely not the case. – Chris Stratton Sep 12 '13 at 22:28
  • Just add a local variable or two to `func1`, assign values to them and make sure they won't be optimized out and it will be overwritten. – aragaer Sep 13 '13 at 06:40
1

As far as scope goes it's easiest to just think of C being compiled down into a series of register and memory manipulations, structures such as blocks, for-loops, if-statements, structs, etc, have no meaning beyond compilation, they are just abstractions to allow you as a programmer to keep your sanity.

As far as the example and memory goes here is my attempt at explaining it.

As everyone is saying you are using certain compilers specific implementations of an, by the standard, undefined action. To understand how this works you can think of the program you are writing as having two memories, the heap and the stack. As an example char *foo = malloc(50); allocates memory on the heap and char foo[] = "Foo" allocates it on the stack. The stack is the memory that remembers what you are doing and it contains a long list of stack frames, each function call adds a frame and each return pops a frame. The heap is the other kind of memory.

To illustrate this we have this program:

int *ptr;
int func() {
  int abc = 123;
  ptr = &abc;
  return 0;
}

int func1() {
  int def;
  printf("func1() :: *abc=%i\n", *ptr);
  def = 200;
  return 0;
}

int main() {
  func();
  printf("main() :: *ptr=%i\n", *ptr);
  func1();
  printf("main() :: *ptr=%i\n", *ptr);
}

And the following will be happening on the stack (- is undefined/unused memory, > to the left is where your program is currently executing, X is data):

|-----|
|-----|
|-----|

As we enter main() it pushes stack frame to the stack:

 |-----|
 |-----|
>|XXXXX| <- This is where all memory needed to execute main() is.

Then you call func() which, in the memory needed to execute, contains the integer 123.

 |-----|
>|XX123| <- This is the stack frame for func()
 |XXXXX| <- Still the stack frame for main()

func() sets the global pointer *ptr to the address of the integer on the stack. This means that when func() returns (and the memory isn't cleared since that would be a waste of CPU cycles) the value remains

 |-----|
 |--123|
>|XXXXX| <- main()

and can still be referenced by *ptr until you call the next function

 |-----|
>|XXXXX| <- This is the stack frame for func1()
 |XXXXX|

Now *ptr will appear to have some random value... but you can still access the memory position and change it. (If func() and func1() each defines just one local integer in their scope it's quite likely that *ptr will point to that integer in func1() too)

Bonus

I haven't tested the program but I would assume that it would print out something like this:

main() :: *ptr=123
func1() :: *ptr=<some random values>
main() :: *ptr=<possibly 200, could be something else>
Hobblin
  • 905
  • 1
  • 12
  • 22
0

You're right that C has a flat memory model. And allows accessing any memory region. Here ptr is a global pointer to an int. So it points to an address where an integer can be stored and retrieved. This will lead to an undefined behaviour in different kinds of scenarios.

sr01853
  • 6,043
  • 1
  • 19
  • 39
0

Scope is a compile time property and deal with when referencing a variable is valid or when it is visible which is different from the storage duration or lifetime of an object which tells when it is valid to access the memory associated with the variable.

In this case abc has automatic storage duration which means that it's duration extends for the block it is in, which in this case is the function func and attempting to access the memory associated with abc outside of that block is undefined behavior, it may appear to work but the results can not be relied on.

From the draft C99 standard section 6.2.4 Storage durations of objects paragraph talks about the various storage durations: static and automatic. In explains that a static variables lifetime is the entire execution of the program and in paragraph 5 it says:

For such an object that does not have a variable length array type, its lifetime extends from entry into the block with which it is associated until execution of that block ends in any way. [..]

So an automatic variable life is the block it is contained in and paragraph 2 says:

The lifetime of an object is the portion of program execution during which storage is guaranteed to be reserved for it. An object exists, has a constant address,25) and retains its last-stored value throughout its lifetime.26) If an object is referred to outside of its lifetime, the behavior is undefined. The value of a pointer becomes indeterminate when the object it points to reaches the end of its lifetime.

So referring to an object outside of it's guaranteed lifetime is undefined behavior.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
0

It's undefined and you shouldn't trust value. With small changes on your code (MSVC 2013) there is a surprise.

int func1()
{   
    /** although scope of abc is over still I can change the value in the address  of abc **/
    printf("the value of abc=%d\r\n", *ptr);
    *ptr = 200;
    printf("the value of abc=%d\r\n", *ptr);
    return 0;
}

int main()
{
    func();
    printf("the value of abc=%d\r\n", *ptr);
    func1();
}

the output is on my computer.

the value of abc=132
the value of abc=1519668
the value of abc=200

ColdCat
  • 1,192
  • 17
  • 29
  • Yes, because calling printf() and/or executing its internals requires more stack than was in use when the variable was stored. – Chris Stratton Sep 12 '13 at 22:34