#define vs. const

Hi all,
Could someone tell me what difference(s) there are when using #define vs. const to setup a reoccurring pin definition? I want to identify pin 2 with the name "Blank". Is one better than the other? (faster, uses less memory. etc)

ie.:
#define Blank 2
vs.
const Blank 2

The first thing to note is that

const Blank 2

is not valid code, which I assume you realise

#define is like replacing the text. It gives the exact same result as a search-replace. So you can get unexpected results....

#define THREE 1 + 2

  Serial.println( 2*THREE ); 
  Serial.println( 2*1 + 2 );

Can you see why both println statements will print "4" and not "6"?

You can use this to your advantage. The Arduino #define ISR does some clever text manipulation so that you use it like a function even though it is not a function.

The advantage of the const declaration is the compiler can check the type of the variable. (Which you forgot.)

In your simple example, the compiler will optimize the result down to the same machine code. Even if you didn't use the const keyword, it works out that it is a constant. The const declaration is more for your benefit, so you don't accidentally change it.

I always prefer const because it's C and I know how to do C (mostly).

#define uses the preprocessor which has its own set of rules and syntax, and I've never really spent much time learning preprocessor. Also it seems like it can sometimes throw you a curve-ball (as in MorganS's example above) if you don't pay attention. I know preprocessor is essential for other things, but for defining constants why even go there?

This hardly demonstrates a problem with the preprocessor.

MorganS:
#define is like replacing the text. It gives the exact same result as a search-replace. So you can get unexpected results....

#define THREE 1 + 2

Serial.println( 2THREE );
  Serial.println( 2
1 + 2 );




Can you see why both println statements will print "4" and not "6"?

If you understand why the 2nd println fails, you know why the 1st also failed. You shouldn't expect otherwise.

#define THREE (1 + 2)    // the way you should write such an expression anywhere (if that IS what you mean)
#define THREE    3       // the obvious way

Defines can be "dangerous" if carelessly used, unless you are absolutely sure what you are doing, you should use const instead. Defines can be "overwritten" causing a define to change its value, const declarations will not allow this and therefore they are safer to use.

And for the memory usage: Same, same - either it fits or it doesn't! :wink:

For trivial examples, pin numbers and whatever, the const is usually better. But #define is better used for calculation, where it looks more like a function.

In those cases, it is usually better to use an actual function, maybe with the inline compiler hint to encourage it to make the program faster by making it bigger.

Like any good tool, it can be used for many purposes, including hammering your own thumb.

Sorry UKHeliBob, I should have written const Blank = 2;
I was in a bit of a rush.

Try again:

const uint8_t Blank = 2;

Thanks for the reply gfvalvo, I think, but posting just a line of code with no explanation just leaves me in the dark. How about so incite into what you are trying to tell me please?

MorganS, I understand why the second println returned 4 but I'm unclear on the value of "#define THREE 1 + 2".
Also why did say I forgot the compiler can check the type of the variable?
"The advantage of the const declaration is the compiler can check the type of the variable. (Which you forgot.)"

Ken_F:
How about so incite into what you are trying to tell me please?

You need to supply a TYPE for the variable, not just a name and a value -- i.e. 'int', 'unsigned long', 'uint32_t', 'float', 'double', 'in16_t', etc, etc, etc.

Thanks gfvalvo, I don't know where my head was at when I typed that, but I do know that. The initial example was just fyi and not of crucial importance to the general question.

So the bottom line as I see it is use "const" instead of "#define" to prevent the names from changing value.

kind-of... bottom line is that they are two different things :) and #define somewhat something of the past for constants (due to inline and compiler optimization techniques);

directives are better suited for pre-processor stuff

Strongly typing variables and constants helps provide cues to the compiler which can warn you if you do something stupid later on - including changing values or overflow in maths etc

Thanks J-M-L, but you're talking over my head with,

directives are better suited for pre-processor stuff

Strongly typing variables and constants helps provide cues to the compiler which can warn you if you do something stupid later on - including changing values or overflow in maths etc

I have no idea what #directives, or pre-processor stuff is or what you mean by "Strongly typing"

The pre-processor is what makes the source code ready for compiling. Strongly typing means that you, as the programmer, instruct the compiler to use exactly the data types you want. Look at the following snippet:

#define PIN_NUMBER 3
...
pinMode(PIN_NUMBER, OUTPUT);
digitalWrite(PIN_NUMBER, HIGH);
digitalWrite(PIN_NUMBER, LOW);

The pre-processor will convert the code and feed the following to the compiler:

pinMode(3, OUTPUT);
digitalWrite(3, HIGH);
digitalWrite(3, LOW);

As you can see, "PIN_NUMBER" does no longer exist, only the value it defines does. Since "int" is the default data type, "3" may occupy 2 bytes in memory because it is un-typed. Now, lets try that with "strong typing":

const uint8_t PIN_NUMBER = 3;
...
pinMode(PIN_NUMBER, OUTPUT);
digitalWrite(PIN_NUMBER, HIGH);
digitalWrite(PIN_NUMBER, LOW);

In this example the pre-processor will not modify anything. "PIN_NUMBER" will exist at compile time and it will occupy exactely one unsigned byte - because you coded it like that.

J-M-L:
#define somewhat something of the past for constants

#define is used for one helluva lot more than simply defining constants.
Just one, absolutely trivial, example:

#define getmax(a,b) ((a)>(b)?(a):(b))

And, of course, it can be abused to within an inch of its life. This is ALL a single #define. Anyone who does something like this should be taken out and shot, but it IS valid code, and WILL work.

#define DECLARE_MODIFICATION_REQUEST_PACKET( T )                                                \
namespace NameSpace                                                                     \
{                                                                                       \
                                                                                        \
class T##ElementModificationRequestPacket;                                                          \
}                                                                                       \
                                                                                        \
DECLARE_STREAMING_TEMPLATES( IMPEXP_COMMON_TEMPLATE_DECLARE, NameSpace::ElementModificationRequestPacket<T>, OtherNameSpace::NetPacketBase )    \
DLLIMPEXP_COMMON_TEMPLATE_DECLARE( NameSpace::ElementModificationRequestPacket<T> )     \
DECLARE_AUTOGENERATION_TEMPLATES( DLLIMPEXP_COMMON_TEMPLATE_DECLARE, NameSpace::T##ModificationRequestPacket, NameSpace::ElementModificationRequestPacket<T> )      \
                                                                                        \
namespace NameSpace {                                                                   \
class DLLIMPEXP_COMMON T##ModificationRequestPacket : public ElementModificationRequestPacket<T>\
{                                                                                       \
public:                                                                                 \
    T##ModificationRequestPacket( NetBase * pParent )                                   \
    : ElementModificationRequestPacket<T>( pParent ), m_Gen() {}                            \
                                                                                        \
    T##ModificationRequestPacket( NetBase * pParent,                                    \
                            Action          eAction,                                    \
                            const T &   rT )                                            \
    : ElementModificationRequestPacket<T>( pParent, eAction, rT ), m_Gen() {}               \
                                                                                        \
    T##ModificationRequestPacket( const T##ModificationRequestPacket & rhs )                        \
    : ElementModificationRequestPacket<T>( rhs ), m_Gen() {}                                \
                                                                                        \
    virtual                     ~T##ModificationRequestPacket( void ) {}                        \
                                                                                        \
    virtual Uint32          GetPacketTypeID( void ) const                           \
    {                                                                                   \
        return Net::T##_Modification_REQUEST_PACKET;                                        \
    }                                                                                   \
                                                                                        \
    virtual OtherNameSpace::ClassID GetClassID ( void ) const                           \
    {                                                                                   \
        return OtherNameSpace::NetBase::GenerateHeader( OtherNameSpace::ID__LICENSING,  \
                                                         Net::T##_Modification_REQUEST_PACKET );    \
    }                                                                                   \
                                                                                        \
    virtual T##ModificationRequestPacket * Create( void ) const                             \
    { return new T##ModificationRequestPacket( m_pParent ); }                                   \
                                                                                        \
    T##ModificationRequestPacket() {}                                                           \
                                                                                        \
protected:                                                                              \
    OtherNameSpace::ObjectAutogeneration<T##ModificationRequestPacket> m_Gen;                       \
                                                                                        \
    friend class OtherNameSpace::StreamingBase::StreamingClassInfoT<T##ModificationRequestPacket >;                     \
    OtherNameSpace::StreamingBase::Streaming<T##ModificationRequestPacket, ElementModificationRequestPacket<T> >    m_Stream;   \
                                                                                        \
};                                                                                      \
}                                                                                       \
DLLIMPEXP_COMMON_TEMPLATE_DECLARE( ThirdNameSpace::ListenerBase<const NameSpace::T##ModificationRequestPacket> )            \
DLLIMPEXP_COMMON_TEMPLATE_DECLARE( ThirdNameSpace::BroadcasterT<const NameSpace::T##ModificationRequestPacket> )            \
typedef  ThirdNameSpace::BroadcasterT<const T##ModificationRequestPacket>  T##ModifiedBroadcaster;

Regards,
Ray L.

Read about Preprocessor directives

for the other, I actualy did not mean to use "strong" in the programing language context (read this) just meant that when you do a define, the implicit type (int, signed int, long, char etc) is attached to the value whereas if your provide a type, the value will be forced to conform to that type....

but no big deal at this stage, focus on the basics :wink:

RayLivingston:
#define is used for one helluva lot more than simply defining constants.

that was my point when I wrote "for constants" --> For constant or simple function, it's obsolete in my opinion (use const or inline) but it has other legit (or far fetched) use cases

Thanks Danois90 that was very informative. I might even understand it. I appreciate you taking the time. If I understand you correctly, using #define IS NOT strong typing because it uses the default data type, but by defining a "const" variable as an 8-bit unsigned integer (const uint8_t) you are telling the compiler what to use and not letting the pre-processor make that default determination. How close am I?

Ken_F:
Thanks Danois90 that was very informative. I might even understand it. I appreciate you taking the time. If I understand you correctly, using #define IS NOT strong typing because it uses the default data type, but by defining a "const" variable as an 8-bit unsigned integer (const uint8_t) you are telling the compiler what to use and not letting the pre-processor make that default determination. How close am I?

The "type" of an expression defined using #define is a function of where/how the #define is actually used. A #define HAS NO TYPE, because it is NOT a variable declaration. It defines a literal string.

#define XYZZY 32
int8_t myInt8 = XYZZY;  //myInt8 is an 8-bit signed value, with value 32
int16_t myInt16 = XYZZY;  //myInt is a 16-bit signed value, with value 32
#define XYZZY (255)
uint8_t myuInt8 = XYZZY;  //myuInt8 is an 8-bit UN-signed value, with value 255
int8_t myInt8 = XYZZY;  //myInt8 is an 8-bit signed value, with value -1
int16_t myInt16 = XYZZY;  //myInt is a 16-bit signed value, with value 255

Regards,
Ray L.