Module 2 · Variables and Data Types
Variables: Named Boxes in Memory
In this lesson
- Declare, initialise and assign, and say which of the three each line does.
- Print a variable's address with
%pand explain what that number is. - Explain why reading a variable you never wrote to is a bug, even when it prints 0.
Zara labels every jar in her kitchen: RICE, SUGAR, TEA. The labels are not the food. They are how she finds the food without opening ten lids.
Then she tries to pour a litre of milk into a tea cup. The cup does not stretch. It holds what it holds, and the rest goes on the floor.
A variable in C is a labelled jar with a fixed size. This lesson is about the label, the size, and the address written on the shelf underneath.
A name, a size and an address
When you write one line like this, four separate things happen.
#include <stdio.h>
int main(void)
{
int marks = 90;
printf("%d\n", marks);
return 0;
}
90
- The compiler sets aside a block of memory. For an
intthat is 4 bytes on the Playground. - It writes in its own notes that the name
marksrefers to that block. - It records that those bytes are to be read as a whole number.
- It stores the bit pattern for 90 into them.
Three of those four are the type doing its job. The type is not decoration. It decides the size, the reading and what you may do with the value.
So a variable is three things at once: a name, a type, and a place. Beginners learn the name and forget the other two, and that is where the surprises come from.
The declaration, with every part named
int marks = 90 ;
| | | | |
| | | | end of the statement
| | | the first value (optional)
| | the assignment operator
| the name you choose
the type: how big the box is and how to read it
- The type comes first and never changes for that name.
- The name follows the rules further down this lesson.
=here means "put this in", not "is equal to".- Leave out
= 90and you have a box with nothing useful in it.
Declare, initialise, assign: three different lines
Three words, and people use them as if they were one. They are not.
#include <stdio.h>
int main(void)
{
int marks; /* declaration: reserve the box, leave it alone */
marks = 90; /* assignment: put a value in an existing box */
int total = 250; /* declaration and initialisation, one line */
printf("%d %d\n", marks, total);
return 0;
}
90 250
Declaring tells the compiler a name and a type. Initialising gives the box its first value at the moment it is created. Assigning puts a value into a box that already exists.
Initialising and assigning look identical, because both use =. The difference is when it happens, and whether there was ever a moment where the box held something unknown.
So int total = 250; is one statement and int total; total = 250; is two. The second one has a gap in the middle, and that gap is where bugs live.
Assignment is not equality
In maths, x = x + 1 is nonsense. In C it is an ordinary instruction.
Read = from right to left: work out the whole right side first, then store the answer in the box on the left. Say "gets" in your head, never "equals".
#include <stdio.h>
int main(void)
{
int x = 5;
x = x + 1; /* x gets the old x plus one */
printf("%d\n", x);
return 0;
}
6
The old value is read, 1 is added to the copy, and the answer goes back into the same box. Nothing is ever solved.
So a line can mention a variable on both sides, and the right side always uses the value from before the line ran.
The address is real, and you can print it
The block of memory the compiler set aside sits at a numbered position. That number is the variable's address.
&marks means "the address of marks". You have already used it without knowing: scanf("%d", &n) hands scanf the address so it can write into your box.
#include <stdio.h>
int main(void)
{
int a = 1;
int b = 2;
int c = 3;
printf("a = %d at %p\n", a, (void *)&a);
printf("b = %d at %p\n", b, (void *)&b);
printf("c = %d at %p\n", c, (void *)&c);
return 0;
}
One run, on one machine, printed this:
a = 1 at 0x7ffd1c4a2b9c
b = 2 at 0x7ffd1c4a2b98
c = 3 at 0x7ffd1c4a2b94
Your numbers will be different. They change between machines and often between runs, so this is the one program in the module whose output nobody can promise.
Two things in that run are worth noticing. The addresses are written in hexadecimal, which lesson 4 of Module 1 explained. And they are 4 apart, because an int takes 4 bytes and the three boxes sit next to each other.
(void *) is a cast, which Module 4 covers properly. %p wants an address in its most general form, and the cast is how you say "treat this as a plain address".
So & is not magic and not new. It is the third fact about a variable, the one you could not see until now.
Reading a variable you never wrote to
This is the most important paragraph in the lesson, so read it twice.
A declared variable that was never given a value holds an indeterminate value. It is not zero. It is whatever bytes were already sitting at that address.
#include <stdio.h>
int main(void)
{
int never_set;
int set_properly = 0;
printf("set: %d\n", set_properly);
printf("unset: %d\n", never_set);
return 0;
}
On the Playground's compiler this builds without a single message, and the second line printed 0 on the runs we made.
That 0 is the dangerous outcome. It looks like proof that C sets variables to zero. It is not. The value is not promised, and a program that depends on it is broken whether or not today's run is correct.
A local compiler with -Wall turned on does say something: warning: 'never_set' is used uninitialized. The Playground's command line has no -Wall on it, so you get silence.
So the rule is simple and has no exceptions: give every variable a value on the line that declares it.
Naming: what the compiler checks, and what your reader checks
The compiler checks four rules, and it is strict about all four.
- Letters, digits and underscores only. No spaces, no hyphens, no other punctuation.
- The first character may not be a digit.
- A keyword is taken.
int,returnanddoublecannot be names. - Case matters.
total,TotalandTOTALare three different variables.
Your reader checks a fifth rule, and it is the one that costs you marks in a code review.
#include <stdio.h>
int main(void)
{
/* Hard to read: nobody knows what these hold. */
int a = 40, x1 = 35, temp = 75;
/* Easy to read: the name is the explanation. */
int maths_marks = 40;
int physics_marks = 35;
int total_marks = maths_marks + physics_marks;
printf("%d %d %d\n", a, x1, temp);
printf("%d\n", total_marks);
return 0;
}
40 35 75
75
This track writes total_marks, not totalMarks. That style is called snake_case, and it is what the C standard library and most C code have always used.
Pick one style and never mix the two in one file. The choice matters less than the consistency.
One variable per line
C lets you declare several names in one statement. It is legal, and this track avoids it.
#include <stdio.h>
int main(void)
{
int a = 1, b, c = 3; /* b has no value: easy to miss */
b = 2;
printf("%d %d %d\n", a, b, c);
return 0;
}
1 2 3
In that line, b sits between two initialised names and is not initialised itself. The eye slides over it.
So write one declaration per line, each with its value. It costs two lines and saves an afternoon.
Store one number under a name, then print it. Change the number, run again.
#include <stdio.h>
int main(void)
{
int stall_number = 7;
printf("Stall %d\n", stall_number);
return 0;
}
Stall 7
The name is doing real work already. printf("Stall 7\n") would print the same line and would be a different program: one that cannot ever print a different stall.
Zara needs the two numbers to change places. The trick is that assignment copies, so the first copy has to be parked somewhere.
#include <stdio.h>
int main(void)
{
int a = 10;
int b = 20;
int spare = 0;
spare = a; /* park the old a */
a = b; /* a gets b */
b = spare; /* b gets the parked value */
printf("a = %d, b = %d\n", a, b);
return 0;
}
a = 20, b = 10
Delete the spare line and write a = b; b = a; instead. Both boxes end up holding 20, because the first line destroyed the old a before the second line could read it.
Read three marks, add them, print the total and each name's size. This is a whole small program with nothing borrowed from later modules.
#include <stdio.h>
int main(void)
{
int maths = 0;
int physics = 0;
int chemistry = 0;
scanf("%d %d %d", &maths, &physics, &chemistry);
int total = maths + physics + chemistry;
printf("Total: %d\n", total);
printf("Each mark takes %d bytes.\n", (int)sizeof(maths));
return 0;
}
Total: 252
Each mark takes 4 bytes.
That output is for the input 90 85 77. Every variable is initialised on its own line before scanf touches it, which costs nothing and removes one whole class of bug.
Where this is used
- A game's score. The original Doom source keeps each player's health and ammunition as plain
intfields in a struct calledplayer_t. The score on your screen is one of those boxes being printed. - A bank balance. Core banking code stores a balance as a whole number of the smallest unit, paisa or cents, in a 64-bit integer. Lesson 3 explains why it is never a decimal type.
- A sensor reading. An Arduino sketch reads a temperature into an
intand prints it. That is the same three lines you just wrote, on a chip the size of a stamp. - A loop counter. Almost every loop in the Linux kernel declares
int iand counts with it. That one name appears tens of thousands of times in the source.
Common mistakes
1. Using a variable before anything was put in it.
int total;
printf("%d\n", total);
The Playground's GCC 12 prints no message at all, because the check that finds this lives behind -Wall and the Playground does not pass it. A local gcc -Wall says warning: 'total' is used uninitialized. The fix is int total = 0;, and it is not optional just because today's run printed 0.
2. Assigning to a name that was never declared.
total = 5;
printf("%d\n", total);
GCC 12 says error: 'total' undeclared (first use in this function) and stops. C has no way to create a variable by assigning to it, which some other languages do. Add the type in front of the first mention.
3. Starting a name with a digit.
int 2nd_place = 3;
GCC 12 gives two messages: error: invalid suffix "nd_place" on integer constant and then error: expected identifier or '(' before numeric constant. The first one reads oddly because the compiler started reading 2 as a number and then found letters glued to it. Write second_place.
4. Putting text in an int box.
int marks = "90";
On the Playground's GCC 12 this is a warning: initialization of 'int' from 'char *' makes integer from pointer without a cast, and the program builds and prints a large meaningless number. GCC 14 makes the same line an error. "90" in double quotes is text; 90 with no quotes is a number.
Kenji is testing the input box of a new tool. He wants the smallest possible program that proves a number went in and came out again.
Input. One line with one integer n.
Output. One line, exactly: n stored, with the number in place of n.
Constraints. -1000000 <= n <= 1000000.
Sample. Input 42 gives 42 stored. One space before the word.
#include <stdio.h>
int main(void)
{
int n = 0;
scanf("%d", &n);
/* One printf. Nothing else. */
return 0;
}
Graded in this module's Problems lesson as echo-number. The negative end of the range is a real hidden test.
Bob is sorting two boxes of books and needs their labels swapped. Use a third variable, exactly as Example 2 does.
Input. One line with two integers a b.
Output. One line with the two numbers in the other order, separated by one space.
Constraints. -1000000 <= a, b <= 1000000.
Sample. Input 10 20 gives 20 10.
#include <stdio.h>
int main(void)
{
int a = 0;
int b = 0;
int spare = 0;
scanf("%d %d", &a, &b);
/* Three assignments, then one printf. */
return 0;
}
Graded as swap-with-third. Printing b and then a in one printf also passes, and it teaches you nothing, so do it the long way first.
Maria wants the average of three marks, printed with two decimal places. This one needs an idea this lesson has only pointed at, and lesson 3 explains it in full.
Input. One line with three integers a b c.
Output. One line with their average, to exactly two decimal places.
Constraints. 0 <= a, b, c <= 100.
Sample. Input 90 85 77 gives 84.00. Input 1 1 2 gives 1.33.
#include <stdio.h>
int main(void)
{
int a = 0;
int b = 0;
int c = 0;
scanf("%d %d %d", &a, &b, &c);
/* Careful: (a + b + c) / 3 throws the fraction away. */
printf("%.2f\n", 0.0);
return 0;
}
Not graded in this module. Dividing one whole number by another gives a whole number in C, which is exactly why 1 1 2 is the interesting input here. Lesson 3 gives you the fix.
Common doubts
Does a variable stop existing when the program ends?
Yes, and earlier than that. It lives from its declaration to the closing brace of the block it sits in. Module 7 covers that properly, under the name scope.
Can I change a variable's type later?
No. The type is fixed at the declaration and stays fixed. You can copy the value into a different variable of another type, which Module 4 covers.
Why does
scanfneed&whenprintfdoes not?printfonly reads your value, so a copy is enough.scanfhas to write into your box, so it needs the address. Module 11 explains the machinery.Is
int x = 0;slower thanint x;?Not in any way you could measure. The compiler usually puts the zero there for free, and a wrong answer is slower than any instruction.
How long can a variable name be?
Long enough. C17 promises the first 63 characters are significant, which no sensible name ever reaches. Choose a name you can say out loud.
Key takeaways
- A variable is three things: a name you chose, a type that fixes the size, and an address.
- Declaring reserves the box, initialising fills it at birth, assigning fills it later.
=means "gets". The right side is worked out first, then stored on the left.&nameis the address, and%pprints it;scanfhas always used it.- An uninitialised variable holds an indeterminate value, and the Playground warns about nothing.
- One declaration per line, each with a value, and names in snake_case.
Next you will find out how big that box really is. Then you will meet the day a number is one too large to fit in it.
End of lesson 1
Mark it done, and your progress moves with you.
Next: Integer Types, Their Ranges, and Overflow