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.
- Preprocess (
gcc -E): expand#include,#define, and conditional compilation. - Compile (
gcc -S): translate each source file to assembly. - Assemble (
gcc -c): produce an object file (.o) of machine code. - 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.
| Type | Typical size | Notes |
|---|---|---|
char | 1 byte | also used for small integers |
int | 4 bytes | default integer |
long | 8 bytes | wider integer |
float | 4 bytes | single precision |
double | 8 bytes | double precision |
T * | 8 bytes | a 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&xtakes the address ofx.*pdereferences, 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 itStrings are null terminated
C strings end in
'\0'. Functions likestrlenandprintf("%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 pointerChaining 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
freeit 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 reuseManual 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
freetwice 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
| Aspect | C | Python |
|---|---|---|
| Typing | static, compile-time | dynamic, runtime |
| Memory | manual (malloc/free) | automatic (garbage collected) |
| Execution | compiled to native code | interpreted |
| Speed | fast, close to hardware | slower, higher level |
| Safety | few guardrails | bounds-checked, no raw pointers |