#define vs const and another question of style

I've returned to programming after a lengthy break building and testing hardware and wish to improve my code as well as update it to suit the hardware. I've read a number of Arduino programming books and the very helpful Tips and Traps guide, but I still have a couple of questions.

  1. #define vs const.
    I understand that const is preferable to #define for reasons of consistency. However I've read that #define "hard codes" for example replacing all references to a named pin in code with its number. Very efficient for a number that is never changed in a programme.
    The implication is that const does the same thing, but if not, #define would seem to be more efficient. However nowhere have I been able to confirm that. Does the compiler handle #define and const keyworded variables in the same way or not?

  2. global vs local variables keyworded static.
    My code contains a number of counters for keeping track of numbers of samples taken. In the original code they are all declared globally. In the interests of "encapsulation" and whilst breaking the code into sensible functions I'm considering whether to leave them global, or make them local to loop and subordinate functions, in which case I understand they will need to be keyworded static in all functions to avoid them being reset each time a function executes.
    But which is regarded as the better approach stylistically and from a code efficiency perspective, and is there a difference?

Advice would be greatly appreciated.

I still have a couple of questions.

I do, too. Why can't you do any research?

The const vs. #define issue has been debated to death here.

The local vs global issue has too.

A const has a type. This helps spot stupid errors like putting 60000 in a 16-bit int. The compiler is staggeringly good at optimising constants, even if you don't use the word const.

#define is like a little programming language of its own. I've seen entire adventure games programmed with these preprocessor directives where the actual program that runs on the target machine is just "hello world".

Local static is better. Then you can have an unlimited number of functions holding their own lastMillis values without mixing up which is which.

Thanks MorganS. That's a great help.
PaulS - I'm sure it has, but my Google search didn't find anything except the distinction that I posted about.

Slow-Learner:
2) global vs local variables keyworded static.
My code contains a number of counters for keeping track of numbers of samples taken. In the original code they are all declared globally. In the interests of "encapsulation" and whilst breaking the code into sensible functions I'm considering whether to leave them global, or make them local to loop and subordinate functions, in which case I understand they will need to be keyworded static in all functions to avoid them being reset each time a function executes.
But which is regarded as the better approach stylistically and from a code efficiency perspective, and is there a difference?

I prefer 'local static'. It prevents coding mistakes where one function can accidentally change a variable because you made a typo. And as MorganS indicated, use of the same names which can make for clearer coding.

'local static' however comes at the cost of extra memory usage (and possibly even cpu cycles). If you e.g. have 100 unsigned long lastMillis variables, that's 400 bytes down the drain. And if all stuff is done sequentially, you can simply use one lastMillis variable and save 396 bytes (possibly even a little more).

It can be a fine balance :wink:

use of the same names which can make for clearer coding.

To me that is very counter intuitive. Surely variables of any type should have explicit names that reflect their purpose. A name such as lastMillis just not cut it as far as I am concerned, but each to his/her own.

Slow-Learner:
Does the compiler handle #define and const keyworded variables in the same way or not?

For all practical purposes yes.

And, typed constants have scope.

Macros do not / are global. Which can be a nightmare to debug. (A tip o'ma hat to everyone who has had to add -E to a GCC command line.)

In the interests of "encapsulation"...

Wrong keyword. "Encapsulation" refers specifically to object oriented programming.

"Scope" is the phrase of interest.

...make them local...

That is the correct choice.

If the data is truly global then wrapping it in a namespace is the best choice. (Though, on tiny processors there can sometimes be little benefit.)

A better choice is to encapsulate the data with some methods then make local instances.

But which is regarded as the better approach stylistically and from a code efficiency perspective, and is there a difference?

Style and efficiency in this context are secondary to "correct". A tenet of correct code is making data as locally scoped as possible.

enums are good.

UKHeliBob:
To me that is very counter intuitive. Surely variables of any type should have explicit names that reflect their purpose. A name such as lastMillis just not cut it as far as I am concerned, but each to his/her own.

OK, lets say you read N different sensors at their own regular intervals. lastReadTime would be the appropriate name (in my opinion).

:slight_smile: :slight_smile: :slight_smile:

lets say you read N different sensors at their own regular intervals. lastReadTime would be the appropriate name (in my opinion).

If there were a number of sensors of the same type then an array of values would probably be the correct solution otherwise individual names such as temperatureLastReadTime, humidityLastReadTime etc would be appropriate (in my opinion) :slight_smile:

The archetype of the local-variable-with-the-same-name-everywhere is the loop counter i.

sterretje:
'local static' however comes at the cost of extra memory usage (and possibly even cpu cycles). If you e.g. have 100 unsigned long lastMillis variables, that's 400 bytes down the drain. And if all stuff is done sequentially, you can simply use one lastMillis variable and save 396 bytes (possibly even a little more).

Local static does not use any more memory than the other options which have the same effect. If the value may be forgotten after the function ends (non static) then that is a different effect. Static also does not cost any CPU cycles and may even save some.

An important distinction touched on above is that a #define is typeless and can lead to some ugly bugs. However, that is no reason to throw it under the bus. In some situations, being typeless is a good thing. For example, a common macro is:

#define ELEMENTCOUNT(x) (sizeof(x) / sizeof(x[0]))

int myArray[20];
float yourArray[50];

// ...a bunch of code...

   for (int i = 0; i < ELEMENTCOUNT(myArray); i++) {
      // do something...
   }
// ...a bunch of code...
   for (int i = 0; i < ELEMENTCOUNT(yourArray); i++) {
      // do something...
   }

The macro works in both cases even though the arrays are of different types because no type needs to be specified in its definition. Using a const or enum requires a type. Also enum and const do create "real" variables that appear in the symbol table. A symbolic constant is simply a textual substitution. That difference becomes more important if we had a symbolic debugger.

econjack:
An important distinction touched on above is that a #define is typeless and can lead to some ugly bugs. However, that is no reason to throw it under the bus. In some situations, being typeless is a good thing....

The macro works in both cases even though the arrays are of different types because no type needs to be specified in its definition.

But being type-less doesn't make it type-safe (or even a good thing). You can still make the mistake of passing a subscript-able type that is not an array:

#define ELEMENT_COUNT(X) sizeof(X)/sizeof(X[0])

char* someString = "Hello World";

void setup() {
  Serial.begin(9600);
  Serial.print(ELEMENT_COUNT(someString));
}

void loop() {

}

fortunately, Arduino's C++11 allows for constexpr functions:

template <typename T, int n>
constexpr size_t ELEMENTCOUNT(T(&)[n]){return n;}

int myArray[25];
float yourArray[50];
char* someString = "Hello World";

void setup() {
  Serial.begin(9600);
  Serial.println(ELEMENTCOUNT(myArray));
  Serial.println(ELEMENTCOUNT(yourArray));
  // Serial.println(ELEMENTCOUNT(someString));  // won't compile because the constexpr function cannot take a non-array
}

void loop() {

}