Build a C Program with GCC 13, Stage by Stage
Most GCC invocations run preprocess, compile, assemble and link in one silent blur, which is exactly why a build failure is hard to place. This guide uses the installed x86_64 GCC 13 driver to build and run a small C program, then repeats it while stopping after each stage on purpose.
The route
Jump straight to the step you need, or tick off Done means at the end.
These examples use x86_64-linux-gnu-gcc-13 from GCC 13.3.0, packaged here as gcc-13 13.3.0-6ubuntu2~24.04.1. Allow about fifteen minutes. You need a shell, the compiler package and a writable working directory. No command in this guide needs sudo.
Checkpoint
Keep your source and generated files in a new directory. The commands below overwrite named outputs when shell redirection or -o is used, so do not point them at valuable files until you have checked the paths.
1. Confirm the compiler and target
Start with read-only checks. The version matters, because GCC options and defaults can differ between releases:
$ command -v x86_64-linux-gnu-gcc-13
/usr/bin/x86_64-linux-gnu-gcc-13
$ x86_64-linux-gnu-gcc-13 --version | head -2
x86_64-linux-gnu-gcc-13 (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
Copyright (C) 2023 Free Software Foundation, Inc.
The unprefixed gcc name may select a different installed compiler. Use the versioned target name when you need this exact one: the manpage describes the prefixed names as the normal form for cross-compiling or selecting a specific version.
2. Create a minimal C source file
Make a file named hello.c in your working directory:
#include <stdio.h>
int main(void)
{
puts("hello from GCC 13");
return 0;
}
The .c suffix tells GCC to treat the input as C source needing preprocessing. Keep the extension accurate: pass an unrecognised suffix and GCC treats the file as an object file for linking rather than source to compile.
Compile and link it into an executable with explicit output naming:
$ x86_64-linux-gnu-gcc-13 -Wall -Wextra -std=c17 -O2 -g hello.c -o hello
$ ./hello
hello from GCC 13
-Walland-Wextrarequest useful warning groups, but do not turn every possible warning into an error.-std=c17selects the C17 language dialect.-O2enables a common optimisation level.-gadds debugging information.
These switches shape the resulting program, so record them in your build system rather than relying on a remembered command.
Checkpoint
Verify the output type without running anything as root:
$ file hello
hello: ELF 64-bit LSB pie executable, x86-64, ...
The exact file wording varies. What matters is that the command succeeded, the file is an x86-64 executable, and running it produced the expected line.
3. Stop after preprocessing
GCC normally performs preprocessing, compilation, assembly and linking in that order. Use -E to stop after preprocessing and write the result to a named file:
$ x86_64-linux-gnu-gcc-13 -std=c17 -E hello.c -o hello.i
$ head -12 hello.i
# 0 "hello.c"
# 0 <built-in>
...
The output is preprocessed C, so headers and macros may make it much longer than the original. It is useful when diagnosing an include path, conditional compilation or macro expansion. It is not an executable and should not be passed to a shell.
Do not confuse -E with a syntax-only check: it asks GCC to produce preprocessor output. For a check that parses the source without producing an object or executable, use -fsyntax-only:
$ x86_64-linux-gnu-gcc-13 -std=c17 -Wall -Wextra -fsyntax-only hello.c
$ echo $?
0
4. Stop before assembly or linking
Use -S to compile the source into assembler text without assembling it:
$ x86_64-linux-gnu-gcc-13 -std=c17 -O2 -S hello.c -o hello.s
$ file hello.s
hello.s: assembler source, ...
Use -c to compile and assemble without linking:
$ x86_64-linux-gnu-gcc-13 -std=c17 -Wall -Wextra -c hello.c -o hello.o
$ file hello.o
hello.o: ELF 64-bit LSB relocatable, x86-64, ...
An object file is an intermediate input for a later link. This separation is the normal pattern for projects with several source files:
$ x86_64-linux-gnu-gcc-13 -c first.c -o first.o
$ x86_64-linux-gnu-gcc-13 -c second.c -o second.o
$ x86_64-linux-gnu-gcc-13 first.o second.o -o program
The final command performs the link. Library options such as -lNAME are order-sensitive, so put an object that needs a library before the corresponding library option.
Warning
Do not add -static, -nostdlib or similar linker changes casually. They alter runtime dependencies and can leave an otherwise correct source unable to link.
5. Inspect what GCC would invoke
When a build fails, the driver can hide the individual compiler, assembler and linker commands. Add -### to print them without executing them:
$ x86_64-linux-gnu-gcc-13 -### hello.c -o hello 2>&1
Using built-in specs.
COLLECT_GCC=x86_64-linux-gnu-gcc-13
...
/usr/libexec/gcc/x86_64-linux-gnu/13/cc1 ...
as --64 ...
... collect2 ...
The paths and temporary names are host-specific: treat this as diagnostic output, not a command to copy blindly. If you need the commands to actually execute with verbose progress, use -v instead. Both options can expose local paths and build settings, so review the output before pasting it into a bug report.
6. Handle the common mistakes
- Missing header: check the spelling and the include search path first.
-I/path/to/includeadds a directory for header lookup; it does not install a missing development package. The manual distinguishes-Ifrom-isystem, which also marks headers as system headers and changes warning treatment. - Undefined reference at link time: check whether the required object or library is present and whether the library appears after the object that uses it. A missing
mainoften means you asked GCC to link a library or object that is not a complete program; use-cwhen you intentionally want an object file without an entry point. - A warning appears only after changing options: compare the full command line. GCC accepts options and file names in mixed order, but repeated options and library placement can still matter. Run
x86_64-linux-gnu-gcc-13 --helpor read the installed manpage when an option's default is unclear; do not infer it from another GCC release.
To undo this guide's generated state, remove only the files you created after checking the path:
$ rm -- hello hello.i hello.s hello.o
Warning
That deletion is irreversible. Keep hello.c if you want to repeat the build, and never use a broad wildcard in a directory containing unrelated work.
Done means
- Versioned driver confirmed: it reports GCC 13.3.0 and the intended x86-64 target.
- C17 build works: the source builds with
-Wall -Wextra, runs, and produces the expected output. - Stages checked individually:
-E,-Sand-ceach produce the expected intermediate kind. - Pipeline visible: you can inspect it with
-###without executing anything. - Clean workspace: generated files are kept in a disposable directory, and no build command required elevated privileges.