C Programming Reference Guide
Quick reference notes for C programming concepts commonly used in exams.
1. Makefiles
What is a Makefile?
A file that tells make how to compile your project automatically.
Windows Reminder
- Use:
mingw32-make
- Executables must end in:
.exe
target: dependencies
gcc command
⚠️ Commands must start with a TAB, not spaces.
Single-File Makefile
build:
gcc main.c -o main.exe
Multi-File Makefile
Most exam tasks use this pattern:
program.exe: main.o utils.o
gcc main.o utils.o -o program.exe
main.o: main.c utils.h
gcc -c main.c
utils.o: utils.c utils.h
gcc -c utils.c
clean:
del *.o program.exe
Benefits:
- Only rebuilds files that changed
clean target removes generated files
Makefile with Rebuild
rebuild: clean program.exe
Using Variables
CC = gcc
CFLAGS = -Wall -c
program.exe: main.o item.o
$(CC) main.o item.o -o program.exe
main.o:
$(CC) $(CFLAGS) main.c
Makefile Exam Worksheet Example
You must be able to build programs such as:
1️⃣ Program that prints the smaller of two numbers
2️⃣ Program that prints the average of all numbers in command-line args
3️⃣ Create a Makefile that builds both
Example Makefile:
all: smaller average
smaller: smaller.o
gcc smaller.o -o smaller.exe
average: average.o
gcc average.o -o average.exe
smaller.o: smaller.c
gcc -c smaller.c
average.o: average.c
gcc -c average.c
clean:
del *.o *.exe
2. Structs (Explained from Zero)
A struct is a custom data type that groups different variables together.
Basic Declaration
struct Person {
char name[20];
int age;
float height;
};
This defines a new type that has 3 members.
How to Declare a Struct Variable
struct Person p1;
How to Access Members
p1.age = 20;
p1.height = 1.80;
strcpy(p1.name, "Jonathan");
Using typedef (Renaming a Struct)
Instead of always typing struct Person, use:
typedef struct Person {
char name[20];
int age;
} Person;
Then you can simply write:
Person p1;
Dynamic Structs (Very Important for Exam)
Person *p = malloc(sizeof(Person));
p->age = 20;
strcpy(p->name, "John");
free(p);
✅ Use -> when accessing members via pointer.
Bitfields (Memory-Saving Technique)
Used when storing many YES/NO or small numeric values:
struct Flags {
unsigned int a : 1; // uses 1 bit
unsigned int b : 1;
unsigned int c : 1;
};
Now the struct might only use 1 byte instead of 12 bytes.
3. Unions (Explained from Zero)
A union allows multiple fields, but only one can exist at a time. All fields share the same memory.
Basic Declaration
union Data {
int i;
float f;
char str[20];
};
Understanding Union Memory
i uses 4 bytes
f uses 4 bytes
str[20] uses 20 bytes
The union's size = 20 bytes (the largest member)
All fields share these same 20 bytes of memory:
--------------------
| 20 bytes shared |
--------------------
Example: How Memory Overlaps
union Data data;
data.i = 15; // stores int in shared memory
data.f = 3.14; // overwrites the int - value is now garbage
When Unions are Useful
- When storing different types, but only one at a time
- To save memory (embedded systems, sensors, message protocols)
- When interpreting raw memory in different formats
4. Struct + Union + Enum (Exam Worksheet - VERY IMPORTANT)
This is an extremely exam-likely pattern. You must be able to build a message handler that uses all three concepts together.
Required Structure
enum MessageType { ERROR, TEMPERATURE, TEXT };
union MessageData {
int errorCode;
float temperature;
char text[50];
};
struct Message {
enum MessageType type;
union MessageData data;
};
What You Must Do
- Store messages (array version + dynamic version)
- Accept user input from a menu
- Display all stored messages
Complete Example (Array Version)
#include <stdio.h>
#include <string.h>
enum MessageType { ERROR, TEMPERATURE, TEXT };
union MessageData {
int errorCode;
float temperature;
char text[50];
};
struct Message {
enum MessageType type;
union MessageData data;
};
int main() {
struct Message messages[10];
int count = 0;
int choice;
while (1) {
printf("\nMessage Handler Menu\n");
printf("1. Enter error message\n");
printf("2. Enter temperature\n");
printf("3. Enter text\n");
printf("4. Display all\n");
printf("5. Exit\n");
scanf("%d", &choice);
if (choice == 1) {
printf("Enter error code: ");
scanf("%d", &messages[count].data.errorCode);
messages[count].type = ERROR;
count++;
}
else if (choice == 2) {
printf("Enter temperature: ");
scanf("%f", &messages[count].data.temperature);
messages[count].type = TEMPERATURE;
count++;
}
else if (choice == 3) {
printf("Enter message: ");
scanf(" %[^\n]", messages[count].data.text);
messages[count].type = TEXT;
count++;
}
else if (choice == 4) {
for (int i=0; i<count; i++) {
if (messages[i].type == ERROR)
printf("Error: %d\n", messages[i].data.errorCode);
else if (messages[i].type == TEMPERATURE)
printf("Temperature: %.2f\n", messages[i].data.temperature);
else if (messages[i].type == TEXT)
printf("Text: %s\n", messages[i].data.text);
}
}
else if (choice == 5) break;
}
return 0;
}
Dynamic Version (Linked List)
This matches the linked list pattern perfectly:
struct Node {
struct Message msg;
struct Node *next;
};
Allocate:
struct Node* n = malloc(sizeof(struct Node));
Access:
n->msg.type = TEXT;
strcpy(n->msg.data.text, "hello");
Useful for union/struct exam programs:
while (1) {
printf("1. Add\n2. Show\n3. Exit\n");
scanf("%d", &choice);
if (choice == 1) {
// add
}
else if (choice == 2) {
// display
}
else break;
}
7. Command Line Arguments
Count Arguments
int main(int argc, char *argv[]) {
printf("Args: %d\n", argc);
}
Find Smaller of Two Arguments
int a = atoi(argv[1]);
int b = atoi(argv[2]);
printf("Smaller = %d\n", (a < b ? a : b));
Average of All Arguments
float sum = 0;
for (int i = 1; i < argc; i++)
sum += atof(argv[i]);
printf("Avg = %.2f\n", sum / (argc - 1));
8. Perfect Exam Template (Makefile + Program)
Makefile
program.exe: main.o message.o
gcc main.o message.o -o program.exe
main.o: main.c message.h
gcc -c main.c
message.o: message.c message.h
gcc -c message.c
clean:
del *.o program.exe
Message Handler Makefile Example
program.exe: main.o message.o
gcc main.o message.o -o program.exe
main.o: main.c message.h
gcc -c main.c
message.o: message.c message.h
gcc -c message.c
clean:
del *.o program.exe
9. Quick Reference Summary
Structs
- Use
. for normal struct access
- Use
-> for pointer access
- Dynamic:
malloc(sizeof(Type))
Unions
- Share memory
- Only one field valid at a time
- Size = largest field
Makefiles
- Use TAB, not spaces
- Windows executables end in
.exe
- Multi-file compilation uses
.o files
Enums
enum Type { ERROR, TEMP, TEXT };
10. Practical Examples
Example 1: Finding the Smaller Number
C Program
#include <stdio.h>
#include <stdlib.h> // Needed for atoi (convert string to int)
// argc = argument count (how many words in a command line)
// argv = argument vector (array of strings holding the words)
int main(int argc, char *argv[]){
// We expect 3 things: the program name, num1, and num2
// e.g., "./smaller 10 20" -> argc is 3
if(argc != 3){
printf("Error: Please provide exactly 2 numbers.\n");
printf("Usage: ./smaller <num1> <num2>\n");
return 1;
}
// Convert strings (argv) to integers
int num1 = atoi(argv[1]);
int num2 = atoi(argv[2]);
if (num1 < num2){
printf("Smaller: %d\n", num1);
}
else{
printf("Smaller: %d\n", num2);
}
return 0;
}
Makefile
# Target 1: The smaller number program
smaller: smaller.c
gcc -Wall -o smaller.exe smaller.c
# Target 2: clean
clean:
del smaller.exe
How to use:
./smaller 10 20 # Output: Smaller: 10
./smaller 50 30 # Output: Smaller: 30
Example 2: BMI Calculator
C Program
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
// 1. Check if we have enough ingredients
// (Program name + Weight + Height = 3)
if (argc != 3) {
printf("Error: Missing values.\n");
printf("Usage: ./bmi <weight_kg> <height_m>\n");
return 1;
}
// 2. Parse data. atof converts string to float.
double weight = atof(argv[1]);
double height = atof(argv[2]);
// 3. Validation (Don't divide by zero or allow negative height!)
if (height <= 0 || weight <= 0) {
printf("Error: Invalid height or weight.\n");
return 1;
}
// 4. The Math
double bmi = weight / (height * height);
// 5. The Vibe Check (Categories)
printf("Your BMI is: %.2f\n", bmi);
printf("Category: ");
if (bmi < 18.5) {
printf("Underweight\n");
} else if (bmi >= 18.5 && bmi <= 24.9) {
printf("Normal\n");
} else if (bmi >= 25 && bmi <= 29.9) {
printf("Overweight\n");
} else {
printf("Obese\n");
}
return 0;
}
Makefile
bmi: bmi.c
gcc -Wall -o bmi.exe bmi.c
clean:
del bmi.exe
How to use:
./bmi 70 1.75 # Output: BMI with category
./bmi 85 1.80 # Output: BMI with category
Key Concepts Demonstrated:
- Command-line argument parsing with
argc and argv
- String to number conversion (
atoi for int, atof for float)
- Input validation
- Conditional logic for categorization
- Makefile targets for building and cleaning
Example 3: Vehicle Fleet Manager (Multi-File Project)
This is a complete multi-file project demonstrating:
- Header files (
.h)
- Multiple source files (
.c)
- Struct + Union + Enum pattern
- Multi-file compilation with Makefile
#ifndef VEHICLE_H
#define VEHICLE_H
// 1. The Tag: Tells us which version of the union we are using
// "Variant data" logic from your notes
typedef enum {
CAR,
TRUCK
} VehicleType;
// 2. The Union: Shares memory. Only ONE of these exists at a time.
// Size = size of largest member (float weight)
typedef union {
int passengers; // Used if type is CAR
float weight; // Used if type is TRUCK
} VehicleData;
// 3. The Struct: Wraps it all together
struct Vehicle {
VehicleType type; // The "Tag"
VehicleData data; // The "Payload"
char model[20]; // Common data for everyone
};
// Function Prototypes
void print_vehicle(struct Vehicle v);
#endif
main.c (Main Program)
#include <stdio.h>
#include <string.h>
#include "vehicle.h"
int main() {
struct Vehicle myFleet[2]; // Array of structs
// Vehicle 1: A Car
// We access members using the dot operator (.)
myFleet[0].type = CAR;
strcpy(myFleet[0].model, "Toyota Prius");
myFleet[0].data.passengers = 5;
// Vehicle 2: A Truck
// Note: We overwrite the SAME memory space 'data', but now as a float
myFleet[1].type = TRUCK;
strcpy(myFleet[1].model, "Ford F-150");
myFleet[1].data.weight = 2.5;
// Print them out
for (int i = 0; i < 2; i++) {
print_vehicle(myFleet[i]);
}
return 0;
}
vehicle.c (Implementation)
#include <stdio.h>
#include <string.h>
#include "vehicle.h"
void print_vehicle(struct Vehicle v) {
printf("Model: %s\n", v.model);
if (v.type == CAR) {
printf("Type: Car\n");
printf("Passengers: %d\n\n", v.data.passengers);
} else if (v.type == TRUCK) {
printf("Type: Truck\n");
printf("Weight: %.2f tons\n\n", v.data.weight);
}
}
Makefile
# Default target - builds the final executable
# Note: Windows executable has .exe extension
all: fleet.exe
# Link object files to create the executable
fleet.exe: main.o vehicle.o
gcc -Wall -g -o fleet.exe main.o vehicle.o
# Compile main.c -> main.o
# Dependency: main.c AND vehicle.h (if header changes, we rebuild)
main.o: main.c vehicle.h
gcc -Wall -g -c main.c -o main.o
# Compile vehicle.c -> vehicle.o
vehicle.o: vehicle.c vehicle.h
gcc -Wall -g -c vehicle.c -o vehicle.o
# Clean up (Windows specific)
# Uses 'del' instead of 'rm' because you are on Windows CMD
clean:
del fleet.exe main.o vehicle.o
How to use:
make # Builds fleet.exe
./fleet.exe # Runs the program
make clean # Removes built files
Expected Output:
Model: Toyota Prius
Type: Car
Passengers: 5
Model: Ford F-150
Type: Truck
Weight: 2.50 tons
Key Concepts Demonstrated:
- Header guards (
#ifndef, #define, #endif)
- Function prototypes in header file
- Multiple source files compiled separately
- Enum for type tagging
- Union for variant data (passengers vs. weight)
- Struct combining enum + union
- Makefile dependencies (rebuilding when headers change)
- Code organization - interface vs. implementation
NOTES
/*Struct:
Struct has a seperate section for each item, eg: Lunchbox for Rice, meat, and veggies.
Memory: Each item gets its own space.
SYNTAX:
struct Student{
int id;
float gpa;
char name[20];
};
//Size = Dize of (int + float + char array) = BIG
Unions:
Union is a single box that can hold ONE thing at a time.
Memory: All members share the same memory location. The size of the union is just the size of the LARGEST item inside.
SYNTAX:
union MessageData{
int errorCode; // 4 bytes
float temperature; //4 bytes
char text[50]; // 50 bytes
};
// Total Size of Union = 50 bytes (Size of the largest member)
*/
/*
Makefiles:
The Syntax:
Makefiles USE TABS NOT SPACES.
Structure:
Target: Dependencies
Command (MUST be a TAB indent, not spaces!)
- Target: The file you want to create (e.g program.exe)
- Dependencies: The files needed to create the target (e.g., main.c)
- Command: The terminal command to build it (e.g. gcc ...)
*/