4
\$\begingroup\$

I read on here that when programming with C++, const variables are better than preprocessor directives, ie #define, for declaring constants. I'm confused about why that is the case.

I understand that a const variable would be stored in static memory, whereas a preprocessor directive would become a literal, and possibly an immediate in assembly, but I don't see why that is a disadvantage.

I've also seen that inline functions are better than macros, but I'm confused about what is the difference.

Thanks!

\$\endgroup\$
7
  • 4
    \$\begingroup\$ For the same reason you use a box cutter with a 3mm knife tip instead of a katana to cut boxes. They do too much with too few restrictions. defines and macros are loose-cannons. Just doing a text replace is not as safe. Variable types exist for a reason. There are situations where the compiler complains about types not matching and I can't figure out a sufficiently simple way to force it so then I use defines to get around the compiler safeguards which shows you how unsafe they can be. \$\endgroup\$
    – DKNguyen
    Commented Jan 2, 2021 at 1:57
  • 2
    \$\begingroup\$ Read Bjarne Stroustrup's "So, what's wrong with using macros?". He invented C++ and used readonly instead of const in early versions. (readonly came with a matching writeonly keyword, back in the day.) \$\endgroup\$
    – jonk
    Commented Jan 2, 2021 at 3:25
  • \$\begingroup\$ For declaring constants as your question states the const will have a flash cost and possible performance cost over the #define. (which are both bad for embedded). One can find possible pros and cons for both solution, there is no "best" way here. Putting the constant in the code where it can be seen vs far away where it cannot is yet another solution that has pros and cons (but from a risk and code production perspective is the ideal solution). \$\endgroup\$
    – old_timer
    Commented Jan 2, 2021 at 3:44
  • 1
    \$\begingroup\$ The answer completely depends on which processor architecture you are targeting, as there is no single class of embedded C++ environment. Using consts on a 100MHz 32-bit ARM microcontroller might be something you should not even think about, but using consts on a low-end 8-bit MCUs such as PIC16F84 or ATTiny85 might be something you have to carefully investigate how the compiler decides to waste resources. \$\endgroup\$
    – Justme
    Commented Jan 2, 2021 at 10:49
  • 3
    \$\begingroup\$ I think this question is a better fit for stackoverflow.com, where it's been asked quite a few times over the years. Here's one answer: stackoverflow.com/a/6442372/66490 \$\endgroup\$
    – TrayMan
    Commented Jan 2, 2021 at 14:54

2 Answers 2

17
\$\begingroup\$

Using a const value lets the compiler do some type checking, but a value you assign with a #define is just used for text substitution so all sorts of unexpected problems can occur.

Regardless of how you define constants, a good compiler should never store them as static values in RAM. A good compiler will often optimize them to immediate values embedded in the instructions themselves or literal values in the literal pool. For an embedded system running out of flash memory the literal pool would also remain in flash and would not take space in RAM.

Similarly for macros vs. inline functions. A macro does text substitution in the preprocessor and can easily lead to unintended consequences if you are not careful. If you don't look at the preprocessor output directly it can be hard to find and debug problems with macros. An inline function, on the other hand, provides the same functional capability as a macro but is easier for the compiler to check and easier to debug if necessary.

\$\endgroup\$
13
  • 1
    \$\begingroup\$ Just an addendum, nothing more. In some Harvard architecture cases, there exist no simple instructions that can read from flash/otp. (In a case or two, it cannot even be done at all and so some hardware mods are used to compensate.) So, often, the toolchain is arranged to reserve equal space in RAM and, prior to starting main(), to copy from flash into RAM. This is most particularly necessary for static-lifetime initialized, writable variables (not a constant, granted, but just the same yet annother issue to be coped with.) So literal constants may very well take up space in RAM. Just a note. \$\endgroup\$
    – jonk
    Commented Jan 2, 2021 at 3:09
  • \$\begingroup\$ pure harvard is textbook only, most are "modified" harvard which is the same as saying "modified" von neumann, with one having the perception of being better than the other when there is no difference. it is very difficult to compile for pure harvard, cant have an operating system, etc. It is true though that const and pure/textbook harvard have issues, so you would have to use #defines or just put the constant in the line of code. \$\endgroup\$
    – old_timer
    Commented Jan 2, 2021 at 3:32
  • \$\begingroup\$ defines can land in the literal pool or in the instruction, const's have to be allocated space, and may require a memory access where a define might not. wasting space and performance is not good for embedded. There is no right answer here you have to write good code either way both have pros and cons. simple #define x 0x1234 and const int x = 0x1234, which is the question here, one has no more risk than the other, one has a performance and resource cost though. \$\endgroup\$
    – old_timer
    Commented Jan 2, 2021 at 3:38
  • 1
    \$\begingroup\$ when used for something other than constants, particularly macros then one does need to use care, but at the same time if you write bad code you write bad code no matter what solution you use, so the risk is minimal. \$\endgroup\$
    – old_timer
    Commented Jan 2, 2021 at 3:39
  • 1
    \$\begingroup\$ Re: type checking vs. text substitution -- this is a common misunderstanding. The type of 1 is int; the type of 1.1 is double. There's no ambiguity there. Creating that text with a macro doesn't change that. \$\endgroup\$ Commented Jan 2, 2021 at 16:20
6
\$\begingroup\$

static const values respect the scope of namespace and class#define doesn't.

If the value is not a POD, then a static const will only have one instance of it, at least per compilation unit (.cpp file), a #define will be instantiated at each use. The compiler may (or may not) merge each compilation unit's usage together, if it can guarantee the constructor is pure (ie it has not side effects).

\$\endgroup\$
4
  • 2
    \$\begingroup\$ No, a #defined value is not unconditionally instantiated when used. Its name is just replaced with its value in the source code, before the compiler decides what to do with it. \$\endgroup\$ Commented Jan 2, 2021 at 15:56
  • 2
    \$\begingroup\$ +1 , because in my opinion the namespace issue is the biggest unavoidable drawback to #define . There are some unexpected-effect issues with #define, to be sure, but standard C/C++ programming idioms can prevent those. Polluting the namespace, you can't fix. That said I still use #define's liberally :-/ \$\endgroup\$
    – Pete W
    Commented Jan 2, 2021 at 16:46
  • \$\begingroup\$ Thank you! What does POD stand for? \$\endgroup\$
    – Dyskord
    Commented Jan 2, 2021 at 23:26
  • 2
    \$\begingroup\$ @Dyskord -- POD = Plain Old Data, or standard layout in C++11 and newer -- some aggregate which is guaranteed to be laid out in a C-compatible fashion, basically \$\endgroup\$ Commented Jan 3, 2021 at 1:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.