Why C, and What a Program Really Is
Where C Runs Today: Six Systems You Already Use
In this lesson
- Name six systems you used today that are written in C.
- Say what C actually does inside each one.
- Explain why kernel and embedded work has stayed in C.
Think about the last few hours. Something woke you. You unlocked a phone, opened a browser, and the page you are reading arrived over a router in your building.
Six separate pieces of C ran before you read this sentence. You did not see any of them, which is exactly the point. C is infrastructure, and infrastructure is invisible until it fails.
This lesson is a tour. Each system gets one named project, and one sentence on what C does there. Then one real line from that project's public interface. Then one honest sentence on why it was not written in something else.
06:00, the alarm: FreeRTOS
The chip inside a fitness band, a smart meter or a microwave is not running Android. It runs a tiny scheduler, often FreeRTOS. FreeRTOS has been written in C since 2003 and now ships inside billions of devices.
What C does there: it decides which task runs next. It does so with a known, fixed amount of memory. There is no garbage collector to pause at a bad moment.
void vTaskDelay( const TickType_t xTicksToDelay );
That is from the FreeRTOS API, documented on freertos.org. It puts the current task to sleep for a fixed number of clock ticks. Why not something else? A device with 64 KB of memory has no room for a runtime. A pause of 30 milliseconds at the wrong moment is a fault, not a slowdown.
06:02, the phone: the Linux kernel
Android runs a Linux kernel. So does most of the internet's server fleet. It is around 30 million lines, overwhelmingly C, started in 1991 and still accepting changes every day.
What C does there: everything between your app and the hardware. Reading the touchscreen, scheduling processes, moving bytes to the storage chip.
ssize_t read(int fd, void *buf, size_t count);
That declaration is the doorway into the kernel, and you can read its manual with man 2 read on any Linux machine. Every file your programs ever open goes through it. Why not something else? The kernel has to speak to hardware directly, and C is the language that does not stand between the two.
06:03, the router: BusyBox and dnsmasq
The box in the corner of your room is a small Linux computer. Most home routers run BusyBox, a single C program holding stripped-down versions of about 400 Unix commands. Most also run dnsmasq, a C program that answers name lookups.
What C does there: it turns "progsity.io" into an address, hands out local addresses, and moves packets, inside a device with a few megabytes of storage.
int socket(int domain, int type, int protocol);
That is the standard socket call, documented in man 2 socket, and it is where almost every network program on earth begins. Why not something else? Because the entire firmware image has to fit in flash memory. A runtime would cost more than the whole program.
06:03, the lesson data: SQLite and Redis
SQLite is one C library, public domain, and the most widely deployed database engine in existence. It is inside your browser, your phone and most aircraft entertainment systems. Redis, in C since 2009, holds short-lived data in memory for web backends, including this one.
What C does there: it manages a file format byte by byte. It answers a query in microseconds, with no server process at all.
int sqlite3_open(const char *filename, sqlite3 **ppDb);
That is SQLite's opening function, documented at sqlite.org. Why not something else? A database engine spends its life moving bytes between memory and disk. It also has to embed inside programs written in twenty other languages. A C library can be called from all of them.
06:04, the tooling: CPython
The standard Python interpreter is a C program called CPython. Somebody who says "Python is slow" is describing the speed of this C program. It reads their instructions one at a time.
What C does there: it holds every Python object and runs the loop that executes bytecode. It also implements the fast parts of the standard library.
PyObject *PyLong_FromLong(long v);
That is from CPython's public C API, documented at docs.python.org. It is how a plain C number becomes a Python integer. Why not something else? A language runtime has to control its own memory layout exactly, and it must be callable from the operating system.
06:04, the picture: zlib and libpng
Every image on this page arrived compressed. zlib, written in C in 1995, is the compression library behind PNG files, HTTP compression and the .zip format. libpng sits on top of it and understands the PNG format itself.
What C does there: it walks over millions of bytes per image. Nothing checks an array bound unless the code asked for it. That is why it is fast, and why it needs careful review.
int deflate(z_streamp strm, int flush);
That is zlib's compression step, documented in the zlib manual at zlib.net. Why not something else? Decoding an image is a tight loop over raw bytes. A language that checks every array access would make your photos load noticeably slower.
Why kernel and embedded work stays in C
Four reasons come up every time, and none of them is nostalgia.
- No hidden runtime. A C program needs nothing running underneath it. A kernel cannot rely on a runtime, because the kernel is what the runtime would run on.
- Predictable timing. There is no pause you did not write. In a pacemaker or a car's brake controller, "usually fast" is not a specification.
- Known memory. You can say exactly how many bytes a structure takes, as Example 1 shows. With 64 KB of memory, that is the difference between shipping and not shipping.
- It is already everywhere. Thirty million lines of working kernel code do not get rewritten because a better language arrived. Rust is being added to Linux, in new drivers, next to the C.
The next three programs show the first three reasons in code you can run.
This is what an embedded engineer does before writing anything: count the bytes.
#include <stdint.h>
#include <stdio.h>
/* One reading from a temperature sensor, laid out by hand. */
struct Reading {
uint32_t seconds; /* 4 bytes: time since the device started */
int16_t celsius; /* 2 bytes: temperature times ten */
int16_t battery; /* 2 bytes: percent times ten */
};
int main(void)
{
struct Reading r = {3600, 267, 812};
printf("one reading is %zu bytes\n", sizeof(struct Reading));
printf("a thousand readings fit in %zu bytes\n", 1000 * sizeof(struct Reading));
printf("at %u seconds: %d.%d C, battery %d.%d%%\n",
r.seconds, r.celsius / 10, r.celsius % 10,
r.battery / 10, r.battery % 10);
return 0;
}
one reading is 8 bytes
a thousand readings fit in 8000 bytes
at 3600 seconds: 26.7 C, battery 81.2%
Eight bytes, and you chose all eight. That is why a sensor with 32 KB of memory can plan to store four thousand readings. You will write structures like this in Module 12.
Run in CompilerEvery byte this program uses is decided before it starts. Nothing is asked for while it runs, so nothing can fail while it runs.
#include <stdio.h>
/* Five alarms, fixed in place. No memory is ever requested while running. */
#define ALARM_COUNT 5
int main(void)
{
int alarm_minute[ALARM_COUNT] = {330, 360, 420, 480, 1320};
const char *label[ALARM_COUNT] = {"early", "wake up", "bus", "class", "sleep"};
int now = 420; /* 07:00, counted in minutes since midnight */
for (int i = 0; i < ALARM_COUNT; i++) {
if (alarm_minute[i] == now) {
printf("ring: %s\n", label[i]);
}
}
printf("checked %d alarms, used %zu bytes of table\n",
ALARM_COUNT, sizeof(alarm_minute));
return 0;
}
ring: bus
checked 5 alarms, used 20 bytes of table
Twenty bytes, five comparisons, one answer, every time. A device that must react within a millisecond is built out of loops that look exactly like this.
Run in CompilerBefore libpng decodes anything, it checks eight bytes. Those eight bytes are fixed by the PNG specification, and every PNG file on earth starts with them.
#include <stdint.h>
#include <stdio.h>
/* The first eight bytes of every PNG file, from the PNG specification. */
static const uint8_t PNG_SIGNATURE[8] = {
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A
};
int looks_like_png(const uint8_t *bytes, int count)
{
if (count < 8) {
return 0;
}
for (int i = 0; i < 8; i++) {
if (bytes[i] != PNG_SIGNATURE[i]) {
return 0;
}
}
return 1;
}
int main(void)
{
uint8_t real_png[10] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00};
uint8_t a_jpeg[10] = {0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46};
printf("file 1 is a PNG: %d\n", looks_like_png(real_png, 10));
printf("file 2 is a PNG: %d\n", looks_like_png(a_jpeg, 10));
return 0;
}
file 1 is a PNG: 1
file 2 is a PNG: 0
Bytes 2, 3 and 4 are the letters P, N and G. Look them up in the ASCII table in Module 1 and you will see 0x50, 0x4E and 0x47. This is the whole of what "reading a file format" means.
Run in CompilerWhere this is used, and what to read next
- SQLite. Start with the C interface list at sqlite.org, then the "How SQLite Works" page. It is the friendliest large C codebase to read, partly because its test suite is far larger than the code.
- Redis. Its source is small enough to read in a weekend. Begin with
sds.c, its string type, which exists because C's own strings are awkward. You will understand why after Module 10. - CPython. Read the "Extending and Embedding" chapter of the Python documentation. It shows you a Python module written in C, which is the clearest bridge between the two languages.
- The Linux kernel. Not yet. Come back after Module 12. The kernel's own documentation directory is where to start, not the source.
Common mistakes
1. Assuming a wide type is the same size everywhere.
struct Reading {
long seconds; /* 8 bytes on Linux, 4 bytes on Windows */
int celsius;
};
There is no error message. The file your sensor writes simply cannot be read on the other machine. The fix is Example 1: use <stdint.h> and say uint32_t when you mean four bytes.
2. Saying "written in C" when you mean "runs on C".
Your Python script is not written in C. It runs on CPython, which is. The distinction matters when somebody asks you to make it faster, because the two answers are completely different.
3. Choosing C because it is fast, for a job where speed is not the problem.
A web page, a form, a report: these spend their time waiting for a network or a human. C would make them harder to write and no faster to use. Pick C when memory layout, timing or size is the constraint.
4. Reading a large C project before you can read a small one.
Opening the Linux kernel in week two is a way to feel stupid, not a way to learn. Every project above is readable, in order, starting around Module 10 of this track.
Write your own six-moment list for your day, on paper, and name the C project behind each moment.
Rules. Six rows: the time, what you did, the project, and one sentence on what C does there. At least two rows must be different from Figure 1.
Check yourself. Every project you name must be one you can find a home page for. If you cannot find it, replace it with one you can.
Take Example 1 and change the sensor's layout to store four readings per record instead of one.
Rules. Keep <stdint.h> types. Print the new size. Before you run it, write down the size you expect, and why.
Check yourself. A predicted size that matches the printed size means you understand the layout. If they differ, the compiler inserted padding, and Module 12 explains why.
Pick one of the six projects and write a one-page briefing on it. Write it for a teammate who has never heard of it.
Rules. Cover five things. What it does, who wrote it and when, and where it runs. Then one thing it deliberately does not do, and one alternative with a real difference. Every claim must come from the project's own site or manual.
Check yourself. A good briefing has at least one fact that surprised you while writing it. If nothing surprised you, you stayed on the home page.
Common doubts
If C is everywhere, why do job ads ask for Python and JavaScript?
Because most jobs build applications, and applications sit on top of this layer. The C jobs exist, in systems, embedded, databases and games, and there are fewer people able to do them.
Is Rust replacing C in these projects?
In new code, sometimes. Linux has accepted Rust drivers since 2022, and Android uses it for new system components. Existing C keeps running beside it, because rewriting working code is expensive and risky.
Can I read one of these codebases now?
Not comfortably. Come back to Redis after Module 11 and SQLite after Module 13. Reading real code is a skill of its own, and Module 16 is built around it.
Are these projects safe, if C has so many memory bugs?
They are heavily tested and heavily reviewed, which is how the risk is managed. SQLite's test suite is far larger than SQLite. The honest summary is that C moves the safety work from the compiler to the process around it.
Do I need to know all six of these to get a job?
No. You need to know one language well enough to read code you did not write. This lesson is a map, not a reading list, and Exercise 3 is the only part you should do now.
Key takeaways
- FreeRTOS, the Linux kernel, BusyBox, SQLite, CPython and zlib are all C, and all ran today.
- C sits wherever memory, timing or size is the scarce thing.
- Kernels and embedded devices stay in C because there is no runtime underneath them.
- In C you can say exactly how many bytes your data takes, and that decides what ships.
- "Written in C" and "runs on C" are different claims, and the difference matters.
- Rust is being added next to C in new code, not swapped in for the old.
Next you will meet the two ways programmers organise a large program. You will also see why this track teaches the older one first.
Keyboard: পরের lesson এ j, আগেরটায় k, editor খুলতে r।