Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mode settings in C: enum vs constant vs defines

Tags:

c

enums

embedded

I have read a few questions on the topic:

  • Should I use #define, enum or const?
  • How does Enum allocate Memory on C?
  • What makes a better constant in C, a macro or an enum?
  • What is the size of an enum in C?
  • "static const" vs "#define" vs "enum"
  • static const vs. #define in c++ - differences in executable size

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?

like image 800
clem steredenn Avatar asked Sep 15 '25 11:09

clem steredenn


2 Answers

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.

like image 99
clem steredenn Avatar answered Sep 17 '25 20:09

clem steredenn


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).
like image 23
Lundin Avatar answered Sep 17 '25 18:09

Lundin