C logoCv23INTERMEDIATE

C

The C programming language reference - types, pointers, arrays, strings, structs, manual memory, the preprocessor, I/O, and modern C23 features.

16 min read
csystemspointersmemoryembedded
Loading your progress

Getting Started

Your first program, compiling, and output.

Hello World

The entry point and printf.

c
// Quick Reference
#include <stdio.h>

int main(void) {
    printf("Hello, World!\n");
    return 0;
}
💡 Every C program starts at main(); return 0 signals success to the OS.
⚡ printf uses format specifiers: %d int, %f double, %c char, %s string, %p pointer.
📌 scanf needs the address of the variable (&age) - it writes through a pointer.
🟢 Always #include the header that declares a function (<stdio.h> for printf).
basicsio

Compile & Run

Build with warnings and a modern standard.

c
// Quick Reference
gcc -std=c23 -Wall main.c -o main
./main
💡 -Wall -Wextra catch most beginner bugs at compile time - always compile with them.
⚡ Pass -std=c23 (or c17) explicitly; the compiler default may be an older dialect.
📌 Build with -g and run under valgrind to catch memory leaks and invalid access.
🟢 C compiles ahead of time to a native binary - there is no runtime/interpreter.
compilecli

Variables & Types

Fundamental types, sizes, and constants.

Types & sizeof

C's built-in types and their sizes.

c
// Quick Reference
int i = 42;
long l = 42L;
double d = 3.14;
char c = 'A';
unsigned u = 10;
bool ok = true;   // C23 keyword
💡 In C23, bool/true/false are keywords - older C needed #include <stdbool.h>.
⚡ Type sizes are platform-dependent; use <stdint.h> (int32_t) when the width matters.
📌 Print sizeof (a size_t) with %zu; using %d there is technically wrong.
🟢 unsigned types wrap around on overflow; signed integer overflow is undefined behavior.
typessizeof

const, enums, and type limits.

c
// Quick Reference
const double PI = 3.14159;
#define MAX 100
#include <limits.h>
INT_MAX;  // largest int
💡 const gives a typed, scoped constant; #define is a raw preprocessor text swap.
⚡ <limits.h> (INT_MAX, CHAR_BIT) and <float.h> (DBL_MAX) expose type ranges.
📌 Enums are the idiomatic way to name related integer constants.
🟢 Prefer const or enum over #define for values - they respect scope and types.
constenumlimits

Operators

Arithmetic, comparison, logical, and bitwise.

Operators

The core operator set.

c
// Quick Reference
+  -  *  /  %          // arithmetic
== != <  >  <= >=      // comparison
&& ||  !               // logical
&  |  ^  ~  << >>        // bitwise
cond ? a : b           // ternary
💡 Integer / truncates toward zero; make an operand a double (2.0) for real division.
⚡ && and || short-circuit - the right side is skipped once the result is decided.
📌 In C, any non-zero value is "true" and 0 is "false" in conditions.
🟢 Bitwise operators (& | ^ ~ << >>) manipulate the binary representation directly.
operators

Control Flow

Branching and looping.

if/else and switch.

c
// Quick Reference
if (x > 0) { ... } else if (x < 0) { ... } else { ... }

switch (n) {
    case 1: ...; break;
    default: ...;
}
💡 switch needs a break per case, or control falls through to the next case.
⚡ Stacked case labels (case 6: case 7:) share one body - a clean way to group.
📌 switch works on integer/char/enum values, not on strings or floats.
🟢 if (ptr) tests non-NULL; if (n) tests non-zero - C treats 0 as the only false.
conditionalsswitch

Loops

for, while, do-while, and jumps.

c
// Quick Reference
for (int i = 0; i < 5; i++) { ... }
while (cond) { ... }
do { ... } while (cond);
break;  continue;
💡 Declaring the loop variable in the for header (for (int i...)) scopes it to the loop (C99+).
⚡ do-while always runs its body once before checking the condition.
📌 break leaves the loop; continue jumps to the next iteration.
🟢 goto is discouraged generally, but a single-exit "goto cleanup" is a common C idiom.
loopsgoto

Functions

Declaring, calling, and scoping behavior.

Definitions, prototypes, and recursion.

c
// Quick Reference
int add(int a, int b);      // prototype (declaration)

int add(int a, int b) {     // definition
    return a + b;
}
💡 Declare a prototype (or include its header) before you call a function.
⚡ C passes arguments by value - pass a pointer (int *p) to modify the caller's variable.
📌 static on a function limits it to the current file; on a local var it persists across calls.
🟢 Mark pointer params const (const char *) when the function only reads through them.
functionsprototypes

Pointers

C's defining feature - addresses and indirection.

Pointer Basics

Address-of, dereference, and arithmetic.

c
// Quick Reference
int n = 5;
int *p = &n;     // p holds the address of n
*p = 10;         // dereference: n is now 10
int *q = nullptr; // C23 (or NULL)
💡 & takes an address; * dereferences a pointer to read/write the pointed-to value.
⚡ Dereferencing NULL or an uninitialized pointer is undefined behavior - always init.
📌 Pointer arithmetic advances by sizeof(*p), not by bytes - p+1 skips one element.
🟢 Use nullptr in C23 (or NULL); a plain 0 works but reads less clearly.
pointers

Callbacks and generic pointers.

c
// Quick Reference
int (*op)(int, int) = &add;   // function pointer
int r = op(2, 3);
void *generic;                // points to anything
💡 Function pointers enable callbacks - passing behavior (like qsort's comparator) as data.
⚡ void* is a generic pointer; cast it to the real type before dereferencing.
📌 typedef makes function-pointer types readable: typedef int (*BinOp)(int,int).
🟢 qsort/bsearch take a void* comparator - the classic function-pointer use in C.
function-pointersvoid-pointer

Arrays

Contiguous fixed-size storage.

Arrays

Declaration, iteration, and the pointer relationship.

c
// Quick Reference
int a[5] = {1, 2, 3};   // rest are 0
int n = sizeof(a) / sizeof(a[0]);   // length
int grid[2][3];         // 2D
💡 sizeof(a)/sizeof(a[0]) gives the length - but only before the array decays to a pointer.
⚡ Passing an array to a function passes a pointer; send the length as a separate argument.
📌 C does no bounds checking - writing past the end is undefined behavior (buffer overflow).
🟢 { 0 } zero-initializes the whole array; partial initializers fill the rest with 0.
arrays

Strings

Null-terminated char arrays and string.h.

C Strings

char arrays and the string library.

c
// Quick Reference
char s[] = "hello";       // char array + '\0'
#include <string.h>
strlen(s);  strcmp(a, b);
strcpy(dst, src);  snprintf(buf, n, "%d", x);
💡 C strings are char arrays terminated by '\0'; strlen counts up to (not including) it.
⚡ Compare strings with strcmp (== compares pointers, not contents).
📌 Prefer snprintf/strncpy over sprintf/strcpy - the bounded versions prevent overflows.
🟢 A string literal ("x") is read-only - modifying it via a char* is undefined behavior.
stringsstring-h

Structs, Unions & Enums

Grouping and naming data.

Composite types and member access.

c
// Quick Reference
struct Point { int x, y; };
struct Point p = {3, 4};
p.x;              // dot for a value
ptr->x;           // arrow for a pointer
💡 Use . to access a struct value's members and -> through a struct pointer.
⚡ typedef struct {...} Name lets you write Name instead of struct Name everywhere.
📌 A union overlaps all members in one memory slot - only one is valid at a time.
🟢 Designated initializers ({.x = 1}) make struct setup clear and order-independent.
structsunionsenums

Dynamic Memory

Heap allocation you manage yourself.

Allocate, resize, and release memory.

c
// Quick Reference
#include <stdlib.h>
int *p = malloc(n * sizeof *p);
if (!p) { /* handle */ }
free(p);
p = nullptr;
💡 sizeof *p (deref) is safer than sizeof(int) - it tracks the pointer's type automatically.
⚡ Always check malloc/realloc for NULL before using the result.
📌 Match every malloc/calloc with exactly one free - unfreed memory is a leak.
🟢 Set a pointer to nullptr after free to avoid use-after-free and double-free bugs.
memorymalloc

Input & Output

Formatted console I/O.

Format specifiers and reading input.

c
// Quick Reference
printf("%d %.2f %s\n", 42, 3.14, "hi");
scanf("%d", &n);
fgets(buf, sizeof buf, stdin);
💡 Match the specifier to the type: %d int, %f double, %s string, %zu size_t, %p pointer.
⚡ scanf writes into addresses, so pass &var - forgetting the & is a classic crash.
📌 Prefer fgets over scanf("%s") / gets for input - it is bounded and keeps spaces.
🟢 fgets leaves the trailing newline in the buffer; overwrite it with '\0' to trim.
ioprintfscanf

File I/O

Reading and writing files.

File I/O

fopen, fgets, fprintf, and fclose.

c
// Quick Reference
FILE *f = fopen("data.txt", "r");
if (!f) { perror("open"); }
fgets(buf, sizeof buf, f);
fclose(f);
💡 Always check fopen for NULL - the file may not exist or be permitted.
⚡ Every fopen needs a matching fclose, or you leak file descriptors and buffered writes.
📌 fgets/fprintf are for text; fread/fwrite handle raw binary blocks.
🟢 Use "b" mode (rb/wb) for binary files, especially for portability on Windows.
file-io

Preprocessor

Text transformation before compilation.

#define, #include, and conditional compilation.

c
// Quick Reference
#define MAX 100
#define SQUARE(x) ((x) * (x))
#include <stdio.h>
#ifndef HEADER_H
#define HEADER_H
#endif
💡 The preprocessor does pure text substitution before the compiler ever runs.
⚡ Always parenthesize macro parameters and the whole body to avoid precedence bugs.
📌 Guard every header with #ifndef/#define/#endif (or #pragma once) against double include.
🟢 #ifdef DEBUG lets you compile in extra logging only when DEBUG is defined (-DDEBUG).
preprocessormacros

Storage Classes & Qualifiers

Lifetime, linkage, and type qualifiers.

Control storage duration and access.

c
// Quick Reference
static int counter;   // file scope OR persistent local
extern int shared;    // defined in another file
const int MAX = 100;  // read-only
volatile int reg;     // do not optimize accesses
💡 A static local keeps its value between calls; a static global/function is file-private.
⚡ extern declares a variable defined in another file - shares one instance across units.
📌 const makes a value read-only; combine with pointers carefully (const int * vs int * const).
🟢 volatile tells the compiler a value can change externally (memory-mapped I/O, signals).
staticexternconstvolatile

Bit Manipulation

Working at the bit level.

Bitwise Tricks

Masks, flags, and shifts.

c
// Quick Reference
x & mask     // test bits
x | flag     // set bits
x & ~flag    // clear bits
x ^ flag     // toggle bits
x << n       // shift left
💡 1u << n makes a mask for bit n; use it to set, clear, toggle, or test that bit.
⚡ Combine flags with |, test with &, remove with & ~flag.
📌 Prefer unsigned types for bit work - right-shifting signed negatives is implementation-defined.
🟢 x << 1 and x >> 1 are fast multiply/divide by 2 (for non-negative values).
bitwiseflags

Standard Library

Essential headers beyond stdio.

Common utility functions.

c
// Quick Reference
#include <stdlib.h>  // atoi, qsort, rand, exit
#include <math.h>    // sqrt, pow, floor
#include <ctype.h>   // isdigit, toupper
#include <time.h>    // time, clock
💡 <stdlib.h> holds conversions (atoi/strtol), qsort/bsearch, rand, malloc, and exit.
⚡ Some systems need -lm to link <math.h> functions (gcc main.c -lm).
📌 <ctype.h> (isdigit, toupper) classifies/converts single characters portably.
🟢 qsort/bsearch are generic via void* - the same function-pointer pattern as callbacks.
stdlibmathlibrary

Command-line & Error Handling

Program arguments and reporting failures.

Read arguments and handle errors.

c
// Quick Reference
int main(int argc, char *argv[]) {
    // argv[0] = program name, argv[1..] = args
}
#include <errno.h>
perror("open failed");
assert(x > 0);
💡 argv[0] is the program name; real arguments start at argv[1], counted by argc.
⚡ C has no exceptions - functions signal failure via return values and the global errno.
📌 perror / strerror(errno) turn an error code into a human-readable message.
🟢 assert catches bugs during development; defining NDEBUG compiles all asserts out.
argc-argverrnoassert

Modern C (C23)

Features from today's standard.

C23 Highlights

Keywords and features new to C23.

c
// Quick Reference
bool ok = true;          // keywords (no <stdbool.h>)
int *p = nullptr;        // typed null
constexpr int SIZE = 10; // compile-time constant
[[nodiscard]] int f(void);
💡 C23 makes bool/true/false/static_assert keywords - no more <stdbool.h> or _ prefixes.
⚡ nullptr is a typed null; constexpr defines genuine compile-time constants.
📌 [[nodiscard]] warns when a function's return value is ignored (e.g. an error code).
🟢 Compile with -std=c23 (gcc 13+/clang 16+) to use these; check __STDC_VERSION__ >= 202311L.
c23modern