A compiler converts C code into machine code in 4 steps:

  1. Preprocessing (Convert all preprocessor instructions (#…) to C code.)
  2. Compiling (Convert C code to assembly.)
  3. Assembling (Compile the necessary libraries.)
  4. Linking (Merge the compiled code with the compiled libraries.)

Libraries

Libraries are pre-written collections of code that can be reused in other programs. On UNIX systems, they are usually located in the /lib/ and /usr/include directories.

Math.h

For example, math.h is very useful to implement complex arithmetic operations.

#include <math.h>
double A = sqrt(9);
double B = pow(2, 4);
int C = round(3.14);
int D = ceil(3.14);
int E = floor(3.99);
double F = fabs(-100);
double G = log(3);
double H = sin(45);
double I = cos(45);
double J = tan(45);

Using Libraries

To use a library, we first have to include it in the C code.

#include <cs50.h> // cs50.h library will be included.

Then, the library must be linked at compile time (unless it is part of the C standard).

gcc -o hello hello.c -lcs50
./hello

Optimization Flags

  • -O2 Optimize for speed
  • -O3 Optimize for speed aggressively
  • -Os Optimize for size
  • -Og Optimize for debugging
  • -Oz Optimize for size aggressively

Make

Make Is a build automation tool that automates the process of compiling, linking and building executables.
An example Makefile (for an app using GTK4) could be as follows:

CC = gcc
CFLAGS = $(shell pkg-config --cflags gtk4)
LIBS = $(shell pkg-config --libs gtk4)

hello: hello.c
	$(CC) $(CFLAGS) -o hello hello.c $(LIBS) -O2

debug: hello.c
	$(CC) $(CFLAGS) -o hello hello.c $(LDFLAGS) -Wall -Wextra -Og -g

clean:
	rm -f hello
make       # Compiles hello.c
make debug # Compiles hello.c using debug flags
make clean # Removes the executable (hello) generated by the make command.

Meson

While make automates compiling, on larger projects, makefiles can grow hard to read quickly, and dependency resolution has to be done using external tools (such as pkg-config).

Meson is a more modern alternative, which is faster, and uses declarative syntax (describe what to build, instead of how to build it). Meson can automatically manage dependencies, header files, and compiler flags.

When meson is executed, it generates a ninja.build file, which is then parsed by ninja, a lower-level build tool.

project('hello', 'c')

gtk4_dep = dependency('gtk4')

executable('hello', 'hello.c',
           dependencies: gtk4_dep)
mkdir build
meson setup build      # Generate files for Ninja in the build dir
meson compile -C build # Compile the project (same as `ninja -C build`)
./build/hello
ninja clean -C build   # Clean executable files in the build directory