Const vs #define – When do you use them and why?

Arduino boards have microcontrollers with notoriously small amounts of RAM.  The Uno only has 2,048 bytes of static RAM available for your program to store variables.  So when you need to keep non-changing variables out of RAM which is best to use const or #define?

What is #define

#define is often misunderstood because it isn’t a programming statement.  Instead, it sets up a macro which causes a text replace to occur before code is compiled.  For example in this code:

#define pin 13
void setup() {
   pinMode(pin, OUTPUT);
}
void loop() {
  digitalWrite(pin, HIGH);
  delay(500);
   digitalWrite(pin, LOW);   
   delay(500);
}

Before the IDE sends the code to the compiler, a pre-processor will go through and do a simple text-replace of all instances of the macro “pin” with the integer 13.  So that means the code actually compiled looks like this:

#define pin 13
void setup() {
   pinMode(13, OUTPUT);
}
void loop() {
  digitalWrite(13, HIGH);
  delay(500);
   digitalWrite(13, LOW);   
   delay(500);
}

This is a very powerful and useful feature.  No RAM is consumed by a variable called “pin”.  In fact, no variable called “pin” exists at all!

What is const

The keyword “const” tells the compiler that a variable (or pointer) can not be modified.  However, it is still a variable and depending on how it is used in the code, may or may not consume RAM.  As it turns out, the compiler used by the IDE, avr-gcc, is smart enough to know that a variable with the const modifier can’t be changed within the active program and will try to leave it out of RAM.

So this code:

const int pin=13;
void setup() {
   pinMode(pin, OUTPUT);
}
void loop() {
  digitalWrite(pin, HIGH);
  delay(500);
   digitalWrite(pin, LOW);   
   delay(500);
}

Will not actually consume any RAM.  The compiler knows there is no reason to create a variable in RAM, so it’ll stay out of RAM.

Using AVR-SIZE

You have probably noticed that when compiling code in the Arduino IDE, the status window will finish with a message like this:

binarysketchsize

The message “binary sketch size” refers to the size of the HEX file for the code, and has no (direct) relation to how much RAM will be used.

How do you know how much RAM is being used?  The avr-gcc toolchains comes with a utility called avr-size which breaks out how much static memory a program will use.  For each of the above code segments, the avr-size command returns the following information on each binary file.

Using #define:

avr-size -C sketch_nov11a.cpp.elf
AVR Memory Usage
----------------
Device: Unknown
Program: 4818 bytes
(.text + .data + .bootloader)
Data: 155 bytes
(.data + .bss + .noinit)

Using const:

avr-size -C sketch_nov11a.cpp.elf
AVR Memory Usage
----------------
Device: Unknown
Program: 4818 bytes
(.text + .data + .bootloader)
Data: 155 bytes
(.data + .bss + .noinit)

Shocking isn’t it?  Whether you use #define or const, the amount of Data memory used is the same: 155 bytes!

Which to use for “variables”:  #define or const?

This leads to the question of when should you use #define or const?

In this case variables, using #define can actually become a bad practice.  Why?  Because it can lead to very hard to find bugs.

Case against using #define for variables

For example, consider this code:

#define pin 13;
void setup() {
    pinMode(pin, INPUT);
}
void loop() {
    digitalWrite(pin, HIGH);
    delay(500);
    digitalWrite(pin, LOW);
    delay(500);
}

What happens when that code gets complied?  The code is turned into this:

#define pin 13;
void setup() {
    pinMode(13;, INPUT);
}
void loop() {
    digitalWrite(13;, HIGH);
    delay(500);
    digitalWrite(13;, LOW);
    delay(500);
}

See the problem?  Is the compiler giving us any clues?  Here’s the output from the compiler:

file included from sketch_apr10a.ino:2:
/Applications/Arduino.app/Contents/Resources/Java/hardware/arduino/cores/arduino/Arduino.h:109: error: expected ',' or '...' before numeric constant
...
sketch_apr10a.ino: In function 'void setup()':
sketch_apr10a:3: error: expected `)' before ';' token
sketch_apr10a:3: error: expected primary-expression before ',' token
sketch_apr10a:3: error: expected `;' before ')' token
sketch_apr10a.ino: In function 'void loop()':
sketch_apr10a:6: error: expected `)' before ';' token
sketch_apr10a:6: error: expected primary-expression before ',' token
sketch_apr10a:6: error: expected `;' before ')' token
sketch_apr10a:7: error: expected `;' before ':' token
sketch_apr10a:8: error: expected primary-expression before ',' token
sketch_apr10a:8: error: expected `;' before ')' token

Conclusion

Since the avr-gcc compiler is smart enough to keep variables out of RAM when using the const keyword, it is better to just use that.  Eventually the “text-replace” nature of #define is going to bite you with a stray semicolon or other unexpected replace.

Reference

For in-depth information on this subject, check out this thread in the Arduino Forums:  Const vs #define – Arduino Forum.

Long comments, URLs, and code tend to get flagged for spam moderation. No need to resubmit.

ALL comments submitted with fake or throw-away services are deleted, regardless of content.

Don't be a dweeb.

Leave a comment

10 thoughts on “Const vs #define – When do you use them and why?

  1. A note for people that think this is a way of saving RAM:
    using #define does not keep them out of RAM, just makes the code simpler to not use unnecessary RAM when using the values. A general rule of thumb is that everything ends up in RAM if the program uses it in any way.
    Also, this “simplification” has no effect when defining strings. A [#define something “bla”] will end up creating a string allocation in RAM and only the handle (pointer to the string) will be substituted throughout the code, which means that it still uses the same about of RAM as if we have used [const char *something = “bla”;].
    For example, code like this:

    #define something “bla”
    printf(something);

    will result in somthing like this:

    (unnamed allocation i ro-RAM at address ) = “bla”
    printf((const char *)0x000B00B5);

  2. The only thing that has me wondering is that I often see #define used in “professional” contexts like adafruit libraries. Perhaps they are being extra sure to not use memory for some cases, or is it just style?

    • One thing to keep in mind is when the #define is processed, as opposed to consts:
      defines are resolved by the pre(!)-processor. so by the time the code arrives at the compiler everything ‘define’d, will be replaced by the actual result.

      The example above shows no discernable difference between consts and defines. But it doesn’t tell the whole story:
      ‘#define’ isn’t the only pre-processor statement. In fact, there’s a host of others. The most important ones (in my opinion) are ‘#ifdef’ ‘#elif’, ‘#endif’ ‘#else’.
      Consider the following code (UNTESTED!):

      #define FOO1
      // #define FOO2
      
      #ifdef FOO1
      void foo(int val)
      {
        Serial.println(val);
      }
      #elif defined(FOO2)
      
      void foo(int val)
      {
        Serial.println(val);
      }
      #else
      void foo(int val)
      {
        Serial.println("No definition of FOO given!!");
      }
      
      #endif
      
      foo();
      

      The preprocessor will define FOO1 (without a value, but that isn’t very important here). It will then evaluate the if tree and add the foo function to the code that corresponds to FOO1. It’ll ignore the rest.

      This is useful for when, as an Adafruit developer, you’re writing code for multiple platforms. you can simply build a switch that will add and configure all kinds of support for different platforms. Or simply warnings / errors for when your code won’t run on a specified platform.

      so yes, defines are useful.

  3. I worked out the answer to my question and the reason I had become confused.

    Even the first example failed for me with the following compile error:
    expected ‘,’ or ‘…’ before numeric constant

    I thought the word pin was causing a replace in pinMode but when I turned on verbose compiler messages I saw that the problem is actually that the word pin exists in the arduino header files which are automatically included by the arduino IDE. The relevant line is here:
    unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout);

    So pin is being replaced with 13 and causing an error.

    To verify this I tried changing the define to this (and the matching uses of pin):
    #define pinM 13

    This still matches a substring of pinMode, but it compiles fine.

    I think the extended lesson is twofold. Using #define is potentially problematic in more ways than just the example given. Also, if you must use #define, choose a macro name that is never going to match something unintentionally.

    • No, “pinMode” won’t match “pin”. Think of it as matching only the “whole word.” If you turn on verbose errors in the IDE’s preferences, you can see where the compiler gets upset. It’s always on the “pin”, but not on “pinMode”.

      • That’s weird. I get a compiler error with your initial sample code but it goes away if I change the pin to PIN where it is supposed to replace. To me, substring matching in define is another really good reason not to use it, also the reason people often use all caps for defines to prevent unforeseen matches. I’m pretty new to all this low level stuff so I’m open to the possibility of being wrong 😉

        • Maybe something else is up? This first code example, compiles fine for me.

          #define pin 13
          void setup() {
             pinMode(pin, OUTPUT);
          }
          void loop() {
            digitalWrite(pin, HIGH);
            delay(500);
             digitalWrite(pin, LOW);   
             delay(500);
          }
          

          But, maybe different compiler versions do things different. I don’t know the C standards well enough (and not really interested in going that deep.)