The language behind operating systems, embedded devices, and most intro "systems" courses. Compiling, variables, pointers, structs, and manual memory management โ with copy-paste examples and live Run/Submit exercises.
Every C program starts execution at main(). Save this as hello.c.
#include <stdio.h>
int main() {
printf("Hello, world!\n");
// single-line comment
/* multi-line
comment */
return 0;
}
#include <stdio.h> is a preprocessor directive โ it pastes in the standard I/O library before compiling, which is where printf/scanf come from. Your program won't compile without a main().Unlike Python, C is compiled before it runs โ a compiler turns your .c file into a native executable.
# compile hello.c into a program called "hello"
gcc hello.c -o hello
# run it
./hello
main (usually return 0;) tells the operating system whether your program succeeded. 0 = success, anything else = an error code.Unlike Python, C is statically typed โ you must declare each variable's type, and it can't change.
int age = 25; // whole numbers, ~4 bytes
float price = 9.99f; // single-precision decimal, 4 bytes
double pi = 3.14159265; // double-precision decimal, 8 bytes โ use this by default
char grade = 'A'; // ONE character, 1 byte (single quotes)
char name[30] = ""; // C's stand-in for a "string" type (double quotes)
#include <stdbool.h> // C99+: gives you a real bool
bool is_online = true; // true=1, false=0
0, 0.0f, '\0', or an empty string) rather than trust a "default." Sizes are machine-dependent-ish โ use sizeof(x) to check.printf's % codes control exactly how a value gets displayed, and can be dressed up with width, padding, sign, and precision modifiers.
printf("%d\n", 42); // int
printf("%f\n", 3.14); // float/double
printf("%c\n", 'A'); // char
printf("%s\n", "hi"); // string
printf("%3d\n", 7); // " 7" โ min width 3, right-justified
printf("%-3d|\n", 7); // "7 |" โ left-justified
printf("%03d\n", 7); // "007" โ zero-padded
printf("%+d\n", 7); // "+7" โ force the sign
printf("%.2f\n", 3.14159); // "3.14" โ 2 digits after the decimal, rounds
printf prints with format specifiers that must match your variable's type. scanf reads input the same way, but needs the variable's address.
int age;
char grade;
char name[30];
printf("Enter your age: ");
scanf("%d", &age); // & = "address of" โ scanf writes THROUGH a pointer
printf("Enter your grade: ");
scanf(" %c", &grade); // leading space skips a leftover '\n' in the buffer
printf("Enter your name: ");
fgets(name, sizeof(name), stdin); // reads a WHOLE line, spaces included
name[strlen(name) - 1] = '\0'; // fgets keeps the '\n' โ strip it (needs <string.h>)
scanf stops at the first whitespace, so it can't read a full name with a space in it โ use fgets for that. And every scanf leaves the trailing \n sitting in the input buffer, which will silently get "read" by the next %c or fgets unless you skip it (a leading space in the format string, or a throwaway getchar()).int a = 7, b = 2;
printf("%d\n", a + b); // 9
printf("%d\n", a / b); // 3 โ integer division truncates!
printf("%d\n", a % b); // 1 โ remainder (modulus)
printf("%f\n", a / (float)b); // 3.5 โ cast one side to get real division
int count = 0;
count++; // increment: count = count + 1
count--; // decrement: count = count - 1
count += 5; // augmented assignment: count = count + 5
count *= 2; // count = count * 2
<math.h> adds the functions printf/operators can't do alone.
#include <math.h>
printf("%f\n", sqrt(16)); // 4.0
printf("%f\n", pow(2, 10)); // 1024.0 โ 2 to the 10th
printf("%f\n", round(4.5)); // 5.0
printf("%f\n", ceil(4.1)); // 5.0 โ rounds up
printf("%f\n", floor(4.9)); // 4.0 โ rounds down
#include <math.h> gives an "implicit declaration" warning โ the compiler guesses a signature and things can silently misbehave.int age = 20;
if (age >= 18) {
printf("Adult\n");
} else if (age >= 13) {
printf("Teen\n");
} else {
printf("Child\n");
}
age >= 18 before age >= 65 means your "senior" branch can never trigger, since every senior is already caught by the adult check.A cleaner alternative to a long if/else if chain when you're comparing one variable against a fixed set of values.
int day = 3;
switch (day) {
case 1: printf("Monday\n"); break;
case 2: printf("Tuesday\n"); break;
case 3: printf("Wednesday\n"); break;
default: printf("Invalid day\n");
}
break "falls through" into the next case โ execution just keeps going instead of stopping. Sometimes that's intentional, but usually it's a bug. Works with int or char values.int temp = 22;
if (temp > 0 && temp < 30) { // AND โ BOTH must be true
printf("Comfortable\n");
}
bool is_weekend = false;
if (is_weekend || temp > 28) { // OR โ AT LEAST ONE true
printf("Relax today\n");
}
if (!is_weekend) { // NOT โ flips true/false
printf("Back to work\n");
}
// for loop โ most common for a known number of iterations
for (int i = 0; i < 5; i++) {
printf("%d\n", i);
}
// while loop โ checks the condition BEFORE each run, open-ended
int n = 3;
while (n > 0) {
printf("%d\n", n);
n--;
}
// do-while โ checks the condition AFTER, so it always runs at least once
int choice;
do {
printf("Enter 1-3: ");
scanf("%d", &choice);
} while (choice < 1 || choice > 3);
do-while is the natural fit for "reprompt until valid" input loops, since the body has to run once before there's anything to check.for (int i = 0; i < 10; i++) {
if (i == 5) break; // stop the loop entirely
if (i % 2 == 0) continue; // skip just this iteration
printf("%d\n", i); // prints 1, 3
}
A loop inside another loop โ the inner counter is conventionally named j so it doesn't collide with the outer i.
// multiplication table, 1-5
for (int i = 1; i <= 5; i++) {
for (int j = 1; j <= 5; j++) {
printf("%4d", i * j); // %4d keeps columns aligned
}
printf("\n");
}
#include <stdlib.h>
#include <time.h>
srand(time(NULL)); // seed once, using the current time
int roll = 1 + (rand() % 6); // 1-6: min + (rand() % (max-min+1))
printf("%d\n", roll);
srand(time(NULL)), rand() produces the exact same sequence of numbers every time you run the program โ it defaults to seed 1.A fixed-size, same-type block of memory. C does not check array bounds for you โ reading/writing past the end is undefined behavior.
int nums[5] = {10, 20, 30, 40, 50};
printf("%d\n", nums[0]); // 10
nums[1] = 99; // modify in place
int len = sizeof(nums) / sizeof(nums[0]); // classic way to get array length
for (int i = 0; i < len; i++) {
printf("%d\n", nums[i]);
}
int scores[5] = {0}; // zero-fill every element (leaving one out zeros ALL of them)
An array of arrays โ think rows and columns. Accessed with two indices, arr[row][col].
int grid[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
printf("%d ", grid[row][col]);
}
printf("\n");
}
C has no string type โ a string is just a char array ending with a hidden '\0' (null terminator). String functions live in <string.h>.
#include <string.h>
char name[20] = "Mo";
printf("%s\n", name); // Mo
printf("%lu\n", strlen(name)); // 2 โ length, not counting '\0'
strcat(name, " Salah"); // append โ make sure the array is big enough!
printf("%s\n", name); // Mo Salah
if (strcmp(name, "Mo Salah") == 0) { // strcmp returns 0 when equal
printf("Match!\n");
}
// an "array of strings" is really a 2D char array
char fruits[3][10] = {"apple", "banana", "coconut"};
printf("%s\n", fruits[1]); // banana
Every function declares its return type and each parameter's type. A prototype up top lets you define the real function body after main().
int add(int a, int b); // prototype: return type + name + param types + ;
int main() {
printf("%d\n", add(2, 3)); // 5 โ C reads top-down, so it needs to know add() exists first
return 0;
}
int add(int a, int b) { // full definition, can live below main()
return a + b;
}
void greet(char *name) { // void = returns nothing
printf("Hi, %s!\n", name);
}
int counter = 0; // GLOBAL โ visible to every function, but avoid these when you can
void example() {
int local_x = 5; // LOCAL โ only exists inside this { } block
} // local_x is gone here
A static local variable is initialized only once and then persists for the entire life of the program between calls โ unlike a normal local, which is recreated (and reset) every time its function runs.
void counter() {
static int x = 0; // set up ONCE, ever โ not on every call
x++;
printf("%d\n", x);
}
int main() {
counter(); counter(); counter(); // prints 1, 2, 3 โ NOT 1, 1, 1
return 0;
}
{ } block ends), static (lives for the whole program, but is still only visible inside the function that declared it), and dynamic (heap memory from malloc โ lives until you free it).A one-line shorthand for a simple if/else: condition ? valueIfTrue : valueIfFalse.
int age = 20;
char *status = (age >= 18) ? "adult" : "minor";
printf("%s\n", status); // adult
int n = 7;
printf("%s\n", (n % 2 == 0) ? "even" : "odd");
Gives an existing type a new nickname, which can make complex declarations more readable.
typedef char String[50]; // now "String" works instead of "char[50]"
String name = "Ada";
printf("%s\n", name);
A named set of integer constants โ clearer than a pile of "magic numbers". Values auto-increment from 0 unless you assign your own.
typedef enum { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY } Day;
Day today = WEDNESDAY;
if (today == SATURDAY || today == SUNDAY) {
printf("Weekend!\n");
} else {
printf("Weekday, day #%d\n", today); // 3
}
// you can also assign your OWN values instead of auto-incrementing from 0
typedef enum {
HTTP_BAD_REQUEST = 400,
HTTP_NOT_FOUND = 404,
HTTP_TEAPOT = 418
} HttpStatus;
switch is the natural partner for an enum โ but C gives no warning if you forget a case (unlike some other languages' exhaustiveness checks), so always keep a default.C's defining feature. A pointer stores a memory address instead of a value. & = "address of", * = "value at this address" (dereference).
int x = 10;
int *p = &x; // p holds the ADDRESS of x
printf("%d\n", *p); // 10 โ dereference: "the value AT that address"
*p = 20; // change x THROUGH the pointer
printf("%d\n", x); // 20 โ x actually changed!
// this is how functions modify their caller's variables (C passes by value otherwise)
void increment(int *n) {
(*n)++;
}
// calling increment(&x) actually changes x in main
NULL and check before using: if (p != NULL) { ... }.An array name decays into a pointer to its first element โ arr[i] is really just shorthand for *(arr + i), with the compiler auto-scaling the offset by the element's size.
int nums[5] = {10, 20, 30, 40, 50};
printf("%d\n", nums[2]); // 30
printf("%d\n", *(nums + 2)); // 30 โ the exact same thing
sizeof(arr) gives its true byte size. But once you pass that array into another function, it decays to a bare pointer โ sizeof there only reports the pointer's own size (e.g. 8), silently losing the element count. Always pass the length alongside the array.A pointer can point to another pointer โ same address/value idea, just one extra hop. Useful when a function needs to change which address the caller's pointer holds, not just the value at that address.
void allocate_int(int **out, int value) {
int *fresh = (int *) malloc(sizeof(int));
if (fresh == NULL) return;
*fresh = value;
*out = fresh; // updates the CALLER's pointer variable itself
}
int main() {
int *p = NULL;
allocate_int(&p, 42); // pass the ADDRESS of the pointer
printf("%d\n", *p); // 42
free(p);
return 0;
}
allocate_int, the new value must be malloc'd (heap), not a plain local variable โ a local lives on the stack and disappears the moment the function returns, leaving *out pointing at freed memory.void* is a generic pointer โ it discards all type information, so you must cast it back to the correct type yourself before dereferencing. It's how C fakes "generic" containers that can hold any type.
void print_as(void *data, int is_float) {
if (is_float) printf("%f\n", *(float *)data);
else printf("%d\n", *(int *)data);
}
int main() {
int whole = 7;
float decimal = 3.14f;
print_as(&whole, 0); // 7
print_as(&decimal, 1); // 3.140000
return 0;
}
void* to the wrong type โ the compiler trusts you completely, and reading it back as the wrong type gives garbage instead of an error. This flexibility is also how you'd write a type-agnostic swap(void *a, void *b, size_t size): malloc a temporary buffer of size bytes and shuffle the three "hands" with memcpy instead of a typed temp variable.A struct groups related variables of different types into one custom type โ C's version of a simple object.
struct Point {
int x;
int y;
};
int main() {
struct Point p1 = {3, 4};
printf("(%d, %d)\n", p1.x, p1.y); // (3, 4)
struct Point *ptr = &p1;
printf("%d\n", ptr->x); // -> is shorthand for (*ptr).x
struct Point points[2] = { {0,0}, {5,5} }; // an array of structs
printf("%d\n", points[1].x); // 5
return 0;
}
Struct fields sit contiguously in memory in the order you declare them โ but sizeof(struct) is often bigger than the sum of its fields, because the compiler inserts invisible padding so each field lands on an aligned address (faster for the CPU to read).
struct Wasteful { // char(1) + padding(3) + int(4) + char(1) + padding(3) = 12 bytes
char flag;
int id;
char letter;
};
struct Tight { // int(4) + char(1) + char(1) + padding(2) = 8 bytes
int id;
char flag;
char letter;
};
printf("%lu\n", sizeof(struct Wasteful)); // 12
printf("%lu\n", sizeof(struct Tight)); // 8
sizeof rather than assume.A union holds one of several possible fields at a time, and all of them share the same memory โ its size equals its largest member, not the sum of all of them. Set one field, then read a different one, and you reinterpret the same raw bytes as the new type.
typedef enum { KIND_INT, KIND_STRING } Kind;
typedef union {
int as_int;
char *as_string;
} Value;
typedef struct {
Kind kind;
Value data;
} Object;
Object o;
o.kind = KIND_INT;
o.data.as_int = 42;
printf("%d\n", o.data.as_int); // 42
printf("%lu\n", sizeof(Value)); // size of its LARGEST member, not both added together
Kind-style field (as above) is the safe, common pattern โ the tag tells you which member is actually valid right now. Reading the "wrong" field for what was last set (e.g. writing a signed value and reading it back as unsigned) reinterprets the same bits and can produce wildly different-looking numbers.Program memory splits into regions. The stack is fast and automatic: every function call pushes a "frame" holding its locals, and returning pops (frees) it โ last in, first out. The heap is manually managed (via malloc/free) for data that must outlive the function that created it, or whose size isn't known until runtime.
// STACK: destroyed the moment the function returns
void make_local() {
int temp = 99; // lives only inside this function call
}
// HEAP: survives after the function returns โ caller owns it now
int *make_heap_int(int value) {
int *p = (int *) malloc(sizeof(int));
if (p != NULL) *p = value;
return p; // caller is now responsible for free()-ing this
}
return &temp;) is a classic bug โ the address still "looks" valid and prints fine, but the stack frame it pointed into is gone, and the next function call will silently reuse and overwrite that same memory. This is also where the phrase "stack overflow" comes from: allocating something too large on the stack (e.g. a huge local array) can exhaust the space set aside for it.C has no garbage collector โ memory you allocate, you must free, or it leaks for the life of the program.
#include <stdlib.h>
int *arr = (int *) malloc(5 * sizeof(int)); // room for 5 ints, on the heap
if (arr == NULL) { // malloc returns NULL if it fails
printf("Out of memory\n");
return 1;
}
for (int i = 0; i < 5; i++) arr[i] = i * i;
printf("%d\n", arr[3]); // 9
free(arr); // give the memory back โ every malloc needs a matching free
arr = NULL; // good habit: avoid an accidental dangling pointer
// calloc: like malloc but zero-initializes every byte (slightly slower, fewer bugs)
int *zeros = (int *) calloc(5, sizeof(int));
// realloc: resize a previous allocation โ check a TEMP pointer first!
int *temp = (int *) realloc(zeros, 10 * sizeof(int));
if (temp != NULL) zeros = temp; // only overwrite the original once you know it worked
free(zeros);
realloc fails it returns NULL โ assigning straight into your original pointer (zeros = realloc(zeros, ...)) would then lose your only reference to the original block, leaking it forever. Always reassign through a temporary pointer first.C never collects garbage for you โ but it's worth knowing the two classic strategies other languages use under the hood, since the ideas show up any time you're managing shared ownership yourself.
ref_count. Anything that keeps a reference to it increments the count; anything that stops decrements it; the object frees itself the instant the count hits 0.FILE *f = fopen("notes.txt", "w"); // "w"=write, "r"=read, "a"=append
if (f != NULL) {
fprintf(f, "Score: %d\n", 95);
fclose(f); // always close what you open
}
char line[100];
f = fopen("notes.txt", "r");
if (f != NULL) {
while (fgets(line, 100, f)) {
printf("%s", line);
}
fclose(f);
}
#include is literal text substitution โ it pastes the target file's contents in place. If the same header ever gets included twice (directly or through another header), you get duplicate-definition errors. A header guard fixes that by only letting the contents through once per compile.
// mymath.h
#ifndef MYMATH_H
#define MYMATH_H
int square(int n);
#endif
#pragma once at the top of the header, doing the same job as the #ifndef/#define/#endif trio above.Real programs get split across a .c file (the actual code) and a matching .h file (just the function signatures, so other files know what's available). Each .c file compiles independently into an object file, then a final step links the object files together into one executable.
// mymath.h โ the public interface
#pragma once
int square(int n);
// mymath.c โ the implementation
#include "mymath.h"
int square(int n) { return n * n; }
// main.c
#include <stdio.h>
#include "mymath.h"
int main() { printf("%d\n", square(5)); return 0; }
# compile each .c into an object file, then link them together
gcc -c mymath.c # -> mymath.o
gcc -c main.c # -> main.o
gcc mymath.o main.o -o program
# or the one-line shortcut for small projects
gcc main.c mymath.c -o program
Full runnable programs. Click a box below (or hit its โธ Solve button) to edit, run, and submit against the reference output โ same workspace as the Python cheat sheet, just compiled and run in a real C sandbox instead of your browser.
Print the sum of a fixed array of integers.
#include <stdio.h>
int main() {
int nums[] = {4, 8, 15, 16, 23, 42};
int len = sizeof(nums) / sizeof(nums[0]);
int sum = 0;
for (int i = 0; i < len; i++) {
sum += nums[i];
}
printf("%d\n", sum);
return 0;
}
Print a string reversed, in place, using two pointers.
#include <stdio.h>
#include <string.h>
void reverse(char *s) {
int i = 0, j = strlen(s) - 1;
while (i < j) {
char tmp = s[i];
s[i] = s[j];
s[j] = tmp;
i++; j--;
}
}
int main() {
char word[] = "pointers";
reverse(word);
printf("%s\n", word);
return 0;
}
Read an item name, a price, and a quantity, then print the formatted total.
#include <stdio.h>
int main() {
char item[30] = "Coffee";
float price = 4.5f;
int qty = 3;
float total = price * qty;
printf("%d x %s @ $%.2f = $%.2f\n", qty, item, price, total);
return 0;
}
Menu-driven: convert kilograms to pounds, or pounds to kilograms.
Convert between Celsius and Fahrenheit based on a character flag.
Check whether a number is prime using a simple divisor loop.
Check a list of years to see which ones are leap years.
Count how many numbers in a fixed array are even versus odd.
Count the vowels in a sentence, handling both upper and lower case.
Print the first 10 numbers of the Fibonacci sequence using a loop.
Raise a number to a power using a loop instead of pow().
Add up the individual digits of a number.
Reverse the digits of an integer using modulo and division.
Classify a triangle as equilateral, isosceles, or scalene given three side lengths.
Convert a decimal integer to its binary representation using an array.
Given a radius, compute circle area and sphere surface area & volume.
#include <stdio.h>
#include <math.h>
const double PI = 3.14159265;
int main() {
double r = 4.0;
double circle_area = PI * pow(r, 2);
double sphere_surface = 4 * PI * pow(r, 2);
double sphere_volume = (4.0 / 3.0) * PI * pow(r, 3);
printf("Circle area: %.2f\n", circle_area);
printf("Sphere surface: %.2f\n", sphere_surface);
printf("Sphere volume: %.2f\n", sphere_volume);
return 0;
}
Implement A = P(1 + r/n)^(nt) for a given principal, rate, years, and compounding frequency.
#include <stdio.h>
#include <math.h>
int main() {
double principal = 1000;
double rate = 5 / 100.0; // 5% as a decimal
double years = 10;
int n = 12; // compounded monthly
double amount = principal * pow(1 + rate / n, n * years);
printf("Final amount: $%.2f\n", amount);
return 0;
}
Compute 10! using a recursive function.
#include <stdio.h>
long factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
int main() {
printf("%ld\n", factorial(10));
return 0;
}
Write a function that swaps two integers by taking pointers to them (the only way to do it in C).
Read two numbers and an operator, and switch on the operator โ guarding against divide-by-zero.
Three functions working together: a computer pick, a user pick, and a winner check.
Loop until a fixed secret number is guessed, with too-high/too-low hints and a try counter.
Sort an array of integers in ascending order using bubble sort.
Find the index of a target value in an unsorted array.
Search a sorted array for a target value by repeatedly halving the range.
Compute the greatest common divisor and least common multiple of two numbers.
Transpose a 2D matrix by swapping rows and columns.
Rotate an array's elements to the left by a given number of positions.
Print the first several rows of Pascal's triangle.
Count how often each letter appears in a string using an array as a lookup table.
Build a small array of structs on the heap, fill it, print it, then free it โ the full malloc/struct/free lifecycle.
#include <stdio.h>
#include <stdlib.h>
struct Student {
char name[20];
int score;
};
int main() {
int n = 3;
struct Student *students = (struct Student *) malloc(n * sizeof(struct Student));
snprintf(students[0].name, 20, "Ada"); students[0].score = 92;
snprintf(students[1].name, 20, "Linus"); students[1].score = 88;
snprintf(students[2].name, 20, "Grace"); students[2].score = 95;
int total = 0;
for (int i = 0; i < n; i++) {
printf("%s: %d\n", students[i].name, students[i].score);
total += students[i].score;
}
printf("Average: %d\n", total / n);
free(students);
return 0;
}
Three functions sharing a running balance: check, deposit, and withdraw, with validation.
#include <stdio.h>
void checkBalance(float balance) {
printf("Balance: $%.2f\n", balance);
}
float deposit(float amount) {
if (amount < 0) { printf("Invalid deposit\n"); return 0; }
return amount;
}
float withdraw(float balance, float amount) {
if (amount > balance) { printf("Insufficient funds\n"); return 0; }
return amount;
}
int main() {
float balance = 100;
balance += deposit(50);
balance -= withdraw(balance, 30);
checkBalance(balance); // $120.00
return 0;
}
Parallel arrays for questions and answers, scored by comparing an uppercased guess to an answer key.
#include <stdio.h>
#include <ctype.h>
int main() {
char questions[][60] = {
"What color is the sky? A) Blue B) Green",
"How many days in a week? A) 5 B) 7"
};
char answer_key[] = {'A', 'B'};
char user_guesses[] = {'a', 'b'}; // simulated, lowercase on purpose
int question_count = sizeof(answer_key) / sizeof(answer_key[0]);
int score = 0;
for (int i = 0; i < question_count; i++) {
printf("%s\n", questions[i]);
char guess = toupper(user_guesses[i]);
if (guess == answer_key[i]) score++;
}
printf("Score: %d/%d\n", score, question_count);
return 0;
}
Pull the current time from the system clock and format it with zero-padded, arrow-operator struct access.
Check whether a word reads the same backward as forward, ignoring case.
Check whether two words are anagrams of each other using letter-count arrays.
Encrypt a message by shifting each letter by a fixed amount, wrapping within the alphabet.
Compute the mean, median, and mode of a fixed array of numbers.
Implement push and pop operations for a stack using a fixed-size array.
Implement enqueue and dequeue operations for a circular queue using a fixed-size array.
Search an array of student structs by name and print the matching record.
Track item quantities and prices in a struct array and compute total inventory value.
Average each student's scores and assign a letter grade using a helper function.
Model a traffic light cycling through states using a typedef'd enum.
Store different value types in one struct using a union with a type tag to know which member is active.