2

I have read a few questions on the topic:

and I understand that enum are usually preferred on the #define macros for a better encapsulation and/or readibility. Plus it allows the compilers to check for types preventing some errors.

const declaration are somewhat in between, allowing type checking, and encapsulation, but more messy.

Now I work in Embedded applications with very limited memory space (we often have to fight for byte saving). My first ideas would be that constants take more memory than enums. But I realised that I am not sure how constants will appear in the final firmware.


Example:

enum { standby, starting, active, stoping } state;

Question

In a resource limited environment, how does the enum vs #define vs static const compare in terms of execution speed and memory imprint?

clem steredenn
  • 352
  • 7
  • 20
  • this link might be helpful: [https://stackoverflow.com/questions/11779361/static-const-vs-define-in-c-differences-in-executable-size](https://stackoverflow.com/questions/11779361/static-const-vs-define-in-c-differences-in-executable-size) – Aashish Kumar Sep 22 '17 at 13:09
  • 3
    @aashish: that link is interesting, but not relevant here; the issue there was a C++ feature which doesn't apply to C programs: operator I overloading. (And five years later, the problem could easily be solved with another C++ feature: `constexpr`). – rici Sep 22 '17 at 13:23
  • Enum-constants are **the only** symbolic constants. `cost` does not mean "constant"! "enum are usually preferred on the #define macros for a better encapsulation and/or readibility" - There is no more encapsulation for enum-constants than macros. They just work at different levels. enum-constants are `int` - **always**. There is nothing wrong using macros, that's from the C++ theoretists who don't write production code (and who _do have other symbolic constants_ in C++). – too honest for this site Sep 22 '17 at 13:35
  • If you are up to max. compactness without always checking the machine code, use macro. Just don't use stupid names like `C`, etc. Establish a good naming scheme and follow it. – too honest for this site Sep 22 '17 at 13:43
  • [Enumeration Constants vs. Constant Objects](http://www.embedded.com/electronics-blogs/programming-pointers/4023879/Enumeration-Constants-vs-Constant-Objects) – kkrambo Sep 22 '17 at 20:04

4 Answers4

4

To try to get some substantial elements to the answer, I made a simple test.

Code

I wrote a simple C program main.c:

#include <stdio.h>

#include "constants.h"

// Define states
#define STATE_STANDBY 0
#define STATE_START   1
#define STATE_RUN     2
#define STATE_STOP    3

// Common code
void wait(unsigned int n)
{
  unsigned long int vLoop;

  for ( vLoop=0 ; vLoop<n*LOOP_SIZE ; ++vLoop )
  {
    if ( (vLoop % LOOP_SIZE) == 0 ) printf(".");
  }
  printf("\n");
}

int main ( int argc, char *argv[] )
{
  int state = 0;
  int loop_state;

  for ( loop_state=0 ; loop_state<MACHINE_LOOP ; ++loop_state)
  {
    if ( state == STATE_STANDBY )
    {
      printf("STANDBY ");
      wait(10);
      state = STATE_START;
    }
    else if ( state == STATE_START )
    {
      printf("START ");
      wait(20);
      state = STATE_RUN;
    }
    else if ( state == STATE_RUN )
    {
      printf("RUN ");
      wait(30);
      state = STATE_STOP;
    }
    else // ( state == STATE_STOP )
    {
      printf("STOP ");
      wait(20);
      state = STATE_STANDBY;
    }
  }

  return 0;
}

while constants.h contains

#define LOOP_SIZE     10000000
#define MACHINE_LOOP  100

And I considered three variants to define the state constants. The macro as above, the enum:

enum {
  STATE_STANDBY=0,
  STATE_START,
  STATE_RUN,
  STATE_STOP
} possible_states;

and the const:

static const int  STATE_STANDBY = 0;
static const int  STATE_START   = 1;
static const int  STATE_RUN     = 2;
static const int  STATE_STOP    = 3;

while the rest of the code was kept identical.

Tests and Results

Tests were made on a 64 bits linux machine and compiled with gcc

Global Size

  • gcc main.c -o main gives

    macro: 7310 bytes
    enum: 7349 bytes
    const: 7501 bytes

  • gcc -O2 main.c -o main gives

    macro: 7262 bytes
    enum: 7301 bytes
    const: 7262 bytes

  • gcc -Os main.c -o main gives

    macro: 7198 bytes
    enum: 7237 bytes
    const: 7198 bytes

When optimization is turned on, both the const and the macro variants come to the same size. The enum is always slightly larger. Using gcc -S I can see that the difference is a possible_states,4,4 in .comm. So the enum is always larger than the macro. and the const can be larger but can also be optimized away.

Section size

I checked a few sections of the programs using objdump -h main: .text, .data, .rodata, .bss, .dynamic. In all cases, .bss has 8 bytes, .data, 16 bytes and .dynamic: 480 bytes.

.rodata has 31 bytes, except for the non-optimized const version (47 bytes).

.text goes from 620 bytes up to 780 bytes, depending on the optimisation. The const unoptimised being the only one differing with the same flag.

Execution speed

I ran the program a few times, but I did not notice a substantial difference between the different versions. Without optimisation, it ran for about 50 seconds. Down to 20 seconds with -O2 and up to more than 3 minutes with -Os. I measured the time with /usr/bin/time.

RAM usage

Using time -f %M, I get about 450k in each case, and when using valgrind --tool=massif --pages-as-heap=yes I get 6242304 in all cases.

Conclusion

Whenever some optimisation has been activated, the only notable difference is about 40 Bytes more for the enum case. But no RAM or speed difference.

Remains other arguments about scope, readability... personal preferences.

clem steredenn
  • 352
  • 7
  • 20
2

and I understand that enum are usually preferred on the #define macros for a better encapsulation and/or readibility

Enums are preferred mainly for better readability, but also because they can be declared at local scope and they add a tiny bit more of type safety (particularly when static analysis tools are used).

Constant declaration are somewhat in between, allowing type checking, and encapsulation, but more messy.

Not really, it depends on scope. "Global" const can be messy, but they aren't as bad practice as global read/write variables and can be justified in some cases. One major advantage of const over the other forms is that such variables tend to be allocated in .rodata and you can view them with a debugger, something that isn't always possible with macros and enums (depends on how good the debugger is).

Note that #define are always global and enum may or may not be, too.

My first ideas would be that constants take more memory than enums

This is incorrect. enum variables are usually of type int, though they can be of smaller types (since their size can vary they are bad for portability). Enumeration constants however (that is the things inside the enum declaration) are always int, which is a type of at least 16 bits.

A const on the other hand, is exactly as large as the type you declared. Therefore const is preferred over enum if you need to save memory.

In a resource limited environment, how does the enum vs #define vs static const compare in terms of execution speed and memory imprint?

Execution speed will probably not differ - it is impossible to say since it is so system-specific. However, since enums tend to give 16 bit or larger values, they are a bad idea when you need to save memory. And they are also a bad idea if you need an exact memory layout, as is often the case in embedded systems. The compiler may however of course optimize them to a smaller size.

Misc advice:

  • Always use the stdint.h types in embedded systems, particularly when you need exact memory layout.
  • Enums are fine unless you need them as part of some memory layout, like a data protocol. Don't use enums for such cases.
  • const is ideal when you need something to be stored in flash. Such variables get their own address and are easier to debug.
  • In embedded systems, it usually doesn't make much sense to optimize code in order to reduce flash size, but rather to optimize to reduce RAM size. #define will always end up in .text flash memory, while enum and const may end up either in RAM, .rodata flash or .text flash.
  • When optimizing for size (RAM or flash) on an embedded system, keep track of your variables in the map file (linker output file) and look for things that stand out there, rather than running around and manually optimizing random things at a whim. This way you can also detect if some variables that should be const have ended up in RAM by mistake (a bug).
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 1
    #define is the related with the preprocessor. Are you sure that it ends up in .text flash memory? – saygins Sep 22 '17 at 14:22
  • Why is it a bad idea to reduce the flash size? – clem steredenn Sep 22 '17 at 22:58
  • @saygins Yes. Since macros are just text replacement they end up embedded inside the program code itself, which is stored in `.text`. For example one machine instruction could be "Load value 123 into register X", where 123 is taken from the define. – Lundin Sep 25 '17 at 11:28
  • 1
    @bilbo_pingouin I didn't say it was a bad idea, only that it usually doesn't make sense, since you value RAM or speed higher. When optimizing for a certain goal, other things suffer. If you optimize for execution speed, you often use up more RAM. If you optimize for RAM use, it is often done at the expense of flash. And if you optimize for program executable size (which is a rare case) it often comes at the expense of execution speed and/or RAM. – Lundin Sep 25 '17 at 11:28
2

enums, #defines and static const will generally give exactly the same code (and therefore the same speed and size), with certain assumptions.

When you declare an enum type and enumeration constants, these are just names for integer constants. They don't take any space or time. The same applies to #define'd values (though these are not limited to int's).

A "static const" declaration may take space, usually in a read-only section in flash. Typically this will only be the case when optimisation is not enabled, but it will also happen if you forget to use "static" and write a plain "const", or if you take the address of the static const object.

For code like the sample given, the results will be identical with all versions, as long as at least basic optimisation is enabled (so that the static const objects are optimised). However, there is a bug in the sample. This code:

enum {
  STATE_STANDBY = 0,
  STATE_START,
  STATE_RUN,
  STATE_STOP
} possible_states;

not only creates the enumeration constants (taking no space) and an anonymous enum type, it also creates an object of that type called "possible_states". This has global linkage, and has to be created by the compiler because other modules can refer to it - it is put in the ".comm" common section. What should have been written is one of these:

// Define just the enumeration constants
enum { STATE_STANDBY, ... };

// Define the enumeration constants and the
// type "enum possible_states"
enum possible_states { STATE_STANDBY, ... };

// Define the enumeration constants and the
// type "possible_states"
typedef enum { STATE_STANDBY, ... } possible_states;

All of these will give optimal code generation.

When comparing the sizes generated by the compiler here, be careful not to include the debug information! The examples given show a bigger object file for the enumeration version partly because of the error above, but mainly because it is a new type and leads to more debug information.

All three methods work for constants, but they all have their peculiarities. A "static const" can be any type, but you can't use it for things like case labels or array sizes, or for initialising other objects. An enum constant is limited to type "int". A #define macro contains no type information.

For this particular case, however, an enumerated type has a few big advantages. It collects the states together in one definition, which is clearer, and it lets you make them a type (once you get the syntax correct :-) ). When you use a debugger, you should see the actual enum constants, not just a number, for variables of the enum type. ("state" should be declared of this type.) And you can get better static error checking from your tools. Rather than using a series of "if" / "else if" statements, use a switch and use the "-Wswitch" warning in gcc (or equivalent for other compilers) to warn you if you have forgotten a case.

David
  • 132
  • 3
0

I used a OpenGL library which defined most constants in an enum, and the default OpenGL-header defines it as #defines. So as a user of the header/library there is no big difference. It is just an aspect of desing. When using plain C, there is no static const until it is something that changes or is a big string like (extern) static const char[] version;. For myself I avoid using macros, so in special cases the identifier can be reused. When porting C code to C++, the enums even are scoped and are Typechecked.

Aspect: characterusage & readability:

#define MY_CONST1 10
#define MY_CONST2 20
#define MY_CONST3 30
//...
#define MY_CONSTN N0

vs.

enum MyConsts {
    MY_CONST1 = 10,
    MY_CONST2 = 20,
    MY_CONST3 = 30,
    //...
    MY_CONSTN = N0 // this gives already an error, which does not happen with (unused) #defines, because N0 is an identifier, which is probably not defined
};
cmdLP
  • 1,658
  • 9
  • 19
  • So, how do you create enum-constants of a different type than `int`? Macros are fine! – too honest for this site Sep 23 '17 at 18:21
  • @jeb That's the enum variable not the enumeration constants, which _must_ be `int` - it's an inconsistency in the language. Furthermore, the size of enum variables is not necessarily something that the programmer can decide, the compiler is free to make them whatever size it thinks is suitable. You have no control over enum sizes cross-platform, they aren't portable if you need them to be a certain size. – Lundin Sep 25 '17 at 11:45
  • @jeb: enum-constants_ cannot be smaller than `int`, they have type `int`. Please read the standard. For the rest, see Lundins comment. I don't see the problem with macros here. Interestingly a lot of people being against macro-like constants often prefer functionlike macros over formal functions, altough possible. – too honest for this site Sep 25 '17 at 11:51