Reputation: 291
I've having trouble understanding what is the point of Symbolic Constants in C, I am sure there is a reason for them but I can't seem to see why you wouldn't just use a variable.
#include <stdio.h>
main()
{
float fahr, celsius;
float lower, upper, step;
lower = 0;
upper = 300;
step = 20;
printf("%s\t %s\n", "Fahrenheit", "Celsius");
fahr = lower;
while (fahr <= upper) {
celsius = (5.0 / 9.0) * (fahr - 32.0);
printf("%3.0f\t\t %3.2f\n", fahr, celsius);
fahr = fahr + step;
}
}
Vs.
#include <stdio.h>
#define LOWER 0
#define UPPER 300
#define STEP 20
main()
{
float fahr, celsius;
printf("%s\t %s\n", "Fahrenheit", "Celsius");
fahr = LOWER;
while (fahr <= UPPER) {
celsius = (5.0 / 9.0) * (fahr - 32.0);
printf("%3.0f\t\t %3.2f\n", fahr, celsius);
fahr = fahr + STEP;
}
}
Upvotes: 18
Views: 14392
Reputation: 234795
The (pre)compiler knows that symbolic constants won't change. It substitutes the value for the constant at compile time. If the "constant" is in a variable, it usually can't figure out that the variable will never change value. In consequence, the compiled code has to read the value from the memory allocated to the variable, which can make the program slightly slower and larger.
In C++, you can declare a variable to be const
, which tells the compiler pretty much the same thing. This is why symbolic constants are frowned upon in C++.
Note, however, that in C (as opposed to C++) a const int
variable is not a constant expression. Therefore, trying to do something like this:
const int a = 5;
int b[a] = {1, 2, 3, 4, 5};
will work in C++ but will get you a compilation error in C (assuming b
was supposed to be a statically bound array).
Upvotes: 22
Reputation: 753695
One good example of why named constants are beneficial comes from the excellent book The Practice of Programming by Kernighan and Pike (1999).
§1.5 Magic Numbers
[...] This excerpt from a program to print a histogram of letter frequencies on a 24 by 80 cursor-addressed terminal is needlessly opaque because of a host of magic numbers:
... fac = lim / 20; if (fac < 1) fac = 1; for (i = 0, col = 0; i < 27; i++, j++) { col += 3; k = 21 - (let[i] / fac); star = (let[i] == 0) ? ' ' : '*'; for (j = k; j < 22; j++) draw(j, col, star); } draw(23, 2, ' '); for (i = 'A'; i <= 'Z'; i++) printf("%c ", i);
The code includes, among others, the numbers 20, 21, 22, 23, and 27. They're clearly related...or are they? In fact, there are only three numbers critical to this program: 24, the number of rows on the screen; 80, the number of columns; and 26, the number of letters in the alphabet. But none of these appears in the code, which makes the numbers that do even more magical.
By giving names to the principal numbers in the calculation, we can make the code easier to follow. We discover, for instance, that the number 3 comes from (80 - 1)/26 and that let should have 26 entries, not 27 (an off-by-one error perhaps caused by 1-indexed screen coordinates). Making a couple of other simplifications, this is the result:
enum { MINROW = 1, /* top row */ MINCOL = 1, /* left edge */ MAXROW = 24, /* bottom edge (<=) */ MAXCOL = 80, /* right edge (<=) */ LABELROW = 1, /* position of labels */ NLET = 26, /* size of alphabet */ HEIGHT = (MAXROW - 4), /* height of bars */ WIDTH = (MAXCOL - 1)/NLET /* width of bars */ }; ... fac = (lim + HEIGHT - 1) / HEIGHT; if (fac < 1) fac = 1; for (i = 0; i < NLET; i++) { if (let[i] == 0) continue; for (j = HEIGHT - let[i]/fac; j < HEIGHT; j++) draw(j+1 + LABELROW, (i+1)*WIDTH, '*'); } draw(MAXROW-1, MINCOL+1, ' '); for (i = 'A'; i <= 'Z'; i++) printf("%c ", i);
Now it's clearer what the main loop does; it's an idiomatic loop from 0 to NLET, indicating that the loop is over the elements of the data. Also the calls to
draw
are easier to understand because words like MAXROW and MINCOL remind us of the order of arguments. Most important, it's now feasible to adapt the program to another size of display or different data. The numbers are demystified and so is the code.
The revised code doesn't actually use MINROW, which is interesting; one wonders which of the residual 1's should be MINROW.
Upvotes: 9
Reputation: 493
Jonathan provides an excellent example of the user of symbolic constants.
It is possible that the program used in the question is not the best one to answer this question. However, given the program, symbolic constants might make more sense in the following case:
#include <stdio.h>
#define FAHRENHEIT_TO_CELSIUS_CONVERSION_RATIO 5.0 / 9.0
#define FAHRENHEIT_TO_CELSIUS_ZERO_OFFSET 32.0
#define FAHRENHEIT_CELSIUS_COMMON_VALUE -40.0
#define UPPER 300.0
#define STEP 20.0
int main()
{
float fahr, celsius;
printf("%s\t %s\n", "Fahrenheit", "Celsius");
fahr = FAHRENHEIT_CELSIUS_COMMON_VALUE;
while (fahr <= UPPER) {
celsius = (fahr - FAHRENHEIT_TO_CELSIUS_ZERO_OFFSET) * (FAHRENHEIT_TO_CELSIUS_CONVERSION_RATIO);
printf("%3.0f\t\t %3.2f\n", fahr, celsius);
fahr = fahr + STEP;
}
}
Possibly this makes it easier to understand why symbolic constants might be useful.
The program includes stdio.h
, a rather common include file. Let's look at some of the symbolic constants defined in stdlib.h
. This version of stdio.h
is from Xcode.
#define BUFSIZ 1024 /* size of buffer used by setbuf */
#define EOF (-1)
#define stdin __stdinp
#define stdout __stdoutp
#define stderr __stderrp
Let's also look at two symbolic constants defined in stdlib.h
.
#define EXIT_FAILURE 1
#define EXIT_SUCCESS 0
These values may vary from system to system but using them makes programming in C a lot easier and portable. The symbolic constants for stdin
, stdout
and stderr
have been known to change in various operating system implementations.
Using BUFSIZ to define character arrays for C input buffers generally makes a lot of sense. Using EXIT_FAILURE and EXIT_SUCCESS makes code much more readable, and I don't have to remember if 0 is failure or success. Would anyone prefer (-1) over EOF?
Using a symbolic constant to define the size of arrays makes it much easier to change code in one place rather than having to search all over for a specific number embedded in the code.
Upvotes: 0
Reputation: 78903
Jonathan made a good point in why you would want to use symbolic constants in C (and in any other programming language, BTW).
Syntactically, in C this is different from C++ and many other languages because it is much restrictive on how you may declare such symbolic constant. So-called const
qualified variables don't account for this as they would in C++.
enum color { red = 0xFF00, green = 0x00FF00, blue = 0x0000FF };
. They are only of some restricted use, because they are fixed to have type int
. So you wouldn't cover all ranges of values that you'd might want with them.'a'
or L'\x4567'
as predefined symbolic constants, if you like. They translate an abstract concept (the character value "a") into the encoding of the executing platform (ASCII, EBDIC, whatever). Upvotes: 2
Reputation: 2835
Variables are scoped locally to the structure they're declared in. Of course you could use variables instead of symbolic constants, but that might take a lot of work. Consider an application that frequently uses radians. The symbolic constant #define TWO_PI 6.28
would be of high value to the programmer.
Upvotes: 3