C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
4: Compiling and Linking Explained
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.hfiles directly into your.cfiles 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
.ofiles. 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
.ofiles and any external libraries (likestdio) 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.hthat contains a function prototype forfloat calculate_distance(float x1, float y1, float x2, float y2). - Create a file named
geometry.cthat 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 thesqrtfunction. - Create a
main.cfile 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.
There are no comments for now.