#define, int or const int

Good morning!

Relative newbie here, looking to use the Arduino I have rather than the Pi...

I've looked at loads of code and have come across a puzzle. Even in the Hello World "blink" sketch I have seen the following:

#define ledPin = 13
int ledPin = 13
const int ledPin = 13

Now, I understand that #define is really a "search an replace" type of thing, looking for ledPin and replacing it with 13.
What about the other two? They are both integer variables, but the last one is also a constant. Is it important to declare it as such? The other example works without. Or is it a matter of choice?

Thanks in advance
MisterG

"const" makes the variable 'read only'.
...so in this case: doesn't matter.

int ledPin = 13

you are creating a variable (ledpin) of int data type and assigning 13 to it.
but you can change the assigned value of ledpin when ever you want.

const int ledPin = 13

you are creating a variable (ledpin) of int data type and assigning 13 to it.
but you can not change the assigned value of ledpin throught out its scope.

1 Like

For pin number you should not use int as it wastes memory, use one of the byte sized types.

Mark

Note that
#define ledPin = 13is not correct. Unless, that is, you really want the pre-processor to replace all instances of ledPin with = 13, which is unlikely

MisterG:
#define ledPin = 13

This is a great example of why #define shouldn't be used.

This is going to introduce confusing compiler messages and a difficult to locate bug...

MisterG:
Is it important to declare it as such? The other example works without. Or is it a matter of choice?

Personally, I avoid #define except for keywords to make reading my code easier. For example, when using pull-up resistors on my buttons, I usually do this:

#define PUSHED LOW
#define UNPUSHED HIGH

const does two things for you:

  1. to will let the compiler complain if your program tries to modify a value. Which is good for things that shouldn't change while your program runs, e.g. pin numbers. and
  2. the compiler will try place the variable in RAM if it can fit in a register.

As for #2, the compiler is pretty smart. If it sees in your program a non-const variable is never modified, it may actually treat it as a constant, never putting it into RAM.

I did a write up here, with a simple explain of how much RAM is used (checked with avr-size) when using const and when not:

There's also a link to in-depth analysis done of this forum:
http://arduino.cc/forum/index.php/topic,86800.15.html

They are both integer variables

Well, not really. Because a #define is a textual replacement, it is "typeless". That is: the code

#define MYVAL 500

void setup() {
  // put your setup code here, to run once:
  int number = MYVAL;
  byte value = MYVAL;
}

void loop() {
  // put your main code here, to run repeatedly:
}

compiles without error, but assigning 500 into a byte probably isn't going to work. This is likely why James says they can introduce hard-to-find bugs. There are, however, times when the fact that a symbolic constant is typeless can be a good thing (e.g., when working with a union). While James clearly doesn't like #define's, they do have their place. If the IDE had a symbolic debugger, I would rarely use #define, since it cannot be traced by a debugger once the preprocessor pass completes.

In either case, I think it helps to upper-case both #define's and const data definitions to signal the reader that the expression is different.

econjack:

They are both integer variables

Well, not really. Because a #define is a textual replacement, it is "typeless".

But its replaced value is not.

But its replaced value is not.

True, but it can assume the resolved data type used in the expression. It is typeless in that it can be used in almost any expression where a data type is used and the compiler won't flag it as a type mismatch where a cast would normally be required.

econjack:

But its replaced value is not.

True, but it can assume the resolved data type used in the expression. It is typeless in that it can be used in almost any expression where a data type is used and the compiler won't flag it as a type mismatch where a cast would normally be required.

Its not type less at all, literal integers are a type of int, and will never be char/unsinged char, only character literals can be chars. Its the normal integral conversions which allow them to be converted and assigned, and the same rules apply regardless of weather the integer is a literal, variable, or constant. This genereates an error: char *c = 500;

Here you can see its clearly an int.

#define WHAT_TYPE_AM_I 500

void setup() {
  Serial.begin( 9600 );
  Func( WHAT_TYPE_AM_I );
}

void loop() {}

void Func( char c ){
  Serial.print( "printing char" );
  Serial.println( c ); 
}

void Func( int i ){
  Serial.print( "printing int" );
  Serial.println( i, HEX ); 
}

void Func( float f ){
  Serial.print( "printing float" );
  Serial.println( f, 2 ); 
}

Its the normal integral conversions which allow them to be converted and assigned, and the same rules apply regardless of weather whether the integer is a literal, variable, or constant.

I was very careful to use the term "expression" in what I said. Normally, if the compiler assigns a "smaller" data type into a "larger" data type (e.g., long = int), the silent cast is done without fanfare. However, the compiler should complain when it assigns a "larger" data type into a "smaller" data type (e.g., byte = int) and it doesn't. While the compiler must use some default data type in the preprocessor pass, it's the resolution of that expression that makes it seem to be typeless.

econjack:
I was very careful to use the term "expression" in what I said. Normally, if the compiler assigns a "smaller" data type into a "larger" data type (e.g., long = int), the silent cast is done without fanfare. However, the compiler should complain when it assigns a "larger" data type into a "smaller" data type (e.g., byte = int) and it doesn't. While the compiler must use some default data type in the preprocessor pass, it's the resolution of that expression that makes it seem to be typeless.

If you ask it nicely, it will provide warnings.

void setup() {
  const long l = 2864434397UL;
  unsigned int i = l;
  unsigned int x = 2864434397;
  signed int y = 2864434397;
  char z = 255;
}

void loop() {}

sketch_jul17a.ino:3:20: warning: large integer implicitly truncated to unsigned type
sketch_jul17a.ino:4:20: warning: large integer implicitly truncated to unsigned type
sketch_jul17a.ino:5:18: warning: overflow in implicit constant conversion
sketch_jul17a.ino:6:12: warning: overflow in implicit constant conversion

True, but compiling with the default settings for the IDE is alarmingly quiet, and those are the settings that most newbies use.

Thanks for all the advice :slight_smile:

It looks like "const int" is a good habit to get into for when I get on to more difficult code and, eventually, writing my own. I hadn't seen "const byte" used in the (limited number of) examples I've seen, but I can see how it will save RAM in a bigger application.

This is SO much more complicated than Python... I'm glad my daughter is using the Raspberry Pi at the moment. I have one, too, but I want it to run an arduino in a project I've not thought of yet!

MisterG:
It looks like "const int" is a good habit to get into for when I get on to more difficult code and, eventually, writing my own. I hadn't seen "const byte" used in the (limited number of) examples I've seen, but I can see how it will save RAM in a bigger application.

If it is not used in an array, then it will probably not save any RAM at all - generally const variables (which don't involve pointers) are simply optimised into 'ldi' instructions wherever they are used - much like #defines.

Saving RAM is important, but the key reason to use const, in my opinion, is to make sure you (the programmer) doesn't make a mistake.

MisterG:
This is SO much more complicated than Python...

With complexity comes power. C/C++ are considered lower-level languages than scripting languages like Python. Python offers simplicity, but at the cost of run-time resources. On a PC (or SBC like a Pi), there are usually plenty to spare. On an 8-bit microcontroller with 2,048 bytes of RAM, resources matter.

econjack:
True, but compiling with the default settings for the IDE is alarmingly quiet, and those are the settings that most newbies use.

Its too quiet to tell you the truth, some warnings would be useful.

The IDE adds '-w' which means Suppress all warnings, even the ones you mentioned should happen do not ( I agree they should be shown ). So just cos you don't see any warnings, does not mean they never happen. Its a mistake if you ask me, some warnings will point out logic errors which will compile fine.

In 1.0.5/1.5.3 and greater, you only get error messages. I guess they base it on the fact newbies can't understand the messages spewed out. Kind of defeats the verbose output mode, the last error is always shown.

pYro_65:
Its too quiet to tell you the truth, some warnings would be useful.

The IDE adds '-w' which means Suppress all warnings, even the ones you mentioned should happen do not ( I agree they should be shown ). So just cos you don't see any warnings, does not mean they never happen. Its a mistake if you ask me, some warnings will point out logic errors which will compile fine.

In 1.0.5/1.5.3 and greater, you only get error messages. I guess they base it on the fact newbies can't understand the messages spewed out. Kind of defeats the verbose output mode, the last error is always shown.

Time to switch to UECIDE! At least that way you get a proper build environment - and can even change the compiler options if you want.

I have used it and I did like it.

Arduino IDE 1.5.7 now has gcc 4.8.1 and C++11 will be turned on in 1.5.8. You can add -std=c++11 to platform.txt for now (1.5.7). Also all the compiler options can be modified ( just not without restarting the IDE ). The IDE itself still has a way to go, but its improving.

For those new to arduino,

I have been programming professionally since 1978, and could see NO reason to use const instead of define, based on my experience and reading of the documentation.

However, the arduino IDE does NOT differentiate between two defined "variables" with the same root properly
(In my way of thinking, anyway, it can get confused)
#define ABC 123.456 * 789
#define ABC_DEF 654.321
Is replaced with
#define 123.456 * 789_DEF 654.123
That isn't too bad, it does get resolved properly, but

#define ABC XYZ
#define ABCDEF QWERTY
#define XYZDEF ABCDEFG
Gives
Gives a compile error (already defined, because ABC is replaced with XYZ in the second line)

#define ABC XYZ
#define DEF ABCDEFG
means that when you use DEF you'll get XYZDEFG not ABCDEFG

once I knew that, I was far more careful, but still used defines instead of consts
Because the documentation led me to believe that consts take up memory, and it is limited

But the I found an include that defined something that caused the same kind of problem. And it took AGES to figure it out. Everything was working, I added a few new functions, and started getting strange results in functions that had been working before, and nothing had changed.

So, I started looking at consts.
Well, to my surprise,
Using const int i_variable_for_whatever does NOT use memory under ant circumstances that I have encountered so far.
AND it is MUCH MUCH easier to ensure that calculations are type converted properly
const double cd_pi_value = 3.14159 ;
const int ci_max_encoder_clicks = 80 ;
#define PI_VALUE 3.14159
#define MAX_ENCODER_CLICKS

double d_pi_clicks = cd_pi_value * ci_max_encoder_clicks ;
vs
double d_pi_clicks = double(PI_VALUE) * double(MAX_ENCODER_CLICKS) ;
double() is required, or it doesn't calculate correctly

When compiled, the version with consts uses the same variable storage memory, and LESS programming space
(Might not for this example, but it did for me full sketch)

So, after 36 years of programming, I have changed my use of constants and defines (on the arduino at least)