The C Language and the GCC Compiler

In this post I explain the C language, the main GCC build stages, and useful GCC flags.
TopicsC

The C Language: A Short Overview

The C programming language is one of the most popular and widespread languages. It is a compiled general-purpose language with static typing. It was developed in 1969-1973 at Bell Labs by Dennis Ritchie.

C is often called a “middle-level” or even “low-level” programming language. It combines high-level language constructs with assembler-like performance and control. It works close to the hardware, which makes low-level data manipulation possible while still giving you structured program flow.

C was originally created for writing the Unix operating system. Later its main area became systems programming: operating systems, drivers, utilities, antivirus tools, and similar software. A large part of Linux is written in C.

C is a compiled language. A compiler translates source code into an executable file made of machine instructions. Platforms differ, so you cannot simply take a compiled program from one platform and run it on another. At the source-code level, though, C programs are portable. Compilers, libraries, and development tools exist for almost every common platform, which means the same source code can be compiled for many targets.

Main Features of C

  • Portability: the same source code can be compiled on almost any platform, assuming a suitable compiler exists.
  • High execution speed.
  • Compact output binaries.

What GCC Is

GCC (GNU Compiler Collection) is a set of compilers and related tools. It includes frontends for C, C++, Objective-C, Fortran, Ada, Go, and other languages. In practice the gcc command is a driver that controls the build stages.

When you run gcc main.c -o app, the driver launches the preprocessor, compiler, assembler, and linker in sequence. You can control these stages with flags: -E for preprocessing only,-S to stop after assembly output, -c to stop at the object file, and then the final link step produces an executable.

GCC is valued for portability, a mature ecosystem, and very detailed documentation. If you need the exact semantics of any flag, the official manual is the right place to check.

Official GCC Documentation

Installing GCC

macOS

brew update
brew install gcc
gcc --version

If Homebrew is not installed yet, install it first from the official website: brew.sh.

Linux

sudo apt update
sudo apt install build-essential gcc gdb make
gcc --version

The build-essential package installs the basic toolchain for building C/C++ on Ubuntu/Debian, including gcc, g++, make, and the libc headers.

Documentation and packages: build-essential in Ubuntu Packages, gcc in Ubuntu Packages.

Windows

On Windows the most practical way to build native C code with GCC is to use MSYS2 together with the MinGW-w64 GCC package.

# Open the "MSYS2 UCRT64" terminal
pacman -Suy
# if the terminal closes after the update, open it again and repeat
pacman -Suy
pacman -S --needed mingw-w64-ucrt-x86_64-gcc
gcc --version

Windows installation references: GCC binaries (official GCC page), MSYS2, Updating MSYS2, MinGW-w64: MSYS2 (GCC), the mingw-w64-ucrt-x86_64-gcc package.

Your First C Program

Create a file named hello.c:

#include <stdio.h>

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

Compile and run it:

gcc hello.c -o hello
./hello

The -o hello flag sets the output filename. Without it, GCC usually producesa.out by default.

Useful GCC Flags

A practical baseline command for a C project looks like this:

gcc main.c -std=c23 -Wall -Wextra -Werror -pedantic -O3 -o app

Language Standard

  • -std=c23 compiles according to the ISO C23 standard.
  • Common alternatives are -std=c99, -std=c11, and -std=c17.
  • If you need the GNU dialect with extensions, use variants such as -std=gnu11 or-std=gnu17.

Documentation: C Dialect Options.

Warnings

  • -Wall enables a large set of basic warnings, though not literally every possible one.
  • -Wextra adds extra checks on top of -Wall.
  • -Wpedantic warns about constructs that go beyond the selected C standard.
  • -Werror turns warnings into errors, which is useful in CI so technical debt does not accumulate.

Documentation: Warning Options.

Optimization

  • -O0 means no optimizations, which is convenient for debugging.
  • -Og is a compromise: optimizations that interfere less with debugging.
  • -O2 is a practical default for production builds.
  • -O3 is more aggressive. Sometimes it is faster, sometimes not. You have to measure.
  • -Os optimizes for binary size.

Documentation: Optimize Options.

Debugging

  • -g adds debug information for gdb and other debuggers.
  • A common local-development combination is -Og -g.

Documentation: Debugging Options.

Build and Link Flags

  • -c compiles into an object file without linking.
  • -o file sets the output filename.
  • -Ipath adds a header search path.
  • -Lpath and -lname control the library search path and the library name during linking.

Documentation: Overall Options, Directory Options, Link Options.

Build Stages

GCC goes through several stages: preprocessing, compilation, assembly, and linking. You can inspect them separately:

gcc -E main.c -o main.i   # preprocessor
gcc -S main.i -o main.s   # C -> ASM
gcc -c main.s -o main.o   # ASM -> object
gcc main.o -o app         # linking

Summary

C gives you precise control over a program, and GCC gives you a reliable toolchain for building and debugging it. A good baseline habit is to compile with warnings enabled (-Wall -Wextra -Werror -pedantic) and fix them immediately.