Go Down

Topic: The difference between #define and const (Read 4807 times) previous topic - next topic

mmcp42



If I understand it correctly...

Code: [Select]
constant int TheValueOne=1;

will take up memory

Code: [Select]
#define TheValueOne 1

doesn't take up any space

so I guess it saves a byte or two

can't work out why "constant  int" is "more modern" though?


That is not true, and depending on the compiler a define may take up more memory than a const. The thing is that the define sort of creates a copy (maybe several copies if you use it a lot) of the value, whereas the const int is a single object. Think about it, where is the 4 stored? It must be in the memory somewhere.

I'm no Arduino compiler expert, but it could be that #define is faster when it comes to execution.



I modify my assertion then

Quote
constant int MyValue=42;

will create an integer that takes up memory
subsequent references to MyValue will generate an address reference

Code: [Select]
#define MyValue 42
takes up no memory at definition time
subsequent use of MyValue will generate a literal appropriate to the operation

the question is - is the address reference to an integer bigger or smaller than the literal?
either way the constant int will take up space

interesting discussion though!
there are only 10 types of people
them that understands binary
and them that doesn't

ofransen


Quote
Think about it, where is the 4 stored? 

Usually as an immediate constant as part of the instruction that references it.


I have no idea about ATMEGA assembly, is that more efficient spacewise than loading from ROM?

Ah! Another point! Does const int const put into ROM or initialised in RAM at startup?

But this all probably not interesting to the original poster...!

Owen F. Ransen

davekw7x

#17
Jul 02, 2011, 06:10 pm Last Edit: Jul 02, 2011, 07:38 pm by davekw7x Reason: 1
Since I am arriving at the party a little late, I won't bother responding to previous guesses (erroneous or not).  I'll just reply to the most recent post.


Does const int const put into ROM or initialised in RAM at startup?


It's in the instruction itself, not in a separate variable space in memory anywhere.

Here's the thing:

For programs compiled with avr-gcc optimized for space (which is what Arduino does), values of variables designated "const" are put directly into the code.  So it costs nothing at run-time to use the const... thingie.  (Note that this action is not part of any C or C++ language standard, and it is not absolutely, positively, unequivocally guaranteed that this will be the case with all compilers or even all versions of avr-gcc, but it is my observation using avr-gcc version 4.3.4 with arduino-0022 on my Linux system.)

Anyhow, for purposes of illustration (and confirmation of my observation), let's take the world-famous Arduino "Blink" sketch.

Compile using 13 everywhere the LED pin is referred to. (That's the way it is in my version of arduino-0022/examples/1.Basics/Blink.pde)

Then use #define ledPin 13 at the top of the sketch and change all occurrences of "13" to "ledPin" and compile again.

Of course you will get the exact same code, right?  I mean, that's what #define is all about, right? (Don't take my word for it---try it.)

Now use const int ledPin = 13; instead of the #define.  Compile again.  I get the same code.  Exactly the same.  Therefore I conclude that there is no "penalty" for using the more robust construct for this example with this compiler and this version of Arduino.  (See Footnote.)

Now, as has been mentioned, in general, using a "const variable" instead of #define can give the compiler some additional error checking capabilities, since type checking can be performed on usage of the "const variable"  This can be a Very Good Thing since it can help the compiler find some of our usage errors.

The main other difference (to me) is that the variables designated "const..." appear to the compiler to be just like variables (except, of course, the user program won't be allowed to change the value at run time).

That means, for example, if you have a function that takes a pointer as an argument, you can send it the address of the "const variable," but you would not be able to do this with a #defined identifier.

Code: [Select]

// To see the difference between a "constant variable" declaration and
// a #define, comment out the next line and uncomment the one after it.
const int bar = 42;
//#define bar 42

void setup()
{
   // Whatever
}

void loop()
{
   int xyz[3] = {1, 2, 3};
   foo(&bar); // The address of the "constant variable"
   foo(xyz);  // The address of xyz[0];
   //
   //whatever else you want to do
   //
   
}

void foo(const int *x)
{
   // Do something with whatever it is that "x" is pointing to
}


Now, for programs like this last example, it's not a matter of whether there is some memory allocated (somewhere) for the "const int" identifier.  (There is.)  It's a matter of whether you can do the same thing with a #define statement. (You can't).



But this all probably not interesting to the original poster...!


Maybe this isn't such a BFD for beginners, but I think it is interesting and, maybe, even useful...



Regards,

Dave

Footnote:
When I said that I got the "exactly the same code," I don't just mean that I got the same sketch size.  I examined the results of executing avr-objdump -d on the generated .elf files in the Arduino build directory.  A "diff" showed no differences at all among the three cases of blink sketch that I mentioned above.

Here's the disassembled code for the loop() function for all three versions. The "ldi r24, 0x0D" instructions are the places where the compiler is using the value of ledPin.
Code: [Select]

000000ea <loop>:
 ea: 8d e0       ldi r24, 0x0D ; 13
 ec: 61 e0       ldi r22, 0x01 ; 1
 ee: 0e 94 e2 00 call 0x1c4 ; 0x1c4 <digitalWrite>
 f2: 68 ee       ldi r22, 0xE8 ; 232
 f4: 73 e0       ldi r23, 0x03 ; 3
 f6: 80 e0       ldi r24, 0x00 ; 0
 f8: 90 e0       ldi r25, 0x00 ; 0
 fa: 0e 94 85 01 call 0x30a ; 0x30a <delay>
 fe: 8d e0       ldi r24, 0x0D ; 13
100: 60 e0       ldi r22, 0x00 ; 0
102: 0e 94 e2 00 call 0x1c4 ; 0x1c4 <digitalWrite>
106: 68 ee       ldi r22, 0xE8 ; 232
108: 73 e0       ldi r23, 0x03 ; 3
10a: 80 e0       ldi r24, 0x00 ; 0
10c: 90 e0       ldi r25, 0x00 ; 0
10e: 0e 94 85 01 call 0x30a ; 0x30a <delay>
112: 08 95       ret


I hate to repeat myself, but there is no guarantee that the resulting executable code will always be the same for the different cases.  If people have different results, maybe they can share them with us.  (Be sure to tell us what version of avr-gcc you are using, and what version of Arduino---I don't know for sure that they have always used the same optimization switches with the same effect that I reported here.)

With Arduino, a const int will always be optimized down to a literal, just like define.  Arduino users do not have to worry about other compilers or architectures or whatever.

Generally, the compiler (which handles const int) is considered superior to the preprocessor (which handles defines).  It's type safe and respects scope.  Among C++ developers I know, its non controversial to avoid the pre processor. But frankly for most Arduino use, its an esoteric difference.

It would be kind of a programming puzzle to construct a bug that one could introduce by using define instead of const in regular usage.

AWOL

Quote
a const int will always be optimized down to a literal

Sometimes two!
"Pete, it's a fool looks for logic in the chambers of the human heart." Ulysses Everett McGill.
Do not send technical questions via personal messaging - they will be ignored.

westfw

Quote
a const int will always be optimized down to a literal,

Don't you need to add "static" to ensure that?  Depending on the optimizer always makes me nervous.  If you care about this level of optimization, you should be paying close attention to how things actually happen in YOUR program.

For a constant like "4", having a strong type is not always a good thing.

Coding Badly

Quote
Don't you need to add "static" to ensure that?


If the symbol is defined in a single module, it does not seem to matter.  I have no idea what happens if the symbol is defined in more than one module.

In other words, "static" forces the symbol to be local to a module so, if preserving SRAM is important, it's a very good idea to include "static".

MartinGS

Good morning!
Quote
But this all probably not interesting to the original poster...!

oo no, i'm getting more interested.

Quote
That means, for example, if you have a function that takes a pointer as an argument, you can send it the address of the "const variable," but you would not be able to do this with a #defined identifier.

noted.
I'm searching google everytime for references in arduino, and most of the time it will bring me to the forum. I can't fully understand yet, but it will be useful when my understanding advances.

Thank you guys!

Udo Klein

With regard to the memory consumption of const vs. #define. It is just not true that const will always use up more memory. It basically depends on how the consts are used. The proper approach is to use them for type safety. However it is important to keep in mind that string constants are a different issue altogether. They do consume Ram which is very tight for the Arduino. Whoever got memory issues needs to take care of string constants. Dealing with them is not achieved with #define either. Actuall #define may end up using even more memory. Dealing with string constants requires the use of the progmem macros.
In any case: most programs around would benefit much more from type safety than from questionable memory footprint reduction. Unless you are ***very*** tight on memory #define is the wrong way.
Check out my experiments http://blog.blinkenlight.net

crimony

Also it can result in different (and unexpected) behaviour. Consider the following (standard C++, not Arduino) code:

Code: [Select]
#include <iostream>
using namespace std;
#define a1 2
const float a2 = 2;
int main(int argc, char **argv)
{
    int b = 3;
    float f1 = b / a1;
    float f2 = b / a2;
    cout << "f1 (define-based) : " << f1 << endl;
    cout << "f2 (const float-based) : " << f2 << endl;
    return 0;
}


Apropos the calculation of f1, because a1 is considered an integer, the division is an integer division, so the result is calculated then converted to a float (result = 1). In the second case (calculation of f2), as a2 is defined as a float, the variable "b" is promoted to a float and the division is floating-point, resulting in the value of f2 being 1.5.

I realise this is a contrived example, and there are a number of ways around, including (float) in the define, or using 2.0 as the value rather than 2. The purpose is to show that behaviour is less predicable when you use #define.

Go Up