Why this matters
A program that cannot remember anything can only print fixed text. Variables are how a program stores values it computed or read, so it can use them later. In C, a variable is something very concrete: a few bytes of memory with a type that says how to interpret them. Understanding that concreteness now explains later surprises, such as why an uninitialized variable prints garbage, or why two variables with the same name can coexist.
What a variable is
A variable is a named region of memory that holds a value of a particular type. When you write
int age = 42;
you ask the compiler for enough memory to hold an int (4 bytes on every system you are likely to use), to call it age in your source, and to put the value 42 in it. In memory it might look like this (addresses vary from run to run; these are made up):
name address bytes (an int is 4 of them)
age 0x7ffd1000 [ 0x2A ][ 0x00 ][ 0x00 ][ 0x00 ] = 42
Remember from the first lesson that the name age does not exist at run time. The compiler turns every use of age into "the 4 bytes at this address" (or a CPU register). The type int tells it how many bytes and how to read them: as a signed whole number.
Declaring, initializing, assigning
Three related things happen with variables:
int width; /* declaration: width exists, its type is int, its value is unknown */
int height = 5; /* declaration with initialization: starts as 5 */
width = 10; /* assignment: store 10 into an existing variable */
height = height + 1; /* read height (5), add 1, store 6 back into height */
- A declaration introduces a name and its type. It must come before the name is used. C reads top to bottom.
- An initializer (
= 5in a declaration) gives the variable its first value. - An assignment (
=as a statement) replaces the current value. The right side is computed first, then stored into the left side. That is whyheight = height + 1makes sense:=is not mathematical equality, it is "store into".
You can declare several variables of the same type at once, int x = 1, y = 2, z;, but one per line is clearer and makes it obvious which ones were initialized.
A small program that uses variables:
#include <stdio.h>
int main(void)
{
int width = 12;
int height = 5;
int area = width * height;
int perimeter = 2 * (width + height);
printf("area = %d\n", area);
printf("perimeter = %d\n", perimeter);
width = width + 1;
printf("new width = %d, area is still %d\n", width, area);
return 0;
}
area = 60
perimeter = 34
new width = 13, area is still 60
The last line shows something important: area was computed once, when its declaration ran. It holds the number 60, not a formula. Changing width afterwards does not change area.
Names
An identifier (a name for a variable, function or type) is made of letters, digits and underscores, may not start with a digit, and is case-sensitive: count, Count and COUNT are three different names. Keywords such as int, return, if, while and for are reserved. C programmers usually write variables in lower_case_with_underscores; you will also see camelCase. Choose names that say what the value means: seconds_elapsed, not s or temp2.
Uninitialized variables
What does a local variable contain if you never give it a value?
#include <stdio.h>
int main(void)
{
int count;
printf("%d\n", count);
return 0;
}
$ gcc -std=c17 -Wall -Wextra -g -O0 -o prog main.c
main.c: In function 'main':
main.c:6:5: warning: 'count' is used uninitialized [-Wuninitialized]
6 | printf("%d\n", count);
| ^~~~~~~~~~~~~~~~~~~~~
main.c:5:9: note: 'count' was declared here
$ ./prog
32767
The 32767 is not chosen by anyone. The memory for count was used for something else before (perhaps by the startup code that ran before main), and C does not clear it, because clearing costs time and C never does work you did not ask for. The value is indeterminate; using it is a bug, and in some cases undefined behavior. Your run may print a different number, or 0, which is worse because it hides the bug.
Warning: Always initialize local variables when you declare them, even if only to 0. It costs nothing measurable and removes a whole class of bugs.
Scope: where a name is visible
A variable declared inside a block { ... } exists from its declaration to the closing brace of that block. This region is its scope. Blocks can nest, and an inner block may declare a name that already exists outside, which shadows (hides) the outer one until the inner block ends:
#include <stdio.h>
int main(void)
{
int x = 5;
{
int x = 99; /* a different variable, also called x */
printf("inner x = %d\n", x);
}
printf("outer x = %d\n", x);
return 0;
}
inner x = 99
outer x = 5
Two separate variables, two separate pieces of memory. Shadowing is legal but confusing; avoid it in real code (gcc's -Wshadow warns about it). Blocks become really useful with if and loops in the control-flow module.
const: a variable that must not change
Adding const to a declaration makes the variable read-only after initialization:
const int days_per_week = 7;
days_per_week = 8; /* error */
main.c:6:19: error: assignment of read-only variable 'days_per_week'
Use const for values that should never change. It documents intent and lets the compiler catch accidental writes. (const becomes much more important with pointers.)
Common mistakes
- Using a name before declaring it: Either the declaration is missing, it comes later in the file, it is in a block that has already ended, or the name is misspelled (remember case matters).
main.c:6:22: error: 'y' undeclared (first use in this function) - Declaring the same name twice in the same block:
error: redefinition of 'x'. Assign to the existing variable instead of writingintagain. - Reading a variable before giving it a value. The compiler often warns, but not always (for example when the assignment happens in only some branches of an
if). - Expecting a variable to update by itself, as in the
areaexample. Variables hold values, not formulas. - A name starting with a digit, such as
int 2nd = 3;: The compiler read2as a number andndas a strange suffix on it.main.c:11:9: error: invalid suffix "nd" on integer constant
Recap
- A variable is typed, named memory; the type sets its size and how its bytes are interpreted.
- Declare before use; initialize at declaration;
=assigns by computing the right side and storing it into the left. - Uninitialized locals hold indeterminate garbage. Initialize everything.
- Scope runs from the declaration to the end of the enclosing block; inner names shadow outer ones.
constmakes a variable read-only after initialization.
Going further: compile the shadowing example with -Wshadow added to the gcc command and read the warning. Then look up the full list of C keywords: there are 44 in C17's syntax including the underscore-prefixed ones like _Bool.