C · Beginner
Variables and Types in C
In short: A C variable is a named piece of memory with a fixed type, declared before use as in int count = 0;. The type decides how many bytes it occupies and how those bytes are read: int and long hold whole numbers, double holds decimals, char holds one character and bool (from <stdbool.h>) holds true or false. A variable declared without a value contains whatever was in memory before.
Names for memory
Every value a C program works with lives somewhere in memory, and a variable gives one of those places a name and a type. int count = 0; reserves enough bytes for an int, calls them count and stores 0 there. From then on count in an expression means the value stored in those bytes, and count = 5 overwrites them.
The type is not decoration. It tells the compiler how many bytes to reserve and how to interpret them: the same four bytes mean one thing as an int and something entirely different as a float. It also fixes what operations produce, which is why dividing two int values gives an int (see operators). A variable's type never changes; only its value does.
The types you will use most: int for whole numbers (32 bits on every mainstream desktop compiler, roughly minus 2.1 billion to plus 2.1 billion), long long when a count can exceed that, double for numbers with a fractional part, char for a single character, which C stores as a small integer holding the character's code, and bool for true and false, available through <stdbool.h> since C99. float is a smaller, less precise decimal type; prefer double. sizeof(type) reports the byte size on the machine that compiles the program; the standard fixes only minimums, so the table below gives the sizes gcc uses on 64-bit Linux, which runs the examples here.
A declaration without an initial value, such as int total;, reserves the bytes but does not clear them. Reading total before assigning it is undefined behaviour: you get whatever happened to be there, or something stranger. Initialise variables when you declare them unless the very next statement assigns them.
Names start with a letter or underscore and continue with letters, digits or underscores; they are case sensitive and cannot be keywords such as int or return. const in front of a declaration makes the variable read-only after initialisation: const double VAT = 0.2; documents that the value is fixed, and the compiler rejects any later assignment.
Printing and reading need a specifier that matches the type exactly. printf uses %d for int, %ld for long, %lld for long long, %f for double (and for float, which is promoted), %c for char and %zu for the size_t that sizeof returns. scanf uses the same letters except that a double needs %lf, and every argument must be the address of the variable, written with &. A mismatch is undefined behaviour that usually prints nonsense, and the compiler does not reliably reject it.
Syntax
type name; /* declared; value is undefined until assigned */
type name = value; /* declared and initialised */
type a = 1, b = 2; /* two variables of the same type */
const type NAME = value; /* read-only after this line */
name = new_value; /* assignment: the type stays, the value changes */A declaration can appear anywhere a statement can (since C99), not only at the top of a block. Declare a variable close to where it is first used.
Common types with gcc on 64-bit Linux
| Type | Bytes | printf | scanf | Holds |
|---|---|---|---|---|
| int | 4 | %d | %d | whole numbers, about -2.1 billion to 2.1 billion |
| unsigned int | 4 | %u | %u | 0 to about 4.3 billion |
| long | 8 | %ld | %ld | whole numbers to about 9.2 quintillion here; only 4 bytes on Windows |
| long long | 8 | %lld | %lld | at least 64 bits on every platform |
| double | 8 | %f | %lf | decimals, about 15 significant digits |
| float | 4 | %f | %f | decimals, about 7 significant digits |
| char | 1 | %c | %c | one character, stored as a small integer |
| bool (stdbool.h) | 1 | %d | - | true (1) or false (0) |
| size_t | 8 | %zu | %zu | sizes and counts; the type sizeof produces |
Declaring, printing and measuring
One variable of each common type is declared with a value and printed with its matching specifier; the last line shows sizeof.
#include <stdio.h>
#include <stdbool.h>
int main(void)
{
int shelves = 12;
long books = 48250L;
double avg_weight = 0.85;
char section = 'H';
bool open_today = true;
printf("Shelves: %d\n", shelves);
printf("Books: %ld\n", books);
printf("Average weight: %.2f kg\n", avg_weight);
printf("Section: %c\n", section);
printf("Open today: %d\n", open_today);
printf("sizeof(int) = %zu, sizeof(double) = %zu\n", sizeof(int), sizeof(double));
return 0;
}
Output
Shelves: 12 Books: 48250 Average weight: 0.85 kg Section: H Open today: 1 sizeof(int) = 4, sizeof(double) = 8
Each variable is declared with its type and an initial value, then printed with the matching specifier. %.2f rounds the double to two decimal places for display without changing the stored value. The bool prints as 1 because true is the integer 1. sizeof yields a size_t, printed with %zu; the sizes are gcc's on the 64-bit Linux build machine.
Reading three different types
One line of input holds a count, a temperature and a unit letter: 6 21.5 C.
#include <stdio.h>
int main(void)
{
int readings;
double celsius;
char unit;
if (scanf("%d %lf %c", &readings, &celsius, &unit) != 3) {
printf("bad input\n");
return 1;
}
double fahrenheit = celsius * 9.0 / 5.0 + 32.0;
printf("%d readings, latest %.1f%c\n", readings, celsius, unit);
printf("That is %.1f F\n", fahrenheit);
printf("Code of '%c' is %d\n", unit, unit);
return 0;
}
Input given to the program: 6 21.5 C
Output
6 readings, latest 21.5C That is 70.7 F Code of 'C' is 67
%d, %lf and %c read an int, a double and a char in that order, each into the address given by &. The space before %c in the format tells scanf to skip the blank between 21.5 and the letter; without it the char would receive the space itself. Because a char is a small integer, printing unit with %d shows its character code, 67 for a capital C.
Values change, types do not
The same int variable is assigned three times; the third assignment hands it a double.
#include <stdio.h>
int main(void)
{
int stock = 40;
printf("Start: %d\n", stock);
stock = stock - 15;
printf("After sale: %d\n", stock);
stock = 7.9; /* the fraction is thrown away */
printf("Assigned 7.9: %d\n", stock);
const int capacity = 100;
printf("Capacity: %d\n", capacity);
return 0;
}
Output
Start: 40 After sale: 25 Assigned 7.9: 7 Capacity: 100
stock is assigned three times and prints three different values, but it is always an int. Assigning 7.9 does not fail: C drops the fraction, so the variable holds 7. capacity is const, so a later capacity = 90; would be a compile-time error; that is what const is for, letting the compiler stop a change you did not mean to make.
Common mistakes
Reading a variable before assigning it
Why it goes wrong:
int total;reserves memory without clearing it.total += 5then adds 5 to garbage, and the result differs between runs, machines and optimisation levels because the behaviour is undefined.Fix: Initialise at the declaration:
int total = 0;. gcc -Wall usually warns withmay be used uninitialized.C · fixint total = 0; total += 5;A printf specifier that does not match the type
Why it goes wrong:
printf("%d", 2.5)does not convert the double; printf reads an int from where it expects one and prints a meaningless number. gcc -Wall warns (format '%d' expects argument of type 'int'), but the program still compiles.Fix: Use
%ffor double,%dfor int,%ldfor long,%cfor char; check the table above when unsure.C · fixdouble price = 2.5; printf("%.2f\n", price);Forgetting & in scanf, or using %f for a double
Why it goes wrong: scanf needs the address of the variable so it can store into it; passing
ninstead of&nhands it a value as if it were an address and typically crashes.%fwith a double's address stores only four of its eight bytes.Fix: Write
&before every plain variable in scanf and use%lffor double.C · fixdouble d; if (scanf("%lf", &d) == 1) { printf("%f\n", d); }Assuming int can hold any whole number
Why it goes wrong: A 32-bit int stops at 2147483647. A product or sum of large values silently exceeds that, and signed overflow is undefined behaviour: the program may print a negative number or anything else.
Fix: Use
long long(at least 64 bits) for totals and products that can grow, and check the input limits of an exercise.C · fixlong long total = 0; total += 3000000000LL; printf("%lld\n", total);
Choosing types for a sensor log
Suppose a program collects temperature readings from a greenhouse every minute. Each reading has a fractional part, so it is a double; the number of readings is a whole number that fits comfortably in an int; the running sum of thousands of readings is still a double; a flag recording whether the heater was on is a bool; and the zone letter that identifies the sensor is a char. Choosing the type for each piece of data up front is most of the design: once the types are right, the printf and scanf lines follow from the table, and -Wall catches many of the remaining mistakes.
double reading;
int count = 0;
double sum = 0.0;
bool heater_on = false;
char zone = 'B';Key points
- Declare before use:
type name;ortype name = value;. - The type fixes the size and meaning of the bytes and never changes; the value can.
- Uninitialised variables hold garbage; reading them is undefined behaviour.
intfor counts,long longfor big totals,doublefor decimals,charfor one character,boolfrom<stdbool.h>.- printf specifiers:
%d,%ld,%lld,%f,%c,%zu; scanf uses%lffor double and needs&. constmakes a variable read-only after initialisation.sizeofreports bytes on the compiling machine; the standard fixes only minimum sizes.
Try it yourself
Read an integer number of crates and a decimal weight per crate from one line, then print the total weight with two decimal places in the form Total: 37.50 kg. The input is 5 7.5.
#include <stdio.h>
int main(void)
{
int crates;
/* declare a double for the weight, read both values, print the total */
return 0;
}
5 7.5Total: 37.50 kg#include <stdio.h>
int main(void)
{
int crates;
double each;
if (scanf("%d %lf", &crates, &each) != 2) {
return 1;
}
printf("Total: %.2f kg\n", crates * each);
return 0;
}
Practise this
Exercises for this lesson are in the C practice set.
Related lessons
- C Syntax and Your First ProgramThe parts of a C program: #include, int main(void), statements, braces, comments and printf, plus how gcc turns the source file into a program you can run.8 min
- Operators in CC arithmetic, comparison, logical, assignment and increment operators: why 7 / 2 is 3, how % behaves, what comparisons return, precedence and = versus ==.9 min
- Strings in CHow C stores text as null-terminated char arrays, reads a line safely with fgets, and copies, joins, compares and searches text with the string.h functions.9 min
Frequently asked questions
What is the difference between float and double in C?
Both hold numbers with a fractional part, but a double uses 8 bytes and keeps about 15 significant digits, while a float uses 4 bytes and keeps about 7. Arithmetic on literals such as 2.5 is done in double anyway, and printf promotes floats to double, so double is the natural default. Use float only when you must store very many values and memory matters.
Why does printf use %f for a double but scanf needs %lf?
printf receives its arguments by value, and C promotes any float argument to double in a variadic call, so %f always sees a double and one specifier covers both types. scanf receives addresses, and a pointer to float and a pointer to double point at different sizes of storage, so it must be told which: %f for a float and %lf for a double. Using %f with the address of a double writes only half of its bytes.
How many bytes is an int in C?
The standard requires only that int can hold at least -32767 to 32767; the actual size is chosen by the compiler and platform. gcc on 64-bit Linux, Windows and macOS all use 4 bytes (32 bits). long differs: 8 bytes on 64-bit Linux and macOS but 4 on Windows, which is why long long is the portable choice for values beyond 32 bits. sizeof(int) tells you the truth for your compiler.
How this page was checked. Every program on it was run with GCC 13 (C11) at build time by the publishing checks, and the output shown is what it printed. Running C inside the browser is not available yet, so the Run button is absent rather than pretending; copy the code and run it with GCC 13 (C11) locally.