Local, Global and Static Variables in C

Table of Contents

A variable is a name we give to a memory storage area that our program can then manipulate. We can specify its size and its type depending on the values it will contain (char, int, long). But we can also control its lifespan and its scope when we declare it. This is why we need to be able to distinguish between local, global and static variables when we program in C.

Local Variables

Local variables are very short-lived. Declared inside a function, they only exist in RAM as long as the function exists. The second their function ends, they disappear!

A Local Variable’s Scope

Let’s create a variable named a in a function as an example, and let’s try to print it from a different function:

#include <stdio.h>

void foo(void)
{
 int a;

 a = 10;
 printf("Foo function: Variable a = %d\n", a);
} // the variable 'a' ceases to exist in RAM here.

int main(void)
{
 foo();
 printf("Main: Variable a = %d\n", a);
 // ERROR : main does not know any variable named 'a'!
 return (0);
}

We will immediately get a compilation error. What is happening here is that the operating system placed the variable a in the stack when we declared it in foo. Then, when the foo function ends, the OS reclaims the RAM storage for both the variable and the function, to attribute it elsewhere. The main function has no reference to any variable named a because it was not declared within this function. Even if it did, the main function could not access a variable that no longer exists.

Thankfully, there are ways to pass the values of variables from one function to another. We could ensure that our foo function returns the value of a, like this:

#include <stdio.h>

int foo(void)
{
 int a;

 a = 10;
 printf("Foo: Variable a = %d\n", a);
 return (a);
} // the variable 'a' ceases to exist here, but we returned its value
  // before its destruction.

int main(void)
{
 int value;

 value = foo(); // the return value of foo is saved here
 printf("Main: Variable a = %d\n", value);
 return (0);
}

Otherwise, we can simply create the variable a in the main function and send it as a parameter of foo:

#include <stdio.h>

void foo(int a)
{
 // Foo has its own copy of the variable 'a' passed in parameters
 printf("Foo: Variable a = %d\n", a);
}

int main(void)
{
 int a;

 a = 10;
 printf("Main: Variable a = %d\n", a);
 foo(a); // Pass the variable 'a' to function foo
 return (0);
}

However, in this case, the variable a in the foo function is not the same as the variable a of the main function: it’s just a copy. If we modify the value of a in foo, the value of a in main remains the same:

#include <stdio.h>

void foo(int a)
{
 // Changing the value of 'a' in foo but not in main
 // since both variables are distinct
 a = 145;
 printf("Foo: Variable a = %d\n", a); // a == 145
}

int main(void)
{
 int a;

 a = 10;
 printf("Main: Variable a = %d\n", a); // a == 10
 foo(a);
 printf("Main: Variable a = %d\n", a); // a == 10
 return (0);
}

The result here will be:

Main: Variable a = 10
Foo: Variable a = 145
Main: Variable a = 10

In this example, there is no confusion or conflict between these two a variables since both have different scopes and “belong” to two different functions. The variable a in the foo function is declared in its parameters. The two local variables are therefore completely independent of each other and point to two distinct memory areas.

Changing the Value of a Local Variable from the Outside

So how can we change the value of the variable a from outside of the function in which it is declared? We need to do a little acrobatic trick: pass the memory address of the variable (its pointer) and change the value stored in that memory area. This works since the function in which we declared the variable has not ended yet.

#include <stdio.h>

void foo(int *a)
{
 *a = 145; // Change what is at the address of 'a'
 printf("Foo: Variable a = %d\n", *a); // *a == 145
}

int main(void)
{
 int a;

 a = 10;
 printf("Main: Variable a = %d\n", a); // a == 10
 foo(&a); // Pass the address of 'a', not the value
 printf("Main: Variable a = %d\n", a); // a == 145
 return (0);
}

Output:

Main: Variable a = 10
Foo: Variable a = 145
Main: Variable a = 145

Here, the foo function declares a variable containing the memory address of the variable a (its pointer). Then it can modify the value stored at that address. This is why when main later reads the value of this variable, it will have changed.

Global Variables

When we declare a variable outside of any function, it is a global variable. This means that it is accessible in any function of the program. Unlike local variables, a global variable does not disappear at the end of a function. It persists until the program comes to an end. This is because typically, the operating system doesn’t store global variables in the stack or in the heap, but in a separate memory area dedicated to globals.

Additionally, a global variable is always initialized to 0 by default.

#include <stdio.h>

int a; // Global variable initialized to 0 by default

void foo(void)
{
 a = 42; // Global variable accessible without
  // having been declared in the function
 printf("Foo: a = %d\n", a); // a == 42
}

int main(void)
{
 printf("Main: a = %d\n", a); // a == 0
 foo();
 printf("Main: a = %d\n", a); // a == 42
 a = 200;
 printf("Main: a = %d\n", a); // a == 200
 return (0);
}

Output:

Main: a = 0
Foo: a = 42
Main: a = 42
Main: a = 200

We can see here that we don’t need to pass the variable or its pointer as a function parameter to be able to access or even modify it.

Priority to the Local Variable

Let’s take note that there might be some ambiguity if we declare a local variable with the same name as a global:

#include <stdio.h>

int a; // Global variable initialized to 0 by default

void foo(void)
{
 a = 42;
 printf("Foo: a = %d\n", a); // a == 42
}

void global_a(void)
{
 // Prints the value of the global variable
 printf("-------------- GLOBAL A: a = %d\n", a);
}

int main(void)
{
 int a; // Local variable with the same name as the global
 a = 100;
 global_a(); // a globale == 0
 printf("Main: a = %d\n", a); // a locale == 100
 foo();
 printf("Main: a = %d\n", a); // a locale == 100
 global_a(); // a globale == 42
 a = 200;
 printf("Main: a = %d\n", a); // a locale == 200
 global_a(); // a globale == 42
 return (0);
}

Output:

-------------- GLOBAL A: a = 0
Main: a = 100
Foo: a = 42
Main: a = 100
-------------- GLOBAL A: a = 42
Main: a = 200
-------------- GLOBAL A: a = 42

Clearly, the local variable takes precedence over the global variable of the same name. We need to keep this in mind, since it could cause confusion during the debugging process.

Scope of a Global Variable

Any function of a program can access a global variable. If we want to use a global variable defined in one file in another file, all we need to do is declare it once again with the extern keyword. This usually implicit keyword tells the compiler that we are declaring something that we are defining elsewhere in the program files.

Let’s take our initial example and separate our two functions, main and foo, into two separate files:

  • main.c
  • foo.c

In the main.c file, we will declare the global variable with the extern keyword, to say that we’re defining this variable elsewhere. It’s a similar declaration to the foo function prototype that we will also define in a separate file. The compiler understands implicitly that it should consider the foo prototype as extern as well.

#include <stdio.h>

extern int a; // Global variable, defined elsewhere

void foo(void); // Foo prototype, defined elsewhere
  // is identical to
  // extern void foo(void);

int main(void)
{
 printf("Main: a = %d\n", a); // a == 100
 foo();
 printf("Main: a = %d\n", a); // a == 42
 a = 200;
 printf("Main: a = %d\n", a); // a == 200
 return (0);
}

In the foo.c file, we declare and define the global variable a as well as the foo function:

#include <stdio.h>

int a = 100; // Global variable declared and defined here

void foo(void)
{
 a = 42;
 printf("Foo: a = %d\n", a); // a == 42
}

Output:

Main: a = 100
Foo: a = 42
Main: a = 42
Main: a = 200

As with function prototypes, we can of course declare extern int a in a header.h file. Incidentally, it is better than defining a global variable directly in the header.

Of course, having a variable that is accessible by any function in any file of a program might quickly prove to be a security concern. Which is why we can make our global variables static…

Static Variables

We can declare a local or global variable as a static. The static keyword has a very simple logic. A static variable is by default a global variable: stored neither in the stack nor the heap, it has the same lifespan as its program. But unlike a true global variable, it has a limited scope:

  • inside a function, it’s a global that’s only visible inside the function in which we declare it.
  • outside any function, it’s a global variable visible only in the file (i.e. in the compilation unit) in which we declare it.

Local Static Variables

So a local static variable is really not a local variable at all. It’s a global variable in disguise, that does not disappear at the end of the function in which we declare it, since it isn’t stored in the stack. However, the static keyword confines it to the scope of its function, like a local variable. And, like any global variable, it is always initialized to 0 by default.

Let’s compare two variables, a local variable and a static variable declared inside a function:

#include <stdio.h>

void foo(void)
{
 int  a = 100;
 static int b = 100;

 printf("a = %d, b = %d\n", a, b);
 a++;
 b++;
}

int main(void)
{
 foo();
 foo();
 foo();
 foo();
 foo();
 return (0);
}

Output:

a = 100, b = 100
a = 100, b = 101
a = 100, b = 102
a = 100, b = 103
a = 100, b = 104

When we call the foo function repeatedly, we can see that the static variable b is incremented, which means it conserves its value long past its function’s lifespan. But the local variable a, which is not static, gets reinitialized each time we call the foo function.

This doesn’t mean that the static variable b is accessible from any other function. It still exists in memory after the foo function ends like a global variable. But like a local variable, we can only access it from the foo function. If we try to print it in the main function for instance, we will get another compilation error. However, we can send its pointer to another function if need be, as we’ve seen with the local variables.

Global Static Variables

For static variables declared outside any function, the static keyword restrains their scope to the file in which we declare them. The functions in the same file following the global static variable’s declaration will be able to use it, but we won’t be able to access it from another of the program’s files.

If we return to our previous example of the global variable across two files, and if we transform the global in foo.c into a static, we will get compilation errors: “undefined reference to ‘a’”. This comes from the fact that we declare in main.c that there is an extern definition of a elsewhere in the program. Except that a is a static variable, which means it is “invisible” to the compiler. Since it cannot find a valid definition of the global variable a, the compiler returns an error. For the compiler, extern and static are exact opposites.

Similarly, it is possible and well-advised to use the static keyword when declaring functions that we only use in a single file of a program. Otherwise, the compiler thinks by default that the declared functions are extern and will have to be linked to other files. Using the static keyword is a simple security measure for a program. It might even speed up compilation in some cases.

Sources and Further Reading

  • B. Kernighan, D. Ritchie, 1988, The C Programming Language, Second Edition, “4.6 Static Variables”, p. 83
  • StackOverflow, C/C++ global vs static global [ stackoverflow,com]
  • StackOverflow, Static declaration of m follows non-static declaration [ stackoverflow.com]
  • Geeks for Geeks, Understanding “extern” keyword in C [ geeksforgeeks.com]
  • StackOverflow, Why and when to use static structures in C programming? [ stackoverflow.com]
  • StackOverflow, Reasons to use Static functions and variables in C [ stackoverflow.com]
  • StackOverflow, Global declaration is in stack or heap? [ stackoverflow.com]

Comments

Related Posts

Handling a File by its Descriptor in C

The available system calls to create or open, read, write, and delete a file in C all make use of a file descriptor.

Read More

Sockets and Network Programming in C

In this hyper-connected electronic world, knowing how to send and receive data remotely with sockets is crucial.

Read More