C Part I · Module 1 · Understanding C and How Programs Work
Compiling and Running a C Program
Chapter 1.4 showed you program output without ever letting you produce it. This chapter closes that gap with two commands — one that turns your source file into something runnable, one that runs it — and with the distinction those two commands exist to teach: compiling and running are separate acts, happening at separate times, and merging them in your head is the cause of most early confusion about C errors. You will also deliberately break two programs, read what the compiler actually says about them, and meet the third kind of failure that no compiler will ever mention. Every command and every message here was run on a real compiler; none of it is illustrative.
Foundation13 min readCPart IFundamentalsToolchain
Part I · Module 1 · Chapter 1.5 · Compiling and Running a C Program
1. Two Acts, Not One
Chapter 1.1 established that your source file is not the program. Everything in this chapter follows from taking that literally.
There are two separate things you do, and they happen at two separate times:
- You compile. A compiler reads your
.cfile and, if it can, produces a new file you can run. Your source is not changed or consumed; a second file appears beside it. - You run. Later — a second later or a month later — you ask the machine to run that second file. The compiler is not involved. It does not need to be installed. It could have been deleted.
That separation is the single most useful thing to hold on to, because it tells you when something went wrong, and that almost always tells you what kind of thing went wrong.
2. Four Kinds of Block, and Why You Must Not Confuse Them
Everything in this chapter happens at a terminal — a window where you type commands and read what the machine says back, rather than clicking. This curriculum assumes you can open one on your system and does not teach that part; every operating system provides one.
From here on this curriculum shows you four different kinds of text, and mistaking one for another wastes real time. They are always marked:
| What you see | What it is | What you do with it |
|---|---|---|
| C source | The contents of a .c file | Type it into a file and save it |
| A command | Something you type at a terminal prompt | Type it and press Enter |
| A diagnostic | What the compiler said | Read it; never type it |
| Program output | What the running program printed | Read it; never type it |
If you are ever unsure which you are looking at, the language label on the block says so. Nothing in this curriculum expects you to work it out.
3. The Smallest Compile Command
Save the stage 2 program from Chapter 1.4 as hello.c:
#include <stdio.h>
int main(void)
{
printf("Hello, world!\n");
return 0;
}Now compile it. The smallest command that does the job is:
cc hello.ccc is the conventional name for "the C compiler installed here". On most systems it is either GCC or Clang — the two implementations Chapter 1.1 named — and you can usually type gcc or clang directly instead if you prefer to be specific.
If nothing is wrong, this command prints nothing at all. That silence is success, and it is worth getting used to: these tools speak up when there is a problem and stay quiet when there is not.
What it produced is a file, and by default it is given an unhelpful name (a.out on most systems). So we name it ourselves:
cc hello.c -o hello-o means "put the output in a file called this". Now there is a file called hello beside your hello.c.
Asking for the C you actually meant
Chapter 1.3 established that the version of C you compile against is a decision. This is where you express it:
cc -std=c17 hello.c -o helloWithout -std=, you get whatever version your compiler happens to default to, which varies between compilers and between versions of the same compiler. Stating it is how you stop your build depending on that.
Asking the compiler to tell you more
A compiler is required to complain about some things and free to stay quiet about many others. By default it uses that freedom generously. These options ask it not to:
cc -std=c17 -Wall -Wextra -Wpedantic hello.c -o hello-Wallturns on a broad set of warnings about things that are legal C but usually mistakes.-Wextraturns on more of them.-Wpedanticasks the compiler to complain when you use something that is not standard C — which, per Chapter 1.3, is how you find out you have relied on an extension.
This is the command this curriculum uses from now on. Every option in it has a reason, and you have now read all four.
4. Running It
Compiling produced a file. Running it is a separate command:
./helloOutput:
Hello, world!The ./ in front is not decoration. It means "the file called hello in this directory". Without it, most shells — the program reading what you type at the terminal — look for a program of that name in a list of standard locations instead, and yours is not in one of them.
Reading the number main returned
Chapter 1.4 claimed that return 0 is a real report to whatever started the program. Here is where you can see it. On a Unix-like shell, $? holds the number the last program returned:
./hello
echo $?Output:
Hello, world!
0Now compile and run the stage 4 program from Chapter 1.4 — the one whose main ends with return 3; — and the same two commands give:
3That number came from your source code, travelled through the compiler into the executable, and was reported when the program finished. Chapter 1.7 explains who is receiving it and why anything cares.
5. When It Does Not Compile
Now break it on purpose. This is not an aside — reading diagnostics is one of the actual skills of programming in C, and the way to acquire it is to cause a few errors deliberately, while you already know what is wrong.
Worked example 1 — a missing semicolon
Remove the semicolon after the printf line:
#include <stdio.h>
int main(void)
{
printf("Hello, world!\n")
return 0;
}Compile it, and the compiler says:
semi.c:5:30: error: expected ';' after expression
5 | printf("Hello, world!\n")
| ^
| ;
1 error generated.Read that in pieces:
semi.c:5:30— the file, the line, and the character position. Start here. Always.error— the compiler refused. No executable was produced.expected ';' after expression— what it wanted and did not find.- The two lines underneath show your line, a
^marking the position, and — helpfully — the character it expected.
One detail is worth noticing, because it will save you time later. The missing semicolon belongs at the end of line 5, and the compiler points at line 5. But it only discovered the problem when it read return on line 6 and found something that could not follow. A diagnostic's line number is where the compiler noticed a problem, which is usually but not always where you need to fix it. When a message seems to point at innocent code, look at the line above.
The fix is the semicolon, and then the compiler says nothing.
Worked example 2 — a misspelled name
Now misspell printf:
#include <stdio.h>
int main(void)
{
prinft("Hello, world!\n");
return 0;
}The compiler says:
typo.c:5:5: error: call to undeclared function 'prinft'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
5 | prinft("Hello, world!\n");
| ^
typo.c:5:5: note: did you mean 'printf'?This message is doing more than pointing at a typo, and it is worth unpacking because it confirms something from two earlier chapters.
The compiler does not say "no such function". It says undeclared — it has never been told that anything called prinft exists. Chapter 1.4 explained that #include <stdio.h> is what tells the compiler about printf; nothing told it about prinft, so it has no idea what that call should look like.
And notice the reference to ISO C99 and later. Older C let a compiler quietly guess at an unknown function. That was removed, so a conforming compiler now has to say something — which is precisely the kind of rule Chapter 1.3 was about. Whether your compiler calls it an error or a warning has varied across versions; either way, it tells you.
6. The Failure No Compiler Will Mention
Two things have gone wrong so far, and both times the compiler stopped you. Here is a third:
#include <stdio.h>
int main(void)
{
printf("2 + 3 = %d\n", 2 * 3);
return 0;
}Two small things here are new. The %d marks a place in the text where a number should go, and the 2 * 3 after the comma supplies it — Chapter 12.2 teaches this properly, and you need nothing more than that here.
Compiled with all four options above, the compiler says nothing whatsoever. Run it:
2 + 3 = 6The program is wrong. It claims to be adding and it is multiplying. There is no rule of C broken here — 2 * 3 is a perfectly valid thing to write — so there is nothing for any compiler to object to. It compiled because it is legal, and it is wrong because of what you meant.
This is the third category, and it is the one that matters most in real work:
| Kind of failure | When you find out | Who tells you |
|---|---|---|
| The source is not valid C | While compiling | The compiler refuses, with a diagnostic |
| The pieces do not fit together | After compiling, while building | A different tool, which Chapter 1.6 introduces |
| The program does the wrong thing | While running, or much later | Nobody. You, or a user |
Chapter 1.1 said "it compiled" is not the same as "it is correct". Line 5 above is what that sentence means in practice, and the whole of Module 13 exists because of the third row.
7. What You Should Be Able to Say Now
- Compiling and running are two separate commands at two separate times; the compiler plays no part in running.
- Compiling either produces an executable or produces diagnostics and no executable — never both.
- A silent compiler means success. These tools speak only when something is wrong.
-onames the output file,-std=c17states which C you meant, and-Wall -Wextra -Wpedanticask the compiler to report things it is allowed to ignore../nameruns a program in the current directory, and$?holds the number it returned.- A diagnostic gives you a file, a line, a category and a description — and the line is where the problem was noticed, which is not always where it must be fixed.
- Diagnostic wording differs between compilers; the requirement is that a diagnostic exist, not what it says.
- A program can compile cleanly and still be wrong, and nothing in the toolchain will tell you.
8. Exercise
Exercise 1 — classify without compiling. For each, say whether the compiler will refuse, warn, or say nothing at all — and why:
(a) A missing closing brace at the end of main.
(b) printf("Total: \n"); where you meant to print a number as well.
(c) retrun 0; instead of return 0;.
(d) A program whose main ends with return 1; when it succeeded.
Exercise 2 — read a diagnostic. Given this message, say which file, which line, which character, whether an executable was produced, and what the compiler was expecting:
main.c:12:5: error: expected expressionExercise 3 — explain the off-by-one. In worked example 1 the semicolon is missing from line 5 and the compiler reports line 5, but the text explains that it only noticed on line 6. Explain in two sentences why a compiler can end up reporting a line later than the one you need to edit, and what habit that suggests.
Exercise 4 — separate the two moments. You compile a program successfully, then edit the source file to print something different, then run the executable again — and the old message appears. Explain why, using §1, and say what you must do.
Exercise 5 — justify each option. Write out the full compile command from §3 and give a one-line reason for each of -std=c17, -Wall, -Wextra, -Wpedantic, and -o.
9. Summary
- Two commands, two moments. Compiling turns source into an executable; running runs that executable. They are independent, and knowing when something failed usually tells you what kind of failure it is.
- Silence is success. A compiler that says nothing has produced your program.
- Every option earns its place:
-oto name the output,-std=c17to state which C you mean,-Wall -Wextra -Wpedanticto make the compiler report what it is otherwise free to ignore. ./programruns it;$?reads back the numbermainreturned — the report Chapter 1.4 promised was real.- A diagnostic is a location, a category and a description. Read the location first, and remember it marks where the problem was noticed.
- Wording varies between compilers. The standard requires a diagnostic, not a sentence.
- The third kind of failure has no diagnostic at all. A program can be perfectly legal C and completely wrong.
You can now write, compile and run a C program, and read the compiler when it objects. What you cannot yet say is what that single cc command actually did — it is not one operation, and the middle table in §6 quietly referred to a tool you have not met. The next chapter opens the command up.
Standards & specifications
- Governing standard
- ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)
Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 are freely available and are what most engineers read day to day; a draft is not the published standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the C Programming curriculum.
