thecodingidiot.com

Writing CYour First Program

Your First Program

Every C programmer has written this program. It does nothing useful. It is six lines — seven if you count the blank one. But it is the first time the toolchain runs end to end, the first time you write something the machine actually executes. Write it, compile it, run it.

#include <stdio.h>
 
int main(void)
{
    printf("Hello, world.\n");
    return (0);
}
gcc hello.c -o hello
./hello
Hello, world.

The rest of this page explains what just happened.


The compilation pipeline

Running gcc hello.c -o hello is not one step — it is four, run in sequence by the compiler driver:

You can stop at each stage and inspect the output:

gcc -E hello.c -o hello.i   # preprocessor output
gcc -S hello.c -o hello.s   # assembly
gcc -c hello.c -o hello.o   # object file
gcc hello.o -o hello        # linker: gcc calls ld with CRT startup filled in

The last line uses gcc, not ld. When you hand gcc an object file, it skips compilation and goes straight to linking — filling in the C runtime paths and library flags for you. Those paths are platform-specific and easy to get wrong, so you do not need to memorise them. In day-to-day work you will just pass .c files to gcc and let it run all four stages at once.

It is still worth stopping at each stage once, to see what the toolchain actually does on your behalf. Open hello.i. Near the bottom you will find your three lines of code, preceded by several hundred lines of declarations pulled in from stdio.h. That is what #include <stdio.h> actually does: it copies a header file into your source before the compiler sees it.

You do not need to understand all of that output now. The point is that compilation is not magic — it is a sequence of text transformations from source to executable, and you can inspect any intermediate step.


Anatomy of hello.c

#include <stdio.h> — a preprocessor directive. Tells the preprocessor to insert the contents of stdio.h (the standard I/O header) here. Without it, the compiler does not know what printf is.

int main(void) — every C program starts here. The operating system calls main when it launches your program. int is the return type — main returns an integer to the shell. void means it takes no arguments (when you have no interest in command-line arguments).

printf("Hello, world.\n") — a function call. printf is declared in stdio.h and defined in the C standard library, which the linker pulls in automatically. \n is a newline character — without it, the shell prompt would appear on the same line as your output.

return (0) — returns 0 to the shell. By convention, 0 means success. Any other value signals an error. The shell can inspect this with echo $? immediately after running your program.


Reading compiler errors

Remove the semicolon from the printf line and compile again:

    printf("Hello, world.\n")
    return (0);
gcc hello.c -o hello
hello.c:5:5: error: expected ';' before 'return'
    5 |     return (0);
      |     ^~~~~~

The compiler says the error is on line 5 (return), but the missing semicolon is on line 4 (printf). The compiler reports where it gave up, not always where the mistake is. When a line looks correct, check the line above.

Add the semicolon back. Compile again. The error disappears.

This error-read-fix loop is the core skill of working with a compiler. You will repeat it thousands of times. It gets faster.


Flags worth using now

Add -Wall -Wextra to every compile command from here on:

gcc -Wall -Wextra hello.c -o hello

-Wall enables most common warnings. -Wextra enables a few more. A warning is not an error — the program still compiles — but warnings are the compiler telling you that something looks wrong. Treat them as errors: fix every warning before moving on.

The tester compiles your programs with -Wall -Wextra. If your program compiles cleanly without those flags but produces warnings with them, the tester will flag it.