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

Sign in to mark items as known and track your progress.

Sign in

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

Constants & Limits

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.

Conditionals & switch

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.

Functions & Prototypes

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

Function Pointers & void*

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.

Structs & typedef

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.

malloc, realloc & free

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.

printf, scanf & fgets

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.

Macros & Directives

#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.

static, extern, const & volatile

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.

stdlib, math, ctype & time

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.

argc/argv, errno & assert

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