Types and variables in C — exercises with shapes, BMI, number bases and a scientific calculator (and the scanf without & that ends in a Segmentation fault)
The night before handing in my first C assignment, the program compiled, asked me for the grade, I typed a 7… and the terminal answered Segmentation fault (core dumped). Not a single clue about which line was failing. I went over the maths, the printf calls, the types. Half an hour later I saw it: one scanf was missing an &. A single character. And the worst part is that gcc had warned me when compiling, but the warning got lost in the rest of the text and I only checked whether the executable appeared. Since then I read every warning as if it were an error, and it’s the first thing I’ll ask of you in these exercises.
You’ve already seen the types and variables in C theory and the three programs in the practice. Now it’s your turn to write the code: four exercises on three levels, three “What’s wrong here?” snippets with real errors, the commented solutions and a cheat sheet to keep at hand in the IC2 labs.
Table of Contents
How to use this article
- Read the statement and the expected output, and write your program in gedit (
gedit shapes.c &). - Compile with
gcc -Wall -std=c11 shapes.c -o shapesand don’t move on until there are no warnings at all. - If you’ve been stuck for more than 10 minutes, open the hint. If you’re still stuck, look only at the part of the solution you need.
- Type the same data as in the expected output and compare line by line. Then try others.
All the exercises use only what’s in this topic: types, operators, casts, printf and scanf. No if yet: when something needs classifying, we’ll use the trick from the theory, a comparison that is 1 or 0.
Basic level
Exercise 1: area and perimeter calculator for geometric shapes
Write a program that asks for the side of a square, the base and height of a rectangle and the radius of a circle (all with decimals), and shows the area and perimeter of the three shapes with 2 decimals. Formulas: square, area = side² and perimeter = 4 × side; rectangle, area = base × height and perimeter = 2 × (base + height); circle, area = π × radius² and perimeter = 2 × π × radius, with π = 3.14159.
Expected output (typing 4, 6, 3 and 5):
=== SHAPES CALCULATOR === Square side: 4 Rectangle base: 6 Rectangle height: 3 Circle radius: 5 --- Square with side 4.00 --- Area: 16.00 Perimeter: 16.00 --- Rectangle 6.00 x 3.00 --- Area: 18.00 Perimeter: 18.00 --- Circle with radius 5.00 --- Area: 78.54 Perimeter: 31.42
Define π once at the top with #define PI 3.14159f and use float for the measurements, read with %f and &. C has no **: the square is side * side. And in the rectangle’s perimeter, watch the brackets: 2 * base + height isn’t the same as 2 * (base + height).
Exercise 2: BMI calculator with classification
Write a program that asks for your first initial, your weight in kilos and your height in metres, works out the body mass index (BMI = weight / height²) and classifies it according to the WHO: underweight (below 18.5), normal (from 18.5 to below 25), overweight (from 25 to below 30) and obese (30 or more). Since we don’t have if yet, the program shows the four categories with a 1 in the right one and a 0 in the others, plus a check line that adds the four (it must give 1).
Expected output (typing S, 70.5 and 1.78):
Your first initial: S Weight (kg): 70.5 Height (m): 1.78 --- BMI for S. --- Weight: 70.50 kg Height: 1.78 m BMI: 22.25 Underweight: 0 | Normal: 1 | Overweight: 0 | Obese: 0 Check (must be 1): 1
Each category is a comparison stored in an int: int normal = bmi >= 18.5f && bmi < 25.0f;. For the initial use scanf(" %c", &initial), with the space. And put brackets in weight / (height * height): without them, C would divide the weight by the height and then multiply by the height.
Intermediate level
Exercise 3: number base converter
Write a program that asks for an integer and shows it in decimal, in hexadecimal (lowercase and uppercase) and in octal, without doing the conversion by hand. Then extract its hundreds, tens and units using only / and % and add them up. Finally, show whether it’s even (1 or 0), how many bytes an int takes and the maximum value of an int.
Expected output (typing 255):
Enter an integer: 255 --- Conversions of 255 --- Decimal: 255 Hexadecimal: ff Hexadecimal: FF Octal: 377 --- Digits (base 10) --- Hundreds: 2, tens: 5, units: 5 Sum of digits: 12 --- Extra information --- Is it even? (1 = yes): 0 Size of int: 4 bytes Maximum int value: 2147483647
Printf converts on its own with %x, %X and %o. For the digits: number % 10 is the last one; number / 10 drops the last one, so number / 10 % 10 is the tens digit. The size comes from sizeof(int) (printed with %zu) and the maximum from the INT_MAX constant in <limits.h>.
Final challenge
Exercise 4: basic scientific calculator
This exercise brings the whole topic together. Write a calculator that asks for two integers A and B and shows in one go the basic operations (including both divisions and the remainder), the powers A², B² and A³, A written as an int, as a float, in hexadecimal and in octal, the size of int and float, and three comparisons (A > B, A == B and A != B) as 1 or 0. The columns have to line up as in the output.
Expected output (typing 15 and 4):
=== SCIENTIFIC CALCULATOR === Number A: 15 Number B: 4 --- Basic operations --- A + B = 19 A - B = 11 A * B = 60 A / B = 3 (integer division) A / B = 3.7500 (real division) A % B = 3 (modulo/remainder) --- Powers --- A² = 225 B² = 16 A³ = 3375 --- Type information --- A as int: 15 A as float: 15.000000 A as hex: f A as octal: 17 sizeof(int): 4 bytes sizeof(float): 4 bytes --- Comparisons --- A > B: 1 A == B: 0 A != B: 1
To keep “3” and “(integer division)” in a column with the real division line, use a fixed, left-aligned width: %-6d. The % of the remainder is written %% inside printf. To show A as a float, convert it with (float)a: if you pass an int to %f, you get garbage.
What’s wrong here?
Three short snippets with one error each. Before reading the explanation, predict what will happen when you compile and run them.
Snippet 1: the grade that never arrives
#include <stdio.h>
int main(void) {
int grade;
printf("Exam grade: ");
scanf("%d", grade); /* the & is missing */
printf("You got a %d\n", grade);
return 0;
}
What do you think happens when you type a 7?
When compiling, gcc warns twice about the same thing:
no_ampersand.c: In function 'main': no_ampersand.c:6: warning: format '%d' expects argument of type 'int *', but argument 2 has type 'int' [-Wformat=] no_ampersand.c:6: warning: 'grade' is used uninitialized [-Wuninitialized] no_ampersand.c:4: note: 'grade' was declared here
And when running it, typing 7:
Exam grade: 7 Segmentation fault (core dumped)
scanf("%d", grade) passes scanf the value of grade, which is still garbage, instead of its address. scanf takes that garbage as if it were a memory address and tries to write the 7 there. That address doesn’t belong to the program, and the system cuts it off with a Segmentation fault. It’s my story from the start, with its real output.
Only checking whether gcc creates the executable. The two warnings say exactly what’s going on: scanf expected an int * (an address) and got an int, and grade is used uninitialised.
The fix is the &:
#include <stdio.h>
int main(void) {
int grade;
printf("Exam grade: ");
scanf("%d", &grade); /* the address of grade */
printf("You got a %d\n", grade);
return 0;
}
Output, typing 7:
Exam grade: 7 You got a 7
When you write a scanf, read the line out loud: “store an integer at the address of grade”. If you can’t say “the address of”, the & is missing.
Snippet 2: the weight that comes out as zero
#include <stdio.h>
int main(void) {
double weight;
printf("Weight (kg): ");
scanf("%f", &weight); /* weight is a double */
printf("Weight read: %.2f kg\n", weight);
return 0;
}
What will it print when you type 70.5?
gcc’s warning:
weight_double.c: In function 'main': weight_double.c:6: warning: format '%f' expects argument of type 'float *', but argument 2 has type 'double *' [-Wformat=]
And the run:
Weight (kg): 70.5 Weight read: 0.00 kg
No crash and no error: the program calmly says you weigh 0.00 kg. %f tells scanf to store a float (4 bytes), but weight is a double (8 bytes). scanf doesn’t know the variable’s real type: it writes 70.5 in float format into the first 4 bytes and leaves the other 4 as they were. The drawing shows it byte by byte:
Reading the 8 bytes as a double gives a tiny number (5.5 × 10⁻³¹⁵) which with 2 decimals is 0.00. On your computer you may get another value if those 4 bytes weren’t zero.
Copying printf’s specifier into scanf. In printf, %f works for float and double; in scanf, double is %lf, no exceptions.
The fix:
#include <stdio.h>
int main(void) {
double weight;
printf("Weight (kg): ");
scanf("%lf", &weight); /* double -> %lf in scanf */
printf("Weight read: %.2f kg\n", weight);
return 0;
}
Output, typing 70.5:
Weight (kg): 70.5 Weight read: 70.50 kg
Snippet 3: the triangle with no area
#include <stdio.h>
int main(void) {
float base = 3.0f, height = 4.0f;
float area = 1 / 2 * base * height; /* area = base * height / 2 */
printf("Triangle area: %.2f\n", area);
return 0;
}
The formula is straight from the textbook, base times height over two. What does it print?
Triangle area: 0.00
0.00, and gcc says nothing, because there’s no type error: it’s all legal. C evaluates from left to right: first 1 / 2, which are two int, so it’s an integer division and gives 0. Then 0 × 3.0 × 4.0 = 0. It doesn’t matter that base and height are float: by the time they arrive, the damage is done.
Writing fractions like 1 / 2 or 4 / 3 in a formula. Between integers they’re 0 and 1. Write them as 1.0f / 2 or reorder so you divide at the end.
The fix is to divide at the end, when there’s already a float in front:
#include <stdio.h>
int main(void) {
float base = 3.0f, height = 4.0f;
float area = base * height / 2; /* float / int: real division */
printf("Triangle area: %.2f\n", area);
return 0;
}
Output:
Triangle area: 6.00
3 × 4 = 12 and 12 / 2 = 6. Whenever you can, check a formula by hand with easy numbers before trusting the output.
Commented solutions
Solution to exercise 1
#include <stdio.h>
#define PI 3.14159f /* constant: change it once and it works everywhere */
int main(void) {
float side, base, height, radius;
printf("=== SHAPES CALCULATOR ===\n");
printf("Square side: ");
scanf("%f", &side); /* & is required: scanf needs the address */
printf("Rectangle base: ");
scanf("%f", &base);
printf("Rectangle height: ");
scanf("%f", &height);
printf("Circle radius: ");
scanf("%f", &radius);
printf("\n--- Square with side %.2f ---\n", side);
printf("Area: %.2f\n", side * side); /* side² = side * side (there is no **) */
printf("Perimeter: %.2f\n", 4 * side); /* int * float = float */
printf("\n--- Rectangle %.2f x %.2f ---\n", base, height);
printf("Area: %.2f\n", base * height);
printf("Perimeter: %.2f\n", 2 * (base + height)); /* brackets: the sum goes first */
printf("\n--- Circle with radius %.2f ---\n", radius);
printf("Area: %.2f\n", PI * radius * radius);
printf("Perimeter: %.2f\n", 2 * PI * radius);
return 0;
}
Output, typing 4, 6 and 3, and 5:
=== SHAPES CALCULATOR === Square side: 4 Rectangle base: 6 Rectangle height: 3 Circle radius: 5 --- Square with side 4.00 --- Area: 16.00 Perimeter: 16.00 --- Rectangle 6.00 x 3.00 --- Area: 18.00 Perimeter: 18.00 --- Circle with radius 5.00 --- Area: 78.54 Perimeter: 31.42
Why it’s done this way:
#define PI 3.14159fputs π in a single place. If tomorrow you want more precision, you change one line. Thefmakes it afloat, like the measurements.- All measurements are
floatbecause a side can be 2.5. If they wereint, scanf with%dwouldn’t read the decimals. 4 * sidemixes anintand afloat: C converts the 4 tofloatand the multiplication is decimal. There’s no division here, so there’s no trap.2 * (base + height)needs brackets because*comes before+: without them you’d get 2 × 6 + 3 = 15 instead of 18.
Checking by hand: 4 × 4 = 16 and 4 × 4 = 16 (the square with side 4 is the only one whose area and perimeter are equal); 6 × 3 = 18 and 2 × (6 + 3) = 18; 3.14159 × 25 = 78.54 and 2 × 3.14159 × 5 = 31.42.
Solution to exercise 2
#include <stdio.h>
int main(void) {
char initial;
float weight, height;
printf("Your first initial: ");
scanf(" %c", &initial); /* space + & with %c too */
printf("Weight (kg): ");
scanf("%f", &weight);
printf("Height (m): ");
scanf("%f", &height);
float bmi = weight / (height * height); /* brackets: height² before dividing */
/* each comparison is 1 or 0: only one of the four can be 1 */
int under = bmi < 18.5f;
int normal = bmi >= 18.5f && bmi < 25.0f;
int over = bmi >= 25.0f && bmi < 30.0f;
int obese = bmi >= 30.0f;
printf("\n--- BMI for %c. ---\n", initial);
printf("Weight: %.2f kg\n", weight);
printf("Height: %.2f m\n", height);
printf("BMI: %.2f\n", bmi);
printf("Underweight: %d | Normal: %d | Overweight: %d | Obese: %d\n",
under, normal, over, obese);
printf("Check (must be 1): %d\n", under + normal + over + obese);
return 0;
}
Output, typing S, 70.5 and 1.78:
Your first initial: S Weight (kg): 70.5 Height (m): 1.78 --- BMI for S. --- Weight: 70.50 kg Height: 1.78 m BMI: 22.25 Underweight: 0 | Normal: 1 | Overweight: 0 | Obese: 0 Check (must be 1): 1
Why it’s done this way:
scanf(" %c", &initial)has the space even though it’s the first read: it does no harm and protects the program if one day you add another read before it.weight / (height * height): without brackets,weight / height * heightwould be (70.5 / 1.78) × 1.78 = 70.5, the weight again.- The four categories are comparisons stored in
int. The limits are written so they don’t overlap:normalstarts at>= 18.5fexactly whereunder(< 18.5f) ends. - The check line adds the four: if the ranges are right, it always gives 1. If one day you see a 0 or a 2, there’s a gap or an overlap in the conditions.
Checking by hand: 1.78 × 1.78 = 3.1684 and 70.5 / 3.1684 = 22.25, between 18.5 and 25: normal.
| Comparison | Value with BMI = 22.25 |
|---|---|
bmi < 18.5f | 0 |
bmi >= 18.5f && bmi < 25.0f | 1 |
bmi >= 25.0f && bmi < 30.0f | 0 |
bmi >= 30.0f | 0 |
| Sum | 1 |
Solution to exercise 3
#include <stdio.h>
#include <limits.h> /* INT_MAX */
int main(void) {
int number;
printf("Enter an integer: ");
scanf("%d", &number);
printf("\n--- Conversions of %d ---\n", number);
printf("Decimal: %d\n", number);
printf("Hexadecimal: %x\n", number); /* printf converts on its own */
printf("Hexadecimal: %X\n", number); /* capital X: capital letters */
printf("Octal: %o\n", number);
int units = number % 10; /* the last digit */
int tens = number / 10 % 10; /* drop one digit and take the last */
int hundreds = number / 100 % 10;
printf("\n--- Digits (base 10) ---\n");
printf("Hundreds: %d, tens: %d, units: %d\n", hundreds, tens, units);
printf("Sum of digits: %d\n", hundreds + tens + units);
printf("\n--- Extra information ---\n");
printf("Is it even? (1 = yes): %d\n", number % 2 == 0);
printf("Size of int: %zu bytes\n", sizeof(int));
printf("Maximum int value: %d\n", INT_MAX);
return 0;
}
Output, typing 255:
Enter an integer: 255 --- Conversions of 255 --- Decimal: 255 Hexadecimal: ff Hexadecimal: FF Octal: 377 --- Digits (base 10) --- Hundreds: 2, tens: 5, units: 5 Sum of digits: 12 --- Extra information --- Is it even? (1 = yes): 0 Size of int: 4 bytes Maximum int value: 2147483647
Why it’s done this way:
- printf doesn’t change the number:
%d,%xand%oonly change how it’s written. The value stored innumberis the same on all four lines. The drawing shows it with the 8 bits of 255:
- The digits come out with integer division and remainder, from right to left. Trace with 255:
| Expression | Step | Result |
|---|---|---|
number % 10 | 255 = 25 × 10 + 5 | 5 (units) |
number / 10 % 10 | 255 / 10 = 25; 25 % 10 | 5 (tens) |
number / 100 % 10 | 255 / 100 = 2; 2 % 10 | 2 (hundreds) |
| sum | 2 + 5 + 5 | 12 |
number % 2 == 0is 1 if it’s even. 255 is odd: it prints 0.sizeof(int)returns asize_t, and its specifier is%zu. With%dgcc warns, and with%lu(as in the first version of this article) it works on Linux but not on every system.
There’s no binary specifier in C11. The C23 standard adds %b, but with -std=c11, as in IC2, it isn’t available: if you need it, you get it bit by bit with % and / by 2.
Solution to exercise 4
#include <stdio.h>
int main(void) {
int a, b;
printf("=== SCIENTIFIC CALCULATOR ===\n");
printf("Number A: ");
scanf("%d", &a); /* & on both: if one is missing, the program can crash */
printf("Number B: ");
scanf("%d", &b);
printf("\n--- Basic operations ---\n");
printf("A + B = %d\n", a + b);
printf("A - B = %d\n", a - b);
printf("A * B = %d\n", a * b);
printf("A / B = %-6d (integer division)\n", a / b); /* %-6d: 6 wide, left-aligned */
printf("A / B = %.4f (real division)\n", (float)a / b);
printf("A %% B = %-6d (modulo/remainder)\n", a % b); /* %% prints a % */
printf("\n--- Powers ---\n");
printf("A² = %d\n", a * a);
printf("B² = %d\n", b * b);
printf("A³ = %d\n", a * a * a);
printf("\n--- Type information ---\n");
printf("A as int: %d\n", a);
printf("A as float: %f\n", (float)a); /* the cast makes %f get a float */
printf("A as hex: %x\n", a);
printf("A as octal: %o\n", a);
printf("sizeof(int): %zu bytes\n", sizeof(int));
printf("sizeof(float): %zu bytes\n", sizeof(float));
printf("\n--- Comparisons ---\n");
printf("A > B: %d\n", a > b);
printf("A == B: %d\n", a == b);
printf("A != B: %d\n", a != b);
return 0;
}
Output, typing 15 and 4:
=== SCIENTIFIC CALCULATOR === Number A: 15 Number B: 4 --- Basic operations --- A + B = 19 A - B = 11 A * B = 60 A / B = 3 (integer division) A / B = 3.7500 (real division) A % B = 3 (modulo/remainder) --- Powers --- A² = 225 B² = 16 A³ = 3375 --- Type information --- A as int: 15 A as float: 15.000000 A as hex: f A as octal: 17 sizeof(int): 4 bytes sizeof(float): 4 bytes --- Comparisons --- A > B: 1 A == B: 0 A != B: 1
Why it’s done this way:
%-6dreserves 6 spaces aligned to the left: the 3 takes 1 and is followed by 5 spaces, so “(integer division)” starts in the same column as “(real division)” after3.7500, which takes 6.A %% BprintsA % B. Notice that in the code that line has one more character than the others, but in the output it lines up.(float)a / bdoes the real division: 15 / 4 = 3.75. And(float)awith%fshows 15.000000: without the cast,%fwould get anintand print garbage, as in the theory.a * a * ais the cube: 15 × 15 × 15 = 3375. With big numbers it would overflow anint(with A = 1300, A³ already goes past 2147483647).- The comparisons are printed directly with
%d: they’re 1 or 0.
Checking by hand: 15 + 4 = 19, 15 − 4 = 11, 15 × 4 = 60, 15 = 4 × 3 + 3 (division 3, remainder 3); 15 in hexadecimal is f and in octal 17 (1 × 8 + 7).
Watch it step by step in Python Tutor
Python Tutor (pythontutor.com) runs C step by step. Choose C (gcc) in the menu and paste snippet 3, extended, to watch the triangle with no area live:
#include <stdio.h>
int main(void) {
float base = 3.0f, height = 4.0f;
int half = 1 / 2; /* 1 / 2 between integers: 0 */
float area_wrong = 1 / 2 * base * height; /* 0 * 3.0 * 4.0 = 0.0 */
float area_right = base * height / 2; /* 12.0 / 2 = 6.0 */
float area_right2 = 1.0f / 2 * base * height;
printf("%d %.2f %.2f %.2f\n", half, area_wrong, area_right, area_right2);
return 0;
}
Its output:
0 0.00 6.00 6.00
Watch for this:
- When the
halfline runs, its box shows 0:1 / 2between integers. area_wrongalso stays at 0: the 0 arrives beforebaseandheight.area_rightis 6:base * heightis already afloat(12.0) when it’s divided by 2.area_right2is 6 too: with1.0fthe first division is already real (0.5).
Cheat sheet: types and variables in C
The essentials in 30 seconds: declare every variable with its type and an initial value; int / int gives int; the printf and scanf specifier has to match the type; in scanf, & in front of each variable, %lf for double and " %c" for char; always compile with -Wall and don’t ignore a single warning.
| I want… | Code | Result |
|---|---|---|
| To see how much a type takes | printf("%zu", sizeof(int)) | 4 |
| A constant | #define PI 3.14159f | replaced before compiling |
| To divide with decimals | (float)a / b | 7 / 3 → 2.3333 |
| The remainder | a % b | 7 % 3 → 1 |
| To chop decimals / round | (int)x / (int)(x + 0.5f) | 7.75 → 7 / 8 |
| A 1 or a 0 from a condition | int ok = grade >= 5.0f; | 1 |
| The next letter / lowercase | c + 1 / c + 32 | 'S' → 'T' / 's' |
To turn '7' into 7 | c - '0' | 7 |
| 2 decimals / fixed width | %.2f / %5d, %-6d, %05d | 7.75 / 6 |
| Hexadecimal / octal | %x, %X / %o | ff, FF / 377 |
A % in printf | %% | % |
| To read… | scanf | Print with printf |
|---|---|---|
int | scanf("%d", &x) | %d |
float | scanf("%f", &x) | %f, %.2f |
double | scanf("%lf", &x) | %f, %.2f |
char | scanf(" %c", &c) | %c (or %d for the code) |
Templates from the exercises:
| Exercise | The key line | With the sample data |
|---|---|---|
| Shapes | PI * radius * radius | 78.54 |
| BMI | weight / (height * height) | 22.25 |
| Bases | number / 10 % 10 | 5 |
| Scientific | printf("A / B = %-6d (…)\n", a / b) | 3 (…) |
| Symptom | Likely cause |
|---|---|
Segmentation fault after typing | A missing & in scanf |
0.00 or an absurd number when reading a decimal | %f in scanf for a double (use %lf) |
| The average or the area comes out without decimals or as 0 | Division between integers (30 / 4, 1 / 2) |
scanf doesn’t wait when reading a char | The space is missing: " %c" |
| A huge negative number or a value that keeps changing | Uninitialised variable or mismatched specifier |
Floating point exception | Integer division by 0 |
| A weird value like 8 where you wrote 010 | A leading 0 means octal |
Before handing in, ask yourself:
- Does it compile with
gcc -Wall -std=c11with no warnings? - Does every scanf have its
&and the right specifier for its type (%lffordouble)? - Is there any division between two integers where you expect decimals?
- Do all variables have a value before they’re used?
With this you close the first C block. The next topic starts where these exercises end: instead of showing a 1 or a 0, the program will decide what to do with them. It’s waiting for you in conditionals in C.
Keep goingAll C articlesDid this article help you? Here is how you can support SergioLearns →




The hardware that holds up the operating system — theory with my IC2 assignment on the printer, an interrupt that stops everything and a cache that hits (and the infinite loop that didn’t freeze my laptop)