Skip to content
VLSI Mentor

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:

  1. You compile. A compiler reads your .c file and, if it can, produces a new file you can run. Your source is not changed or consumed; a second file appears beside it.
  2. 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.

Four stages. First, a source file named hello.c that you wrote. Second, the compile command, which either produces an executable file or produces diagnostic messages and no executable. Third, the run command, which is a separate act performed on the executable. Fourth, the program's output in the terminal.Compile once, then run — two separate acts01THE SOURCE FILEhello.c — the one you wrote02COMPILE ITeither an executable appears, or complaints do — never both03RUN THE EXECUTABLEa separate act; the compiler is no longer involved04OUTPUTwhat the running program prints
Figure 1 — the two commands and the two moments. Compiling either produces an executable or produces complaints and no executable; those are the only two outcomes. Running is a separate act on the file that compiling produced, and the compiler plays no part in it.

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 seeWhat it isWhat you do with it
C sourceThe contents of a .c fileType it into a file and save it
A commandSomething you type at a terminal promptType it and press Enter
A diagnosticWhat the compiler saidRead it; never type it
Program outputWhat the running program printedRead 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:

Azvya Education Pvt. Ltd.VLSI Mentor
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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cc hello.c

cc 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cc -std=c17 hello.c -o hello

Without -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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cc -std=c17 -Wall -Wextra -Wpedantic hello.c -o hello
  • -Wall turns on a broad set of warnings about things that are legal C but usually mistakes.
  • -Wextra turns on more of them.
  • -Wpedantic asks 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
./hello

Output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
./hello
echo $?

Output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Hello, world!
0

Now 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
3

That 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:

Azvya Education Pvt. Ltd.VLSI Mentor
semi.c — deliberately broken
#include <stdio.h>

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

Compile it, and the compiler says:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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:

Azvya Education Pvt. Ltd.VLSI Mentor
typo.c — deliberately broken
#include <stdio.h>

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

The compiler says:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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:

Azvya Education Pvt. Ltd.VLSI Mentor
logic.c — compiles perfectly
#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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
2 + 3 = 6

The 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 failureWhen you find outWho tells you
The source is not valid CWhile compilingThe compiler refuses, with a diagnostic
The pieces do not fit togetherAfter compiling, while buildingA different tool, which Chapter 1.6 introduces
The program does the wrong thingWhile running, or much laterNobody. 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

  1. Compiling and running are two separate commands at two separate times; the compiler plays no part in running.
  2. Compiling either produces an executable or produces diagnostics and no executable — never both.
  3. A silent compiler means success. These tools speak only when something is wrong.
  4. -o names the output file, -std=c17 states which C you meant, and -Wall -Wextra -Wpedantic ask the compiler to report things it is allowed to ignore.
  5. ./name runs a program in the current directory, and $? holds the number it returned.
  6. 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.
  7. Diagnostic wording differs between compilers; the requirement is that a diagnostic exist, not what it says.
  8. 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
main.c:12:5: error: expected expression

Exercise 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: -o to name the output, -std=c17 to state which C you mean, -Wall -Wextra -Wpedantic to make the compiler report what it is otherwise free to ignore.
  • ./program runs it; $? reads back the number main returned — 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.