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:
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:
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:
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:
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.
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.
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.
Fan of making things beep, blink and fly. Created AddOhms. Stream on Twitch. Video Host on element14 Presents and writing for Hackster.IO. Call sign KN6FGY.
I had historically used #define for global values but wanted to know if I should be using const instead for global values and I came across this article. What this article concludes is there seems to be no practical difference between them. The only claim made here is that #define may cause bugs if you don’t realize you don’t end a compiler macro with a semicolon. So I decided to try changing a couple of #defines to static const to see what happens and I started getting lots of compiler warnings “‘xxxx’ defined but not used”. This stems from the fact that I have a single header file that contains all my global values as #defines. Things such as pin definitions, BLE characteristics, ON and OFF value, MILLISECONDS_PER_HOUR, etc… Having them in one location rather than sprinkled throughout the code is the point of global values. Having these values as #defines, if they are included but unused in a particular file they are simply ignored and I get no compiler warnings. But if I create these global values as static consts and I don’t use them ALL of then in EVERY file in which I include my project global values, I get the “‘xxxx’ defined but not used” compiler warning. So I find #define works better for values that don’t change I’ll reserve the use of const for it’s original purpose, preventing a passed pointer from being modified by the receiver.
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);
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!):
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.
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 😉
Like other websites, this one uses cookies to remember things. Mostly, I use Google Analytics to know how many people come here. ¯\_(ツ)_/¯
By clicking “Accept”, you consent to the use of ALL the cookies.
This website uses cookies to improve your experience while you navigate through the website. Out of these, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may affect your browsing experience.
Necessary cookies are absolutely essential for the website to function properly. These cookies ensure basic functionalities and security features of the website, anonymously.
Cookie
Duration
Description
cookielawinfo-checkbox-advertisement
1 year
Set by the GDPR Cookie Consent plugin, this cookie is used to record the user consent for the cookies in the "Advertisement" category .
cookielawinfo-checkbox-analytics
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics".
cookielawinfo-checkbox-functional
11 months
The cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional".
cookielawinfo-checkbox-necessary
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary".
cookielawinfo-checkbox-others
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other.
cookielawinfo-checkbox-performance
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance".
CookieLawInfoConsent
1 year
Records the default button state of the corresponding category & the status of CCPA. It works only in coordination with the primary cookie.
viewed_cookie_policy
11 months
The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data.
Functional cookies help to perform certain functionalities like sharing the content of the website on social media platforms, collect feedbacks, and other third-party features.
Cookie
Duration
Description
language
session
This cookie is used to store the language preference of the user.
Performance cookies are used to understand and analyze the key performance indexes of the website which helps in delivering a better user experience for the visitors.
Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics the number of visitors, bounce rate, traffic source, etc.
Cookie
Duration
Description
_ga
2 years
The _ga cookie, installed by Google Analytics, calculates visitor, session and campaign data and also keeps track of site usage for the site's analytics report. The cookie stores information anonymously and assigns a randomly generated number to recognize unique visitors.
_ga_LHR6J24XSY
2 years
This cookie is installed by Google Analytics.
_gat_gtag_UA_42726312_1
1 minute
Set by Google to distinguish users.
_gid
1 day
Installed by Google Analytics, _gid cookie stores information on how visitors use a website, while also creating an analytics report of the website's performance. Some of the data that are collected include the number of visitors, their source, and the pages they visit anonymously.
browser_id
5 years
This cookie is used for identifying the visitor browser on re-visit to the website.
CONSENT
2 years
YouTube sets this cookie via embedded youtube-videos and registers anonymous statistical data.
Advertisement cookies are used to provide visitors with relevant ads and marketing campaigns. These cookies track visitors across websites and collect information to provide customized ads.
Cookie
Duration
Description
VISITOR_INFO1_LIVE
5 months 27 days
A cookie set by YouTube to measure bandwidth that determines whether the user gets the new or old player interface.
YSC
session
YSC cookie is set by Youtube and is used to track the views of embedded videos on Youtube pages.
yt-remote-connected-devices
never
YouTube sets this cookie to store the video preferences of the user using embedded YouTube video.
yt-remote-device-id
never
YouTube sets this cookie to store the video preferences of the user using embedded YouTube video.
yt.innertube::nextId
never
This cookie, set by YouTube, registers a unique ID to store data on what videos from YouTube the user has seen.
yt.innertube::requests
never
This cookie, set by YouTube, registers a unique ID to store data on what videos from YouTube the user has seen.
13 Comments
Pingback: Algumas técnicas para otimizar seu código do Arduino – Argumentum Ad Computum
I had historically used #define for global values but wanted to know if I should be using const instead for global values and I came across this article. What this article concludes is there seems to be no practical difference between them. The only claim made here is that #define may cause bugs if you don’t realize you don’t end a compiler macro with a semicolon. So I decided to try changing a couple of #defines to static const to see what happens and I started getting lots of compiler warnings “‘xxxx’ defined but not used”. This stems from the fact that I have a single header file that contains all my global values as #defines. Things such as pin definitions, BLE characteristics, ON and OFF value, MILLISECONDS_PER_HOUR, etc… Having them in one location rather than sprinkled throughout the code is the point of global values. Having these values as #defines, if they are included but unused in a particular file they are simply ignored and I get no compiler warnings. But if I create these global values as static consts and I don’t use them ALL of then in EVERY file in which I include my project global values, I get the “‘xxxx’ defined but not used” compiler warning. So I find #define works better for values that don’t change I’ll reserve the use of const for it’s original purpose, preventing a passed pointer from being modified by the receiver.
Love this – Thank you!
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);
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!):
[arduino firstline=”1″]
#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();
[/arduino]
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.
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.
Isn’t there a second problem? Won’t pinMode become 13;Mode? If not why?
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.
[arduino firstline=””]
#define pin 13
void setup() {
pinMode(pin, OUTPUT);
}
void loop() {
digitalWrite(pin, HIGH);
delay(500);
digitalWrite(pin, LOW);
delay(500);
}
[/arduino]
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.)
Pingback: Hằng trong C - Technology & Photography
Pingback: Too Much #define in the Rover Constants Header Files? | The Rappelling Rover