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
203: The Difference Between Declaration and Definition
I’ve lost count of how many times I've seen developers hit a wall with the linker because they treat declarations and definitions as the same thing. In most high-level languages, you just "create a variable" and move on. In C, you have to be much more explicit about whether you are telling the compiler that something exists or you are actually creating it.
The "Header-Only" Trap
Imagine you're building a simple game and you want a global variable to track the player's score across multiple files. The naive approach is to put everything in a header file so every other file can see it. You might create a game.h like this:
// game.h
int player_score = 0;
At first glance, this feels efficient. You include game.h in main.c and combat.c, and suddenly both files have access to player_score. If you only have one source file, this works perfectly. But the moment you have two or more .c files including that header, the compiler does exactly what you told it to do: it creates a brand new integer named player_score in every single translation unit.
The compiler doesn't complain because, as far as it's concerned, each file is an isolated island. The disaster happens during the linking stage. The linker tries to smash all those object files together into one executable and suddenly finds three different variables all named player_score. It has no idea which one is the "real" one, so it throws a "multiple definition" error and gives up.
Promises vs. Reality
To fix this, we have to separate the declaration from the definition. I like to think of a declaration as a promise. When you declare something, you're telling the compiler: "Look, there is a variable named player_score of type int somewhere else in this project. I'm not giving you the memory for it right now, but trust me, it exists. Just let me use the name for now."
The extern keyword is how we make that promise. Here is the professional way to handle this:
// game.h
extern int player_score; // Declaration: "It exists somewhere!"
// game.c
int player_score = 0; // Definition: "Here it is. Allocate the memory."
Now, when main.c includes game.h, it sees the extern keyword and says, "Okay, I don't need to reserve space for this variable here; I'll just leave a placeholder." When the linker finally runs, it sees the placeholder in main.c and the actual memory allocation in game.c, and it simply connects the two. One definition, many declarations.
Where the Line Gets Blurry
You've probably already used this concept with functions without realizing it. When you include <stdio.h>, you aren't including the actual code for printf. You're including a function declaration (the prototype). The definition of printf lives inside the C standard library, which is linked into your program at the end.
The trade-off here is a bit of extra bookkeeping. You have to manage both a header and a source file. It feels like more work, but it's the only way to keep your binary size sane and your linker happy. If you define a variable in a header, you're bloating your object files with redundant data and inviting linker collisions. If you declare it with extern, you're creating a single source of truth.
📋 Practical Task
Fixing the Multiple Definition Collision in "ScoreTracker"
You have been handed a small project with two source files, main.c and logic.c, and one header file, shared.h. The code is failing to link because shared.h contains a definition for int current_level = 1; which is being included in both C files.
Your task:
- Modify
shared.hso thatcurrent_levelis only declared, not defined. - Modify
logic.c(or create a newshared.c) to provide the single definition forcurrent_level. - Ensure that both
main.candlogic.ccan still read and write to the variable without causing a linker error.
There are no comments for now.