Why do most C developers use define instead of const? [duplicate]
Asked Answered
C

9

109

In many programs a #define serves the same purpose as a constant. For example.

#define FIELD_WIDTH 10
const int fieldWidth = 10;

I commonly see the first form preferred over the other, relying on the pre-processor to handle what is basically an application decision. Is there a reason for this tradition?

Coumarone answered 26/10, 2010 at 13:51 Comment(8)
Note that for integer constants, you can also use an enum like in ``enum {FIELD_WIDTH = 10};´´which can be useful if you need a compile-time constant in a function scope.Gehring
You can #define at function scope too although you have to remember to #undef it at the end of the functionSedgewick
@CashCow: That´s right, but then you´re undefining a possible #define at file scope (which may have been defined in an include file you have not written yourself).Gehring
@Jochen: When there is a collision with another macro definition, then your #define has already overwritten the previous definition and you've ignored a compiler warning, so you can as well #undef it and produce a compiler error when the macro is used further down in your code, to remind you that you should change the macro name for your function scope macro.Adon
@Secure: Sure, but then you have to handle the name clash. The advantage of local scopes is that they avoid it. Of course, using a local enum is not bullet-proof either: If a global #define rewrites FIELD_WIDTH to something else, say 20, the enum would be rewritten to `enum {20 = 10};` If the global and the local definition of FIELD_WIDTH are enums, though, both definitions do coexist (the local definition shadows the global one). (See eetimes.com/discussion/programming-pointers/4023879/… for further information on this topic.Gehring
@Jochen Walter: How hard can it be to handle a macro name clash in function scope? You get a compiler error message with it, search it in the source and see what is done there. Enums may be easier here, sure, but if I need or want to use macros for any of the reasons given in this thread, there is no choice. If I use x where y can happen, then I have to handle y. At least I get a compiler error with macros. How do I handle a silent shadowing when it is the thing I don't want? BTW, your link is about C++, this topic is about C.Adon
@Secure: I'm not saying that this is the most important question in software engineering. If you prefer #defines to enums, stick with them. I switched from #defines to enums because I see no point in using the preprocessor for compile-time integer constants when I can have them in plain C (with scoping, type checking, regular C syntax, proper indentation). For non-integer constants, of course, you have no choice. Shadowing is not a major concern of mine. We have the same with local variables and I never heard this to be a big impediment to C programming.Gehring
@Secure: The link in my previous comment was wrong. It should read eetimes.com/discussion/programming-pointers/4023858/…. Thank you for pointing this mistake out to me.Gehring
I
182

There is a very solid reason for this: const in C does not mean something is constant. It just means a variable is read-only.

In places where the compiler requires a true constant (such as for array sizes for non-VLA arrays), using a const variable, such as fieldWidth is just not possible.

Iconoscope answered 26/10, 2010 at 13:58 Comment(14)
+1 for correct answer among a sea of wrong ones from C++ coders who don't know C.Mabuse
@Bart is there any reason to do the same thing for a character constant?Coumarone
@C. Ross: Consistency. All manifest constants are usually defined with #defines. const is only used to indicate a read-only (access path to a) variable. I know, const in C is just plain broken :-)Iconoscope
You can not say something like const int size 4; and then later do char str[size]?Widera
@fahad: Not in C89. In C99 you can do so for a variable of automatic storage duration, but it is technically a VLA.Homogenetic
@fahad: A VLA is a 'Variable Length Array'. It is an array whose size is not known until runtime and can only be used for automatic variables (i.e. can only be allocated on the stack).Iconoscope
@Homogenetic & @Bart:thats something interesting,you can use stack at run time without the help of heap..Widera
@fahad: You can, but be warned that you will not get a reliable error if your array is too large for the remaining stack space. That lands you firmly in the land of undefined behaviour.Iconoscope
In C++0x you can use constexpr to allow const variables to specify the size of an array.Catharine
@Exception: C and C++ are very different languages in their treatment of const. In C++, const can be used for proper constants, in C it can not.Iconoscope
Not only the const cannot be used in non-VLA array sizes, it cannot be used for creating other const either. For example: const uint8_t SECONDS_PER_MINUTE = 60U; const uint16_t SECONDS_PER_HOUR = 60U * SECONDS_PER_MINUTE;. I miss a way to have typed constants (const is not constant, as @Bart wrote).Tacho
Well this is embarrassing, I'm surprised it took me this long to realize this. I just compiled with gcc -pedantic and realized that gnu89 uses variable length arrays with const. I don't know if using VLA for arrays that could be defined at compile time matters or not (e.g. for optimization). I'm finally being convinced that C and C++ are separate languages. Or at least it's important to know where they differ.Blok
const int arsize = 10; int myar[arsize]; Compiled with gcc. Am I misinterpreting "In places where the compiler requires a true constant (such as for array sizes for non-VLA arrays), using a const variable, such as fieldWidth is just not possible."? error: variable-sized object may not be initialized int myar[arsize] = {0,1}; compiles but isn't allowed to be initialized!Midlothian
@MinhTran: Note that the error message states "variable-sized object". This means that your array is a variable-length array (VLA).Iconoscope
D
25

They're different.

const is just a qualifier, which says that a variable cannot be changed at runtime. But all other features of the variable persist: it has allocated storage, and this storage may be addressed. So code does not just treat it as a literal, but refers to the variable by accessing the specified memory location (except if it is static const, then it can be optimized away), and loading its value at runtime. And as a const variable has allocated storage, if you add it to a header and include it in several C sources, you'll get a "multiple symbol definition" linkage error unless you mark it as extern. And in this case the compiler can't optimize code against its actual value (unless global optimization is on).

#define simply substitutes a name with its value. Furthermore, a #define'd constant may be used in the preprocessor: you can use it with #ifdef to do conditional compilation based on its value, or use the stringizing operator # to get a string with its value. And as the compiler knows its value at compile time it may optimize code based on that value.

For example:

#define SCALE 1

...

scaled_x = x * SCALE;

When SCALE is defined as 1 the compiler can eliminate the multiplication as it knows that x * 1 == x, but if SCALE is an (extern) const, it will need to generate code to fetch the value and perform the multiplication because the value will not be known until the linking stage. (extern is needed to use the constant from several source files.)

A closer equivalent to using #define is using enumerations:

enum dummy_enum {
   constant_value = 10010
};

But this is restricted to integer values and doesn't have advantages of #define, so it is not widely used.

const is useful when you need to import a constant value from some library where it was compiled in. Or if it is used with pointers. Or if it is an array of constant values accessed through a variable index value. Otherwise, const has no advantages over #define.

Derbent answered 26/10, 2010 at 14:20 Comment(7)
+1 this answer is the most complete, discussing the uses and limits of enum too, and the few cases where a const-qualified variable is actually what you want.Mabuse
+1 for mentioning enum as an alternative - that should be used a lot more often. One major advantage is that the symbol is available to the debugger.Musaceous
I'd rather think that it is debugger disadvantage to be not able of using defines, rather than advandage of enum to be usable in debugger.Derbent
And the advantage of #define over enum is that you can #ifdef to test its presence or use its value in #if, which makes it a lot more useful for controlling build-time choices or offering features which might be present on some platforms but not others.Mabuse
R..: Yes! Of course! The only exception I know (not counting obvious optimisations) is GCC builtins which give control over compilation using constant expressions, like __builtin_choose_expr.Derbent
Newer versions of gdb / gcc do support the use of #define constants in the debugger (I think the -gdb3 flag must be passed to gcc at compilation time to enable this).Colostrum
The part about optimization is not true for any compiler doing link-time optimization.Tailspin
M
14

The reason is that most of the time, you want a constant, not a const-qualified variable. The two are not remotely the same in the C language. For example, variables are not valid as part of initializers for static-storage-duration objects, as non-vla array dimensions (for example the size of an array in a structure, or any array pre-C99).

Mabuse answered 26/10, 2010 at 13:56 Comment(2)
Any other case of non-vla array in C99 apart from " array in a structure" ?Agnail
Arrays with static storage duration. Compound literal arrays. Array types used in typedef with file scope. Maybe others.Mabuse
S
7

Expanding on R's answer a little bit: fieldWidth is not a constant expression; it's a const-qualified variable. Its value is not established until run-time, so it cannot be used where a compile-time constant expression is required (such as in an array declaration, or a case label in a switch statement, etc.).

Compare with the macro FIELD_WIDTH, which after preprocessing expands to the constant expression 10; this value is known at compile time, so it can be used for array dimensions, case labels, etc.

Stotts answered 26/10, 2010 at 14:15 Comment(2)
So const operates more like java's final, allowing only one assignment?Coumarone
@C. Ross: for all practical purposes, yes, although I think there may be some non-trivial differences in semantics (I don't use const all that much in C code, and I'm still at the bottom of the Java learning curve).Stotts
C
6

To add to R.'s and Bart's answer: there is only one way to define symbolic compile time constants in C: enumeration type constants. The standard imposes that these are of type int. I personally would write your example as

enum { fieldWidth = 10 };

But I guess that taste differs much among C programmers about that.

Christenechristening answered 26/10, 2010 at 14:44 Comment(0)
S
4

Although a const int will not always be appropriate, an enum will usually work as a substitute for the #define if you are defining something to be an integral value. This is actually my preference in such a case.

enum { FIELD_WIDTH = 16384 };
char buf[FIELD_WIDTH];

In C++ this is a huge advantage as you can scope your enum in a class or namespace, whereas you cannot scope a #define.

In C you don't have namespaces and cannot scope an enum inside a struct, and am not even sure you get the type-safety, so I cannot actually see any major advantage, although maybe some C programmer there will point it out to me.

Sedgewick answered 26/10, 2010 at 14:53 Comment(1)
In C++, is there any 'nice' way to define an enum that will behave as an "int", including working with arithmetic operators? I know it's possible to define operator overloads, and such things can be done in a macro so as not to be too totally evil, but I'm unaware of any simple language keyword for something like that. Does one exist that I'm unaware of? If not, it seems an odd omission from the language, since C++ is supposed to allow easy adaptation of C programs.Hui
V
2

According to K&R (2nd edition, page 211) the "const and volatile properties are new with the ANSI standard". This may imply that really old ANSI code did not have these keywords at all and it really is just a matter of tradition. Moreover, it says that a compiler should detect attempts to change const variables but other than that it may ignore these qualifiers. I think it means that some compilers may not optimize code containing const variable to be represented as intermediate value in machine code (like #define does) and this might cost in additional time for accessing far memory and affect performance.

Vivianna answered 26/10, 2010 at 14:13 Comment(3)
+1 for citing K&R but -1 for answering that it's about optimization rather than language requirements. Net 0.Mabuse
No it implies that const and volatile were introduced by ANSI standard C. K&R second edition was actually published before the standard was ratified.Ladida
Lots of C programmers use strchr to search for a character instead of strtof because strtof was not added until C99.Mabuse
L
1

Some C compilers will store all const variables in the binary, which if preparing a large list of coefficients can use up a tremendous amount of space in the embedded world.

Conversely: using const allows flashing over an existing program to alter specific parameters.

Lilybelle answered 26/10, 2010 at 14:54 Comment(7)
Regarding your "conversely", this would be highly implementation-specific. A good implementation would inline all const variables and optimize them out if their addresses are never taken, just like how it inlines functions.Mabuse
I've seen several implementations that use/abuse that in embedded systems across a couple platforms (AVR, HCS08). They're defined as const but with some compiler directives that cause them to be stored at a defined memory address. They seem to work just like a * constLilybelle
@R..: Things get really nasty in C at the embedded level. You can't assume much about C compilers.Genarogendarme
Indeed that's a huge abuse. The proper implementation is #define myregister (*(volatile const uint32_t *)0xdecafbad) or similar.Mabuse
where do you think the #define values are stored if not in the binary?Slade
@David, the preprocessor's memory?Lilybelle
So how do they make their way into the executable?Slade
D
1

The best way to define numeric constants in C is using enum. Read the corresponding chapter of K&R's The C Programming Language, page 39.

Decollate answered 1/6, 2015 at 11:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.