const VS #define in C language
When to use each of them, how does it work, and which is better
#Define
To start with a simple example, we’ll use the following code in C++(it works exactly the same in mere C, so the duplicate of explanation is redundant) for demonstrating the functioning of this method:
#include <cstdio>
#define BUFFER 100
int main() {
printf("buffer=%d", BUFFER);
return 0;
}
Afterwards, we are going to unwrap this ‘define’ preprocessor directive shrouded in mystery; as it hard nose definition of a preprocessor directive says, it is handled by preprocessor; if you don’t know yet how it works, here is a very good explication from zero to hero on Stackoverflow: https://stackoverflow.com/questions/6264249/how-does-the-compilation-linking-process-work

Using only 3 short commands in Linux we argued how the preprocessor handled ‘BUFFER’ and after preprocessing the extended source code doesn’t contain anymore the symbol BUFFER, and, hence we cannot access it as pointer, reference or dereference it or pass it to other files in a bigger project so the idea of global environment variable vanishes while opting to use the define directive.
Const
#include <cstdio>
const int buffer = 100;
int main() {
printf("buffer=%d", buffer);
return 0;
}
On the other hand, the const keyword is handled by the compiler(so one step latter) which is storing the value assigned to a variable even after the code is assembled by the assembler. That means it is traceable; either the project is more complex or the value is used on a much larger scale rather than a single file; we can observe this behavior in the following snippet

Firstly, the command g++ -E using_const.cpp -o using_const.i offers us the extended source code in the file specified after the -o flag which stands for output. Secondly, the command g++ -S using_const.i offers us the assembly code having an implicit output the file renamed with the extension .s; when using the cat on the brand new created files we can see that the symbol ‘buffer’ persists after preprocessor phase and even after compilation(it would have been weird if the variable disappeared during compilation), nevertheless with the obvious drawback of having more variables on the stack and having responsibility over the memory handling for security and performance.
Where the difference matters…
For instance if you have a small function and you want to declare it using define as to be handled by the preprocessor. Suppose that function raises an integer argument to some power like the square of a number. Here would be the version using a constant function; you can even declare it inline, the effect is going to be the same.
#include <iostream>
using namespace std;
const int square(int x) {
return x*x;
}
int main() {
int a = 5;
cout << square(++a);
return 0;
}
// 36
Obviously the pre-increment operator is called before the square function. The result of the function combined with the post-increment operator would have been 25, instead of 36, but that’s nothing new yet.
Here is the implementation with define:
//
// Created by nick on 8/11/24.
//
#include <iostream>
#define square(x) x*x
using namespace std;
int main() {
int a = 5;
cout << square(++a);
// it breaks down into:
// (++a)*(++a)
return 0;
}
// 49
Because of the fact that the pre-increment operator is called two times before the square function, it results into calculating the square of 7, not 6. This is just a smooth and gentle example of how these two implementations might differ from one another, but things go even behind this and I truly encourage you to spot those use cases.
Thank you!
Thank you for your time! If you enjoyed reading my article and feel like it helped you in the acquisition of information and comprehension of the topic it would be really supporting to give a clap, a comment and a share to your friends!