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.
#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?
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?
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.
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).
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.
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).
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)
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: