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
68: Macros and #define
Up until now, we've been hard-coding values or using variables for everything. But as you start building larger projects, you'll find yourself repeating the same magic numbers or writing the same tiny, one-line formulas over and over. That's where the preprocessor comes in, specifically with #define.
Think of a macro not as a variable, but as a "find and replace" instruction for the compiler. Before your code is even compiled into machine instructions, the preprocessor scans your file and swaps every instance of your macro name with the value you defined. It's fast, it's lightweight, and it can be dangerous if you aren't careful.
Setting up our physics constants
Let's imagine we're writing a small physics module for a 2D game. We need the value of PI and a gravity constant. I could use a const float, but in C, using #define for global constants is a standard pattern because it doesn't occupy any memory at runtime.
#include <stdio.h>
#define PI 3.14159
#define GRAVITY 9.81
int main() {
float radius = 5.0;
float area = PI * radius * radius;
printf("The area of the circle is: %f\n", area);
return 0;
}
Simple enough. The compiler just sees 3.14159 * radius * radius. No variable lookup is required.
Adding a shortcut for squaring
Since we're doing physics, we're going to be squaring numbers constantly. Writing x * x is fine, but I want something that looks cleaner. I'll create a function-like macro for this. I'll call it SQUARE.
#define SQUARE(x) x * x
Now, I can just write SQUARE(radius). It feels like a function, but it's actually just the preprocessor pasting code. I'll try to calculate the area of a circle where the radius is a small calculation, like (2 + 3).
float area = PI * SQUARE(2 + 3);
I'm expecting the radius to be 5, so the square should be 25. But when I run this, I get a weird result. The area comes out way too low. Why? Because I forgot that macros are literal text replacement. The preprocessor expanded my code to this:
float area = 3.14159 * 2 + 3 * 2 + 3;
Order of operations ruined me. The multiplication happened before the addition, and I ended up with something entirely different from 5 * 5.
Protecting the macro with parentheses
This is the classic "gotcha" with C macros. Whenever you're writing a macro that takes arguments, you have to wrap everything in parentheses. You wrap the individual arguments and the entire expression. It looks a bit cluttered, but it's the only way to ensure the math stays correct regardless of what the user passes in.
#define SQUARE(x) ((x) * (x))
Now, if I call SQUARE(2 + 3), it expands to ((2 + 3) * (2 + 3)). That's exactly what we want. Here is the corrected version of our physics snippet:
#include <stdio.h>
#define PI 3.14159
#define GRAVITY 9.81
#define SQUARE(x) ((x) * (x))
int main() {
float radius = 2 + 3;
float area = PI * SQUARE(radius);
printf("Radius: %.2f\n", radius);
printf("Area: %.2f\n", area);
return 0;
}
One last tip: you'll notice I used uppercase for PI and SQUARE. This is a convention in C. Since macros don't follow the same scoping rules as variables and can change your code in unexpected ways, we use all-caps to scream to other developers: "Heads up! This is a macro, not a variable!"
📋 Practical Task
Building a Cylinder Volume Calculator
Create a program that calculates the volume of a cylinder. You must use #define for the following:
- A constant for
PI(3.14159). - A macro called
CUBEthat takes one argument and returns its cube (the value multiplied by itself three times). Ensure you use parentheses to prevent the order-of-operations bug we discussed.
In your main function, prompt the user to enter a radius and a height. Calculate the volume using the formula: Volume = PI * (radius squared) * height. To make it interesting, try to use your CUBE macro to calculate the volume of a "perfect cylinder" (where height equals radius) as a second output.
There are no comments for now.