Step 56. Starting C — In the Language the Machine Knows Directly
Level 1 — Programming and the Computer’s Insides | Difficulty ★★★☆☆ | Estimated time: 4 hours
Prerequisites: Steps 41–55 complete. You can build programs in Python. You can open a Linux terminal (WSL or VirtualBox Ubuntu).
- What you need: a Linux terminal, gcc (we install it together in section 3-1).
- Caution: today’s practice only creates, translates, and runs files inside your own computer — 100% safe.
We’ve come this far with Python. But in the deep places of security — operating systems, network equipment, the famous vulnerabilities — a different language is laid down: C. The OS kernel, Python itself, and most system tools are made in C. C is a hard part for beginners. It’s confusing at first. That’s normal.
Why meet C now? Because C is the language that shows "what happens in memory" most raw. The things Python handled automatically for us, C leaves in our hands. That freedom is C’s power — and at the same time, the place where vulnerabilities like buffer overflow are born. The purpose of this unit is not to become a C master. Gaining "eyes that see memory" — that is the whole goal, and a sufficient one.
1. Learning Objectives
By the end of this chapter, you will be able to:
- Explain the difference between an interpreted language (Python) and a compiled language (C)
- Compile a C program with gcc on Linux and run it with
./ - Use the main function and printf’s format specifiers (%d, %s)
- Receive input with scanf, knowing the rule of attaching
&before the variable - Read line numbers from compile error messages and fix them yourself
2. Background Knowledge — Today’s Tools and Concepts
Today’s Tools at a Glance
| Category | Details |
|---|---|
| Language/environment | C language, Linux terminal (WSL Ubuntu), the gcc compiler (measured: Ubuntu 13.3.0) |
| Today’s syntax | #include <stdio.h>, int main(void), printf and format specifiers (%d, %s), scanf and &, the semicolon (;) |
| Concepts needed | Compilation (translation), executables, data types (int, char arrays), a variable’s address |
2-1. Interpreting and Translating — The Decisive Difference Between Python and C
Python is an interpreted language that reads and executes line by line. It’s the way an interpreter simultaneously translates each sentence. C is different. It translates the entire source code wholesale into machine language (the commands the CPU knows directly), makes an executable file, and runs that file.
This translation is called compiling, and the translator is a compiler. It’s like translating the whole book in advance and then reading it — execution is far faster. In exchange, you must retranslate every time you fix something.
2-2. gcc — The Most Famous Translator
The representative C compiler of the Linux world is gcc. gcc hello.c -o hello means "translate hello.c and make an executable called hello." It’s the command we’ll keep typing today.
2-3. main — The Program’s Front Door
A C program always starts at the main function. When a compiled executable wakes up, it looks for main first. Unlike Python, which flows from the topmost line, C’s door only opens if this front door exists.
2-4. printf and Format Specifiers — Fill-in-the-Blank Output
C’s output function printf takes "a sentence frame" and "values to insert" separately. In printf("Age: %d", age), %d is the blank, and age is the value that goes into it. %d means an integer and %s a string — these are format specifiers (markers of the blank’s shape).
The kind of blank and the kind of value must match. What happens when they mismatch — we experiment directly today.
2-5. Why We Do It on Linux
You can do C on Windows too, but the standard stage for security study is Linux. Touching it directly in that world, where the operating system and tools are made of C, is the most natural. The practice ahead is also based on Linux (Ubuntu).
3. Follow Along
From today, we work in a Linux terminal. Either WSL Ubuntu, entered by typing wsl in PowerShell, or VirtualBox Ubuntu — either is fine.
3-1. Installing and Checking gcc
Input (Linux terminal)
sudo apt install gcc -y
gcc --version
gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; ...
(Measured 2026-09-09. The version number may differ by environment.)
How to read it: sudo means "with administrator privileges," apt is Ubuntu’s app store, and -y means "proceed without asking." If you see the version, translator preparation is complete. If it’s already installed, the install step says it’s already the newest version.
3-2. Your First C Program — Hello, C
Input (create hello.c with the nano editor)
nano hello.c
When the editor opens, enter the following.
#include <stdio.h>
int main(void) {
printf("Hello, C!\n");
return 0;
}
How to save and exit: Ctrl+O (save), Enter (confirm), Ctrl+X (exit).
How to read it: the first line #include <stdio.h> is "bring the manual (header file) for printf." int main(void) is the front door, and between the braces {} is what to do. \n is a newline, and return 0 is the parting greeting meaning "ended well." And the semicolon (;) at the end of each statement — this thing Python didn’t have is C’s period.
3-3. Compiling and Running
Input
gcc hello.c -o hello
./hello
Hello, C!
(Measured 2026-09-09.)
How to read it: if gcc ends quietly, the translation succeeded and an executable called hello was created. The ./ in ./hello means "in the current folder." Linux won’t run a program in the current folder without this marker (check it yourself and you’ll get hello: command not found — covered in Wall 2).
Predict: what happens if you delete one semicolon from hello.c and compile again? Delete one on purpose. The error message shows a line number. The measured message is in Wall 1 of section 6.
Why: "write (nano) → translate (gcc) → run (./)" — these three beats are C’s daily rhythm.
3-4. Variables and Format Specifiers
Input (vars.c)
#include <stdio.h>
int main(void) {
int age = 20;
char name[] = "Lee";
printf("Name: %s\n", name);
printf("Age: %d\n", age);
printf("Age next year: %d\n", age + 1);
return 0;
}
Compile and run
gcc vars.c -o vars
./vars
Name: Lee
Age: 20
Age next year: 21
(Measured 2026-09-09.)
How to read it: in C, you declare the data type first when making a variable. int age is "integer box age," and char name[] is "character-sequence box name" (in C, a string is an array of characters). Values are inserted in order into printf’s blanks (%s, %d).
Predict: what happens if you put a string in the
%dspot? Will it compile? Will it run? Experiment yourself. The measured result is in Wall 4 — this experiment gives you the feel that "C trusts us."
3-5. Receiving Input — The First Appearance of scanf and &
Input (ask.c)
#include <stdio.h>
int main(void) {
int age;
printf("Enter your age: ");
scanf("%d", &age);
printf("Age you entered: %d\n", age);
return 0;
}
Compile and run
gcc ask.c -o ask
./ask
Enter your age: 20
Age you entered: 20
(Measured 2026-09-09. The 20 on the first line is input I typed on the keyboard.)
How to read it: scanf is printf’s opposite direction. It reads keyboard input matched to the blank (%d) and stores it in a variable. But an & is attached before the variable. It means "the address of age." You’re telling scanf "the address of the box to put the value in."
Why you must give the address is dealt with head-on in the pointer chapter. For now, memorize it as "attach & to scanf." This & is the seed of this entire unit.
3-6. Names Too — The All-in-One Input Program
Input (profile.c)
#include <stdio.h>
int main(void) {
char name[20];
int age;
printf("Enter your name: ");
scanf("%19s", name);
printf("Enter your age: ");
scanf("%d", &age);
printf("%s is %d years old.\n", name, age);
return 0;
}
Compile and run
gcc profile.c -o profile
./profile
Enter your name: Lee
Enter your age: 20
Lee is 20 years old.
(Measured 2026-09-09.)
How to read it: two things to note. First, name has no &. Because a character sequence (array) has a name that itself behaves like an address — for now, accept it as a rule. Second, the 19 in %19s is a safety device meaning "at most 19 characters." It means "I won’t accept input longer than the box (20 slots)" — this one character is a vaccine that blocks one famous vulnerability.
3-7. Turning On Warnings — Hiring a Strict Teacher
Attach the -Wall option to gcc and it becomes "show me all warnings."
Input (warn.c)
#include <stdio.h>
int main(void) {
int x;
printf("%d\n", x);
return 0;
}
Input
gcc -Wall warn.c -o warn
warn.c: In function 'main':
warn.c:5:5: warning: 'x' is used uninitialized [-Wuninitialized]
5 | printf("%d\n", x);
| ^~~~~~~~~~~~~~~~~
warn.c:4:9: note: 'x' was declared here
4 | int x;
| ^
(Measured 2026-09-09.)
How to read it: it compiled, but a warning appeared. It’s advice saying "you used x without putting a value in it — did you mean to?" This program spews a garbage value every run. An error is a translation failure; a warning is "translated, but suspicious." In C, treating warnings like errors is the habit of the skilled. From now on, let’s always compile with gcc -Wall file.c -o executable.
Why: C is a language that trusts us, so we must turn on the device that checks that trust ourselves.
4. Missions & Exercises
Mission — A Self-Introduction Card Program
Today’s work: make a self-introduction card program in C.
- Receive a name (string), age (integer), and favorite number (integer) in turn.
- Print the received content nicely. Example of the output shape:
=== Self-Introduction Card ===
Name: Lee
Age: 20
Favorite number: 7
In 10 years you'll be 30!
- Delete one semicolon on purpose and compile, practicing checking the line number in the error message and fixing it.
- Do at least one format-specifier experiment, like putting a name in the
%dspot, and write the result in your notes. - Store the finished .c file and the executable distinctly. The source is the .c; the artifact is the executable.
Exercises
Q1. Python can run right after writing, while C needs a step called compiling. What consequences does this difference create for "execution speed" and "the existence of an executable file," respectively?
Q2. On Linux, why does typing just hello for the program you just made give "command not found"? Explain both why ./ solves it and the security benefit this brings.
Q3. scanf takes not the variable’s value but its address (&age). Why is the value not enough? Explain from the perspective of "you have to go to the box to put something in it."
Q4. What is the 19 in scanf("%19s", name), and why not 20? And why is scanf("%s", name), which omits this number, dangerous? Explain from the perspective of "box size."
5. Model Answers & Completion Criteria
Mission Model Answer
#include <stdio.h>
int main(void) {
char name[20];
int age, fav;
printf("Name: ");
scanf("%19s", name);
printf("Age: ");
scanf("%d", &age);
printf("Favorite number: ");
scanf("%d", &fav);
printf("=== Self-Introduction Card ===\n");
printf("Name: %s\n", name);
printf("Age: %d\n", age);
printf("Favorite number: %d\n", fav);
printf("In 10 years you'll be %d!\n", age + 10);
return 0;
}
Compile and run (measured 2026-09-09)
gcc -Wall card.c -o card
./card
Name: Lee
Age: 20
Favorite number: 7
=== Self-Introduction Card ===
Name: Lee
Age: 20
Favorite number: 7
In 10 years you'll be 30!
How to verify: ① When compiled with -Wall, there must not be a single warning. ② When you delete a semicolon, error: expected ';' appears, and it’s enough if you can look at the line number and fix it. ③ It’s enough to have seen with your eyes that a strange number comes out in the format experiment — "C runs it even when it’s wrong" is that experiment’s harvest.
Exercise Solutions
Q1 solution. Python interprets every time it runs, so it starts fast but pays an interpretation cost per line. Once C has been translated once, execution afterward runs as machine language itself, so it’s far faster. And compilation leaves behind a tangible thing called an executable file — an object in the language the CPU knows directly, runnable even without the source.
Q2 solution. Because Linux looks for commands only in folders registered in PATH (the search path), and the current folder isn’t included there. ./hello explicitly says "the hello in the current folder." Security benefit: you don’t fall into the trap of a fake command with the same name planted in an unfamiliar folder.
Q3 solution. scanf must know "into which slot" to put the input value. Hand it a value (say, 20), and that’s just a number, not the box’s address — it can’t find its way to the box. Give it the address (&age), and scanf can go to that slot and put the value in.
Q4 solution. 19 is the limit "read at most 19 characters." The reason for accepting only up to 19 when the box has 20 slots is that the end of a C string always needs one more slot for an invisible terminator (the null character). Omit this number and input longer than the box overflows as-is, overwriting the neighboring memory — the basic form of the vulnerability called buffer overflow.
Completion Criteria Checklist
- [ ] I can explain the difference between an interpreted language and a compiled language
- [ ] I can compile with gcc and run with ./
- [ ] I can use printf’s format specifiers %d and %s
- [ ] I can explain why scanf needs & in terms of "address"
- [ ] I can find the line number in a compile error message and fix it
- [ ] I compiled and ran the self-introduction card program
6. Common Pitfalls & Fixes
Wall 1. Missing Semicolon — The #1 Beginner Error
Symptom: an error like this when compiling (measured 2026-09-09):
hello_err.c:4:26: error: expected ';' before 'return'
4 | printf("Hello, C!\n")
| ^
| ;
Cause: you left out the semicolon at the end of the statement. C doesn’t tolerate a sentence without a period.
Fix: look at the line number in the error message (line 4 here), and check the end of that line or the end of the line right above. It kindly even prints the ; marker for you.
Wall 2. Running Without ./
Symptom (measured 2026-09-09):
bash: line 1: hello: command not found
Cause: Linux doesn’t look in the current folder when searching the command path (PATH). It’s also a security design that blocks the fake-command trap in unfamiliar folders.
Fix: specify "the one in the current folder," like ./hello. Through this mistake, you feel in your bones why PATH (the environment variable from Step 51) exists.
Wall 3. Forgetting scanf’s &
Symptom: compiling with -Wall shows a warning like this, and running kills the program or makes it behave strangely (measured 2026-09-09):
noamp.c:5:13: warning: format '%d' expects argument of type 'int *', but argument 2 has type 'int' [-Wformat=]
5 | scanf("%d", age);
| ~^ ~~~
Cause: you gave scanf the variable’s value (int) instead of its address (int *). It’s like handing over "the number inside the box" instead of "the box’s address."
Fix: memorize the incantation "for scanf, & (except character arrays)." The warning message tells you exactly that it translated, but the kinds mismatched.
Wall 4. The Format Specifier and the Value’s Kind Mismatch
Symptom: you put a string in the %d spot, and after a compile warning, output like this appears (measured 2026-09-09):
fmtmix.c:6:14: warning: format '%d' expects argument of type 'int', but argument 2 has type 'char *' [-Wformat=]
(run output) -1509063356
Cause: the kind of blank (%d is an integer) and the inserted value (a string) differ. C "trusts" and runs it anyway, so a nonsense number comes out — the address where the string sits, read as if it were an integer.
Fix: match the kinds of blank and value. And remember this screen — "runs without an error" and "is correct" are different things.
Wall 5. Can’t Get Out of nano
Symptom: trapped in the editor, flustered.
Cause: nano’s shortcuts are written at the bottom of the screen, but at first you don’t see them. The ^ symbol means Ctrl.
Fix: Ctrl+O → Enter (save), Ctrl+X (exit). If you exit without saving, it asks "Save modified buffer?" — answer Y.
7. Summary
Today’s Concepts
| Concept | One-line description |
|---|---|
| Compiling | Translating the entire source wholesale into machine language |
| Executable file | Compilation’s artifact — a program in the language the CPU knows directly |
| main function | A C program’s front door — execution always starts here |
| Format specifier | The blank markers of printf/scanf (%d integer, %s string) |
| Error vs warning | Translation failure vs translated but suspicious — fix both |
Today’s Syntax and Commands
| Syntax/command | What it does |
|---|---|
gcc file.c -o name |
Translate C source and produce an executable |
./name |
Run the program in the current folder |
gcc -Wall ... |
Compile with all warnings on (default habit) |
printf("...%d...\n", value) |
Print with a value inserted in the blank |
scanf("%d", &variable) |
Receive input and store it via the variable’s address |
char name[20] + %19s |
Only 19 characters into a 20-slot box — overflow prevention |
The Instinct That Matters More Than Syntax
Three beats: "write, translate, run." The borderline between the world where you lived with only Python and the world after today is this "translate" step. Today you made a translated program for the first time, and that small executable is an artifact in the language the CPU knows directly.
One more. C is a language that "trusts the programmer completely," so accidents happen exactly as much as you err. As you saw with %19s today, the difference between "code that knows the box size" and "code that doesn’t" is exactly the difference between having a vulnerability and not. It’s syntax for now, but this syntax is the root of the whole security story ahead. And compile errors are not enemies but strict teachers who catch problems before execution — don’t fear error messages; make a habit of reading from the line number.
Once every box is checked, Step 56 is complete. Click the checkbox in the sidebar to save your progress.