Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practice on writing constant parameters for embedded systems

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...)
like image 920
Sdlion Avatar asked Jan 07 '14 19:01

Sdlion


2 Answers

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.

like image 186
bblincoe Avatar answered Oct 14 '22 16:10

bblincoe


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.

like image 38
Lundin Avatar answered Oct 14 '22 18:10

Lundin