gcc vs clang and the compiler driver model
gcc and clang are the commands you type, but the command is a driver. It decides
which phases are needed, selects target defaults, invokes compiler frontends, chooses an
assembler/linker path, and adds runtime objects and libraries for hosted C. GCC and Clang
have different internals and ecosystems, but from the shell they expose the same mental
model: one driver command orchestrates many lower-level tools. When you pass -v or -###,
the driver stops being quiet.
The reset: do not picture
gcc main.cas one compiler blob. Picture a driver reading inputs and flags, then producing a concrete pipeline for your target.
How it really works
GCC is both a compiler collection and a driver interface. For C, the GCC driver normally
invokes preprocessing/compiler components such as cc1, an assembler such as as, and a
link step through collect2/ld. Clang is both a GCC-compatible driver and an LLVM-based
compiler frontend; its internal compile step often appears as clang -cc1, with LLVM IR and
LLVM code generation behind it. Clang commonly uses an integrated assembler, though it can
also use external tools.
The driver chooses phases from the inputs and options:
| Command shape | Driver work |
|---|---|
gcc -E main.c |
preprocess only |
gcc -S main.c |
compile to assembly |
gcc -c main.c |
compile and assemble to .o |
gcc main.o helper.o -o app |
link existing objects |
clang -target x86_64-unknown-linux-gnu -c x.c |
compile for a specific target triple |
Flags such as -I, -D, -O2, -g, -Wall, -Wextra, -std=c17, -fPIC, -shared,
-L, -l, and -Wl,option belong to different parts of the pipeline. The driver routes
them. -I and -D affect preprocessing. -O2, -g, and -std= affect compilation.
-L, -l, and many -Wl, flags affect linking. This is why the same command line can mix
language options, codegen options, and linker options without you invoking every tool by
hand.
Why the driver adds things
A hosted C program needs more than your object files. It needs startup code that enters the
C runtime, arranges argc/argv/environment state, calls initializers, calls main, and
exits correctly. It also needs libc and compiler runtime helpers for operations the target
does not inline directly. The driver knows which startup objects and libraries match the
target. Direct ld commands often fail because they skip that knowledge.
This matters for the OS project later. Hosted user-space code should usually let the driver
add defaults. Freestanding kernels do the opposite: use a cross-compiler and flags such as
-ffreestanding, -nostdlib, and a linker script because the hosted runtime is the wrong
runtime.
Executable artifact: compare drivers and inspect -###
The example lives in
examples/toolchain-and-linking/gcc-vs-clang-and-the-compiler-driver-model/. It builds the
same program through gcc and clang, then asks Clang to print a compile-only phase trace.
On this macOS host, /usr/bin/gcc is Apple clang in GCC-compatible-driver clothing, which is
itself a useful platform fact.
#include <stdio.h>
static int twice(int value) {
return value * 2;
}
int main(void) {
printf("driver model: %d\n", twice(4));
return 0;
}
Run it:
cd examples/toolchain-and-linking/gcc-vs-clang-and-the-compiler-driver-model
./run.sh
Real output from this machine:
== build with gcc driver ==
driver model: 8
== build with clang driver ==
driver model: 8
== gcc driver identity on this host ==
Apple clang version 15.0.0 (clang-1500.3.9.4)
Target: x86_64-apple-darwin24.6.0
== clang driver phase trace (-###, compile only) ==
"-cc1"
"-triple" "x86_64-apple-macosx14.5.0"
"-emit-obj"
-### prints the commands the driver would run without actually running them. The excerpt
shows the hidden -cc1 frontend invocation, the target triple, and the request to emit an
object file. On Linux with real GCC installed, gcc -v would show GCC's own cc1, as,
and linker path instead; the driver model is the same even when the implementation differs.
Failure modes & trade-offs
- Assuming
gccmeans GNU GCC everywhere. On macOS,/usr/bin/gccis commonly Apple clang. Always check--versionwhen behavior matters. - Sending a flag to the wrong phase. Linker flags often need
-Wl,; preprocessor flags do nothing at link time;-lfoodoes nothing during-c. - Calling
lddirectly. You skip startup objects, default libraries, library search decisions, and compiler runtime helpers unless you add them yourself. - Relying on perfect GCC/Clang compatibility. Clang accepts many GCC flags, but warning behavior, extensions, sanitizer support, inline assembly details, and diagnostics can differ.
- Cross-target confusion. The target triple controls ABI, object format, relocation types, default include paths, and library expectations.
- Freestanding vs hosted mismatch. A kernel build should not accidentally pull in the host C runtime; a normal app should not accidentally omit it.
In practice
- Use
-vwhen defaults matter. It shows include paths, tool paths, target choices, and linker invocation details. - Use
-###with Clang for a clean driver trace. It is especially useful when teaching or debugging phase routing. - Prefer the driver for ordinary links. Let
gcc/clangcall the linker unless you are deliberately writing a low-level link command. - Write portable warnings first.
-Wall -Wextra -Wpedanticand a chosen-std=help make GCC/Clang differences visible early. - Be explicit for OS work. Cross-compiler prefix, target triple,
-ffreestanding,-nostdlib, and a linker script are not decoration; they define the environment.
Connects to: Toolchain & Linking · The pipeline: preprocess → compile → assemble → link · Static linking and archives · Why read assembly · OS from Scratch
Sources
- GCC Manual — Overall Options —
-E,-S,-c,-v, and driver phase control. https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html - GCC Internals —
collect2— why GCC may wrap or augment the final linker invocation. https://gcc.gnu.org/onlinedocs/gccint/Collect2.html - Clang command guide — Clang driver options, target selection,
-###, and GCC-compatible behavior. https://clang.llvm.org/docs/CommandGuide/clang.html - LLVM documentation — Clang design — frontend/driver/LLVM architecture background. https://clang.llvm.org/docs/DriverInternals.html
- GNU
ldmanual — the linker the driver eventually configures on GNU-style systems. https://sourceware.org/binutils/docs/ld/ - OSDev Wiki — GCC Cross-Compiler — why freestanding OS work uses an explicit cross-toolchain instead of host defaults. https://wiki.osdev.org/GCC_Cross-Compiler