Best practice on writing constant parameters for e

2019-04-23 11:56发布

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...)

5条回答
手持菜刀,她持情操
2楼-- · 2019-04-23 12:42

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.

查看更多
走好不送
3楼-- · 2019-04-23 12:42

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.

查看更多
beautiful°
4楼-- · 2019-04-23 12:48

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.

查看更多
爱情/是我丢掉的垃圾
5楼-- · 2019-04-23 12:54

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).

查看更多
家丑人穷心不美
6楼-- · 2019-04-23 12:56

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.

查看更多
登录 后发表回答