A Program Is Instructions in Memory

When you run a program, the operating system copies it from disk into your computer's memory and points the CPU at the first instruction. From there the CPU does one thing, over and over, billions of times a second: read the instruction at the current address, do what it says, move on to the next one.

That is the whole machine. Memory holds both the instructions and the data they work on, and everything a program does comes down to reading bytes from memory, writing bytes to memory, or doing arithmetic on bytes it has pulled into the CPU.

This course keeps that picture in front of you the whole way through, because C is unusual among modern languages in that it does not hide memory from you. Once you know where a value lives, how many bytes it occupies, and who is responsible for it, C stops looking like a pile of strange symbols and starts looking like an honest, fairly small description of what the machine is actually doing. That is why memory is our map.

Your First C Program

#include <stdio.h>

int main(void)
{
    printf("Hello, world!\n");
    return 0;
}

Save that as hello.c, compile it, run it, and you get:

Hello, world!

Six lines, and every one of them is doing a job. We will take them apart shortly, but first: what does "compile it" actually mean?

From hello.c to Something the Machine Can Run

hello.c is a text file, and the CPU cannot execute text. Getting from one to the other takes four stages, and gcc will stop after any one of them if you ask it to:

Stage Command What comes out
Preprocess gcc -E hello.c Your source with every #include and #define expanded, still C text
Compile gcc -S hello.c hello.s, the same program written in assembly for your CPU
Assemble gcc -c hello.c hello.o, an object file containing real machine code
Link gcc hello.c -o hello hello, a finished executable you can run

Preprocessing is pure text substitution, done before the real compiler ever sees your file. The line #include <stdio.h> is replaced by the entire contents of the stdio.h header, which is how a six-line file becomes hundreds of lines of preprocessed output.

Compiling is where your C is checked and translated into instructions for your particular CPU. Assembling turns those instructions from text into the numbers the processor decodes. Linking stitches your object file together with the C standard library, so that your call to printf (a function you did not write) ends up pointing at code that genuinely exists. That is also why linker errors read differently from compiler errors: undefined reference to 'helper' means the compiler was perfectly happy, but nobody could find the function's body.

In day to day work you run all four stages with one command:

gcc -std=c17 -Wall -Wextra hello.c -o hello

and the editor in this course does exactly that for you in a single step.

The Anatomy of main

#include <stdio.h> asks the preprocessor to paste in the declarations for the standard input and output library. Without it, the compiler has never heard of printf. C assumes nothing: you include what you use.

int main(void) is where execution begins. Every C program has exactly one main, and both of those words deserve attention. int is the return type: main hands an integer back to the operating system when it finishes, so never write void main().

(void) says this function takes no arguments, and in C that is not the same as leaving the parentheses empty. int main() means "unspecified parameters", which switches off argument checking rather than promising there are none. (In C++ the two forms mean the same thing, which is why you may have seen int main() written elsewhere.) In C, write int main(void).

The braces mark the body. Statements inside run top to bottom, each one ending in a semicolon.

return 0; is the value handed back to the operating system, where 0 means success and anything else means something went wrong. Reaching the closing brace of main returns 0 for you, and main is the only function in C that gets that free pass, but write the return out anyway so a reader can see you meant it.

Printing with printf

printf is short for "print formatted", and the name is the important part. Its first argument is not simply text to display, it is a format string. printf copies that string to the output character by character until it reaches something with a special meaning.

You have already met one special thing: \n is an escape sequence standing for a single newline character. printf never adds a newline on its own, so leaving it off means the next thing printed continues on the same line:

#include <stdio.h>

int main(void)
{
    printf("Memory is the map. ");
    printf("C is the vehicle.\n");
    return 0;
}
Memory is the map. C is the vehicle.

Two calls, one line of output, because only the second one ends the line.

The other special thing is a conversion specifier: a placeholder that printf fills in from the arguments you pass after the format string. %d means "an int goes here":

#include <stdio.h>

int main(void)
{
    printf("Six sevens are %d\n", 6 * 7);
    return 0;
}
Six sevens are 42

The specifier has to match the type of the value you give it. %d is for int, and you will meet the specifiers for other types as you meet the types themselves. One habit to start now: the format string should always be a literal you wrote yourself. Text that arrived from a file or a user belongs in an argument, never in that first slot, because a format string you did not write is a genuine security hole with a name (the format string vulnerability).

Reading Your First Compiler Warning

The next program is wrong. It is here so you can watch the compiler catch it.

#include <stdio.h>

int main(void)
{
    printf("The answer is %d\n");
    return 0;
}

The format string promises an int with %d, but no int is passed. Compiled with -Wall, gcc says:

hello.c: In function 'main':
hello.c:5:28: warning: format '%d' expects a matching 'int' argument [-Wformat=]
    5 |     printf("The answer is %d\n");
      |                           ~^
      |                            |
      |                            int

Look at how much is in there: the file, the line, the column, your own source line reprinted, a caret pointing at the exact specifier that is unsatisfied, and the name of the check that found it. That is a bug report, filed automatically, before you ran anything.

And notice that it is a warning, not an error. The program still compiles, still links, still runs, and prints "The answer is" followed by a number. That number is not a leftover zero or a lucky guess. Reading an argument that was never passed is undefined behaviour, which means the C standard places no requirement whatsoever on what the program does. Three consecutive runs of this program printed -259776504, -304741432, and -867562248. It could just as well have printed the same wrong number every time for a year and then broken on the day you edited an unrelated line. Undefined behaviour does not mean "it crashes" and it does not mean "it prints garbage": it means no promises, and it is the phrase you will see most often in this course.

So build the habit immediately: a warning is the compiler telling you where the bug is, and warning-free is the only acceptable state for your code. Everything you write on this platform is compiled with:

gcc -std=c17 -Wall -Wextra -O2 -fsanitize=address

-Wall and -Wextra turn on the diagnostics you just saw, and -fsanitize=address adds checks that run alongside your program and catch memory mistakes in the act. They are on for a reason. Let them do their job.

Key Takeaways

  • A program is instructions and data sitting in memory, executed one at a time by the CPU. C keeps that picture visible, which is why memory is this course's map.
  • gcc turns hello.c into an executable in four stages: preprocess (-E), compile (-S), assemble (-c), and link (no flag). One gcc command runs all four.
  • #include <stdio.h> must come before you use printf. C knows nothing you have not included.
  • Write int main(void). In C, empty parentheses mean "unspecified parameters", not "no parameters".
  • Write return 0; at the end of main to report success, even though C would supply it for you.
  • printf's first argument is a format string: \n is a newline, %d is a placeholder for an int, and printf adds no newline of its own.
  • A conversion specifier with no matching argument is undefined behaviour, and -Wall reports it with the file, line, and column. Treat every warning as a bug you have not tripped over yet.