Learn C Programming

Lesson 6 of 8 · Meet C: Your First Programs, Symbol by Symbol

Module 1 · Meet C: Your First Programs, Symbol by Symbol

Compile and Run It Yourself: the Playground and a Local Toolchain

FreeReading

In this lesson

  • Run a program in the Playground, with input, and read its output pane.
  • Compile and run the same program on your own machine, if you want one.
  • Turn on -Wall -Wextra, read the warning it gives, and say what -E, -S, -c and -lm do.

The Playground is a real GCC in a sandbox on a machine in Singapore. A local toolchain is the same GCC on your own disk. Neither is the serious one; they are the same compiler in two places.

So this lesson is not an instruction to install anything. Everything in this track runs in your browser, forever. Read the second half when you want your own copy, and skip it happily until then.

The Playground: a real GCC, on somebody else's machine

Every example in this track has a Run link, which opens the Playground with the program already loaded. Four parts of that page matter.

The editor holds your program. The Run button sends it away, and a second later the output pane shows what it printed. The input box is where you type what the program reads, which is how every problem in this module is tested.

Your file is written to disk on that machine, compiled, and run inside a sandbox with a time limit and a memory limit. If your program waits for input that never comes, the time limit ends it, and that is a real result rather than a hang.

There is also a limit on how many runs a minute one visitor gets. A "too many requests" message means wait, not break.

So a Playground run is a compile and a run on a real machine. The one thing it hides from you is the command line.

The exact command the Playground uses

It is this, and it is pinned, which means you cannot change it from the page:

gcc -O2 -std=c17 -lm

Three things follow from it, and the third one is the one to remember.

-O2 asks for optimised machine code, so the program runs fast. -std=c17 is the standard this track teaches. -lm is already there, so the maths library is always linked for you.

And there is no -Wall on it. The Playground prints the messages GCC gives by default, and nothing more.

That has a consequence worth stating plainly. A message you see on your own machine may be silent in the Playground. An unused variable, a %s handed a number, an if with one =: all three are quiet there and all three are warnings under -Wall.

The reason is a rule rather than an oversight. The compiler options are fixed per language and the list of options a client may add is empty. Every reader gets the same toolchain.

So the Playground is the honest floor: if it builds there, it builds for your teacher too. Your own machine is where you go looking for more warnings than the floor requires.

Installing GCC, on three operating systems

Only if you want to. Five lines each.

Linux. Open a terminal. On Debian or Ubuntu run sudo apt update and then sudo apt install build-essential. On Fedora run sudo dnf install gcc. Check it with gcc --version. You are done.

macOS. Open Terminal and run xcode-select --install, then accept the dialog that appears. That installs the Command Line Tools, which include a compiler. Check it with gcc --version. On macOS that name actually runs Clang, which is fine for everything in this track.

Windows, the recommended way. Install WSL, the Windows Subsystem for Linux, by running wsl --install in PowerShell as administrator and restarting. Open the Ubuntu app it gives you and follow the Linux instructions above. You now have a real Linux toolchain beside Windows, which is what most professional C work uses.

Windows, without WSL. Install MSYS2 from its own site, open its UCRT64 shell, and run pacman -S mingw-w64-ucrt-x86_64-gcc. Add its bin folder to your PATH if you want gcc in other terminals. This is a real GCC, and one difference matters below.

So every path ends in the same place: a gcc you can type at a prompt.

The four commands you will actually type

Save a program as hello.c and you are four commands away from everything in this module.

  1. Compile. gcc -std=c17 -Wall -Wextra hello.c -o hello reads your file and writes a program called hello.
  2. Run. ./hello on Linux and macOS; hello.exe or .\hello.exe on Windows.
  3. Read the status. echo $? prints the number main returned, which lesson 1 promised was not decoration.
  4. Link the maths library. gcc -std=c17 hello.c -o hello -lm, needed on Linux whenever you call sqrt, pow or sin.

Two notes about that last one. On Linux the maths code sits in a separate library file. Leaving out -lm gives you undefined reference to 'sqrt' from the linker, which is Module 0, lesson 4 in action. On Windows with MSYS2, and on macOS, it links without the flag. The Playground passes -lm always, so the question never arises there.

So -Wall -Wextra is the habit, and -lm is the flag you will forget exactly once.

The stage flags: stopping the pipeline early

Module 0, lesson 4 described four stages: preprocessor, compiler, assembler, linker. Three flags stop after each of the first three, and they are the cheapest way to see that the stages are real.

FlagStops afterWhat you getIs it text?
-EThe preprocessorhello.i: your file with every header pasted inYes, and it is long
-SThe compilerhello.s: assembly for your CPUYes, and your strings are in it
-cThe assemblerhello.o: machine code, not yet a programNo, do not open it
noneThe linkerhello: a program you can runNo

Try gcc -E hello.c | wc -l once. Seven lines of yours become more than a thousand, which is stdio.h arriving. It explains why a missing header is a stage 1 failure.

One terminal session, five commands, with the output of each terminal $ gcc -std=c17 -Wall -Wextra hello.c -o hello no output at all: a clean build says nothing $ ./hello Order 15 boxes $ gcc -std=c17 -Wall -Wextra marks.c -o marks marks.c:6:9: warning: unused variable 'total' $ gcc -std=c17 diagonal.c -o diagonal -lm $ ./diagonal Diagonal: 7.071
Figure 1. Five commands and their real output. A build that prints nothing is a build that worked; the warning in the middle is the one Example 2 below explains.

Reading your first warning

A warning is the compiler saying "this is legal, and I do not believe you meant it". It never stops the build.

The three you will meet first, all of them under -Wall and all of them silent without it:

  • warning: unused variable 'total'. You declared it and never used it, which usually means an edit is half finished.
  • warning: format '%s' expects argument of type 'char *', but argument 2 has type 'int'. The placeholder and the value disagree, and this one usually ends in a crash.
  • warning: suggest parentheses around assignment used as truth value. The = and == trap from lesson 2, caught.

Treat every one as an error you have not been bitten by yet. Beginners who build with warnings on find bugs days earlier than beginners who do not, and professional projects turn warnings into errors on purpose.

So a clean build under -Wall -Wextra is a stronger claim than a clean build in the Playground, and that is the only way the two differ.

The gcc command line, part by part

gcc -std=c17 -Wall -Wextra hello.c -o hello
 |     |         |           |      |
 |     |         |           |      the name of the program to write
 |     |         |           your input file
 |     |         turn on the useful warnings
 |     pick the language standard
 the program that runs all four stages for you
  • Leave out -o hello and you get a file called a.out, or a.exe on Windows.
  • -std=c17 matters: the same file can be legal C17 and refused as C89.
  • -Wall -Wextra are two flags, not one. -Wall is not actually all of them.
  • Add -lm at the end, after your file, when you call a maths function on Linux.
Example 1: the program to compile first

Nothing new in the C. The point is the command: this file builds clean under -Wall -Wextra, so anything the compiler says to you later is about your edit.

#include <stdio.h>

int main(void)
{
    int physics = 37;
    int chemistry = 41;
    int total = physics + chemistry;

    printf("Total: %d\n", total);
    printf("Subjects: 2\n");
    return 0;
}
Total: 78
Subjects: 2

Save it as marks.c, build it with gcc -std=c17 -Wall -Wextra marks.c -o marks, and run ./marks. A clean build prints nothing, which surprises everybody once.

Run in Compiler
Example 2: a warning, and what it is worth

One line is added. The output does not change at all, and the compiler now has something to say, but only if you asked it to.

#include <stdio.h>

int main(void)
{
    int marks = 90;
    int total = 0;          /* declared, then never used again */

    printf("Marks: %d\n", marks);
    return 0;
}
Marks: 90

In the Playground: no message. With gcc -std=c17 -Wall -Wextra: warning: unused variable 'total' [-Wunused-variable] on the line that declares it.

It is a small warning with a large habit behind it. Nine times out of ten an unused variable is a line you meant to finish, and the tenth is a variable you should delete.

Run in Compiler
Example 3: the program that needs -lm

A maths function, a real answer, and the one flag beginners meet before they understand linking.

#include <math.h>
#include <stdio.h>

int main(void)
{
    double side = 5.0;

    /* sqrt is declared in math.h and lives in the maths library. */
    double diagonal = side * sqrt(2.0);

    printf("Diagonal: %.3f\n", diagonal);
    return 0;
}
Diagonal: 7.071

In the Playground this runs as written, because -lm is on the pinned command line. On Linux, gcc diagonal.c -o diagonal gives you undefined reference to 'sqrt' and gcc diagonal.c -o diagonal -lm does not. On macOS and MSYS2 it links either way.

Same source, same compiler, three different answers about one flag. That is the linker being a separate program, exactly as Module 0 said.

Run in Compiler

Where this is used

  • Every CI pipeline in the world. A build server runs these same commands with no human watching, and a warning-free build is often a merge requirement. The habit you form here is the habit the job needs.
  • The Linux kernel. Its build turns on a long list of warning flags and treats many as errors. At 30 million lines, a warning nobody reads is a warning nobody fixes.
  • The Progsity runner. The machine behind the Run button compiles your file with the pinned command line and runs it in a sandbox. Then it sends back what your program printed. Same GCC, someone else's disk.
  • Every Makefile you will ever read. A Makefile is mostly these flags, written down once so that nobody has to remember them, which is Module 15.

Common mistakes

1. Looking for a program called hello when you never named it.

gcc hello.c
./hello

The shell answers bash: ./hello: No such file or directory. There is no message from GCC, because it did what you asked: it wrote a.out. Add -o hello, or run ./a.out.

2. Forgetting -lm on Linux.

gcc -std=c17 diagonal.c -o diagonal

The linker says undefined reference to 'sqrt', and the compiler said nothing at all, because math.h promised the function exists. This is the clearest example in the whole track of stage 1 and stage 4 disagreeing.

3. Editing the file and running the old program.

./hello

No message, and the output is your previous version, which can cost you an hour of confusion. A source file is not a program. Compile again after every edit, or let a Makefile remember for you.

4. Assuming the Playground and your machine agree about warnings.

int n = 5;
printf("%s\n", n);

In the Playground: nothing, because the format check lives behind -Wall. Locally with -Wall: warning: format '%s' expects argument of type 'char *', but argument 2 has type 'int'. Both are the same GCC 12 behaviour under different flags, and the program is broken in both places.

Brain teaser

Bob compiles one file three times, changing only the flags, and gets three different results from the same source.

gcc -std=c17 quiz.c -o quiz
gcc -std=c17 -Wall -Wextra quiz.c -o quiz
gcc -std=c89 -Wall -Wextra -pedantic-errors quiz.c -o quiz

The file is Example 2 from this lesson with one change: the declaration of total is moved below the printf. Say what each of the three commands prints, and which one refuses to build. Then say which result Bob should trust.

Two of the three differences are about warnings, and one is about a rule that changed between two standards. Ask where C89 allowed a declaration to sit, and what that last flag does to a complaint.

Exercise 1Easy

Remove the warning from this program without changing a single character of its output.

#include <stdio.h>

int main(void)
{
    int marks = 90;
    int bonus = 5;

    printf("Marks: %d\n", marks);
    return 0;
}

What the compiler says. With -Wall -Wextra: warning: unused variable 'bonus'. In the Playground: nothing, so this exercise is one you do on your own machine or by reading carefully.

Two fixes, one right answer. Deleting the line removes the warning. Using the variable in the printf removes the warning and changes the output, which the task forbids. Decide which one the task allows, and say in one sentence why the other is worse.

Check yourself. Build with gcc -std=c17 -Wall -Wextra and see nothing at all. The output must still be exactly Marks: 90.

Run in Compiler
Exercise 2Medium

Write a program that prints 2 raised to the power 10 using pow from math.h, and add one comment that explains why a Linux build needs -lm.

Output. Exactly one line: 2 to the 10 is 1024.

Rules. Use pow(2.0, 10.0) and print it with %.0f, because pow returns a double. The comment must mention the linker, and it must not simply repeat the flag.

#include <math.h>
#include <stdio.h>

int main(void)
{
    /* Your one comment goes here, about the linker. */
    printf("2 to the 10 is %.0f\n", pow(2.0, 10.0));
    return 0;
}

Check yourself. It runs unchanged in the Playground. On Linux, build it once without -lm to see the linker error, then once with it. Reading that error on purpose is the point of the exercise.

Run in Compiler

Common doubts

  • Do I have to install a compiler to finish this track?

    No. Every lesson, every example and every graded problem runs in the Playground. Install one when you want to build a project of several files, which is Module 15.

  • WSL or MSYS2 on Windows?

    WSL, if you can. It gives you the same Linux toolchain that servers and CI use, so error messages and flags match what you read online. MSYS2 is lighter and perfectly good for this track.

  • Why can I not add -Wall in the Playground myself?

    Because the command line is fixed per language and the list of options a page may add is empty, on purpose. Everybody's program is compiled the same way, which is what makes a graded run fair.

  • Is Clang acceptable instead of GCC?

    Yes. It accepts everything in this track and its messages are often clearer. Keep in mind that the exact wording of a warning differs, so a message here may not match yours word for word.

  • Can I use an IDE instead of a terminal?

    Of course, and most professionals do both. Learn the four commands anyway: an IDE's build button is running them, and when it fails, the terminal is where you find out why.

Key takeaways

  • The Playground is a real GCC 12 in a sandbox, with the command line gcc -O2 -std=c17 -lm pinned.
  • There is no -Wall there, so a warning you see locally can be silent in the Playground.
  • On your own machine, gcc -std=c17 -Wall -Wextra file.c -o name is the habit to build.
  • A clean build prints nothing at all.
  • -E, -S and -c stop after the preprocessor, the compiler and the assembler.
  • -lm links the maths library, which Linux needs and the Playground already passes.

That closes Part Zero. Next comes the module's graded problem set, ten problems with hidden tests, and then the module test.

End of lesson 6

Mark it done, and your progress moves with you.

Next: Problems: First Programs