9

This is a case of "static const” vs “#define” in C" for embedded systems.

On large/mid projects with "passed-down" code and modules, what is the best practice on writing constant parameters for your include files, modules, etc?

In a code "passed-down" where you don't know if the names you're choosing are defined in some other included file or might be called with extern or as macros in some other file that might include your file.

Having these 3 options:

  1. static const int char_height = 12;
  2. #define CHAR_HEIGHT 12
  3. enum { char_height = 12 };

which one would be better (on an embedded system with unknown memory constraints)?

The original code uses mainly #define's for this, but these kind of constants are haphazardly implemented in several ways (and at different locations even in the same files) since it seems several people developed this demo software for a certain device.

Specifically, this is a demo code, showing off every hardware and SDK feature of a certain device.

Most of the data I'm thinking about is the kind used to configure the environment: screen dimensions, charset characteristics, something to improve the readability of the code. Not on the automatic configuration a compiler and pre-processor could do. But since there's a lot of code in there and I'm afraid of global name conflicts, I'm reluctant to use #define's

Currently, I'm considering that it would be better to rewrite the project from scratch and re-implement most of the already written functions to get their constants from just one c file or reorganize the constants' implementation to just one style.

But:

  1. This is a one person project (so it would take a lot of time to re-implement everything)
  2. The already implemented code works and it has been revised several times. (If it's not broken...)
Community
  • 1
  • 1
Sdlion
  • 525
  • 8
  • 18
  • Can you make them all `static const`? Or are some of them used as a constant (e.g. as an array size or a `case` label)? – mafso Jan 07 '14 at 19:12
  • I haven't started to code and I haven't finished reading all the code yet. But in those cases I won't use `static const`. – Sdlion Jan 07 '14 at 20:55

5 Answers5

6

Always consider readability and memory constraints. Also, macros are simply copy/paste operations that occur before compilation. With that being said I like to do the following:

  • I define all variables that are constant as being static const if they are to be used in one c file (e.g. not globally accessible across multiple files). Anything defined as const shall be placed in ROM when at file scope. Obviously you cannot change these variables after they're initialized.
  • I define all constant values using #define.
  • I use enumerations where it adds to readability. Any place where you have a fixed range of values I prefer enumerations to explicitly state the intent.

Try to approach the project with an object oriented perspective (even though c isn't OO). Hide private functions (don't create a prototype in the header), do not use globals if you can avoid it, mark variables that should only reside in one c module (file) as static, etc.

bblincoe
  • 2,393
  • 2
  • 20
  • 34
  • 1
    One note of caution here: a fair number of embedded c debuggers will have issues with visibility of variables defined as static to a compilation unit unless you are actually within the code of that compilation unit. – Speed8ump Jan 07 '14 at 20:19
  • @bblincoe Do you think it would be better to place #define's that are constant values in just a separate but context related file since they're global to begin with? Like global_conf.c lcd_conf.c uart_conf.c Avoiding the use of #define's for constant values on files with code would prevent name conflicts on "I didn't know/remembered that define" cases. Though for this case, first I would have to search every file for a name before using it in a define. – Sdlion Jan 07 '14 at 21:17
  • Object orientation is a program design method which is universal for any programming language. Some languages have support for certain OO features, some have not. C supports private encapsulation of data and functions and creation of autonomous objects. The only fundamental OO feature it does not support is inheritance. (Type safety, constructors/destructors, operator overloading, template programming, exception handling etc etc are all handy features, but they aren't necessary to write OO programs.) – Lundin Jan 08 '14 at 10:41
  • 1
    @Sdlion I prefer to group `#define`s in with their related content. For instance, if I have a UART driver I will place all my `#define` statements related to UART in `uart.h` along with any function prototypes that would be considered public. I would place private `static const` variables in `uart.c` to limit visibility by outside modules including the `uart.h` file. Also, in `uart.c` I'd place my implementation of, say the function, `void uartWrite(char byte);`. – bblincoe Jan 08 '14 at 12:36
  • 2
    Btw the statement "This allows them to be in ROM and not eat up precious RAM" is strange. Whether a `const` ends up in RAM or ROM depends on which scope it is declared in and also whether the compiler optimizes the constant or nor. Constants allocated at file scope, which this question is about, always end up in ROM. – Lundin Jan 08 '14 at 15:50
4

They are 3 different things that should be used in 3 different situations.

  • #define should be used for constants that need to be evaluated at compile time. One typical example is the size of a statically allocated array, i.e.

    #define N 10 
    
    int x[N];
    

    It is also fine to use #define all constants where it doesn't matter how or where the constant is allocated. People who claim that it is bad practice to do so only voice their own, personal, subjective opinions.

    But of course, for such cases you can also use const variables. There is no important difference between #define and const, except for the following cases:

  • const should be used where it matters at what memory address a constant is allocated. It should also be used for variables that the programmer will likely change often. Because if you used const, you an easily move the variable to a memory segment in EEPROM or data flash (but if you do so, you need to declare it as volatile).

    Another slight advantage of const is that you get stronger type safety than a #define. For the #define to get equal type safety, you have to add explicit type casts in the macro, which might get a bit harder to read.

    And then of course, since consts (and enums) are variables, you can reduce their scope with the static keyword. This is good practice since such variables do not clutter down the global namespace. Although the true source of name conflicts in the global namespaces are in 99% of all cases caused by poor naming policies, or no naming policies at all. If you follow no coding standard, then that is the true source of the problem.

    So generally it is fine to make constants global when needed, it is rather harmless practice as long as you have a sane naming policy (preferably all items belonging to one code module should share the same naming prefix). This shouldn't be confused with the practice of making regular variables global, which is always a very bad idea.

  • Enums should only be used when you have several constant values that are related to each other and you want to create a special type, such as:

    typedef enum
    {
      OK,
      ERROR_SOMETHING,
      ERROR_SOMETHING_ELSE
    } error_t;
    

    One advantage of the enum is that you can use a classic trick to get the number of enumerated items as another compile-time constant "free of charge":

    typedef enum
    {
      OK,
      ERROR_SOMETHING,
      ERROR_SOMETHING_ELSE,
    
      ERRORS_N  // the number of constants in this enum
    } error_t;
    

    But there are various pitfalls with enums, so they should always be used with caution.

    The major disadvantage of enum is that it isn't type safe, nor is it "type sane". First of all, enumeration constants (like OK in the above example) are always of the type int, which is signed.

    The enumerated type itself (error_t in my example) can however be of any type compatible with char or int, signed or unsigned. Take a guess, it is implementation-defined and non-portable. Therefore you should avoid enums, particularly as part of various data byte mappings or as part of arithmetic operations.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • In this specific case, there's no proper naming policy. So I guess that as long I held to a sane naming policy, my code should not conflict with anything else (using a prefix should be enough to differ enough from everything else). Then what I get from this is that the problem with `enum` is that is not type-safe (is implementation-defined) and it won't help with the readability of the code. – Sdlion Jan 08 '14 at 16:57
  • @Sdlion It may help the readability, but at the same time it comes with many subtle problems, as mentioned in my answer. – Lundin Jan 08 '14 at 18:00
2

I agree with bblincoe...+1

I wonder if you understand what the differences are in that syntax and how it can/might affect implementation. Some folks may not care about implementation but if you are moving into embedded perhaps you should.

When bblincoe mentions ROM instead of RAM.

static const int char_height = 12;

That should, ideally, consume .text real estate and pre-init that real estate with the value you specified. Being const you wont change it but it does have a placeholder? now why would you need a placeholder for a constant? think about that, certainly you could hack the binary down the road for some reason to turn something on or off or change a board specific tuning parameter...

Without a volatile though that doesnt mean that compiler has to always use that .text location, it can optimize and put that value in as instructions directly or even worse optimize math operations and remove some math.

The define and enum do not consume storage, they are constants that the compiler chooses how to implement, ultimately those bits if they are not optimized away, land somewhere in .text sometimes everywhere in .text, depends on the instruction set how its immediates work the specific constant, etc.

So define vs enum is basically do you want to pick all the values or do you want the compiler to pick some values for you, define if you want to control it enum if you want the compiler to choose the values.

So it really isnt a best practice thing at all it is a case of determining what your program needs to do and choosing the appropriate programming solution for that situation.

Depending on the compiler and the target processor, choosing volatile static const int vs not doing that can affect the rom consumption. But it is a very specific optimization, and not a general answer (and has nothing to do with embedded but with compiling in general).

old_timer
  • 69,149
  • 8
  • 89
  • 168
  • Thank you for the insight on the compiler part. Most of the data I'm thinking about is the kind used to configure the environment: screen dimensions, charset characteristics, something to improve the readability of the code. Not on the automatic configuration a compiler and pre-processor could do. But since there's a lot of code in there, I'm reluctant to use #define's – Sdlion Jan 07 '14 at 21:00
  • not knowing what is defined, remember that although hardware can be spun, it is generally very static. A number that does some magic in a hardware register is not something dynamic, that chip uses that set of numbers period, you can hardcode that in your code, you dont need to add a lot of features that are geared toward maintenance, future changes, etc. That hardware is static. now if these defines are other things that may be dynamic in the future a define or a hard coded value may still be okay. – old_timer Jan 07 '14 at 21:19
1

Dan Saks explains why he prefers the enumeration constant in these articles, Symbolic Constants and Enumeration Constants vs Constant Objects. In summary, avoid macros because they don't observe the usual scope rules and the symbolic names are typically not preserved for symbolic debuggers. And prefer enumeration constants because they are not susceptible to a performance penalty that may affect constant objects. There is a lot more details in the linked articles.

kkrambo
  • 6,643
  • 1
  • 17
  • 30
  • Though those articles refer to C and C++ I think it's more biased to C++ embedded code. In the collection of questions regarding this topic I've read that in C++ using #define's are highly discouraged but in C is common practice since sometimes it can't be avoided. Still, it helps pointing that if the constant is an integer number, an enumeration is a good choice. – Sdlion Jan 08 '14 at 01:42
  • It's not clear to me if a static const would be stored in code or in RAM, since Dan Saks differs from other people comments and blog posts about C programming. – Sdlion Jan 08 '14 at 01:48
  • 1
    Enums have however a lot of other issues and unexpected behavior. – Lundin Jan 08 '14 at 10:44
  • 1
    @Sdlion A static const is always stored in ROM on an embedded system. If you would read articles written by lets say PC programmers, they would be a bit confusing, since no true ROM exists in a PC program's address space. The whole PC program is executed from RAM and the constants in a PC program are stored in a read-only part of that RAM. – Lundin Jan 08 '14 at 15:58
1

Another thing to considerer is performance. A #define constant can usually be accessed faster than a const variable (for integers) since the const will need to be fetched from ROM (or RAM) and the #define value will usually be an immediate instruction argument so it is fetched along with the instruction (no extra cycles).

As for naming conflicts, I like to use prefixes like MOD_OPT_ where MOD is the module name OPT means that the define is a compile-time option, etc. Also only include the #defines in your header files if they're part of the public API, otherwise use an .inc file if they're needed in multiple source files or define them in the source file itself if they're only specific to that file.

fernan
  • 349
  • 1
  • 6
  • `#define` will only be faster if the `const` is declared as volatile, otherwise the compiler should/may optimize it to be included in the program's machine code. – Lundin Jan 08 '14 at 12:56
  • though is helpful having a naming convention for `#define`'s That's what worries me the most, on this project – Sdlion Jan 08 '14 at 16:31