C is a statically typed, compiled language that exposes memory directly through pointers. Its power and its pitfalls both come from making the programmer manage memory manually rather than relying on a garbage collector.

Compilation Model

C source becomes an executable in distinct stages, each of which can be inspected with gcc flags.

  1. Preprocess (gcc -E): expand #include, #define, and conditional compilation.
  2. Compile (gcc -S): translate each source file to assembly.
  3. Assemble (gcc -c): produce an object file (.o) of machine code.
  4. Link: combine object files and libraries, resolving symbols into one executable.
#include <stdio.h>
#define PI 3.14159        // textual substitution, no type
 
int main(void) {
    printf("pi = %f\n", PI);
    return 0;             // exit status returned to the shell
}

Unlike Python’s interpreter, this happens ahead of time, so type errors are caught at compile time and the result runs as native machine code (see Computer Organization). Always compile with -Wall -Wextra to surface warnings.

Types and Sizes

C types have fixed, platform-dependent sizes. sizeof reports the byte size at compile time.

TypeTypical sizeNotes
char1 bytealso used for small integers
int4 bytesdefault integer
long8 byteswider integer
float4 bytessingle precision
double8 bytesdouble precision
T *8 bytesa pointer, on a 64-bit system

Integer overflow is silent

Signed overflow is undefined behavior; unsigned wraps modulo 2^n. There is no exception like Python, so the program simply produces a wrong value.

Pointers

A pointer holds a memory address. It is the central C concept and the topic of Pointers and Dynamic Memory in C.

int x = 42;
int *p = &x;   // p holds the address of x
*p = 10;       // dereference: x is now 10
  • &x takes the address of x.
  • *p dereferences, accessing the value stored at that address.
  • A null, uninitialized, or dangling pointer dereference is undefined behavior.

Arrays and Strings

An array name decays to a pointer to its first element, so arr[i] is exactly *(arr + i). There is no bounds checking.

int arr[3] = {1, 2, 3};
char *s = "hi";        // string literal: {'h', 'i', '\0'}
size_t len = strlen(s); // counts up to the '\0', not including it

Strings are null terminated

C strings end in '\0'. Functions like strlen and printf("%s") rely on it; a missing terminator reads past the buffer into undefined memory.

Structs

A struct groups related fields into one type, laid out contiguously (with possible padding for alignment).

struct Point { int x; int y; };
struct Point pt = {1, 2};
pt.x = 5;              // direct member access
struct Point *pp = &pt;
pp->y = 7;             // (*pp).y through a pointer

Chaining structs with pointers builds dynamic data structures; see Linked Lists.

Stack vs Heap

Two regions, two lifetimes

  • Stack: holds local variables and call frames. Allocation is automatic and freed when the function returns. Fast but limited in size, and returning a pointer to a local is a bug.
  • Heap: holds dynamically allocated memory that lives until you free it explicitly. Flexible but manually managed. The layout mirrors the process address space in Processes and Threads.

malloc and free

Heap memory is requested and released by hand.

int *nums = malloc(n * sizeof(int));  // allocate n ints
if (nums == NULL) { return 1; }       // allocation can fail
for (int i = 0; i < n; i++) nums[i] = i;
free(nums);                           // release
nums = NULL;                          // avoid dangling reuse

Manual Memory Pitfalls

  • Memory leak: allocated memory never freed, so usage grows unbounded.
  • Dangling pointer: using memory after free, or a pointer to a returned local.
  • Double free: calling free twice on the same pointer.
  • Buffer overflow: writing past an array’s bounds, corrupting adjacent memory.
  • Uninitialized memory: reading a variable before assigning it.

These are all undefined behavior and a common source of crashes and security bugs. Tools like valgrind and AddressSanitizer detect them at runtime.

C vs Python

AspectCPython
Typingstatic, compile-timedynamic, runtime
Memorymanual (malloc/free)automatic (garbage collected)
Executioncompiled to native codeinterpreted
Speedfast, close to hardwareslower, higher level
Safetyfew guardrailsbounds-checked, no raw pointers