Hello, World!
1.1 A Brief History of C
C was born in 1972, created by Dennis Ritchie at Bell Labs. It is the language used to write the Unix kernel and the foundation of modern languages (C++, Java, CPython for Python). Master C and you hold the key to understanding how computers work at the lowest level.
1.2 The Simplest C Program
1 2 3 4 5 6
#include <stdio.h>
int main(void) {
printf("Hello, World!\n");
return 0;
} 1.3 Line-by-Line Breakdown
#include <stdio.h> — a preprocessor directive that includes the standard input/output header, making printf available.
int main(void) — the program entry point, where the operating system begins execution.
printf(...) — prints text to the terminal, \n means newline.
return 0; — returns 0 to the operating system, indicating the program ended normally.
1.4 Compiling and Running
1 2
$ gcc hello.c -o hello
$ ./hello The Compilation Pipeline
2.1 The Four Stages of Compilation
Turning C source into an executable goes through four steps:
1 2
hello.c -> preprocess(.i) -> compile(.s) -> assemble(.o) -> link(executable)
expand #include generate assembly generate machine code merge library functions 2.2 Seeing Each Step with gcc
1 2 3 4
$ gcc -E hello.c -o hello.i // preprocess only
$ gcc -S hello.c -o hello.s // compile only, generate assembly
$ gcc -c hello.c -o hello.o // assemble only, generate object file
$ gcc hello.o -o hello // link to produce executable 2.3 Common gcc Options
1 2 3 4
-Wall // enable all warnings (recommended)
-g // generate debug info (for use with gdb)
-O2 // optimization level 2
-std=c99 // specify the C standard Variables and Data Types
3.1 Basic Data Types
1 2 3 4 5 6 7 8 9 10 11 12
#include <stdio.h>
int main(void) {
int age = 25; // integer
char grade = 'A'; // single character
float pi = 3.14f; // single-precision float
double e = 2.71828; // double-precision float
printf("age=%d, grade=%c, pi=%.2f, e=%.5f\n",
age, grade, pi, e);
return 0;
} 3.2 Type Sizes (typical 64-bit values)
1 2 3 4 5 6
int -> 4 bytes (-2^31 ~ 2^31-1)
char -> 1 byte (-128 ~ 127)
float -> 4 bytes (~6-7 significant digits)
double -> 8 bytes (~15-16 significant digits)
long -> 8 bytes
short -> 2 bytes 3.3 Checking Actual Sizes with sizeof
1 2 3
printf("int: %zu bytes\n", sizeof(int));
printf("char: %zu bytes\n", sizeof(char));
printf("double: %zu bytes\n", sizeof(double)); printf Formatted Output
4.1 Format Specifier Cheat Sheet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
#include <stdio.h>
int main(void) {
int n = 42;
double f = 3.14159;
char c = 'X';
char *s = "hack";
printf("int: %d\n", n); // 42
printf("float: %.2f\n", f); // 3.14 (2 decimal places)
printf("char: %c\n", c); // X
printf("str: %s\n", s); // hack
printf("hex: %x\n", n); // 2a (hexadecimal)
printf("oct: %o\n", n); // 52 (octal)
printf("pad: %05d\n", n); // 00042 (zero-padded)
printf("left: %-5d|\n", n); // 42 | (left-aligned)
return 0;
} 4.2 The Format Syntax
1 2 3 4 5 6
%[flags][width][.precision]type
flags: - (left-align), 0 (zero-pad), + (show plus sign)
width: minimum field width
precision: number of decimals (float) or max characters (string)
type: d(integer) f(float) c(char) s(string) x(hex) %(percent sign itself) scanf Keyboard Input
5.1 Reading Basic Types
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#include <stdio.h>
int main(void) {
int age;
double score;
char name[20];
printf("Enter age: ");
scanf("%d", &age);
printf("Enter score: ");
scanf("%lf", &score);
printf("Enter name: ");
scanf("%s", name); // an array name is already an address, no & needed
printf("Hello %s, %d years old, score %.1f\n", name, age, score);
return 0;
} 5.2 Key Points
& the address-of operator: scanf needs to know where to store the data, so ordinary variables must be prefixed with &.
%lf vs %f: scanf reads double with %lf, printf prints double with %f.
Strings (arrays): an array name is already the first element's address, so no & is needed.
5.3 scanf's Return Value
1 2 3 4 5
int n = scanf("%d %d", &a, &b);
// n == 2 means two values were read successfully
// n == 1 means only one was read
// n == 0 means input did not match
// n == EOF means end of input (Ctrl+D) Operators, Complete Guide
6.1 Arithmetic Operators
1 2 3 4 5 6 7 8
int a = 17, b = 5;
printf("%d\n", a + b); // 22 plus
printf("%d\n", a - b); // 12 minus
printf("%d\n", a * b); // 85 times
printf("%d\n", a / b); // 3 integer division (truncates the fraction)
printf("%d\n", a % b); // 2 remainder
printf("%d\n", a++); // 17 postfix ++ (return first, then increment)
printf("%d\n", ++a); // 19 prefix ++ (increment first, then return) 6.2 Relational and Logical
1 2 3 4 5 6 7
int x = 5;
x > 3 && x < 10 // true -> 1
x < 3 || x > 10 // false -> 0
!x // false -> 0 (x is nonzero)
// In C: 0 means false, nonzero means true
// Result of relational ops: 1(true) or 0(false) 6.3 Operator Precedence (simplified)
1 2 3 4 5 6 7 8 9 10
high -> low:
() [] -> . // parentheses / member access
! ~ ++ -- (type) // unary
* / % // multiply/divide/modulus
+ - // add/subtract
< <= > >= // relational
== != // equality
&& // logical AND
|| // logical OR
= += -= *= /= // assignment Type Conversion
7.1 Implicit Type Conversion
1 2 3 4 5 6 7 8 9 10 11
int i = 10;
double d = 3.14;
// int + double -> automatically promoted to double
double result = i + d;
printf("%.2f\n", result); // 13.14
// int / int -> integer division (loses the fraction)
int a = 7, b = 2;
double bad = a / b; // 3.0 instead of 3.5!
printf("%.1f\n", bad); // 3.0 7.2 Explicit Type Casting
1 2 3 4 5 6 7 8 9 10 11 12 13
int a = 7, b = 2;
// cast first, then divide
double good = (double)a / b;
printf("%.2f\n", good); // 3.50
// equivalent form
double good2 = a / (double)b;
printf("%.2f\n", good2); // 3.50
// cast only a, b is promoted automatically
double good3 = 1.0 * a / b;
printf("%.2f\n", good3); // 3.50 7.3 Conversion Rules: Low Precision → High Precision
1 2 3 4 5 6 7 8
char -> short -> int -> long -> float -> double
// during arithmetic, lower precision is automatically promoted to higher
// no precision is lost
// high -> low (requires an explicit cast, may lose data):
double pi = 3.14159;
int truncated = (int)pi; // 3, the fractional part is discarded Comments and Code Style
8.1 Two Kinds of Comments
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// single-line comment — introduced in C99
/*
* multi-line comment
* good for large explanations
*/
int main(void) {
// TODO: implement the encryption feature
int key = 42; // secret key value
/* old code backup
int old_key = 0;
*/
return 0;
} 8.2 Naming Conventions
1 2 3 4 5 6 7 8 9 10 11 12 13
// ✅ good naming
int student_count;
int maxRetries;
double cpu_temperature;
// ❌ bad naming
int x, y, z; // unclear meaning
int a1, a2, a3; // meaningless
int data; // which data?
// C naming style: snake_case (lowercase + underscores)
// constants may be all caps: MAX_BUFFER_SIZE
// type names capitalized first letter: typedef struct Student Student; 8.3 Code Style Checking
1 2 3 4 5 6 7
// always add these when compiling with gcc:
$ gcc -Wall -Wextra -std=c99 -g hello.c -o hello
// -Wall : common warnings
// -Wextra : extra warnings
// -std=c99: use the C99 standard
// -g : debug info ASCII and Characters
9.1 The Essence of char
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#include <stdio.h>
int main(void) {
char c = 'A';
printf("char: %c\n", c); // A
printf("ASCII: %d\n", c); // 65
// char is essentially a small integer
char next = c + 1;
printf("next: %c (%d)\n", next, next); // B (66)
// case conversion
char lower = c + 32; // 'a' - 'A' = 32
printf("lower: %c\n", lower); // a
return 0;
} 9.2 Common ASCII Values
1 2 3 4 5 6 7 8 9 10 11
'0' = 48 '9' = 57
'A' = 65 'Z' = 90
'a' = 97 'z' = 122
'\n' = 10 '\t' = 9
'\0' = 0 ' ' = 32
// check digit: c >= '0' && c <= '9'
// check uppercase: c >= 'A' && c <= 'Z'
// check lowercase: c >= 'a' && c <= 'z'
// uppercase to lowercase: c + ('a' - 'A') i.e. c + 32
// lowercase to uppercase: c - ('a' - 'A') i.e. c - 32 9.3 Escape Characters
1 2 3 4 5
\n newline \t tab
\0 null char \\ backslash
\' single quote \" double quote
\r carriage return \b backspace
\x41 hex A \101 octal A Phase Project: Simple Calculator
10.1 Goal
Write a program that reads two numbers and an operator (+ - * /) and prints the result. This is a comprehensive test of the first 9 days.
10.2 Reference Implementation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
#include <stdio.h>
int main(void) {
double a, b;
char op;
printf("Enter an expression (e.g. 3 + 5): ");
scanf("%lf %c %lf", &a, &op, &b);
double result;
switch(op) {
case '+': result = a + b; break;
case '-': result = a - b; break;
case '*': result = a * b; break;
case '/':
if (b == 0) {
printf("Error: the divisor cannot be 0\n");
return 1;
}
result = a / b;
break;
default:
printf("Unsupported operator: %c\n", op);
return 1;
}
printf("= %.2f\n", result);
return 0;
} 10.3 Challenge Tasks
1. Add remainder operation % (note both operands must be integers)
2. Make the program loop until the user enters q to quit
3. Add a square operation ^ (call the math library or implement it yourself)
if Conditional Statements
11.1 if Basic Syntax
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#include <stdio.h>
int main(void) {
int score;
printf("Enter score: ");
scanf("%d", &score);
if (score >= 90) {
printf("Excellent!\n");
} else if (score >= 60) {
printf("Pass\n");
} else {
printf("Fail\n");
}
return 0;
} 11.2 Conditional Expressions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// comparison operators: == != < > <= >=
// logical operators: &&(AND) ||(OR) !(NOT)
int age = 20;
char gender = 'M';
if (age >= 18 && gender == 'M') {
printf("adult male\n");
}
// short-circuit evaluation
if (age > 0 && 100 / age > 5) {
// if age <= 0, 100/age is not executed
// avoiding: division by zero
printf("ok\n");
} 11.3 The Ternary Operator
1 2 3 4 5 6 7 8
int a = 10, b = 20;
int max = (a > b) ? a : b;
printf("max = %d\n", max); // 20
// equivalent to:
int max2;
if (a > b) max2 = a;
else max2 = b; switch Multi-branch
12.1 switch Basic Structure
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#include <stdio.h>
int main(void) {
int day;
printf("Enter weekday (1-7): ");
scanf("%d", &day);
switch(day) {
case 1: printf("Monday\n"); break;
case 2: printf("Tuesday\n"); break;
case 3: printf("Wednesday\n"); break;
case 4: printf("Thursday\n"); break;
case 5: printf("Friday\n"); break;
case 6:
case 7: printf("Weekend!\n"); break; // merges cases
default: printf("Invalid input\n");
}
return 0;
} 12.2 What If You Forget break?
1 2 3 4 5 6 7 8 9
int x = 2;
switch(x) {
case 1: printf("one\n");
case 2: printf("two\n"); // matched here
case 3: printf("three\n"); // no break, keeps running!
case 4: printf("four\n"); // keeps running!
default: printf("default\n");
}
// output: two three four default 12.3 switch vs if-else
1 2 3 4 5 6
// switch can only compare equality (==)
// switch only works with integral and char types
// if-else can use any condition
// multiple equality checks -> switch is clearer
// range checks (score >= 90) -> use if-else while Loop
13.1 while Basic Form
1 2 3 4 5 6 7 8 9 10
#include <stdio.h>
int main(void) {
int i = 1;
while (i <= 5) {
printf("%d ", i);
i++; // don't forget to update the condition variable!
}
printf("\n");
return 0;
} 13.2 Countdown and Summing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// countdown
int n = 5;
while (n > 0) {
printf("%d... ", n);
n--;
}
printf("Launch!\n");
// output: 5... 4... 3... 2... 1... Launch!
// sum from 1 to 100
int sum = 0, k = 1;
while (k <= 100) {
sum += k;
k++;
}
printf("sum = %d\n", sum); // 5050 13.3 Infinite Loops
1 2 3 4 5 6 7 8 9 10
// classic infinite loop (terminate with Ctrl+C)
while (1) {
printf("forever...\n");
}
// equivalent form
while (1) { // always true
// must break out from inside
if (someCondition) break;
} for Loop
14.1 for Syntax
1 2 3 4 5 6 7 8 9 10
#include <stdio.h>
int main(void) {
// for (init; condition; update)
for (int i = 0; i < 5; i++) {
printf("%d ", i);
}
printf("\n");
// output: 0 1 2 3 4
return 0;
} 14.2 All Three Parts of for Are Optional
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// omit the init
int i = 0;
for (; i < 5; i++) { ... }
// omit the condition -> infinite loop
for (int i = 0;; i++) {
if (i >= 5) break;
}
// omit the update
for (int i = 0; i < 5;) {
printf("%d ", i);
i += 2; // manual update
}
// output: 0 2 4
// omit everything = infinite loop
for (;;) { ... } 14.3 Practical Patterns
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// reverse order
for (int i = 10; i > 0; i--) {
printf("%d ", i);
}
// 10 9 8 7 6 5 4 3 2 1
// step size
for (int i = 0; i <= 20; i += 5) {
printf("%d ", i);
}
// 0 5 10 15 20
// nested loops
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
printf("%d%d ", i, j);
}
printf("\n");
} do-while Loop
15.1 do-while Syntax
1 2 3 4 5 6 7 8 9 10 11
#include <stdio.h>
int main(void) {
int n;
do {
printf("Enter a positive number: ");
scanf("%d", &n);
} while (n <= 0); // the semicolon cannot be omitted!
printf("You entered: %d\n", n);
return 0;
} 15.2 while vs do-while
1 2 3 4 5 6 7 8 9 10 11 12 13
// while: check before executing (may not run at all)
int i = 10;
while (i < 5) {
printf("%d ", i); // never executes
i++;
}
// do-while: execute then check (runs at least once)
int j = 10;
do {
printf("%d ", j); // prints 10
j++;
} while (j < 5); 15.3 Menu System
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
int choice;
do {
printf("\n=== Menu ===\n");
printf("1. Start game\n");
printf("2. Settings\n");
printf("3. Exit\n");
printf("Choose: ");
scanf("%d", &choice);
switch(choice) {
case 1: printf("Game started!\n"); break;
case 2: printf("Settings...\n"); break;
case 3: printf("Goodbye!\n"); break;
default: printf("Invalid choice\n");
}
} while (choice != 3); break and continue
16.1 break — Exiting a Loop
1 2 3 4 5 6 7 8 9 10 11 12
#include <stdio.h>
int main(void) {
// find the first number divisible by 7
for (int i = 1; i <= 100; i++) {
if (i % 7 == 0) {
printf("Found: %d\n", i);
break; // immediately break out of the for loop
}
}
// output: Found: 7
return 0;
} 16.2 continue — Skip This Round, Continue to the Next
1 2 3 4 5 6 7 8 9
// print the odd numbers from 1-10
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue; // skip even numbers
}
printf("%d ", i);
}
printf("\n");
// output: 1 3 5 7 9 16.3 break Only Exits the Innermost Loop
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (j == 1) break; // only breaks the inner for
printf("%d%d ", i, j);
}
}
// output: 00 10 20
// break only affects the inner loop, the outer continues
// want to break out of multiple levels? use goto or a flag variable
int found = 0;
for (int i = 0; i < 3 && !found; i++) {
for (int j = 0; j < 3; j++) {
if (i == 1 && j == 1) { found = 1; break; }
}
} goto and Labels
17.1 goto Basic Usage
1 2 3 4 5 6 7 8 9 10 11 12 13
#include <stdio.h>
int main(void) {
int i = 0;
loop: // label
printf("%d ", i);
i++;
if (i < 5) goto loop;
printf("\n");
return 0;
}
// output: 0 1 2 3 4 17.2 A Reasonable Use: Error Handling
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
int process_file(const char *path) {
FILE *f = fopen(path, "r");
if (!f) goto err_open;
char *buf = malloc(1024);
if (!buf) goto err_malloc;
if (fread(buf, 1, 1024, f) < 0) goto err_read;
// ... process data ...
free(buf);
fclose(f);
return 0; // success
err_read:
free(buf);
err_malloc:
fclose(f);
err_open:
return -1; // failure
} 17.3 A Reasonable Use: Breaking Out of Nested Loops
1 2 3 4 5 6 7 8 9 10 11
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 100; j++) {
for (int k = 0; k < 100; k++) {
if (found_target(i, j, k)) {
goto done; // break out of three levels at once
}
}
}
}
done:
printf("Target found!\n"); Nested Loops: The Multiplication Table
18.1 The 9x9 Multiplication Table
1 2 3 4 5 6 7 8 9 10
#include <stdio.h>
int main(void) {
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= i; j++) {
printf("%dx%d=%-2d ", j, i, i * j);
}
printf("\n");
}
return 0;
} 18.2 Printing Triangle Patterns
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// right triangle
for (int i = 1; i <= 5; i++) {
for (int j = 1; j <= i; j++) {
printf("* ");
}
printf("\n");
}
// *
// * *
// * * *
// * * * *
// * * * * *
// pyramid
int n = 5;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < n - i; j++) printf(" ");
for (int j = 0; j < 2*i - 1; j++) printf("*");
printf("\n");
} 18.3 Checking for Primes
1 2 3 4 5 6 7 8 9 10 11 12
// print the prime numbers within 100
for (int n = 2; n <= 100; n++) {
int is_prime = 1;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) {
is_prime = 0;
break;
}
}
if (is_prime) printf("%d ", n);
}
// 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 Phase Project: Guessing Game
19.1 Random Number Basics
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
#include <stdio.h>
#include <stdlib.h> // rand(), srand()
#include <time.h> // time()
int main(void) {
srand(time(NULL)); // use time as the seed
int target = rand() % 100 + 1; // random number 1~100
int guess, tries = 0;
do {
printf("Guess a number from 1-100: ");
scanf("%d", &guess);
tries++;
if (guess > target)
printf("Too high!\n");
else if (guess < target)
printf("Too low!\n");
else
printf("Congratulations! Guessed it in %d tries!\n", tries);
} while (guess != target);
return 0;
} 19.2 srand and rand
1 2 3 4 5 6 7 8 9 10
// rand() returns 0 ~ RAND_MAX (usually 2147483647)
// no seed -> same result every run
srand(42); // fixed seed -> reproducible
srand(time(NULL)); // time seed -> different each run
// generate in the [1, 100] range:
int n = rand() % 100 + 1;
// generate in the [min, max] range:
int n = rand() % (max - min + 1) + min; 19.3 Challenge Tasks
1. Limit guesses to at most 10; show “Game Over” when exceeded
2. Record the historical best score (via file or variable)
3. Add difficulty selection (Easy 1-50, Normal 1-100, Hard 1-1000)
Phase Summary and Review
20.1 The Complete Control Flow Family
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// ===== conditionals =====
if (condition) { ... }
else if (condition) { ... }
else { ... }
switch(val) { case N: ...; break; default: ...; }
result = condition ? valueA : valueB;
// ===== loops =====
while (condition) { ... } // check first, then execute
for (init; cond; update) { ... } // counting loop
do { ... } while (condition); // execute first, then check
// ===== flow control =====
break; // break out of loop/switch
continue; // skip this iteration
goto label; // jump (use with care)
return val; // exit the function 20.2 A Selection Guide
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// when to use what?
// when you know the number of iterations -> for
for (int i = 0; i < n; i++) { ... }
// unknown count, possibly 0 -> while
while (data readable) { process(data); }
// runs at least once -> do-while
do { get input } while (input invalid);
// many equality comparisons -> switch
switch(op) { case '+': ...; break; }
// range checks -> if-else
if (score >= 90) { ... } 20.3 Common Bug Checklist
2. switch missing break causing fall-through
3. while loop forgetting to update the condition variable → infinite loop
4. Off-by-one loop bounds (< or <=?)
5. do-while missing the trailing semicolon
Function Basics
21.1 Why We Need Functions
Code without functions is like a pot of porridge — all the logic mixed together. Functions let you cut the code into small pieces, each doing one thing with inputs and outputs. This is the foundation of “modular programming.”
21.2 Defining and Calling
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#include <stdio.h>
// function definition: return type name(parameter list) { ... }
int add(int a, int b) {
int sum = a + b;
return sum; // return the result
}
int main(void) {
int result = add(3, 5); // call the function
printf("3 + 5 = %d\n", result);
printf("10 + 20 = %d\n", add(10, 20));
return 0;
} 21.3 void Functions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// no return value: void
void greet(const char *name) {
printf("Hello, %s!\n", name);
// no return is needed, you may also write return;
}
// no parameters: void
int get_answer(void) {
return 42;
}
int main(void) {
greet("hacker");
printf("answer = %d\n", get_answer());
return 0;
} Function Arguments and Pass-by-Value
22.1 Pass-by-Value
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#include <stdio.h>
void try_swap(int a, int b) {
int tmp = a;
a = b;
b = tmp;
printf("inside function: a=%d, b=%d\n", a, b);
}
int main(void) {
int x = 10, y = 20;
try_swap(x, y);
printf("outside function: x=%d, y=%d\n", x, y);
return 0;
} x and y were not swapped! Because C is pass-by-value — the function receives copies of x and y, and modifying the copies doesn't affect the originals.
22.2 Using Pointers for a Real Swap
1 2 3 4 5 6 7 8 9 10 11 12
void swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
int main(void) {
int x = 10, y = 20;
swap(&x, &y); // pass addresses
printf("x=%d, y=%d\n", x, y); // x=20, y=10
return 0;
} 22.3 Array Arguments Degrade to Pointers
1 2 3 4 5 6 7
// these two forms are fully equivalent
void print_arr(int arr[], int n) { ... }
void print_arr(int *arr, int n) { ... }
// an array passed to a function decays into a pointer
// sizeof(arr) inside the function is the pointer size (8), not the array size!
// so you must additionally pass the length parameter n Return Values and Returning Multiple Values
23.1 The return Statement
1 2 3 4 5 6 7 8 9 10 11 12 13
// return immediately ends the function and returns a value
int max(int a, int b) {
if (a > b) return a;
return b;
// the code below never executes
printf("this line will never print\n");
}
// a void function uses return to exit early
void print_if_positive(int n) {
if (n <= 0) return; // early return
printf("%d\n", n);
} 23.2 “Returning” Multiple Values via Pointers
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#include <stdio.h>
// return quotient and remainder at the same time
void divmod(int a, int b, int *quotient, int *remainder) {
*quotient = a / b;
*remainder = a % b;
}
int main(void) {
int q, r;
divmod(17, 5, &q, &r);
printf("17 / 5 = %d remainder %d\n", q, r);
// 17 / 5 = 3 remainder 2
return 0;
} 23.3 Returning Multiple Values via a Struct
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
typedef struct {
int quotient;
int remainder;
} DivResult;
DivResult divmod(int a, int b) {
DivResult r;
r.quotient = a / b;
r.remainder = a % b;
return r;
}
// usage:
DivResult r = divmod(17, 5);
printf("%d remainder %d\n", r.quotient, r.remainder); Function Declarations and Prototypes
24.1 The Compiler Needs the Function Signature
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
#include <stdio.h>
// option 1: define before the call
int add(int a, int b) {
return a + b;
}
int main(void) {
printf("%d\n", add(3, 5)); // OK
return 0;
}
// option 2: declare the prototype first, define it later
int add(int a, int b); // function prototype (declaration)
int main(void) {
printf("%d\n", add(3, 5));
return 0;
}
int add(int a, int b) { // definition
return a + b;
} 24.2 Declarations in Header Files
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
// math_utils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
int add(int a, int b);
int max(int a, int b);
double average(int arr[], int n);
#endif
// math_utils.c
#include "math_utils.h"
int add(int a, int b) { return a + b; }
int max(int a, int b) { return a > b ? a : b; }
double average(int arr[], int n) { ... }
// main.c
#include "math_utils.h" // include the declarations
int main(void) {
printf("%d\n", add(3, 5));
return 0;
} 24.3 The Danger of Implicit Declarations
1 2 3 4 5 6 7 8 9 10 11
// ❌ dangerous: calling an undeclared function
int main(void) {
printf("%d\n", my_func(3)); // the compiler assumes it returns int
return 0;
}
// if my_func actually returns double or has different parameter types
// -> undefined behavior!
// ✅ correct: declare it first or include the header
// implicit declarations were removed in C99, this now errors
// always compile with gcc -Wall, it warns about undeclared functions Variable Scope
25.1 Levels of Scope
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
#include <stdio.h>
int global_var = 100; // global variable: visible to the whole file
void func(int param) { // param: function scope
int local = 10; // local variable: visible within the function
printf("global=%d, param=%d, local=%d\n",
global_var, param, local);
}
int main(void) {
int local = 20; // main's local variable
func(5);
// printf("%d", param); // error! param not visible
// printf("%d", local); // this is main's local=20
return 0;
} 25.2 Block Scope
1 2 3 4 5 6 7 8 9 10 11 12 13 14
int main(void) {
int x = 1;
{
int x = 2; // inner x shadows outer x
printf("%d\n", x); // 2
{
int x = 3;
printf("%d\n", x); // 3
}
printf("%d\n", x); // 2
}
printf("%d\n", x); // 1
return 0;
} 25.3 Pros and Cons of Global Variables
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// global variable: defined outside all functions
int counter = 0; // global
void increment(void) {
counter++; // access the global variable directly
}
// ⚠ dangers of global variables:
// 1. any function can modify them, hard to track
// 2. not thread-safe
// 3. breaks modularity
// 4. naming conflicts
// ✅ better: pass by parameter
void increment(int *counter) {
(*counter)++;
} Recursion
26.1 The Basic Structure of Recursion
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#include <stdio.h>
// factorial: n! = n * (n-1)!
// base case: 0! = 1
long factorial(int n) {
if (n <= 1) return 1; // base case
return n * factorial(n-1); // recursive call
}
int main(void) {
for (int i = 0; i <= 10; i++)
printf("%d! = %ld\n", i, factorial(i));
return 0;
} 26.2 The Fibonacci Sequence
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// fibonacci: F(n) = F(n-1) + F(n-2)
// F(0)=0, F(1)=1
int fib(int n) {
if (n <= 1) return n;
return fib(n-1) + fib(n-2);
}
// call tree of fib(5):
// fib(5)
// / \
// fib(4) fib(3)
// / \ / \
// fib(3) fib(2) fib(2) fib(1)
// ...
// exponential complexity! O(2^n)
// fib(40) is already very slow 26.3 The Three Essentials of Recursion
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// 1. base case: when to stop recursing
// 2. recursive step: move toward the base case
// 3. combine results: how to use the sub-problem solutions
// ❌ forgot the base case -> infinite recursion -> stack overflow
void bad(int n) {
bad(n); // this never stops!
}
// ❌ not moving toward the base case -> also stack overflow
void bad2(int n) {
if (n == 0) return;
bad2(n); // n never changes, so never equals 0
} Storage Classes: static and extern
27.1 static Local Variables
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#include <stdio.h>
int counter(void) {
static int count = 0; // initialized only once!
count++;
return count;
}
int main(void) {
printf("%d\n", counter()); // 1
printf("%d\n", counter()); // 2
printf("%d\n", counter()); // 3
return 0;
} A static local variable is initialized only once and is not destroyed after the function ends, but its scope remains inside the function. It's like a “global variable inside the function.”
27.2 static Global Variables/Functions
1 2 3 4 5 6 7 8 9 10
// file1.c
static int internal_data = 42; // visible only in file1.c!
static void helper(void) { ... } // visible only in file1.c!
// file2.c
extern int internal_data; // ❌ link error! static limits visibility
// internal_data is not accessible in file2.c
// static global variables/functions = private to the file
// similar to private in other languages 27.3 extern
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// file1.c
int shared_var = 100; // global variable definition
// file2.c
extern int shared_var; // declaration: references the variable in file1.c
// no memory is allocated, just tells the compiler "it is defined elsewhere"
void use_it(void) {
printf("%d\n", shared_var); // 100
}
// better: declare it in a header
// shared.h: extern int shared_var;
// file1.c: #include "shared.h"; int shared_var = 100;
// file2.c: #include "shared.h"; Multi-file Programming
28.1 Project Structure
1 2 3 4 5 6 7 8 9 10
myproject/
├── main.c // main program
├── math_utils.h // header (declarations)
├── math_utils.c // implementation
├── string_utils.h
├── string_utils.c
└── Makefile
// compile:
$ gcc -Wall main.c math_utils.c string_utils.c -o myapp 28.2 Header Guards
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// math_utils.h
#ifndef MATH_UTILS_H // if not defined
#define MATH_UTILS_H // then define it
int add(int a, int b);
int max(int a, int b);
#endif // MATH_UTILS_H
// prevent duplicate inclusion:
// if two files both #include "math_utils.h"
// without the guard -> duplicate definition error
// with the guard -> the second #ifndef is false, it is skipped
// C23 adds #pragma once (more concise)
#pragma once
int add(int a, int b); 28.3 What Goes in a Header File
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// ✅ what header files should contain:
// - function declarations (prototypes)
// - macro definitions (#define)
// - type definitions (typedef, struct)
// - extern variable declarations
// - inline functions
// ❌ what header files should not contain:
// - function definitions (implementations) <- put in .c files
// - variable definitions <- put in .c files
// - static variables/functions <- meaningless
// exception: inline functions may go in headers
static inline int square(int x) {
return x * x;
} Inline Functions and Macros
29.1 Function-like Macros
1 2 3 4 5 6 7 8 9 10 11 12 13
#include <stdio.h>
// function-like macro: textual substitution
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define ABS(x) ((x) < 0 ? -(x) : (x))
int main(void) {
printf("%d\n", SQUARE(5)); // 25
printf("%d\n", MAX(3, 7)); // 7
printf("%d\n", ABS(-42)); // 42
return 0;
} 29.2 Macro Pitfalls
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// ❌ forgot the parentheses
#define BAD_SQUARE(x) x * x
int result = BAD_SQUARE(3 + 2);
// expands to: 3 + 2 * 3 + 2 = 3 + 6 + 2 = 11 (not 25!)
// ✅ correct: wrap each parameter and the whole thing in parentheses
#define GOOD_SQUARE(x) ((x) * (x))
// expands to: ((3 + 2) * (3 + 2)) = 25
// ❌ side-effect problem
#define BAD_MAX(a, b) ((a) > (b) ? (a) : (b))
int x = 5, y = 3;
int z = BAD_MAX(x++, y);
// x may be incremented twice! 29.3 inline Functions
1 2 3 4 5 6 7 8 9 10 11 12
// inline: suggests the compiler inline-expand it
// safer than a macro (has type checking), faster than a normal function (no call overhead)
static inline int square(int x) {
return x * x;
}
int main(void) {
int a = 5;
printf("%d\n", square(a + 1)); // 36, safe!
// square(a++) also has no side-effect problem
return 0;
} Phase Project: Recursive Maze Solving
30.1 The Recursive Backtracking Idea
A maze can be represented with a 2D array: 0 = path, 1 = wall. From the start, recursively try the four directions; if you find the exit return success, if you hit a dead end backtrack.
30.2 Solving the Maze
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
#include <stdio.h>
#define ROWS 5
#define COLS 5
// 0=path, 1=wall
int maze[ROWS][COLS] = {
{0,1,0,0,0},
{0,1,0,1,0},
{0,0,0,1,0},
{1,1,1,1,0},
{0,0,0,0,0}
};
int visited[ROWS][COLS] = {0};
// returns 1 if an exit is found
int solve(int r, int c) {
// out of bounds / wall / already visited
if (r<0||r>=ROWS||c<0||c>=COLS) return 0;
if (maze[r][c]==1 || visited[r][c]) return 0;
// reached the destination
if (r==ROWS-1 && c==COLS-1) {
visited[r][c] = 1;
return 1;
}
visited[r][c] = 1;
// try: down -> right -> up -> left
if (solve(r+1,c)) return 1;
if (solve(r,c+1)) return 1;
if (solve(r-1,c)) return 1;
if (solve(r,c-1)) return 1;
visited[r][c] = 0; // backtrack
return 0;
}
int main(void) {
if (solve(0, 0)) {
printf("Path found!\n");
for (int r=0;r<ROWS;r++){
for(int c=0;c<COLS;c++)
printf(visited[r][c]?"* ":"# ");
printf("\n");
}
} else printf("No solution!\n");
return 0;
} 30.3 The Recursive Backtracking Template
1 2 3 4 5 6 7 8 9 10 11 12 13 14
int backtrack(state) {
if (goal reached) {
record the solution;
return 1; // success
}
if (out of bounds || invalid) return 0; // failure
mark the current state as visited;
for (each choice) {
if (backtrack(new state)) return 1; // recurse
}
undo the mark; // backtrack
return 0;
} One-dimensional Arrays
31.1 Declaration and Initialization
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#include <stdio.h>
int main(void) {
// declaration
int arr[5]; // 5 ints, uninitialized (garbage values)
// declare and initialize
int nums[] = {10, 20, 30, 40, 50};
int scores[5] = {90, 85, 78}; // the last two become 0 automatically
int zeros[100] = {0}; // all initialized to 0
// C99: designated initializers
int a[5] = {[2] = 30, [4] = 50}; // {0,0,30,0,50}
// access
printf("%d\n", nums[0]); // 10 (index starts at 0)
printf("%d\n", nums[4]); // 50
nums[2] = 99; // modify
return 0;
} 31.2 Traversing and Summing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
int nums[] = {10, 20, 30, 40, 50};
int n = sizeof(nums) / sizeof(nums[0]); // compute the element count!
// sum
int sum = 0;
for (int i = 0; i < n; i++) {
sum += nums[i];
}
printf("sum = %d, avg = %.1f\n", sum, (double)sum / n);
// sum = 150, avg = 30.0
// find the maximum
int max = nums[0];
for (int i = 1; i < n; i++) {
if (nums[i] > max) max = nums[i];
}
printf("max = %d\n", max); // 50 31.3 Array Layout in Memory
1 2 3 4 5 6 7 8 9 10
int arr[4] = {10, 20, 30, 40};
// memory layout (contiguous):
// address: 0x100 0x104 0x108 0x10c
// content: [ 10 | 20 | 30 | 40 ]
// index: [0] [1] [2] [3]
// array name = address of the first element
printf("%p\n", arr); // 0x100
printf("%p\n", &arr[0]); // 0x100 (same!) Array Traversal and Operations
32.1 Reversing an Array
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
void reverse(int arr[], int n) {
int left = 0, right = n - 1;
while (left < right) {
// swap
int tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
left++;
right--;
}
}
int main(void) {
int a[] = {1, 2, 3, 4, 5};
reverse(a, 5);
// a = {5, 4, 3, 2, 1}
for (int i = 0; i < 5; i++) printf("%d ", a[i]);
return 0;
} 32.2 Array Rotation
1 2 3 4 5 6 7 8 9 10 11
// rotate right by k: [1,2,3,4,5] k=2 -> [4,5,1,2,3]
void rotate(int arr[], int n, int k) {
k = k % n; // handle the k > n case
// three-step reversal:
// 1. reverse the whole array: [5,4,3,2,1]
reverse(arr, n);
// 2. reverse the first k: [4,5,3,2,1]
reverse(arr, k);
// 3. reverse the last n-k: [4,5,1,2,3]
reverse(arr + k, n - k);
} 32.3 Deduplication (Sorted Array)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// returns the deduplicated length
int dedup(int arr[], int n) {
if (n == 0) return 0;
int write = 1; // write position
for (int read = 1; read < n; read++) {
if (arr[read] != arr[read - 1]) {
arr[write++] = arr[read];
}
}
return write; // new length
}
int a[] = {1,1,2,3,3,3,4,5,5};
int len = dedup(a, 9); // len=5, a={1,2,3,4,5} Two-dimensional Arrays
33.1 Declaration and Initialization
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
int matrix[3][4]; // 3 rows, 4 columns
// initialization
int grid[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
// partial initialization (unspecified become 0)
int g[3][3] = {
{1},
{0, 2},
{0, 0, 3}
};
// g = {{1,0,0}, {0,2,0}, {0,0,3}}
// all zeros
int zero[5][5] = {0}; 33.2 Traversing and Operating
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
int grid[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
int rows = 3, cols = 3;
// print
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", grid[i][j]);
}
printf("\n");
}
// sum of the main diagonal
int diag = 0;
for (int i = 0; i < rows; i++) {
diag += grid[i][i];
}
printf("diagonal = %d\n", diag); // 1+5+9=15 33.3 Matrix Transpose
1 2 3 4 5 6 7 8 9 10 11
// transpose: swap rows and columns
void transpose(int m[][3], int n) {
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
int tmp = m[i][j];
m[i][j] = m[j][i];
m[j][i] = tmp;
}
}
}
// note: j starts from i+1, otherwise it would swap twice and restore the original! Character Arrays and Strings
34.1 String = Character Array + \0
1 2 3 4 5 6 7 8 9 10 11 12
#include <stdio.h>
int main(void) {
// C has no string type! strings are char arrays
char s1[] = "hello"; // adds \0 automatically, length 6
char s2[] = {'h','e','l','l','o','\0'}; // add \0 manually
char s3[10] = "hi"; // remaining positions are filled with \0
printf("%s\n", s1); // hello
printf("%zu\n", sizeof(s1)); // 6 (includes \0)
printf("%zu\n", strlen(s1)); // 5 (excludes \0)
return 0;
} 34.2 Strings and Pointers
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
char s[] = "hello";
char *p = s; // p points to s[0]
// traverse with a pointer
while (*p != '\0') {
printf("%c", *p);
p++;
}
// hello
// string literals are stored in a read-only region
char *lit = "world"; // OK, but cannot be modified
// lit[0] = 'W'; // ❌ segmentation fault!
// to modify, use an array
char arr[] = "world";
arr[0] = 'W'; // OK
// the array is on the stack, with its own copy 34.3 String Input
1 2 3 4 5 6 7 8 9 10 11 12 13
char name[20];
// method 1: scanf (unsafe, does not check the length)
scanf("%s", name); // no &!
// scanf stops at whitespace, only reads the first word
// method 2: fgets (safe, recommended)
fgets(name, sizeof(name), stdin);
// reads one line (including spaces), at most sizeof-1 characters
// note: fgets includes the newline \n
// method 3: scanf with a width limit
scanf("%19s", name); // reads at most 19 characters + \0 String Functions
35.1 Core String Functions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
#include <stdio.h>
#include <string.h>
int main(void) {
char src[] = "hello";
char dst[20];
// strlen: length (excluding \0)
printf("%zu\n", strlen(src)); // 5
// strcpy: copy
strcpy(dst, src);
printf("%s\n", dst); // hello
// strcat: concatenate
strcat(dst, " world");
printf("%s\n", dst); // hello world
// strcmp: compare
printf("%d\n", strcmp("abc", "abc")); // 0 (equal)
printf("%d\n", strcmp("abc", "abd")); // negative
printf("%d\n", strcmp("abd", "abc")); // positive
return 0;
} 35.2 Safe Versions
1 2 3 4 5 6 7 8 9 10 11 12
// strcpy does not check the destination size -> buffer-overflow risk
// strncpy: limit the length
strncpy(dst, src, sizeof(dst) - 1);
dst[sizeof(dst) - 1] = '\0'; // strncpy does not guarantee \0!
// strncat: limit the concatenation length
strncat(dst, src, sizeof(dst) - strlen(dst) - 1);
// strncmp: compare only the first n characters
if (strncmp(str, "GET ", 4) == 0) {
// matches an HTTP GET request
} 35.3 Implementing strlen Yourself
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// understand the principle: traverse until \0
size_t my_strlen(const char *s) {
size_t len = 0;
while (s[len] != '\0') {
len++;
}
return len;
}
// pointer version
size_t my_strlen2(const char *s) {
const char *p = s;
while (*p) p++;
return p - s; // pointer difference = number of characters
} Bubble Sort
36.1 The Principle of Bubble Sort
Bubble sort: compare adjacent elements pairwise, letting the larger ones “bubble” to the end. Each pass bubbles the largest value to the end. n elements need n-1 passes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
#include <stdio.h>
void bubble_sort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
// after each pass, the largest is already at the end
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// swap
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main(void) {
int a[] = {64, 34, 25, 12, 22, 11, 90};
int n = sizeof(a)/sizeof(a[0]);
bubble_sort(a, n);
for (int i = 0; i < n; i++) printf("%d ", a[i]);
return 0;
} 36.2 Optimization: Early Termination
1 2 3 4 5 6 7 8 9 10 11 12 13 14
void bubble_sort_opt(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
int swapped = 0; // marks whether this pass swapped
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
swapped = 1;
}
}
if (!swapped) break; // no swap = already sorted
}
} 36.3 Complexity Analysis
1 2 3 4 5 6 7 8 9 10
// time complexity:
// worst (reversed): O(n²) -> n*(n-1)/2 comparisons
// best (sorted): O(n) -> optimized version needs only 1 pass
// average: O(n²)
// space complexity: O(1) in-place sort
// stability: ✅ stable (equal elements are not swapped)
// good for: small data sets or nearly sorted data
// not good for: large data sets (use quicksort or mergesort) Array Search Algorithms
37.1 Linear Search
1 2 3 4 5 6 7 8 9 10 11 12
// works on unsorted arrays, O(n)
int linear_search(int arr[], int n, int target) {
for (int i = 0; i < n; i++) {
if (arr[i] == target) {
return i; // found, return the index
}
}
return -1; // not found
}
int a[] = {5, 3, 8, 1, 9, 2};
int idx = linear_search(a, 6, 8); // idx=2 37.2 Binary Search
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// requires a sorted array! O(log n)
int binary_search(int arr[], int n, int target) {
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // prevents overflow!
if (arr[mid] == target)
return mid; // found
else if (arr[mid] < target)
left = mid + 1; // in the right half
else
right = mid - 1; // in the left half
}
return -1; // not found
}
int a[] = {1, 3, 5, 7, 9, 11, 13};
int idx = binary_search(a, 7, 7); // idx=3 37.3 Speed Comparison
1 2 3 4 5 6 7 8
// searching among 1 million data items:
// linear search: at most 1 million comparisons
// binary search: at most 20 comparisons! (log₂1000000 ≈ 20)
// prerequisite of binary search: the array is sorted
// sorting cost: O(n log n)
// if you search only once -> linear search is faster
// if you search many times -> sort first then binary search is more worthwhile Command-line Arguments
38.1 Parameters of main
1 2 3 4 5 6 7 8 9 10 11
#include <stdio.h>
// argc: number of arguments (including the program name)
// argv: array of argument strings
int main(int argc, char *argv[]) {
printf("Argument count: %d\n", argc);
for (int i = 0; i < argc; i++) {
printf("argv[%d] = %s\n", i, argv[i]);
}
return 0;
} 38.2 Practical Argument Parsing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Usage: %s <filename> [options]\n", argv[0]);
return 1;
}
const char *filename = argv[1];
int verbose = 0;
for (int i = 2; i < argc; i++) {
if (strcmp(argv[i], "-v") == 0) {
verbose = 1;
} else if (strcmp(argv[i], "--help") == 0) {
printf("Help info...\n");
return 0;
}
}
if (verbose) printf("Processing file: %s\n", filename);
// ... process the file ...
return 0;
} 38.3 Converting Strings to Numbers
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
#include <stdlib.h>
// atoi: string to int
int n = atoi("42"); // 42
int bad = atoi("hello"); // 0 (returns 0 on conversion failure)
// strtol: safer, can detect errors
char *endptr;
long val = strtol("123abc", &endptr, 10);
// val=123, endptr points to "abc"
if (*endptr != '\0') {
printf("partial conversion: %ld\n", val);
}
// atof: string to double
double d = atof("3.14"); // 3.14 Phase Project: Grades Management
39.1 Requirements
Implement a student grades management system: store several students' grades and support entry, query, sorting, and computing the average and the highest score.
39.2 Implementation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
#include <stdio.h>
#define MAX_STUDENTS 100
typedef struct {
char name[20];
int score;
} Student;
void input_students(Student s[], int *n) {
printf("Enter the number of students (max %d): ", MAX_STUDENTS);
scanf("%d", n);
for (int i = 0; i < *n; i++) {
printf("Name score: ");
scanf("%s %d", s[i].name, &s[i].score);
}
}
void sort_by_score(Student s[], int n) {
for (int i = 0; i < n-1; i++)
for (int j = 0; j < n-1-i; j++)
if (s[j].score < s[j+1].score) {
Student t = s[j]; s[j] = s[j+1]; s[j+1] = t;
}
}
void print_stats(Student s[], int n) {
int sum = 0, max = s[0].score;
for (int i = 0; i < n; i++) {
sum += s[i].score;
if (s[i].score > max) max = s[i].score;
}
printf("\n=== Score ranking ===\n");
for (int i = 0; i < n; i++)
printf("%d. %s: %d\n", i+1, s[i].name, s[i].score);
printf("\nAverage: %.1f\n", (double)sum/n);
printf("Highest: %d\n", max);
}
int main(void) {
Student students[MAX_STUDENTS];
int n;
input_students(students, &n);
sort_by_score(students, n);
print_stats(students, n);
return 0;
} 39.3 Challenge Tasks
1. Add a “find by name” feature
2. Add an “add/delete student” feature
3. Save data to a file and load it on the next startup
Phase Summary and Review
40.1 Core Knowledge Points
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// ===== arrays =====
int arr[n]; // declaration
sizeof(arr)/sizeof(arr[0]); // get length (only valid at the array name)
arr[i]; // access (0~n-1)
// array name = address of the first element, decays to pointer when passed
// no bounds checking!
// ===== 2D arrays =====
int m[rows][cols]; // row-major storage
void f(int arr[][COLS], int rows); // must specify the column count
// ===== strings =====
char s[] = "hello"; // adds \0 automatically, sizeof=6, strlen=5
char *p = "hello"; // read-only! cannot be modified
// safe input: fgets(s, sizeof(s), stdin)
// safe copy: strncpy, safe compare: strncmp
// ===== algorithms =====
// bubble sort: O(n²), stable
// linear search: O(n), no sorting needed
// binary search: O(log n), requires sorting 40.2 Common Mistakes Checklist
2. sizeof(arr) inside a function is the pointer size (8), not the array size
3. char *s = "hello"; s[0]='H'; → segmentation fault
4. strcmp returns 0 for equal (counterintuitive)
5. fgets reads in the newline \n
6. Binary search mid=(left+right)/2 can overflow
Pointer Basics
41.1 What Is a Pointer
A pointer is a variable that stores a memory address. Ordinary variables store values; pointers store addresses. Through a pointer you can directly read and write the data at that address — this is C's most powerful yet most dangerous feature.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#include <stdio.h>
int main(void) {
int x = 42;
int *p = &x; // p stores the address of x
printf("value of x: %d\n", x); // 42
printf("address of x: %p\n", &x); // 0x7ffd...
printf("value of p: %p\n", p); // same as &x
printf("pointed-to: %d\n", *p); // 42 (dereference)
*p = 100; // modify x through the pointer!
printf("x now: %d\n", x); // 100
return 0;
} 41.2 Two Core Operators
1 2 3 4 5 6 7 8 9 10 11 12 13
// & address-of: get the memory address of a variable
int x = 10;
int *p = &x; // &x = the address of x
// * dereference: get the value a pointer points to
int y = *p; // y = 10
*p = 20; // x = 20 (modify through the pointer)
// remember: & = "the address of ..."
// * = "the value pointed to by ..."
// & and * are inverse operations
printf("%d\n", *(&x)); // x itself = 20 41.3 Pointer Types
1 2 3 4 5 6 7 8 9 10 11 12 13 14
int *pi; // pointer to int
double *pd; // pointer to double
char *pc; // pointer to char
int **ppi; // pointer to an int pointer (double pointer)
// the pointer type determines:
// 1. how many bytes are read when dereferencing (int=4, double=8, char=1)
// 2. how many bytes a pointer moves with +1
int x = 0x12345678;
int *pi = &x;
char *pc = (char *)&x;
printf("%d\n", *pi); // reads 4 bytes
printf("%d\n", *pc); // reads 1 byte (little-endian: 0x78=120) Pointers and Arrays
42.1 Array Name vs Pointer
1 2 3 4 5 6 7 8 9 10
int arr[] = {10, 20, 30, 40, 50};
int *p = arr; // array name = address of the first element
// these are all equivalent!
printf("%d\n", arr[2]); // 30
printf("%d\n", p[2]); // 30
printf("%d\n", *(arr+2)); // 30
printf("%d\n", *(p+2)); // 30
// arr[i] is just syntactic sugar for *(arr+i)! 42.2 Traversing an Array with Pointers
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
int arr[] = {10, 20, 30, 40, 50};
int n = 5;
// traverse with a pointer
int *p = arr;
for (int i = 0; i < n; i++) {
printf("%d ", *p);
p++; // move to the next element
}
// 10 20 30 40 50
// pointer subtraction to get the element count
int *start = arr;
int *end = arr + n;
printf("element count: %td\n", end - start); // 5 42.3 An Array Name Isn't Fully a Pointer
1 2 3 4 5 6 7 8 9 10 11 12 13
int arr[5] = {1,2,3,4,5};
int *p = arr;
// sizeof differs!
printf("%zu\n", sizeof(arr)); // 20 (5×4 bytes)
printf("%zu\n", sizeof(p)); // 8 (pointer size)
// arr cannot be modified! arr++ is illegal!
// arr = ... is also not allowed!
// p++ is fine, p = ... is also fine
// arr is a "constant pointer": int * const
// p is an "ordinary pointer": int * Pointer Arithmetic
43.1 Adding an Integer to a Pointer
1 2 3 4 5 6 7 8 9 10 11 12 13
int arr[] = {10, 20, 30, 40, 50};
int *p = arr; // points to arr[0]
p + 1; // points to arr[1] (address +4 bytes)
p + 3; // points to arr[3] (address +12 bytes)
p - 2; // points to arr[-2]? (unsafe!)
// pointer +1 actually moves sizeof(type) bytes
// int pointer +1 -> address +4
double *pd = ...;
pd + 1; // address +8 (sizeof(double))
char *pc = ...;
pc + 1; // address +1 (sizeof(char)) 43.2 Pointer Subtraction
1 2 3 4 5 6 7 8 9 10 11 12 13
int arr[] = {10, 20, 30, 40, 50};
int *p1 = &arr[1]; // the 2nd element
int *p2 = &arr[4]; // the 5th element
// pointer subtraction = element count (not byte count!)
printf("%td\n", p2 - p1); // 3
// pointer comparison
printf("%d\n", p1 < p2); // 1 (p1 is before p2)
printf("%d\n", p1 == p2); // 0
// practical: get the array length
int len = (arr + 5) - arr; // 5 43.3 Pointer Traversal Patterns
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
int arr[] = {10, 20, 30, 40, 50};
// pattern 1: pointer + index
for (int *p = arr; p < arr + 5; p++) {
printf("%d ", *p);
}
// pattern 2: double pointer
int *begin = arr;
int *end = arr + 5;
while (begin < end) {
printf("%d ", *begin);
begin++;
}
// both print: 10 20 30 40 50 Pointers and Strings
44.1 String Traversal
1 2 3 4 5 6 7 8 9 10 11 12 13 14
char *s = "hello";
// traverse with a pointer
char *p = s;
while (*p != '\0') {
printf("%c ", *p);
p++;
}
// h e l l o
// compute the string length
int len = 0;
for (char *p = s; *p; p++) len++;
// len = 5 44.2 String Copying
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// implement strcpy yourself
void my_strcpy(char *dst, const char *src) {
while (*src != '\0') {
*dst = *src;
dst++;
src++;
}
*dst = '\0'; // don't forget the terminating null!
}
// classic C programmer style (compact version)
void my_strcpy2(char *dst, const char *src) {
while ((*dst++ = *src++) != '\0')
; // empty loop body
}
// execution order of *dst++ = *src++:
// 1. assign *src to *dst
// 2. src++, dst++
// 3. check whether the assigned value is \0
// 4. if not \0, continue 44.3 const Modifying Pointers
1 2 3 4 5 6 7 8 9 10
// const means different things in different positions!
const char *p1; // pointer to const char: cannot change the value via p1
char * const p2; // const pointer: cannot change what p2 points to
const char * const p3; // neither can be changed
// mnemonics: const is left of * -> cannot change the value; const is right of * -> cannot change the pointer
// use const char* for function params to mean "read-only, no modification"
size_t strlen(const char *s); // won't modify the content s points to Pointers as Function Arguments
45.1 Modifying External Variables
1 2 3 4 5 6 7 8 9 10 11 12 13
#include <stdio.h>
// modify an external variable through a pointer
void add_ten(int *n) {
*n += 10;
}
int main(void) {
int x = 5;
add_ten(&x); // pass the address
printf("%d\n", x); // 15
return 0;
} 45.2 Multiple Return Values
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// return both the max and min at the same time
void minmax(int arr[], int n, int *min, int *max) {
*min = *max = arr[0];
for (int i = 1; i < n; i++) {
if (arr[i] < *min) *min = arr[i];
if (arr[i] > *max) *max = arr[i];
}
}
int main(void) {
int a[] = {3, 7, 1, 9, 4};
int lo, hi;
minmax(a, 5, &lo, &hi);
printf("min=%d, max=%d\n", lo, hi); // min=1, max=9
return 0;
} 45.3 Avoiding Copying Large Arrays
1 2 3 4 5 6 7 8 9 10 11 12 13
// ❌ bad: passing by value copies the whole struct
void print_student(Student s) {
printf("%s: %d\n", s.name, s.score);
}
// ✅ good: pass a pointer, only copies the 8-byte address
void print_student(const Student *s) {
printf("%s: %d\n", s->name, s->score);
// use -> instead of . to access members
}
// const means the function won't modify the struct
// passing a pointer is efficient but does not change the original data Function Pointers
46.1 Function Pointer Syntax
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
#include <stdio.h>
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int main(void) {
// declare a function pointer: return type (*name)(params)
int (*op)(int, int);
op = add; // point to the add function
printf("%d\n", op(3, 5)); // 8
// op(3,5) is equivalent to (*op)(3,5)
op = sub;
printf("%d\n", op(10, 3)); // 7
op = mul;
printf("%d\n", op(4, 6)); // 24
return 0;
} 46.2 Callback Functions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// implement a "strategy pattern" with function pointers
void process(int arr[], int n, int (*transform)(int)) {
for (int i = 0; i < n; i++) {
arr[i] = transform(arr[i]);
}
}
int square(int x) { return x * x; }
int negate(int x) { return -x; }
int dbl(int x) { return x * 2; }
int main(void) {
int a[] = {1, 2, 3, 4, 5};
process(a, 5, square); // square: 1,4,9,16,25
process(a, 5, negate); // negate: -1,-4,-9,-16,-25
process(a, 5, dbl); // double: -2,-8,-18,-32,-50
return 0;
} 46.3 The qsort Library Function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#include <stdlib.h>
// comparison function: returns negative/0/positive
int cmp_int(const void *a, const void *b) {
return *(int*)a - *(int*)b; // ascending
// descending: *(int*)b - *(int*)a
}
int main(void) {
int arr[] = {5, 2, 8, 1, 9, 3};
int n = 6;
qsort(arr, n, sizeof(int), cmp_int);
// arr = {1, 2, 3, 5, 8, 9}
return 0;
} Dynamic Memory Allocation
47.1 malloc and free
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
#include <stdio.h>
#include <stdlib.h>
int main(void) {
// allocate space for 10 ints on the heap
int *arr = malloc(10 * sizeof(int));
if (arr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
// use it
for (int i = 0; i < 10; i++) {
arr[i] = i * i;
}
for (int i = 0; i < 10; i++) {
printf("%d ", arr[i]);
}
// 0 1 4 9 16 25 36 49 64 81
// free it! always remember free!
free(arr);
arr = NULL; // good habit: set NULL after free
return 0;
} 47.2 calloc and realloc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// calloc: allocate and zero it out
int *a = calloc(10, sizeof(int));
// 10 ints, all 0
// equivalent to: malloc + memset(0)
// realloc: resize
int *b = malloc(5 * sizeof(int));
// ... use 5 elements ...
// grow to 20
int *tmp = realloc(b, 20 * sizeof(int));
if (tmp) {
b = tmp; // realloc may return a new address!
// the first 5 elements are kept, the extra 15 are uninitialized
}
// shrinking is the same: realloc(b, 3 * sizeof(int))
// free
free(b); 47.3 Memory Leaks and Dangling Pointers
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// ❌ memory leak: malloc without free
void leak(void) {
int *p = malloc(100 * sizeof(int));
// function ends, p is lost, but the memory is not released!
// -> leaks 400 bytes
}
// ❌ dangling pointer: keep using after free
int *p = malloc(sizeof(int));
free(p);
*p = 42; // ❌ using freed memory!
// ❌ double free
int *p = malloc(sizeof(int));
free(p);
free(p); // ❌ double free! crash
// ✅ safe pattern
free(p);
p = NULL; // set NULL right after free
// free(NULL) is safe (does nothing) Multiple-Level Pointers
48.1 Double Pointers
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
#include <stdio.h>
int main(void) {
int x = 42;
int *p = &x; // single pointer: points to x
int **pp = &p; // double pointer: points to p
printf("x = %d\n", x); // 42
printf("*p = %d\n", *p); // 42
printf("**pp = %d\n", **pp); // 42
// modify the single pointer through the double pointer
int y = 99;
*pp = &y; // p now points to y!
printf("*p = %d\n", *p); // 99
return 0;
} 48.2 Dynamically Allocating a 2D Array
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// allocate a rows×cols 2D array
int **alloc_2d(int rows, int cols) {
int **m = malloc(rows * sizeof(int *));
for (int i = 0; i < rows; i++) {
m[i] = malloc(cols * sizeof(int));
}
return m;
}
// free it
void free_2d(int **m, int rows) {
for (int i = 0; i < rows; i++) {
free(m[i]);
}
free(m);
}
// usage
int **grid = alloc_2d(3, 4);
grid[1][2] = 42;
free_2d(grid, 3); 48.3 Modifying the Pointer Itself
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// to change what a pointer points to inside a function
// you need a double pointer!
void alloc_string(char **str) {
*str = malloc(100);
strcpy(*str, "hello");
}
int main(void) {
char *s = NULL;
alloc_string(&s); // pass the address of s
printf("%s\n", s); // hello
free(s);
return 0;
}
// ❌ wrong: passing a single pointer cannot modify the original pointer
void bad_alloc(char *str) {
str = malloc(100); // only modifies the copy!
} Phase Project: Dynamic Array
49.1 Goal
Implement a dynamic array: initial capacity 4, auto-expanding to 2x when elements exceed capacity. Support push_back and print operations.
49.2 Implementation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int *data; // data pointer
int size; // current element count
int capacity; // total capacity
} Vec;
void vec_init(Vec *v) {
v->capacity = 4;
v->size = 0;
v->data = malloc(v->capacity * sizeof(int));
}
void vec_push(Vec *v, int val) {
if (v->size >= v->capacity) {
v->capacity *= 2; // double the capacity
v->data = realloc(v->data, v->capacity * sizeof(int));
}
v->data[v->size++] = val;
}
void vec_print(Vec *v) {
for (int i = 0; i < v->size; i++)
printf("%d ", v->data[i]);
printf("\n(size=%d, cap=%d)\n", v->size, v->capacity);
}
void vec_free(Vec *v) {
free(v->data);
v->data = NULL;
v->size = v->capacity = 0;
}
int main(void) {
Vec v;
vec_init(&v);
for (int i = 1; i <= 10; i++)
vec_push(&v, i);
vec_print(&v);
// 1 2 3 4 5 6 7 8 9 10
// (size=10, cap=16)
vec_free(&v);
return 0;
} 49.3 Challenge Tasks
1. Add vec_pop (remove the last element) and vec_get (access by index)
2. Add shrinking: when size < capacity/4, shrink to capacity/2
3. Generalize: use void* and an element-size parameter to support any type
Phase Summary and Review
50.1 Core Pointer Concepts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
// ===== basics =====
int x = 10;
int *p = &x; // & address-of
int y = *p; // * dereference
*p = 20; // modify through the pointer
// ===== pointers and arrays =====
int arr[5];
int *p = arr; // array name = first address
arr[i] == *(arr+i) // equivalent!
p++ // moves sizeof(int) bytes
// ===== pointers and functions =====
void f(int *p) { *p = 10; } // modify an external variable
f(&x); // pass the address
// pass a pointer: modify externally / return multiple values / avoid copying
// ===== function pointers =====
int (*fp)(int,int) = &add;
fp(3,5); // call
// uses: callbacks, qsort, strategy pattern
// ===== dynamic memory =====
int *p = malloc(n * sizeof(int)); // allocate
// ... use it ...
free(p); p = NULL; // free
// calloc: allocate + zero it
// realloc: resize 50.2 Pointer Danger Checklist
2. Dangling pointer: used after free → set to NULL after free
3. Memory leak: malloc without free → pair them up
4. Out-of-bounds access: pointer beyond range → check boundaries carefully
5. Returning a local variable's address → use static or malloc
6. Double free → set to NULL after free
7. Modifying string literals → use char[] rather than char*
Struct Basics
51.1 Definition and Usage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
#include <stdio.h>
// define a struct type
struct Student {
char name[20];
int age;
double score;
};
int main(void) {
// declare and initialize
struct Student s1 = {"Alice", 20, 92.5};
// designated initializers (C99)
struct Student s2 = {.name="Bob", .age=22, .score=85.0};
// access members: . operator
printf("%s, %d years old, score %.1f\n",
s1.name, s1.age, s1.score);
// modify members
s2.score = 90.0;
printf("%s: %.1f\n", s2.name, s2.score);
return 0;
} 51.2 Simplifying with typedef
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// method 1: define the struct first, then typedef
typedef struct Student {
char name[20];
int age;
double score;
} Student;
// after that the struct keyword can be omitted
Student s = {"Alice", 20, 92.5};
// method 2: anonymous struct + typedef
typedef struct {
int x;
int y;
} Point;
Point p = {3, 4};
// method 3: self-reference (linked list node)
typedef struct Node {
int data;
struct Node *next; // must use struct Node*
} Node; 51.3 Struct Size and Memory Alignment
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
struct A {
char c; // 1 byte
int i; // 4 bytes
};
printf("%zu\n", sizeof(struct A)); // 8! not 5
// c takes 1 byte, then 3 bytes of padding, i takes 4 bytes
// this is called "memory alignment", improving CPU access efficiency
// reordering can save space
struct B {
char c; // 1
char c2; // 1
char c3; // 1
char c4; // 1
int i; // 4
};
printf("%zu\n", sizeof(struct B)); // 8
// ordering members from large to small reduces padding
typedef struct {
double d; // 8
int i; // 4
char c; // 1 + 3padding
} Efficient; // sizeof=16 Struct Arrays and Pointers
52.1 Struct Arrays
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
typedef struct {
char name[20];
int score;
} Student;
int main(void) {
Student class[] = {
{"Alice", 92},
{"Bob", 85},
{"Carol", 78},
{"Dave", 95}
};
int n = sizeof(class)/sizeof(class[0]);
// traverse
for (int i = 0; i < n; i++) {
printf("%-10s %3d\n", class[i].name, class[i].score);
}
return 0;
} 52.2 Struct Pointers and the -> Operator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Student s = {"Alice", 92};
Student *p = &s;
// access with . (direct)
printf("%s\n", s.name);
// access with -> (through a pointer)
printf("%s\n", p->name); // equivalent to (*p).name
// -> is syntactic sugar for (*p).
// p->name == (*p).name
// p->score == (*p).score
// modify through the pointer
p->score = 95;
printf("%d\n", s.score); // 95 52.3 Sorting Structs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
#include <stdlib.h>
#include <string.h>
// sort by score, descending
int by_score(const void *a, const void *b) {
const Student *sa = a;
const Student *sb = b;
return sb->score - sa->score; // descending
}
// sort by name, ascending
int by_name(const void *a, const void *b) {
const Student *sa = a;
const Student *sb = b;
return strcmp(sa->name, sb->name);
}
// usage
qsort(class, n, sizeof(Student), by_score);
qsort(class, n, sizeof(Student), by_name); Structs and Functions
53.1 Pass by Value vs Pass by Pointer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
typedef struct {
int x, y;
} Point;
// pass by value: copies the whole struct (slow)
void print_point(Point p) {
printf("(%d, %d)\n", p.x, p.y);
}
// pass a pointer: copies only the 8-byte address (fast)
void print_point_ptr(const Point *p) {
printf("(%d, %d)\n", p->x, p->y);
// const: promise not to modify
}
// pass a pointer to modify
void move_point(Point *p, int dx, int dy) {
p->x += dx;
p->y += dy;
}
int main(void) {
Point pt = {3, 4};
print_point_ptr(&pt); // (3, 4)
move_point(&pt, 10, 20);
print_point_ptr(&pt); // (13, 24)
return 0;
} 53.2 Returning Structs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// since C99 a struct can be returned directly
Point create_point(int x, int y) {
Point p = {x, y};
return p; // return-by-value copy (or RVO optimization)
}
// output through a pointer (avoid copying)
void create_point2(int x, int y, Point *out) {
out->x = x;
out->y = y;
}
int main(void) {
Point p1 = create_point(5, 6);
Point p2;
create_point2(7, 8, &p2);
return 0;
} 53.3 Struct Assignment and Copying
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Point a = {1, 2};
Point b;
// direct assignment = member-by-member copy
b = a; // b.x=a.x, b.y=a.y
// structs containing arrays can also be assigned directly
typedef struct {
char name[20];
int age;
} Person;
Person p1 = {"Alice", 20};
Person p2 = p1; // the array is copied too!
// this is a special behavior of struct assignment
// ordinary arrays cannot be assigned directly: int a[5]; int b[5]; b=a; ❌ Unions
54.1 Union Basics
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
#include <stdio.h>
// union: all members share the same memory
// size = the size of the largest member
union Data {
int i; // 4 bytes
float f; // 4 bytes
char str[8]; // 8 bytes
};
// sizeof(union Data) = 8 (largest member)
int main(void) {
union Data d;
d.i = 42;
printf("int: %d\n", d.i); // 42
d.f = 3.14f;
printf("float: %.2f\n", d.f); // 3.14
printf("int: %d\n", d.i); // garbage value! overwritten
return 0;
} 54.2 A Union Use Case: Type Tags
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
// implement a "variant type" with a union
typedef enum { INT, FLOAT, STR } Type;
typedef struct {
Type type; // marks which type it currently is
union {
int i_val;
float f_val;
char *s_val;
} val;
} Variant;
void print_variant(Variant *v) {
switch(v->type) {
case INT: printf("INT: %d\n", v->val.i_val); break;
case FLOAT: printf("FLOAT: %.2f\n", v->val.f_val); break;
case STR: printf("STR: %s\n", v->val.s_val); break;
}
}
// usage
Variant v1 = {.type=INT, .val.i_val=42};
Variant v2 = {.type=STR, .val.s_val="hello"};
print_variant(&v1); // INT: 42
print_variant(&v2); // STR: hello 54.3 struct vs union
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// struct: each member has its own memory
typedef struct {
int i; // 4 bytes
float f; // 4 bytes
} S; // sizeof = 8
// i and f coexist independently, without affecting each other
// union: all members share memory
union {
int i; // 4 bytes
float f; // 4 bytes
} u; // sizeof = 4
// i and f share the same 4 bytes
// writing i overwrites f, writing f overwrites i
// only one is valid at a time Enums
55.1 enum Basics
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
#include <stdio.h>
// define an enum
typedef enum {
RED, // 0
GREEN, // 1
BLUE // 2
} Color;
typedef enum {
MON = 1,
TUE, // 2
WED, // 3
THU, // 4
FRI, // 5
SAT, // 6
SUN // 7
} Day;
int main(void) {
Color c = GREEN;
printf("color = %d\n", c); // 1
Day today = WED;
switch(today) {
case MON: printf("Monday\n"); break;
case WED: printf("Wednesday\n"); break;
case SUN: printf("Weekend\n"); break;
}
return 0;
} 55.2 Practical Enum Patterns
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
// state machine
typedef enum {
STATE_IDLE,
STATE_RUNNING,
STATE_PAUSED,
STATE_ERROR,
STATE_COUNT // automatically gives the enum count!
} State;
const char *state_names[] = {
"IDLE", "RUNNING", "PAUSED", "ERROR"
};
void print_state(State s) {
if (s >= 0 && s < STATE_COUNT)
printf("state: %s\n", state_names[s]);
}
// error codes
typedef enum {
OK = 0,
ERR_NULL = -1,
ERR_RANGE = -2,
ERR_IO = -3,
} ErrorCode; 55.3 enum vs #define
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// enum
typedef enum { MAX_BUF = 1024, TIMEOUT = 30 } Config;
// advantages: has a type (the debugger can show the name)
// scope (C11: can specify the underlying type)
// automatic numbering
// #define
#define MAX_BUF 1024
#define TIMEOUT 30
// advantages: no memory (replaced at preprocessing)
// usable for array sizes (enum can too in C89)
// disadvantages: no type checking, the debugger cannot see the name
// suggestion: use enum for integer constants, #define for strings/expressions
// C99+: can also use static const The Full Picture of typedef
56.1 Uses of typedef
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// 1. simplify structs
typedef struct { int x, y; } Point;
Point p; // no need to write struct Point
// 2. simplify complex types
typedef unsigned long size_t; // the standard library defines it this way
typedef int (*CompareFn)(const void*, const void*);
CompareFn cmp = my_compare; // clearer than int (*cmp)(...)
// 3. cross-platform types
typedef long int64_t; // 64-bit system
typedef int int32_t; // 32-bit system
// the standard header <stdint.h> provides these
// 4. function pointer (callback)
typedef void (*Callback)(int event, void *data);
void register_callback(Callback cb); 56.2 Function Pointer typedef
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
// without typedef: long and hard to read
void process(int *arr, int n,
int (*transform)(int)) { ... }
// with typedef: clear
typedef int (*TransformFn)(int);
void process(int *arr, int n, TransformFn tf) { ... }
// event callback system
typedef void (*EventHandler)(int code, const char *msg);
typedef struct {
EventHandler on_error;
EventHandler on_success;
EventHandler on_timeout;
} EventHandlers;
void my_error_handler(int code, const char *msg) {
fprintf(stderr, "ERROR[%d]: %s\n", code, msg);
}
EventHandlers handlers = {
.on_error = my_error_handler,
// ...
}; 56.3 typedef vs #define
1 2 3 4 5 6 7 8 9 10 11
// typedef: handled by the compiler, has type checking
typedef int *IntPtr;
IntPtr a, b; // both a and b are int* ✅
// #define: handled by the preprocessor, pure text substitution
#define IntPtr int*
IntPtr a, b; // expands to: int* a, b -> a is int*, b is int! ❌
// typedef can handle complex types
// #define cannot correctly handle pointer types
// conclusion: use typedef to name types, not #define File Operations
57.1 File Read/Write Basics
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
#include <stdio.h>
#include <string.h>
int main(void) {
// write to a file
FILE *fp = fopen("data.txt", "w");
if (!fp) {
perror("fopen failed");
return 1;
}
fprintf(fp, "Hello File!\n");
fprintf(fp, "Line 2\n");
fputs("another line\n", fp);
fclose(fp); // don't forget to close it!
// read from a file
fp = fopen("data.txt", "r");
if (!fp) { perror("fopen"); return 1; }
char line[256];
while (fgets(line, sizeof(line), fp)) {
// fgets reads one line (including \n)
line[strcspn(line, "\n")] = 0; // strip the \n
printf("read: %s\n", line);
}
fclose(fp);
return 0;
} 57.2 Open Modes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
"r" read (file must exist)
"w" write (truncates existing content, creates if it doesn't exist)
"a" append (creates if it doesn't exist)
"r+" read/write (file must exist)
"w+" read/write (truncates existing content)
"a+" read + append
// binary mode (add b):
"rb" binary read
"wb" binary write
// text mode vs binary mode:
// on Windows text mode converts \n <-> \r\n
// binary mode does no conversion, recommended
// on Linux/Mac there is no difference 57.3 Binary File Read/Write
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
typedef struct {
char name[20];
int age;
double score;
} Student;
// write binary
Student s = {"Alice", 20, 92.5};
FILE *fp = fopen("student.dat", "wb");
fwrite(&s, sizeof(Student), 1, fp);
fclose(fp);
// read binary
Student s2;
fp = fopen("student.dat", "rb");
fread(&s2, sizeof(Student), 1, fp);
fclose(fp);
printf("%s %d %.1f\n", s2.name, s2.age, s2.score);
// Alice 20 92.5 The Preprocessor and Macros
58.1 Macro Definitions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
// object-like macro (constant)
#define PI 3.14159
#define MAX_BUF 1024
#define VERSION "2.0"
// function-like macro
#define MIN(a,b) ((a)<(b)?(a):(b))
#define ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0]))
// multi-line macro: continue with \
#define SWAP(a,b,type) do { \
type tmp = a; \
a = b; \
b = tmp; \
} while(0)
// stringify #
#define STR(x) #x
printf("%s\n", STR(hello)); // "hello"
// token pasting ##
#define VAR(n) var_##n
int VAR(1) = 10; // int var_1 = 10; 58.2 Conditional Compilation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
#define DEBUG 1
#if DEBUG
printf("debug info: x=%d\n", x);
#endif
#ifdef DEBUG
// compiled when DEBUG is defined
#endif
#ifndef HEADER_H
#define HEADER_H
// header guard
#endif
// platform-specific
#ifdef _WIN32
// Windows code
#elif defined(__linux__)
// Linux code
#elif defined(__APPLE__)
// macOS code
#endif
#if __STDC_VERSION__ >= 201112L
// C11 features
#endif 58.3 Predefined Macros
1 2 3 4 5 6 7 8 9 10 11 12 13
printf("file: %s\n", __FILE__); // source file name
printf("line: %d\n", __LINE__); // current line number
printf("date: %s\n", __DATE__); // compile date
printf("time: %s\n", __TIME__); // compile time
printf("standard: %ld\n", __STDC_VERSION__); // C standard version
// practical debug macro
#define LOG(fmt, ...) \
fprintf(stderr, "[%s:%d] " fmt "\n", \
__FILE__, __LINE__, ##__VA_ARGS__)
LOG("x=%d, y=%d", x, y);
// [main.c:42] x=10, y=20 Bitwise Operations
59.1 The Six Bitwise Operators
1 2 3 4 5 6 7 8 9 10 11 12 13
#include <stdio.h>
int main(void) {
unsigned int a = 0b1100; // 12
unsigned int b = 0b1010; // 10
printf("AND: %04b\n", a & b); // 1000 (8)
printf("OR: %04b\n", a | b); // 1110 (14)
printf("XOR: %04b\n", a ^ b); // 0110 (6)
printf("NOT: %u\n", ~a); // invert all bits
printf("LEFT: %04b\n", a << 2); // 110000 (48)
printf("RIGHT:%04b\n", a >> 1); // 0110 (6)
return 0;
} 59.2 Bit Manipulation Tricks
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
// assume there is an 8-bit flag register
uint8_t flags = 0b00000000;
// set bit 3 (to 1)
flags |= (1 << 3); // 00001000
// clear bit 3 (to 0)
flags &= ~(1 << 3); // 00000000
// toggle bit 3
flags ^= (1 << 3); // 00001000
// check whether bit 3 is 1
if (flags & (1 << 3)) { /* bit 3 is 1 */ }
// swap two variables (without a temporary variable)
a ^= b; b ^= a; a ^= b;
// check parity
if (n & 1) { /* odd */ } else { /* even */ }
// multiply/divide by powers of 2
n << 1; // n * 2
n >> 1; // n / 2
n << 3; // n * 8 59.3 Bit Fields
1 2 3 4 5 6 7 8 9 10 11 12 13
// precisely specify how many bits each member occupies in a struct
typedef struct {
unsigned int ready : 1; // 1 bit
unsigned int error : 1; // 1 bit
unsigned int mode : 2; // 2 bits (0-3)
unsigned int channel: 4; // 4 bits (0-15)
} StatusReg; // 8 bits total = 1 byte
StatusReg sr = {.ready=1, .mode=3, .channel=7};
printf("size: %zu\n", sizeof(sr)); // 4 (minimum alignment)
printf("mode: %u\n", sr.mode); // 3
// uses: hardware register mapping, network protocol headers, saving memory Final Project: A Mini Shell
60.1 Goal
Implement a mini command-line Shell: support built-in commands (echo, help, exit), command parsing, and command history. This is the ultimate test of everything you learned over 60 days — involving string parsing, function pointers, dynamic memory, structs, and file operations.
60.2 Implementation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MAX_ARGS 10
#define MAX_HISTORY 100
char *history[MAX_HISTORY];
int hist_count = 0;
// built-in commands
typedef struct {
char *name;
void (*func)(int argc, char **argv);
} Command;
void cmd_echo(int argc, char **argv) {
for (int i = 1; i < argc; i++)
printf("%s%s", argv[i], i<argc-1?" ":"");
printf("\n");
}
void cmd_help(int argc, char **argv) {
printf("Available commands:\n"
" echo <text> - print text\n"
" help - show help\n"
" history - show history\n"
" exit - quit\n");
}
void cmd_history(int argc, char **argv) {
for (int i = 0; i < hist_count; i++)
printf("%3d %s", i+1, history[i]);
}
Command commands[] = {
{"echo", cmd_echo},
{"help", cmd_help},
{"history", cmd_history},
{NULL, NULL}
};
// parse the command line
int parse(char *line, char **argv) {
int argc = 0;
char *tok = strtok(line, " \n");
while (tok && argc < MAX_ARGS-1) {
argv[argc++] = tok;
tok = strtok(NULL, " \n");
}
argv[argc] = NULL;
return argc;
}
int main(void) {
char line[256];
char *argv[MAX_ARGS];
printf("hackshell v1.0 - type help to see commands\n");
while (1) {
printf("hack> ");
fflush(stdout);
if (!fgets(line, sizeof(line), stdin)) break;
// save the history
if (hist_count < MAX_HISTORY)
history[hist_count++] = strdup(line);
int argc = parse(line, argv);
if (argc == 0) continue;
if (strcmp(argv[0], "exit") == 0) break;
// find the built-in command
int found = 0;
for (int i = 0; commands[i].name; i++) {
if (strcmp(argv[0], commands[i].name) == 0) {
commands[i].func(argc, argv);
found = 1;
break;
}
}
if (!found) {
// try to execute a system command
pid_t pid = fork();
if (pid == 0) {
execvp(argv[0], argv);
printf("command not found: %s\n", argv[0]);
exit(1);
} else {
wait(NULL);
}
}
}
// clean up the history
for (int i = 0; i < hist_count; i++) free(history[i]);
printf("Goodbye!\n");
return 0;
} 60.3 Skills You've Mastered
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// in 60 days you will be able to:
// ✅ understand the whole process of a C program from source to executable
// ✅ confidently use variables, operators, and control flow
// ✅ write and call functions, understand scope and storage classes
// ✅ operate arrays and strings, implement sorting and searching
// ✅ understand pointers, and use pointers to manipulate memory
// ✅ use dynamic memory management (malloc/free)
// ✅ define structs, unions, and enums
// ✅ perform file read/write operations
// ✅ use the preprocessor and macros
// ✅ do bitwise operations
// ✅ implement data structures such as linked lists and stacks
// ✅ write a mini shell! 60.4 What's Next
Data Structures and Algorithms: linked lists, stacks, queues, trees, graphs, sorting, dynamic programming
Systems Programming: processes, threads, IPC, signals
Network Programming: sockets, TCP/UDP, HTTP
Operating Systems: read CSAPP, write a mini OS
Open-source Projects: read the source of Redis, SQLite, nginx