Scope is about where in a program you can use a variable. A variable might only be usable inside one function, or it might be usable everywhere in the file.

There are two kinds you will see first:

  • Local scope variables (local variables)
  • Global scope variables (global variables)

Local scope

A variable defined inside a function is local to that function. Nothing outside can see it.

#include <stdio.h>
 
int main() {
	int rpm = 900;
	printf("rpm is %d\n", rpm);
	return 0;
}

Here rpm only exists inside main. Its scope is the curly brackets of the function.

Using a local variable from another function

Now add a second function that tries to change rpm:

#include <stdio.h>
 
void revUp(void);
 
int main() {
	int rpm = 900;
	printf("main: rpm is %d\n", rpm);
	revUp();
	return 0;
}
 
void revUp(void) {
	rpm = 800;
	printf("revUp: rpm is %d\n", rpm);
}
main.c: In function 'revUp':
main.c:14:2: error: 'rpm' undeclared (first use in this function)

revUp has no idea what rpm is. That variable belongs to main, so revUp can’t touch it. You can use a local variable anywhere inside its own function, but not outside of it.

Note

The void revUp(void); line near the top is a function prototype. It tells the compiler the function exists before it sees the body. We will cover that in a later note.

Global scope

If several functions need the same variable, move it out of the function and into the global scope, which is the space outside every function.

#include <stdio.h>
 
int rpm = 900;
 
void revUp(void);
 
int main() {
	printf("main: rpm is %d\n", rpm);
	revUp();
	printf("main again: rpm is %d\n", rpm);
	return 0;
}
 
void revUp(void) {
	rpm = 800;
	printf("revUp: rpm is %d\n", rpm);
}
main: rpm is 900
revUp: rpm is 800
main again: rpm is 800

Now every function in this file can see rpm. The order of events:

  1. main prints 900.
  2. main calls revUp.
  3. revUp changes the global rpm to 800 and prints it.
  4. Control goes back to main, which prints 800, because it was the same variable all along.

Same name in local and global scope

You can use the same name in both places. The compiler is fine with it because they live in different scopes.

#include <stdio.h>
 
int limit = 6000;
 
int main() {
	int limit = 7000;
	printf("limit is %d\n", limit);
	return 0;
}
limit is 7000

The local variable wins. When the names match, the compiler always looks in the local scope first. The local limit hides the global one. This is called shadowing.

What you cannot do is define two variables with the same name in the same scope:

int limit = 6000;
float limit = 7000;
error: conflicting types for 'limit'

Block scope

Curly brackets alone make a scope. You don’t need a function for it.

#include <stdio.h>
 
int rpm = 4000;
 
int main() {
	{
		int rpm = 7000;
		printf("inside block: %d\n", rpm);
	}
	printf("outside block: %d\n", rpm);
	return 0;
}
inside block: 7000
outside block: 4000

The inner rpm only lives between its own brackets. Once the program leaves the block, that variable is gone, so the second printf finds the global rpm.

Important

When execution leaves the scope of a local variable, the variable dies. It no longer exists.

Try to predict the output of this program before reading on.

#include <stdio.h>
 
int rpm;
 
int main() {
	rpm = 2500;
 
	{
		int rpm = 4500;
		printf("inside block: %d\n", rpm);
	}
 
	printf("outside block: %d\n", rpm);
 
	int boost;
	boost = boost + 10;
	printf("boost: %d\n", boost);
 
	return 0;
}

Walking through it

rpm is a global variable. Inside main, the first line sets it to 2500.

Then a block starts with curly brackets, and a new local rpm is defined inside it with the value 4500. This is legal because the local one lives in a different scope. Inside the block, the local rpm hides the global one, so the first printf prints 4500.

When the block ends, the local rpm dies. The next printf sees the global rpm again, which is still 2500.

So the first two lines of output are:

inside block: 4500
outside block: 2500

The uninitialized variable

Next we define boost without giving it a value, then run boost = boost + 10.

In an assignment, the right hand side is evaluated first. So the program reads boost, adds 10, and stores the result back into boost. But boost never had a value, so it holds whatever garbage was already sitting in that memory. Garbage plus 10 is still garbage, and the last line is unpredictable.

boost: 32778

Your number will be different, and it may be different on every run.

Warning

Local variables are not initialized automatically. If you don’t give them a value, they contain random leftover data.

You might get lucky and see 10, because the leftover value happened to be 0. Don’t rely on that. It can change with the compiler, the optimization level or even the machine.

Let the compiler warn you

Compile with warnings turned on:

gcc -Wall main.c -o main
main.c: In function 'main':
main.c:17:10: warning: 'boost' is used uninitialized [-Wuninitialized]

The compiler is telling you that you read a variable before giving it a value. This one is harmless in a tiny example, but the same mistake in a bigger program causes bugs that are very hard to track down. Treat this warning seriously.

The fix is to always give a local variable a starting value:

int boost = 0;

Global vs local defaults

Global variables are different. They start at 0 if you don’t initialize them. That is why int rpm; at the top of the program was safe even before we assigned 2500.

Global variablesLocal variables
ScopeVisible to all functions in the programOnly inside the body of the function (or block) where they are defined
Access from another fileYes, by declaring it there with externNo
Default value0 if not initializedUnpredictable, also called a garbage value
LifetimeUntil the program finishes executingUntil the function in which they are defined finishes executing