Skip to Content
Course content

4: Compiling and Linking Explained

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I want to start today with a scenario I've seen happen to almost every developer I've mentored. You've decided to be organized. Instead of putting 500 lines of code in one file, you've split your project into a few different files. It feels professional. Then, you run your build command and you're hit with a wall of text that looks like this:

undefined reference to `calculate_total_price'`
collect2: error: ld returned 1 exit status

Wait, what? You look at your code, and `calculate_total_price` is right there in shopping_cart.c. You didn't misspell it. The code is perfectly valid. This is the moment where most learners assume they've broken the compiler, but in reality, the compiler actually finished its job just fine. The problem is happening later, in a phase called linking.

The Dreaded 'Undefined Reference' Error

Let's look at the broken setup. You likely have two files that look something like this:

// cart.h
int calculate_total_price(int quantity, float price);

// shopping_cart.c
#include "cart.h"
int calculate_total_price(int quantity, float price) {
    return quantity * price;
}

// main.c
#include "cart.h"
int main() {
    int total = calculate_total_price(2, 19.99);
    return 0;
}

If you tried to compile this by running gcc main.c -o store, you'll get that "undefined reference" error. Here is why: when you run that command, GCC compiles main.c into machine code. It sees the function prototype in cart.h and says, "Okay, I trust that calculate_total_price exists somewhere. I'll leave a placeholder here and let the linker figure it out later."

But since you only told GCC to look at main.c, it never looked at shopping_cart.c. The linker searched your final binary for the actual logic of that function, found nothing but a placeholder, and panicked.

Telling the Linker Where the Code Lives

The fix is simple, but it requires understanding that compiling a program is actually a multi-stage pipeline. To fix the error, you need to include all the .c files in your build command:

gcc main.c shopping_cart.c -o store

Now, GCC compiles both files into "object files" (temporary files ending in .o) and then the linker stitches them together. The placeholder in main.o is now filled with the actual address of the code found in shopping_cart.o. Everything connects, and your program runs.

The Four Stages of the Build Pipeline

To really master C, you have to stop thinking of "compiling" as one single step. I think of it as a factory assembly line. If you know which stage is failing, you can fix bugs ten times faster.

  • Preprocessing: The preprocessor handles everything starting with a #. It copies the contents of your .h files directly into your .c files and expands macros. It's basically a glorified "find and replace" tool.
  • Compilation: This is where the actual "magic" happens. The compiler takes the preprocessed code and turns it into Assembly language. This is where syntax errors (like missing semicolons) are caught.
  • Assembly: The assembler converts that assembly code into Object Code (binary). These are the .o files. At this stage, the code is machine-readable, but it's incomplete—it doesn't know where functions in other files are located.
  • Linking: This is the final stage. The linker takes all your .o files and any external libraries (like stdio) and resolves the addresses. This is where the "undefined reference" errors happen.

Headers are Promises, Not Code

A common mistake is thinking that #include "cart.h" actually "imports" the code from shopping_cart.c. It doesn't. It only imports the declaration.

Think of a header file as a contract or a promise. By putting int calculate_total_price(int, float); in a header, you are telling the compiler: "I promise that a function with this exact signature exists somewhere in the project. Please let me call it for now, and I'll provide the actual implementation during the linking stage."

If you put the actual function logic (the body with the { }) inside a header file and include that header in multiple .c files, you'll run into the opposite problem: a "multiple definition" error. The linker will find two identical versions of the function and won't know which one to use. Keep your promises in the .h and your logic in the .c.




📋 Practical Task

Building a Multi-File Coordinate System

Your goal is to create a small program that calculates the distance between two points on a 2D plane, split across multiple files to practice the compilation and linking process.

Requirements:

  • Create a file named geometry.h that contains a function prototype for float calculate_distance(float x1, float y1, float x2, float y2).
  • Create a file named geometry.c that implements the logic for that function (you can use the Pythagorean theorem: sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1))). You will need to #include <math.h> for the sqrt function.
  • Create a main.c file that calls this function with four test coordinates and prints the result.

The Challenge:

Try to compile your program using gcc main.c -o distance first. Observe the linker error. Then, fix the command to include geometry.c.

Pro Tip: Because you are using the math.h library, you might see another "undefined reference" to sqrt. In C, the math library is not linked by default. You must add the -lm flag (Link Math) to the end of your command: gcc main.c geometry.c -o distance -lm.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.