
C
First appeared 1972 · Dennis Ritchie
The portable assembler that every other language is eventually implemented in.
Overview
C is a general-purpose, procedural programming language developed in the early 1970s by Dennis Ritchie at Bell Labs. Designed for system programming and operating system development, C provides precise control over memory and hardware, allowing developers to write highly efficient and optimized code. Its simplicity, structured programming paradigm, and low-level capabilities have made it a foundational language in the world of computing. C introduced key programming concepts such as data types, structured control flow with if-else and switch statements, loops, functions, and pointers, which allowed programmers to manage memory and resources effectively. Over decades, C has influenced numerous other languages including C++, C#, Java, and Objective-C. Its standard library provides essential functionality such as input/output operations, string manipulation, mathematical functions, and memory management, which makes it versatile for both small-scale programs and large-scale system software. Many critical components of modern computing, including operating systems like Unix, Linux kernels, embedded systems, and performance-critical software, are written in C due to its ability to produce fast, efficient, and portable code. C’s role in education is also significant; it provides a strong foundation in programming principles, algorithmic thinking, and understanding how computers manage memory and execute instructions at a low level. The language encourages careful management of resources, understanding of pointers and memory allocation, and mastery of data structures, which helps programmers develop a deep understanding of how software interacts with hardware. Over time, ANSI and ISO standards have formalized C, ensuring that programs written in one environment can be compiled and run in another with minimal changes. This portability, combined with C’s power and flexibility, has contributed to its long-standing popularity and continued relevance. Its influence extends beyond just programming languages; it shaped the development of compilers, development tools, and programming methodology. The design philosophy of C emphasizes clarity, efficiency, and simplicity, which continues to inspire new generations of software engineers and developers. Its longevity and widespread use are a testament to its effectiveness, and even in the modern era of high-level languages, C remains a cornerstone of programming education, systems programming, and embedded application development.
Key facts
The reference details, without the paragraph.
- First appeared
- 1972
- Designed by
- Dennis Ritchie at Bell Labs
- Typing
- Static and weak — the compiler checks types but casts can override almost anything
- Execution
- Compiled ahead of time to native machine code
- Memory model
- Manual — you call `malloc` and you call `free`
- Package manager
- None standard; the system package manager, vcpkg or Conan fill the gap
- File extensions
- .c, .h
- Current standard
- C23 (ISO/IEC 9899:2024), with C11 and C17 still widely targeted
- Runtime
- Almost none — a few kilobytes of startup code
- Licence
- Open ISO standard; GCC, Clang and MSVC implement it independently
History
How the language got here — the decisions that still shape how you write it.
C was developed by Dennis Ritchie at Bell Labs between 1969 and 1973 during the creation of the Unix operating system. The primary goal was to design a language that allowed direct access to memory and low-level operations while maintaining portability and efficiency. The predecessor language, B, had limitations in terms of data types and functionality, and C expanded upon B’s syntax and capabilities, introducing structures, typed variables, and more sophisticated control flow. C enabled Unix to be rewritten from assembly language into a high-level language, which was a revolutionary achievement at the time, dramatically improving portability across hardware platforms. Its design focuses on simplicity and minimalism, providing the essential tools for efficient programming without unnecessary complexity. Over the years, C became the language of choice for system software, embedded systems, and high-performance applications. Its procedural paradigm, combined with manual memory management through pointers and dynamic allocation, gives developers unparalleled control over program execution and resource management. The ANSI C standardization (C89/C90) and later updates (C99, C11, C18) have ensured consistency and portability of the language across diverse platforms and compilers. The language’s influence is profound; it has shaped countless other languages, including C++, C#, Java, Objective-C, and even scripting languages like Perl and Python in terms of syntax and operational philosophy. C’s ecosystem includes a wide variety of compilers, debugging tools, and educational resources, making it accessible to learners and professionals alike. Its use in operating systems, device drivers, embedded firmware, and critical software systems demonstrates its robustness and reliability. Dennis Ritchie’s contribution to C and computing in general has been monumental, laying the groundwork for decades of software development and computer science education. Today, C continues to be actively used, taught, and appreciated for its efficiency, elegance, and foundational principles, maintaining its relevance in the modern programming landscape.
- 1969–1972
Born to write an operating system
Dennis Ritchie develops C at Bell Labs as a systems language for the PDP-11, evolving it from Ken Thompson's B. The goal is a language high-level enough to be readable and low-level enough to write a kernel in.
- 1973
Unix is rewritten in C
Moving Unix from assembly to C makes the operating system portable to new hardware. This is the moment portability became an expectation rather than a luxury.
- 1978
K&R — the book
Kernighan and Ritchie's *The C Programming Language* defines the language for a generation and gives the world its `hello, world`. It remains one of the clearest technical books ever written.
- 1989
ANSI C (C89)
Standardisation brings function prototypes and a defined standard library, ending the era of every compiler having its own dialect.
- 1999
C99
Declarations anywhere in a block, `//` comments, `stdint.h` fixed-width integers, variable-length arrays and `inline`. Most modern C is essentially C99 plus a few later additions.
- 2011
C11
A memory model for threads, atomics, `_Static_assert` and optional bounds-checked library functions. C finally has defined semantics for multi-threaded programs.
- 2024
C23
`nullptr`, `constexpr`, `typeof`, binary literals, and `#embed` for including binary files directly. Careful modernisation, without abandoning what makes C C.
- Today
The substrate everything sits on
Linux, Windows, macOS kernels, SQLite, Redis, CPython, Git, FFmpeg, OpenSSL and the runtimes of most other languages are written in C. Its ABI is the lingua franca that lets any two languages talk.
What it is good at
The reasons teams pick it, stated concretely.
Predictable, inspectable performance
There is no garbage collector to pause you, no hidden allocation, no runtime deciding things behind your back. What you write maps closely onto what the processor does, which is why real-time, embedded and kernel code lives here.
It runs on everything
From an eight-bit microcontroller with two kilobytes of RAM to a supercomputer, there is a C compiler. No other language comes close to that reach.
The universal interface between languages
Python, Ruby, Rust, Go, Java and JavaScript all speak the C ABI. If you want a library callable from every language, you write it in C or expose a C interface.
Small enough to hold in your head
Roughly thirty keywords and a compact grammar. The language is genuinely simple — the difficulty is entirely in what you must manage yourself, not in the syntax.
It teaches you what the machine is doing
Pointers, the stack and heap, alignment, endianness and cache behaviour stop being abstractions. Programmers who have written C debug problems in higher-level languages faster because they understand what is underneath.
Trade-offs
Every language costs you something. Knowing what, before you commit, is the whole point.
Memory safety is entirely your responsibility
Buffer overflows, use-after-free, double frees and dangling pointers are not caught by anything unless you invoke a tool. Decades of security vulnerabilities in critical infrastructure trace back to exactly this.
Undefined behaviour is a real hazard
Signed overflow, reading uninitialised memory or breaking strict aliasing do not merely produce wrong answers — the optimiser is entitled to assume they never happen and may delete surrounding code. Bugs can appear only at higher optimisation levels.
A thin standard library
No hash maps, no dynamic arrays, no string type worth the name. You either write these yourself, copy them between projects, or pull in a third-party library.
No dependency management story
There is no `cargo` or `npm`. Dependencies mean system packages, vendored source, git submodules or a build-system incantation, and it differs on every platform.
Strings are a permanent source of bugs
NUL-terminated character arrays with manual length tracking are behind an enormous share of C's security history. `strcpy`, `sprintf` and `gets` should be treated as deprecated regardless of what the compiler says.
Code examples
Not syntax tours — the idioms that make code read like the language rather than a translation of another one.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Caller owns the returned pointer and must free() it. Say so, in a comment,
every time — C has no way to express ownership in the type system. */
char *duplicate(const char *source) {
size_t length = strlen(source) + 1;
char *copy = malloc(length);
if (copy == NULL) return NULL; /* always check malloc */
memcpy(copy, source, length);
return copy;
}
int main(void) {
char *greeting = duplicate("hello, world");
if (greeting == NULL) return 1;
printf("%s\n", greeting);
free(greeting);
greeting = NULL; /* stops accidental use-after-free */
return 0;
}typedef struct {
int *items;
size_t count;
size_t capacity;
} IntVec;
int vec_push(IntVec *vec, int value) {
if (vec->count == vec->capacity) {
size_t next = vec->capacity ? vec->capacity * 2 : 8;
int *grown = realloc(vec->items, next * sizeof *grown);
if (grown == NULL) return -1; /* original buffer still valid */
vec->items = grown;
vec->capacity = next;
}
vec->items[vec->count++] = value;
return 0;
}
void vec_free(IntVec *vec) {
free(vec->items);
*vec = (IntVec){0};
}#include <stdio.h>
#include <string.h>
int build_path(char *out, size_t out_size, const char *dir, const char *file) {
/* snprintf always NUL-terminates and reports the length it wanted. */
int needed = snprintf(out, out_size, "%s/%s", dir, file);
if (needed < 0 || (size_t)needed >= out_size) {
return -1; /* truncated — treat as an error */
}
return needed;
}
char path[64];
if (build_path(path, sizeof path, "/var/log", "app.log") < 0) {
fprintf(stderr, "path too long\n");
}#include <stdint.h>
#include <stdbool.h>
/* int is not guaranteed to be 32 bits; long is 64 on Linux and 32 on Windows.
When the width matters — file formats, protocols, hardware — be explicit. */
typedef struct {
uint32_t magic;
uint16_t version;
uint8_t flags;
} Header;
static_assert(sizeof(Header) <= 8, "header must fit the wire format");
/* Build with the compiler on your side:
cc -std=c17 -Wall -Wextra -Werror -fsanitize=address,undefined -g main.c */Common pitfalls
The mistakes that cost everyone an afternoon at least once.
Off-by-one on array bounds
A loop running `for (i = 0; i <= n; i++)` over an `n`-element array writes one past the end. C will not stop you, and the corruption may not appear until much later.
Returning a pointer to a local variable
The stack frame is gone the moment the function returns. The pointer looks valid and reads plausible-looking garbage. Return heap memory or write into a caller-provided buffer.
`sizeof` on a pointer, not an array
Inside a function, an array parameter is a pointer, so `sizeof(arr)` gives the pointer size, not the array size. Always pass the length alongside the pointer.
Ignoring the return value of `malloc`, `realloc` and `fopen`
All of them can fail. An unchecked NUL dereference is the crash you will spend an afternoon on.
Integer overflow on signed types
Signed overflow is undefined behaviour, not wraparound. The optimiser may assume it cannot happen and remove your overflow check. Use unsigned types for wrapping arithmetic, or check before the operation.
Macros that evaluate arguments twice
`#define MAX(a,b) ((a)>(b)?(a):(b))` called as `MAX(i++, j)` increments `i` twice. Parenthesise everything and prefer `static inline` functions where you can.
In production
Where it is running at scale, and what it is doing there.
Microsoft
Operating systems (Windows kernel) and system utilities.
Apple
macOS and iOS system-level components.
Linux Foundation
Linux kernel and system-level software.
Embedded Systems Manufacturers
Firmware and low-level device programming.
Learning path
A realistic order to learn things in, with something to build at each step.
- 1
Weeks 1–2
Syntax and the compilation model
Types, control flow, functions, arrays, and what the preprocessor, compiler and linker each actually do. Understanding why a linker error differs from a compile error saves hours later.
Build this: Write a program that reads a file line by line and reports the longest line, compiled with `-Wall -Wextra`.
- 2
Weeks 3–5
Pointers and memory
Pointer arithmetic, the stack versus the heap, `malloc`/`free`, structs, and how arrays decay to pointers. This is the conceptual centre of C — do not move on until it is genuinely comfortable.
Build this: Implement a linked list and a dynamic array with clean allocation and deallocation, checked under Valgrind or AddressSanitizer.
- 3
Weeks 6–8
The standard library and tooling
File I/O, string functions and their safe variants, `stdint.h`, Make or CMake, and debugging with GDB or LLDB. Learn to read a core dump.
Build this: Build a small command-line tool with a Makefile, argument parsing and proper error reporting.
- 4
Months 3–4
Systems programming
POSIX APIs — file descriptors, `fork`, `exec`, sockets, signals — plus pthreads and the C11 memory model. This is where C stops being an exercise and starts being useful.
Build this: Write a small TCP server that handles multiple clients and shuts down cleanly on SIGINT.
- 5
Ongoing
Correctness under pressure
Undefined behaviour and what the optimiser is permitted to assume, static analysis with clang-tidy or Coverity, fuzzing with libFuzzer or AFL, and reading real code — SQLite and Redis are unusually well written.
Build this: Fuzz a parser you wrote and fix everything it finds.
Ecosystem and tooling
The tools you will end up installing whichever project you join.
| Tool | Category | What it does |
|---|---|---|
| GCC / Clang | Compiler | The two dominant compilers; Clang's diagnostics are friendlier, GCC's platform coverage is wider |
| CMake | Build | The de facto cross-platform build system generator, despite everyone's complaints about it |
| Make | Build | Simple, universal, and entirely adequate for a project of moderate size |
| GDB / LLDB | Debugging | Interactive debuggers — breakpoints, watchpoints and post-mortem core dump analysis |
| Valgrind | Debugging | Detects memory leaks and invalid accesses without recompiling |
| AddressSanitizer / UBSan | Debugging | Compiler-instrumented runtime checks; faster than Valgrind and catch more undefined behaviour |
| clang-tidy / cppcheck | Code quality | Static analysis that finds bugs before the program runs |
| vcpkg / Conan | Packaging | Package managers that give C the dependency handling the language never standardised |
C libraries
30 catalogued, each with installation, worked examples and best practices.
libcurl
The HTTP client behind an enormous share of software — every protocol, every platform.
Web & HTTPOpenSSL
The cryptography and TLS library most of the internet depends on.
Securityzlib
The DEFLATE compression library that gzip, PNG and countless formats are built on.
Serialization & FormatsXlib
Xlib is the standard C library for interfacing with the X Window System (X11) on Unix-like operating systems. It allows applications to create and manage windows, handle user input events, draw graphics, and communicate with the X server.
UI & GraphicsSQLite
A complete SQL database in a single file, embedded directly in your process.
Databases & Cachinglibxml2
libxml2 is a C library for parsing XML documents. It provides a comprehensive set of functions for reading, validating, navigating, and manipulating XML data efficiently, and supports standards like XPath, XInclude, and XPointer.
Serialization & Formats
Frequently asked
Should I still learn C in 2026?
If you want to work on operating systems, embedded devices, databases, game engines or language runtimes, it is close to mandatory. Even if you never ship C professionally, learning it changes how you understand every other language — memory, indirection and cost stop being abstract.
C or C++ first?
C first, if you have the patience. It is far smaller, and it forces you to understand pointers and memory without the safety nets. Going the other way, C++'s abstractions can hide exactly what you most need to see.
Is C being replaced by Rust?
In new systems projects where memory safety is the priority, Rust is genuinely taking share, including inside the Linux kernel. But there are billions of lines of working C in production, an unmatched compiler and platform reach, and the C ABI remains the interoperability standard. Displacement will be measured in decades, not years.
Which standard should I target?
C17 is the safe default — universally supported and effectively C11 with defect fixes. Use C99 if you must support very old toolchains, and C23 if your compilers are current and `constexpr`, `nullptr` and `#embed` would help.
How do I avoid memory bugs?
Build every debug configuration with `-fsanitize=address,undefined`, run your tests under it, and enable `-Wall -Wextra -Werror`. Adopt conventions: one owner per allocation, free on every path, set freed pointers to NULL, and never use the unbounded string functions. Fuzz anything that parses untrusted input.
How do I handle dependencies?
For small projects, vendor the source — many good C libraries are a single `.c` and `.h` pair designed for exactly this. For larger ones, vcpkg or Conan with CMake is the closest thing to a modern workflow the ecosystem offers.




