Scope of Variables

Now let's look at another distinct way to categorize variables: the scope of a variable is the part of the program in which it can be accessed. Here, we are concerned with local scope and global scope. Variables with global scope are called global variables. These variables are defined outside any function and therefore by default can be accessed from any function.[27] Global variables are always in the static storage class, as we have already seen. Variables with local scope are called local variables. These variables are defined in a function and are accessible only while that function is executing; they can be either static or auto and are auto by default.

[27] Variables can be defined either inside a function (local variables) or outside a function (global variables); by contrast, code must always be inside a function.

Figure 5.13 shows the valid combinations of scope and storage class.

Figure 5.13. Scope vs. storage class


Susan had some comments on this topic.

Susan: On this scope stuff; I think you are going to have to help me understand exactly what is inside a function and what is outside a function. I am not too sure I know what the difference is.

Steve: All code is inside a function. However, some variables (global variables) are outside all functions and therefore shareable by all functions. Variables that are defined inside functions are called local, because they are available only to the code that's in that function.

Susan: I am validating this with you now. Please correct any misconceptions.

  1. Only variables are declared outside functions.

  2. No code is written outside functions.

  3. Up to this point I am not to be aware of anything else going on outside a function.

Steve: Correct.

Before we get deeper into the notion of scope, I think we should revisit the question of variable initialization in the light of the notion of global and local variables. This is a difficult topic, so it wouldn't be surprising if you don't find it obvious; Susan didn't. I wrote the next section to explain this topic to her.

Automatic vs. Static Allocation

What makes a variable static or auto is when its storage is assigned, and therefore when its address is known. In the case of a static variable, this happens at link time. In the case of an auto variable it happens when the function where it is defined is entered.[28]

[28] By the way, if you were worried about keeping track of the address of every variable, that's the compiler's problem, not yours. The important distinction between a static and an auto variable is when the address is assigned, not what the actual address is.

This distinction affects initialization because it's impossible to initialize something until you know where it is. Therefore, an auto variable cannot be initialized until the function where it is defined is entered. This also means that you cannot assume that an auto variable will retain its value from one execution of the function where it's defined to the next execution of that function, because the variable might be at a different location the next time.

These restrictions do not apply to static variables, because their addresses are known at link time and don't change thereafter. A variable defined outside all functions (a global variable) is automatically in the static storage class, because otherwise its address would never be assigned.[29] Since its address is known at link time, the initialization of such a variable can be and is performed before the start of main.

[29] Since all global variables are already in the static storage class, you don't have to (and shouldn't) use the keyword static when declaring a global variable. In another confusing legacy from C, the word static has an entirely different and unrelated meaning when used for a global variable.

A static variable that is defined inside a function is different from one defined globally, in that it is not initialized until the function where it is defined is entered for the first time. However, its value is retained from one execution of its function to another, because its address is fixed rather than possibly varying from one call of the function to the next as can occur with an auto variable. For this property to be of use, the initialization of a static variable in a function must be performed only once; if it were performed on each entry to the function, the value from the previous execution would be lost. Therefore, that initialization is done only once, when the function is first entered.

Susan wanted some more explanation of what happens at link time and the related question of why we would want to use static variables.

Susan: Will you tell me again what happens at link time? Let's see, I think it goes like this: The source code is translated into an object file and then the object file is linked to the hardware to make executable code. Is that how it goes?

Steve: Not quite. The object file is linked with library files containing predefined functions in compiled form.

Susan: Now, tell me again why you would want a variable to be static? What is the advantage to that? Does it just take up less room and be more efficient?

Steve: The advantage is that a static variable keeps its value from one function call to the next. For example, suppose you wanted to count the number of times that a function was called. You could use a static variable in the function, initialize it to 0, and add 1 to it every time the function was called. When the program was done, the value of that variable would be the number of times the function was called. Obviously, we couldn't use an auto variable to do that, as it would have to be initialized every time the function started or we'd have an uninitialized variable.

Susan: I can see how using a static variable would work but I don't see why an auto variable couldn't do the same thing. Well, I guess it would change each time the function would be used. Are you saying in this case that the variable has to be global?

Steve: Not exactly. Although we could use a global variable, we could also use a static local variable. Figures 5.14 through 5.19 are some sample programs to illustrate the situation.

Figure 5.14. Using an auto variable and initializing it (codecount1.cpp)
#include <iostream>
using namespace std;

short counter()
{
  short count = 0;

  count ++;

  cout << count << " ";
  return 0;
}

int main()
{
  short i;

  for (i = 0; i < 10; i ++)
     counter();

  return 0;
}

Figure 5.15. Using an auto variable and not initializing it (codecount2.cpp)
#include <iostream>
using namespace std;

short counter()
{
  short count;

  count ++;

  cout << count << " ";

  return 0;
}

int main()
{
  short i;

  for (i = 0; i < 10; i ++)
     counter();

  return 0;
}

Figure 5.16. Using a local static variable and initializing it explicitly (codecount3.cpp)
#include <iostream>
using namespace std;

short counter()
{
  static short count = 0;

  count ++;

  cout << count << " ";

  return 0;
}

int main()
{
  short i;

  for (i = 0; i < 10; i ++)
     counter();

  return 0;
}

Figure 5.17. Using a local static variable and not initializing it explicitly (codecount4.cpp)
#include <iostream>
using namespace std;

short counter()
{
  static short count;

  count ++;

  cout << count << " ";

  return 0;
}

int main()
{
  short i;

  for (i = 0; i < 10; i ++)
     counter();

  return 0;
}

The Scope Resolution Operator

Let me interrupt the conversation here to point out something new in versions 5 and 6 of this little program: the “::” scope resolution operator. This is an example of what we were discussing in the section called “using, namespace, and std” on page 98 in Chapter 3. Susan's question was how we would refer to our own variables if they had the same names as variables in the standard library. The answer is to use the scope resolution operator to tell the compiler that we want to use global identifiers defined in our source code, not ones of the same name in the standard library. If we leave off the scope resolution operator in front of count in these programs, the compiler will report an error when it tries to compile them, because there is an identifier named count in the std namespace, and and the line using namespace std; at the beginning of each of our programs tells the compiler that we want to be able to access all the identifiers in the std namespace without specifying explicitly that they are from that namespace.

Now let's continue with the analysis of scope.

Figure 5.18. Using a global variable and initializing it explicitly (codecount5.cpp)
#include <iostream>
using namespace std;

short ::count = 0;

short counter()
{
  ::count ++;

  cout << ::count << " ";

  return 0;
}

int main()
{
  short i;

  for (i = 0; i < 10; i ++)
     counter();

  return 0;
}

Figure 5.19. Using a global variable and not initializing it explicitly (codecount6.cpp)
#include <iostream>
using namespace std;

short ::count;

short counter()
{
  ::count ++;

  cout << ::count << " ";

  return 0;
}

int main()
{
  short i;

  for (i = 0; i < 10; i ++)
     counter();

  return 0;
}

Steve: Now that I've cleared that up, what will each of these programs do when run?

Susan: Now, let me think about this. Variables are: static or auto, global or local.

Local is for use only within functions. These variables are mostly auto, and will be auto by default, but they can be static; in the latter case, they will be initialized to 0.

Steve: Correct; a static numeric variable is initialized to 0 if no other provision is made by the programmer to initialize it. One fine point: a local variable can be used only within one function.

Susan: Global variables are declared only outside functions. They are always allocated storage at link time, like static local variables.

Steve: Correct.

Susan: Variables with static allocation are fixed because they are initialized at link time and thus are done just once and never change. But they can be local or global.

Steve: Not exactly. The address of a statically allocated variable is set once and never changes; its value can change just like the value of an auto variable can.

Susan: That's what I had mixed up. I was confusing addresses with values.

Steve: OK, as long as you're straightened out now.

Susan: An auto variable is assigned an address when the function where it is defined is entered. All auto variables are local.

Steve: Correct.

Susan: Now, here is where I am confused. What is the difference between at link time and when the function where it is defined is entered? Does at link time mean when you are done with your source code and you are making an executable?

Steve: Yes.

Susan: And does when the function where it is defined is entered mean when the program is already made into an executable and you are running the program?

Steve: Yes.

Susan: I am confused about what we mean by initialization. I am confusing declaring a value for a variable and the designation of an address for a variable. It almost seems as if we are using these two meanings for the same term. I always thought that initializing a variable meant just assigning a value to it.

Steve: Initializing a variable means assigning an initial value to it. In the case of an auto variable, this must be done every time the function where it is declared is entered; whereas with a static variable, it is done once.

Susan: OK, this is what I imagined this to mean. Then how come, in your figure of a stack, you have values assigned to the places where the variables are?

Steve: Those values are present only after the variables to which they correspond have been initialized. In Figure 5.12, for example, the contents of the address corresponding to Result are shown as ???, rather than as a valid value; whereas the values of First and Second are shown as initialized, because they have already been set to values equal to the input arguments provided by the caller.

Susan: Remember when I started tracing the “sorting” program? It had random numbers in the places where numbers are supposed to go, and when I actually entered a value then those random numbers were replaced by that value. And is that why you put ??? there, because you know that something is there but you don't know exactly what? It is just whatever until you can put a real value into those slots.

Steve: Right.

Susan: So if you leave it alone by not initializing it, then it keeps the last value it had each time it goes through the loop and therefore the count goes up?

Steve: Yes, except that the initial value isn't reliable in that case. In the case of the example count programs, that value happened to be 0, but there's no reason to expect that in general.

Susan: I want you to know that it was not immediately apparent to me just what the code in the example programs was doing; it really does look kinda strange. Then I noticed that this code called a function named counter. Why? Couldn't this work without using a function call?

Steve: No, it wouldn't work without a function call, because the whole point is that when a function is called, the auto variables defined in that function have unknown values. How would I show that without a function call?

Susan: I see that. But I still don't get the point, because they all did the same thing except 1. The results for that were the following: 1 1 1 1 1 1 1 1 1 1. The results for the rest of the programs were all the same, being 1 2 3 4 5 6 7 8 9 10. So, if they all do the same thing, then what is the point? Now, what really makes me mad about this is why 1 has that result. This bothers me. Obviously it is not incrementing itself by 1 after the first increment, it is just staying at one. Oh, wait, okay, okay, maybe. . . how about this: If you initialize it to 0 then each time it comes up through the loop it is always 0 and then it will always add 1 to 0 and it has to do that 10 times.

Steve: Right, except that #2 does the same thing as the others only by accident; you got lucky and happened to have a 0 as the starting value of the uninitialized local variable in that example.

Susan: Then on the initialized local static variable, why does it work? Because it is static, and because its address is one place and won't budge; that means its value can increment. Well, would that mean it isn't written over in its location every time the function is called so we can add a value to it each time through?

Steve: Right.

Susan: And then the uninitialized static one works for the same reason the auto uninitialized one works.

Steve: Not quite. A static local variable is always initialized to something, just like a global variable is. If you don't specify an initial value for a static local numeric variable, it will be initialized to 0 during the first execution of the function where it is defined. On the other hand, as I mentioned above, you just got lucky with the uninitialized local variable example, which happened to have the starting value 0. Other people using other computers to run the program may have different results for that example.

Susan: Now as for global, this is hard. Let me guess. Do the global initialized and uninitialized work for the same reasons I said earlier?

Steve: The global variables are always initialized, whether you specify an initial value or not; if you don't specify one, it will be 0.

Susan: That's what you said about static numeric variables. Are they the same? Well, they have to be the same because only static variables can be global, right?

Steve: Correct. Global variables are always statically allocated.

Susan: So if you don't initialize a numeric variable then it can become any number unless it is a static numeric without explicit initialization and then it will be 0 by default?

Steve: Correct.

Susan: OK, let me see if I have this. All static really means is that the variable is put in an address of memory that can't be overwritten by another variable but can be overwritten when we change the variable's value?

Steve: Right.

Susan: These are tricks, and you know I don't like tricks. If they are global numeric variables, whether explicitly initialized or not, they are static; therefore they will at least have a value of 0. In example 5 this is stated explicitly but not in example 6, so the variable also will take the value of 0 by default, therefore these two programs are effectively identical, just like 3 and 4. That is why examples 3, 4, 5, and 6 have the same results.

Steve: Well, obviously the trick didn't work; you crossed me up by getting the right answers anyway.

Let's pause here to look at a sample program that has examples of all the types of variables and initialization states we've just discussed. These are:[30]

[30] Remember, there aren't any global auto variables, because they would never be initialized.

  1. global, not explicitly initialized

  2. global, explicitly initialized

  3. auto, uninitialized

  4. auto, initialized

  5. local static, not explicitly initialized

  6. local static, explicitly initialized

Careful examination of the sample program shown in Figure 5.20 will help you to visualize how and where each of these variable types might be used. As usual, you can compile and run it to see what it does; running it under the debugger is probably more helpful than running it directly.

Figure 5.20. Using variables of different scopes and storage classes (codescopclas.cpp)
#include <iostream>
using namespace std;

short count1; // A global variable, not explicitly initialized
short count2 = 5; // A global variable, explicitly initialized

short func1()
{
  short count3; // A local auto variable, not explicitly initialized
  short count4 = 22; // A local auto variable, explicitly initialized
static short count5; // A local static variable, not explicitly initialized
static short count6 = 9; // A local static variable, explicitly initialized

  count1 ++; // Incrementing the global variable count1.
  count2 ++; // Incrementing the global variable count2.
  count3 ++; // Incrementing the local uninitialized auto variable count3.
  count4 ++; // Incrementing the local auto variable count4.
  count5 ++; // Incrementing the local static variable count5.
  count6 ++; // Incrementing the local static variable count6.

  cout << "count1 = " << count1 << endl;
  cout << "count2 = " << count2 << endl;
  cout << "count3 = " << count3 << endl;
  cout << "count4 = " << count4 << endl;
  cout << "count5 = " << count5 << endl;
  cout << "count6 = " << count6 << endl;
  cout << endl;

  return 0;
}

int main()
{
  func1();
  func1();

  return 0;
}

Figure 5.21. The results of using variables of different scopes and storage classes (codescopclas.out)
count1 = 1
count2 = 6
count3 = -32715
count4 = 23
count5 = 1
count6 = 10

count1 = 2
count2 = 7
count3 = -32715
count4 = 23
count5 = 2
count6 = 11

The results shown should help to answer the question of when we would want to use a static variable rather than an auto variable: whenever we need a variable that keeps its value from one execution of a function to another. You may be wondering where that weird value for count3 came from. Since we never initialized it, we can't complain when its value is meaningless. Although the compiler can warn us about such problems in some cases, they are still a significant source of errors in C++ programs, so it's worthwhile remembering to look out for them.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset