Join WhatsApp

Join Now

Join Telegram

Join Now

“Sir, Who Changed PI?” — A Real Lesson in Constants in C

By Sagar Miraje

Published On:

Follow Us

I still remember that Wednesday when “constant” become the topic. It was around 3:15 PM, and the lab was unusually quiet. Too quiet, considering the batch that was in front of me. These were the same students who’d usually debate whether int was better than float for temperature readings, or whether they could use char for everything just to be cheeky. 

But not that day. 

That day, something had broken in the code something small, something innocent, and yet it wrecked our entire output. 

The culprit? A variable named PI. 

Or rather, a variable that should’ve never been a variable in the first place. 

The Mistake That Taught Me More Than a Textbook 

We were working on a simple geometry based program to calculate areas of circles and cylinders. Riya had written: 

float PI = 3.14; 
 

Another teammate, unknowingly, wrote: 

PI = 3.10; 
 

She was trying to debug some rounding issues. She thought maybe it was a typo. And because PI was just a regular variable, the compiler didn’t complain. 

But the result? 

All area calculations were off. And nobody knew why. 

When I walked over, I asked, “Who changed PI?” 

Riya looked at me, half laughing, half distraught. “Sir, I think we broke mathematics.” 

And that’s when it hit me. I needed to show them constants not as a keyword or a preprocessor directive, but as a philosophy. As a discipline

What Is a Constant, Really? 

In my lectures, I often say: 

“A constant is a promise. A handshake between you and your code that says, ‘This value will never change.’” 

Unlike a variable that can adapt and evolve, a constant stands its ground. Once defined, it doesn’t bend. It’s the moral compass of your logic especially when your code gets big, messy, or collaborative. 

Two Main Ways to Define Constants in C: 

  1. #define 
  1. const keyword 

Let me walk you through both. Not as syntax dumps but as lived experience. 

#define: When the Preprocessor Becomes Your Substitute Teacher 

#define PI 3.14159 
 

This one line has saved me more time and confusion than you can imagine. 

I often tell students: imagine you’re writing an essay and every time you want to say “United States of America”, you have to type the whole thing. Tedious, right? 

Instead, what if you just say USA and let the reader understand? 

That’s what #define does. It tells the preprocessor (note: not the compiler!) to replace every instance of PI with 3.14159 before the actual compilation even begins. 

I showed them with this example: 

#define MAX_LIMIT 100 
#define PI 3.14159 
 
int main() { 
   printf(“PI is: %.5f\n”, PI); 

 

And voila. Output: 
PI is: 3.14159 

Magic? No. Just preprocessing. 

Common Mistakes With #define 

Over the years, I’ve seen students do some wonderfully chaotic things with #define. Let’s talk about them. 

1. Ending it with a semicolon 

#define PI 3.14159; 
 

Nope. That semicolon sneaks into your code wherever you use PI. I had a student once tell me his if condition kept throwing errors. Why? Because he did this: 

if (radius > PI) { 
   // do something 

 

Which the preprocessor translated to: 

if (radius > 3.14159; ) { 
   // error 

 

Boom. Compilation error. All because of a semicolon. 
Rule: Never use ; at the end of #define. 

2. Using lowercase names 

Once I had a macro named rate. Later, we declared a local variable also named rate. And chaos ensued. 

The preprocessor doesn’t care about your intentions. It blindly replaces rate with the macro value everywhere, including variable assignments. I remember Abhay’s shocked face when his assignment turned into: 

20 = 15; 
 

From then on, we decided: 
 Use UPPERCASE names for macros. That’s not just convention it’s protection. 

3. Expecting replacements inside strings 

#define VALUE 89 
printf(“VALUE is %d”, VALUE); 
 

Result: VALUE is 89  

But if you do: 

printf(“VALUE”); 
 

It prints “VALUE”, not “89”. 

Why? 

Because macros don’t get replaced inside double quotes. 
Strings are sacred. Preprocessors leave them alone. 

const Keyword: When You Want Type Safety and Scope Awareness 

Now, let’s say I want to define a constant that behaves like a variable in type and scope, but I don’t want anyone to change it. 

I write: 

const float PI = 3.14159; 
 

Here’s the beauty of const: it’s enforced by the compiler. 

Try to reassign it: 

PI = 3.10; 
 

Boom. Compiler yells: 

error: assignment of read only variable ‘PI’ 
 

I remember a student named Harsh trying to “hack” it by using pointer indirection. It didn’t end well. We got into a lovely side conversation about memory safety after that. 

Practical Use Cases for Constants 

Over time, I’ve trained myself (and my students) to instinctively define constants for anything that: 

  • Represents a limit: 
    #define MAX_USERS 100 
  • Involves a rate or coefficient: 
    const float TAX_RATE = 0.18; 
  • Might be used across multiple places: 
    #define COMPANY_NAME “CodeCraze” 
  • Might accidentally be overwritten: 
    const int OTP_EXPIRY_SECONDS = 300; 

One time, a student used 10 in 12 different places for an array size. Then changed it to 15 in one place and forgot the rest. 
You can imagine how that played out. 

We fixed it with: 

#define SIZE 15 
 

And peace returned to the codebase. 

Advanced Use: Macros as Mini Functions 

#define ADD(x, y) ((x) + (y)) 
 

I remember giving this as an assignment once: 

printf(“Result: %d”, 5 * ADD(2, 3)); 
 

Half the class said 35. The other half said 23. 

Only a few got it right: 23. 

Why? Because: 

First expansion, then evaluation. 

The macro turns into: 

5 * (2 + 3) 
 

Which is 5 * 5 = 25. Right? 
No! They missed the parentheses. 

Without those, it becomes: 

5 * 2 + 3 = 10 + 3 = 13 
 

or worse: 

5 * 2 + 3 = 5 * 2 = 10 + 3 = 13 
 

Moral: always wrap macro parameters and results in parentheses. Always. 

Bonus: Predefined Macros 

These come in super handy for debugging or logging: 

printf(“Compiled on %s at %s\n”, __DATE__, __TIME__); 
 

Output: 
Compiled on Jul 19 2025 at 16:42:55 

Great for tracking versions or builds. 

So, What Do I Tell My Students Today? 

  • Use #define when you need a global, simple, and read only replacement. 
  • Use const when you want a type safe, scoped, and compiler enforced constant. 
  • Never use semicolons after #define. 
  • Use uppercase for macros to avoid name collisions. 
  • Always parenthesize macro parameters and expressions. 

And of course: 

If something should never change, declare it as a constant because logic deserves protection. 

I hope this post felt less like documentation and more like a walk through a real classroom one where mistakes become lessons and code becomes conversation. 
Until next time, keep your logic safe and your constants safer. 

Happy coding. 

Can I change a const variable using a pointer? 

Technically, yes with typecasting but it’s undefined behavior and extremely bad practice. 

Can const be used with pointers? 

Yes! You can have const int *p, int * const p, and const int * const p. Each has different meanings. 

What’s the difference between #define and const? 

#define is a preprocessor directive (no type checking), while const is a typed, compiler checked variable. 

Can I use expressions in #define? 

Yes, but be cautious. Always use parentheses to prevent precedence issues. 

I am Sagar Miraje, a Computer Science graduate with a Bachelor of Technology and 7 years of experience as a C language programming developer. I specialize in writing efficient, low-level code for systems programming, embedded applications, and performance-critical software. My strength lies in optimizing memory usage, handling pointers, and working close to the hardware to deliver fast and reliable solutions. Over the years, I’ve contributed to core system modules, debugged complex runtime issues, and collaborated with cross-functional engineering teams. I’m passionate about problem-solving and always eager to push the limits of what C programming can achieve.

Leave a Comment