the linker (the program that links the .o and libraries)

gcc main.o functions.o -o executable

The GCC linker does not first read main.o in cascade.

The linker processes all object (.o) files and libraries in the order they appear on the command line, and then performs a single complete pass to resolve all symbols.

  1. Adds to a pending list all symbols that main.o calls but does not define
  2. Process functions.o: Add your defined symbols and use that definition to resolve the pending call that came from main.o
  3. Process Libraries: Finally, it automatically links with the standard C library (libc) to resolve functions such as printf or malloc

The linker does not operate in a sequential “cascade” of execution as a program would

Its function is to unify the memory map

The key is that it must resolve each symbol it is named (e.g. xxxx) by finding its definition somewhere in the list of files you gave it (.o or libraries)

Linking order is vital, especially when working with external libraries or object files


General Rule (One-Way Dependence)

If an object file A.o calls a function defined in B.o, the recommended practice (and often required by many linkers) is to put A.o before B.o on the command line.

  • if main calls a function in functions)
    • gcc main.o functions.o -o prog

The linker reads what is needed, not what is executed.

Since main.c is the starting point, it is usually placed first in the object file list.

processes inputs from left to right

  • is wrong
    • gcc -lm file.o -o exe
    • Since it hasn’t read any code from your program (file.o) yet, it has no pending symbols to resolve
    • Simply discard the library and move on to the next file
      • The linker reads your object file. It finds a function call (e.g., sin() or cos()) and marks it as a pending external symbol
      • The linker has already passed the -lm library (where sin() resides) and will not go back. Since the pending symbol (sin()) was not found in any subsequent object file, the link fails with an “undefined symbol” error
  • is correct
    • gcc file.o -lm -o exe
      • process file.o: The linker reads your object file. It finds the call to sin() and marks it as a pending external symbol
      • Process -lm: The linker opens the libm library. Since it now has pending symbols (such as sin()), it searches libm for the binary code for the sin() function, appends it to the executable, and marks that symbol as resolved
      • Success: All symbols are resolved, and the linking ends successfully

The golden rule is: The library should always come after the code that calls it

Code (which depends) → Library (which defines)

The linker (invoked by GCC) has a clear goal: to resolve all undefined symbols (functions or variables that are called but whose actual code is in another file) and unify everything into a binary executable file.

Linker Input: The linker takes object files (.o) and binary libraries.

Linker output: The linker always produces an executable file (or a shared/dynamic library, but not a .o)


There are 2 phases when creating a C executable:

  • compilation
  • linking

There are 2 necessary phases and one does not include the other


Do I have to put all the .o files in gcc to generate the executable?

To create the final executable program, you must include all the .o files that contain the code implementations you used.

To create the final executable program, you need to include all the .o files that contain the code implementations you used.

gcc I must indicate the .o in order, all the files, if the project is huge the gcc command becomes huge

When you use GCC to link object (.o) files, you must tell it all the .o files that contain the code you’re using, and the order is crucial when it comes to libraries. If the project is huge, the GCC command line will become huge if you try to do everything manually.

To create the final executable, the linker needs all the object files that define the functions called in your code.

If main.o calls a function in functions.o, and functions.o calls a function in networking.o, you need to pass all three to the gcc command.

The Rule of Order (Bookstores)

As discussed above, the linker processes from left to right in a single pass.

This is especially important for libraries (.a or .so) that are indicated with the -l flag (such as -lm for the math library).

Principle: Code that calls functions must appear before the library that defines those functions.

Example: gcc main.o networking.o -lm -o prog (The code needs -lm, so -lm goes at the end).

If the project is huge (with 100 or more .c files), writing and maintaining a gigantic gcc command is inefficient, error-prone, and impractical.

The standard solution for managing the compilation of large projects is to use automation systems, the most common and traditional being the Makefile (using the make tool).

A Makefile is a text file that defines the dependencies and commands for building your project. Instead of typing a long command each time, you simply type make.

Disadvantage: Makefiles are tightly tied to the operating system and compiler you’re using. If you want to compile your project on Windows, macOS, and Linux, you’ll need to write and maintain three different Makefiles.

cmake

Advantage (Cross-Platform): CMake doesn’t compile the project directly. Instead, it parses your script (CMakeLists.txt) and automatically generates the native build system you need:

  • On Linux, it generates Makefiles.
  • On Windows, it generates Visual Studio project files.
  • On macOS, it generates Xcode project files.

CMake is more advanced because it solves the portability problem that Makefiles present.

Modular Compilation: Compile only the .c files that have been modified, saving time (e.g., if you only changed functions.c, just recompile functions.c to functions.o).

Automated Linking: Generate a list of ALL necessary .o files and pass them to the gcc command for final linking, ensuring the correct order and inclusion of all libraries.

In a real project, you don’t write a gigantic gcc command; you delegate that task to the Makefile or more modern build systems (like CMake).

The linker first looks for the function call (the “undefined symbol”) in a .o file, and then later finds the binary code that defines it (in a .o or a library).

Therefore, in real projects, this task is never done manually. A Makefile or advanced build tools (such as CMake or Meson) are used to automatically manage the list of .o files and link dependencies for you. The build tool takes the complexity of manual linking off your plate (for example: otherwise you should keep gcc commands very long).


Or can I put a .o, another . and have it generate another .o?

You can’t simply join two .o files together to create a third .o file; the linker is designed to create the executable.

You can’t simply join two .o files together to create a third .o file; the linker is designed to create the executable.

You can’t just put A.o and B.o and expect the linker to produce C.o. If you wanted a third object file, you’d have to compile a third source file: gcc -c C.c -o C.o

If you run gcc -c standalone_file.c -o standalone_file.o, the result is an object file (.o)


What happens if a .c does not use another .c, is it a final executable or a .o?

The compiler creates the .o without any problems, since it has no external symbols to resolve (apart from the standard library functions, which are assumed)

If you run gcc standalone_file.c -o final_executable, the result is an executable file.

gCC compiles it to a temporary .o and then calls the linker.

Checking for main symbol: The linker looks for the main() function in that .o

If you have a .c file that doesn’t require another .c file, the C compiler will directly create an executable binary, as long as that .c file contains the main() function.

An .o file is only created if you explicitly tell GCC not to link using the -c flag:

gcc -c my_single_file.c -o my_single_file.o

In this case, you’re forcing the process to stop after compilation and before linking. This .o will contain the main() code, but it won’t be executable until it’s passed to the linker to resolve the libc dependencies.


stdio is an executable or .o?

stdio (for stdio.h) is not an executable nor a .o file

stdio.h: It is just a header file that contains the declarations (function prototypes) of the C Standard Library (such as printf)

Implementation: The code for functions declared in stdio.h is already compiled and packaged into the operating system’s Standard C Library (libc).

The linker automatically searches this libc to find the binary implementation of printf, scanf, etc., and connects it to the final executable.