Call for comments: Programming traps and tips

@Experienced programmers / Arduino users:

I am drawing up a list of tips and hints for a "sticky" or fixed web page. This is the current draft, below. Please comment, as you see fit, on:

  • Errors I have made
  • Suggested improvements
  • Suggested re-ordering of tips/traps
  • Any other ideas

Please, please, no four pages of discussion about whether or not goto is harmful. :slight_smile:

Note: Although it is usually bad form to edit a post after people have commented on it, to keep things in one place I will incorporate suggestions directly below. However to keep the comments meaningful the original text will be kept with a strikeout through it, as far as possible.


Trap: Using 'string' instead of "string".

eg.

Wrong! ...

char foo = 'start';

Correct:

char foo [] = "start";

Note that unlike some languages (eg. Lua, PHP) strings cannot be placed in single quotes. A letter in a single quote is used as a way of specifying (usually) a single character, eg.

char forwardCommand = 'F';

Trap: Reading too much from serial / ethernet.

eg.

Wrong! ...

if (Serial.available ())
  {
  char a = Serial.read ();
  char b = Serial.read ();
  }

Correct: See http://www.gammon.com.au/serial


Trap: Not using arrays.

Tip: Use arrays to hold multiple similar things.

eg.

int ledPin1 = 3;
int ledPin2 = 4;
int ledPin3 = 5;
int ledPin4 = 6;

Instead of:

int ledPin [4] = { 3, 4, 5, 6 };

Or preferably:

const byte ledPin [4] = { 3, 4, 5, 6 };

Trap: Trying to check a pin number rather than a pin.

eg.

Wrong! ...

const byte switchPin = 4;

...

if (switchPin == HIGH) 
  {
  // switch is pressed
  }

Correct:

const byte switchPin = 4;

...

if (digitalRead (switchPin) == HIGH) 
  {
  // switch is pressed
  }

Trap: Using "=" (assign) when you mean "==" (compare).

eg.

Wrong! ...

if (temperature = 10)

Correct:

if (temperature == 10)

Trap: Doing a serial print, or delay, inside an interrupt service routine.

eg.

Wrong! ...

ISR (TIMER0_OVF_vect)
  {
  Serial.print ("Timer overflowed!");
  delay (100);
  }

Neither will work properly because interrupts are turned off inside interrupt routines.


Trap: Using too much RAM.

eg.

int myReadings [2000];

The above line will use 4 kB of RAM. The Uno and similar boards only have 2 kB of RAM.


Tip: Place string literals for printing inside the F() macro.

eg.

Instead of:

  Serial.print ("Program starting.");

Use:

  Serial.print (F("Program starting."));

This saves the string being copied from PROGMEM (program memory) into RAM (random access memory). You usually have a lot more program memory than RAM.


Trap: Trying to sprintf a float.

eg.

float foo = 42.56;
char buf [20];
sprintf (buf, "%f", foo);

On the Arduino, sprintf does not support floats.


Trap: Trying to sscanf a float.

eg.

float foo;
char buf [] = "123.45";
sscanf (buf, "%f", &foo);

On the Arduino, sscanf does not support floats.


Trap: Trying to add one to a variable in a way that is not defined.

eg.

Wrong! ...

i = i++;

The result of the above statement is not defined. That is, it is not necessarily "i + 1".

http://c-faq.com/expr/seqpoints.html

Instead use:

i++;

Or:

i = i + 1;

A note about "undefined behavior":

When an instance of undefined behavior occurs, so far as the language specification is concerned anything could happen, maybe nothing at all.


Trap: Calling a function without using parentheses.

eg.

Wrong! ...

void takeReading ()
  {
  // do something
  }
  
void loop ()
  {
  takeReading;   // <-- no parentheses
  }

The above code compiles but will not do anything useful.

Correct:

void takeReading ()
  {
  // do something
  }
  
void loop ()
  {
  takeReading ();
  }

The parentheses are required even if the function takes no arguments.


Trap: Doing multiple things after an "if" without using braces:

eg.

Wrong! ...

  if (temperature > 40)
    digitalWrite (furnaceControl, LOW);
    digitalWrite (warningLight, HIGH);

The indentation suggests that you want to do both things.

Correct:

  if (temperature > 40)
    {
    digitalWrite (furnaceControl, LOW);
    digitalWrite (warningLight, HIGH);
    }  // end if temperature > 40

Note the comments to make it clear what the closing brace refers to.


Trap: Overwrite the end of an array.

eg.

Wrong! ...

int foo [5];
foo [5] = 42;

The above code will corrupt memory. If you have 5 elements in an array, they are numbered: 0, 1, 2, 3, 4.


Trap: Overflowing an integer.

eg.

Wrong! ...

unsigned long secondsInDay = 60 * 60 * 24;

Small literal constants (like 60) are treated by the compiler as an int type, and have a maximum value of 32767. Multiplying such numbers together has a maximum value of 32767 still.

Correct:

unsigned long secondsInDay = 60UL * 60 * 24;

The "UL" suffix promotes the number to an unsigned long.


Tip: Put things might vary, and other configuration numbers, at the start of a sketch as constants.

Tip: Put things that you might want to change (such as pin numbers, baud rates, etc.) at the start of a sketch as constants.

Instead of:

void setup ()
  {
  Serial.begin (115200);
  pinMode (5, OUTPUT);
  digitalWrite (5, LOW);
  pinMode (6, OUTPUT);
  digitalWrite (6, HIGH);

Use (for ease of changing later):

const unsigned long BAUD_RATE = 115200;
const byte LED_PIN = 5;
const byte MOTOR_PIN = 6;

void setup ()
  {
  Serial.begin (BAUD_RATE);
  pinMode (LED_PIN, OUTPUT);
  digitalWrite (LED_PIN, LOW);
  pinMode (MOTOR_PIN, OUTPUT);
  digitalWrite (MOTOR_PIN, HIGH);

Tip: Use a consistent and widely-accepted naming style for variables and constants.

In C it is traditional to put constants in all UPPER-CASE and variables in mixed upper/lower case.

The Arduino libraries tend to use camelCase (like "pinMode" and "digitalWrite") where you run words together (without underscores) starting with lower case, and using an upper case letter for each subsequent word.

However constants usually use all upper-case and thus have an underscore to separate individual words (like "NUM_DIGITAL_PINS").


Tip: Go easy with leading underscores in variable names.

In C++ these variable names are reserved:

Reserved in any scope, including for use as implementation macros:

  • identifiers beginning with an underscore and an uppercase letter
  • identifiers containing adjacent underscores (or "double underscore")

Reserved in the global namespaces:

  • identifiers beginning with an underscore

Therefore a global variable like this should not be used:

int _foo;

Also in any context a variable like this should not be used:

int _MotorPin;     // <--- underscore followed by upper-case letter
int My__Variable;  // <--- double underscores

I suggest not using leading underscores. If you want to differentiate class variables from non-class variables, use a trailing underscore.


Tip: Do not use goto

It is widely-accepted that the use of the goto statement in C/C++ is not only unnecessary, but harmful. There are occasional exceptions, but if you are reading a beginner's tutorial (like this) you definitely won't fall into the category of needing to use goto.

In almost every case where goto seems useful, it can be avoided by restructuring your code. For loops use "for", "while" or "do". To break out of a loop use "break". To leave a function use "return".

Nice!

Only comment: isn't the Not using arrays one more a Tip than a Trap?- (it's not actually wrong, just verbose)

Agreed. I was also thinking about style guidelines (eg. indentation, braces) but maybe that will ignite a "style war". :stuck_out_tongue:

Trap:
Putting a semicolon after everything: #define MY_CONSTANT 42;
Wrong

#define MY_CONSTANT 42

Right

for (int i = 0; i < MY_CONSTANT; i++);

(almost certainly) wrong

Good point, I knew I would forget a few things like that.

Probably a comparison between #define and "const int" would be in order.

Trap:
Over prettifying comments

// I want my comment to look symmetrical \\
int butTheCompilerNeverSeesThisDeclaration;

Maybe mentioning the importance of a consistent style and refering to some style guides as examples ?

Tip: Do not use goto

It is widely-accepted that the use of the goto statement in C/C++ is not only unnecessary, but harmful.

Add a reference to Edsger Dijkstra's 1968 paper?

Tip: Put things might vary, and other configuration numbers, at the start of a sketch as constants.

Better (?): Tip: Put things that might vary, and other configuration numbers, at the start of a sketch as constants.

Tip: Put things might vary, and other configuration numbers, at the start of a sketch as constants.

...

In C it is traditional to put constants in all UPPER-CASE and variables in mixed upper/lower case.

The Arduino libraries tend to use camelCase (like "pinMode" and "digitalWrite") where you run words together (without underscores) starting with lower case, and using an upper case letter for each subsequent word.

However constants (as suggested above) use all upper-case and thus an underscore to separate individual words (like "NUM_DIGITAL_PINS").

I think the explanation is a separate tip.

All many/most of your "tips and traps" so far are for generic C and C++, and there are many fine introductions covering all this (and much more) already available on the Internet. However, there probably is a need to cover "tips and traps" for Arduino-specific peculiarities, i.e., where it departs from C/C++ standards (the C preprocessor especially).

e.g.: #include madness - Programming Questions - Arduino Forum

Edit: "Tip: Put things might vary, and other configuration numbers, at the start of a sketch as constants."

I think I know what you mean, but in general, things that might vary need to be variables, and things that don't should be declared constants.

What you are getting at, of course, is that constant values that are used in several places in the program should be declared at the start of the sketch as constants with meaningful names, aiding both readability and maintainability of the program. Might be clearer with a reword, especialliy for newbies.

many/most of your "tips and traps" so far are for generic C and C++, and there are many fine introductions covering all this (and much more) already available on the Internet.

That's true, and/but I was trying to unify the ones we commonly see into one page.

However, there probably is a need to cover "tips and traps" for Arduino-specific peculiarities ...

Yes, a good point, and maybe a tip or two about the way include files are handled, and how to build multi-file projects in the IDE would be in order.

There's a fine line between stating the obvious, covering too much (and having the page be unwieldy) or covering too little.

Maybe have two pages....

  • Tips and Traps: Beginners
  • Tips and Traps: Experienced C / C++ users

JimboZA:
Maybe have two pages....

  • Tips and Traps: Beginners
  • Tips and Traps: Experienced C / C++ users

Maybe better to have two documents, or two sections within the one document:

a) General tips and traps (C and C++ programming)
b) Arduino-specific tips and traps (Ardunio differences wrt C/C++ standards)

I don't really see it as an experienced programmer/beginner dichotomy, because even beginners will get confused if also learning from another text that assumes standard C/C++.

I would suggest always using braces around conditional code. It makes the intended scope of the condition explicit (so the reader doesn't have to guess what was intended) and avoids the risk of dangling else statements.

if(a == true)
    if(b == true)
        c = 1;
  else
      c = 2; // when does this execute?

It also avoids problems where the condition only controls a single statement, which can be subtly dangerous if a second statement is added subsequently, especially if that second statement is the result of macro expansion.

if(a == true)
    Serial.print("Value is"); Serial.print(b);

As a general guide:
Don't use macros to define constant values - use const variables.
Don't use macros to generate executable statements - use inline functions.

Specifically for Arduino:
When defining a function that receives or returns a user-defined type, define the type in a header file and #include that in the .ino file. (This prevents the IDE from breaking your code by inserting a function prototype in the wrong place.)

RbSCR:

Tip: Put things might vary, and other configuration numbers, at the start of a sketch as constants.

Better (?): Tip: Put things that might vary, and other configuration numbers, at the start of a sketch as constants.

pico:
I think I know what you mean, but in general, things that might vary need to be variables, and things that don't should be declared constants.

Reworded to address both those points.

RbSCR:
I think the explanation is a separate tip.

Done.

Add a reference to Edsger Dijkstra's 1968 paper?

I can't see a ready URL that links directly to it. I'll add a link to the Wikipedia article.

PeterH:
I would suggest always using braces around conditional code. It makes the intended scope of the condition explicit (so the reader doesn't have to guess what was intended) and avoids the risk of dangling else statements.

I know where you are coming from, but I always find this sort of thing unnecessarily complex:

  if (index >= MAX_INDEX)
    {
    index = 0;
    }

Compared to:

  if (index >= MAX_INDEX)
    index = 0;

That's doubled the lines of code for no real gain.

Certainly for nested "if"s, I would be very inclined to use the braces or things just get crazy.

As a general guide:
Don't use macros to define constant values - use const variables.
Don't use macros to generate executable statements - use inline functions.

Yes, I think a short mention of constants would be in order.

Specifically for Arduino:
When defining a function that receives or returns a user-defined type, define the type in a header file and #include that in the .ino file. (This prevents the IDE from breaking your code by inserting a function prototype in the wrong place.)

Yes, although this tends not to hit beginners so much. Plus you can do your own function prototypes these days to work around that.

Whoops! Hit the 9500 byte posting limit. More tips follow:


Tip: Don't try to out-think the compiler

The compiler aggressively optimizes your code. In general you don't need to try to improve on its performance by attempting your own optimizations, especially if that makes the code obscure and hard to maintain. In particular you almost never need to use assembler code to get speed improvements, because the compiler generates good assembler code from your C source.

However you can help the compiler by choosing good data types. For example, for small numbers use char or byte, for medium size numbers int or unsigned int, and for larger numbers long or unsigned long. The limits for such types on the Arduino (8-bit) platform are:

char:                 -128  to         +127
byte:                    0  to         +255
int:                -32768  to       +32767
unsigned int:            0  to       +65535
long:          -2147483648  to  +2147483647
unsigned long:           0  to  +4294967295

Note that for storing times (eg. from millis() or micros() function calls) you should use unsigned long.

Also the String class tends to be slow, and also a bit of a memory hog. Try to avoid using it.


Trap: Don't put a semicolon at the end of every line

Semicolons end statements. However "if", "for" and "while" are compound statements, so they should not have a semicolon after them like this:

Wrong! ...

  if (temperature > 40);    // <----- this semicolon is not required!
    {
    digitalWrite (furnaceControl, LOW);
    digitalWrite (warningLight, HIGH);
    }

  for (int i = 0; i < 10; i++);   // <----- this semicolon is not required!
    digitalWrite (i, HIGH);

The semicolons above (as indicated) terminate the "if" and "for" so they don't do anything useful. The lines following them are just unconditional blocks of code which are always executed, once.

Correct:

  if (temperature > 40)
    {
    digitalWrite (furnaceControl, LOW);
    digitalWrite (warningLight, HIGH);
    }

  for (int i = 0; i < 10; i++)
    digitalWrite (i, HIGH);

Similarly this is wrong:

#define LED_PIN 10;    // <--- no semicolon!
#define LED_PIN = 10;  // <--- no semicolon, no "=" symbol

Correct:

#define LED_PIN 10

Or, better still:

const byte LED_PIN = 10;

Using a const rather than a #define has various advantages, including the one of consistency. Now the semicolon and "=" symbol are required. This is consistent with the way you write variables. However there is no performance or space penalty for using constants like this (compared to using #define).


Trap: Don't over-prettify your comments

Wrong! ...

// I want my comment to look symmetrical \\
int butTheCompilerNeverSeesThisDeclaration;

The comment looks cute, but the trailing backslash makes the following line part of the comment (because of line folding).

Correct:

// Here is my comment with no trailing backslash
int someVariable;

Trap: Trying to initialize multiple variables

For example:

int x, y, z = 12;

Only z is initialized there.

Good:

int x = 1, y = 2, z = 12;

Better:

int x = 1;
int y = 2;
int z = 12;

Trap: Not initializing local function variables

Wrong! ...

void calculateStuff ()
  {
  int a;

  a = a + 1;

Since "a" was not initialized, adding one to it is undefined.


Trap: Trying to work on a "set"

Wrong! ...

digitalWrite ((8, 9, 10), HIGH);

Whilst the above will compile, it only sets a single pin high (pin 10).

Correct:

digitalWrite (8, HIGH);
digitalWrite (9, HIGH);
digitalWrite (10, HIGH);

Or:

for (byte i = 8; i <= 10; i++)
  digitalWrite (i, HIGH);

Trap: Returning a pointer to a local variable

Wrong! ...

char * getString ()
{
  char s [] = "abc";
  return s;
}

The string "s" is allocated on the stack and goes out of scope when the function returns, and is no longer valid. There are various work-arounds, one would be:

Better:

char * getString ()
{
  static char s [] = "abc";
  return s;
}

That isn't perfect (you might call getString twice and try to use both returned values) however at least it won't actually crash.

Other work-arounds are:

  • Make the variable you are planning to change global.
  • Pass the variable by reference and change that.

Trap: Leaving a semicolon off after a "return"

Wrong!

  if (a < 10)
    return     // <----- no semicolon!
  a = a + 1;

The code above compiles as if it were written:

  if (a < 10)
    return a = a + 1;

Tip: Let the compiler do the work for you

A. Instead of working out how many seconds there are in a day on a calculator and then typing it in, for example:

const unsigned long secondsInDay = 86400;  // 60 * 60 * 24

Let the compiler do it:

const unsigned long secondsInDay = 60UL * 60 * 24;

B. Let the compiler work out how big things are:

Instead of:

char buffer [20];

// later ...

if (i < 20)
  {
  buffer [i] = c;
  }

Use this:

char buffer [20];

// later ...

if (i < sizeof (buffer))
  {
  buffer [i] = c;
  }

Now, if you ever change 20 to 30, the code still works properly, without having to remember to change it in multiple places.

For arrays of things larger than one byte you can use this macro to find how large the array is:

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

For example:

int myArray [20];

int numberOfItems = ARRAY_SIZE (myArray);   // <--- will be 20 in this case

C. Let the compiler work out how many things you put into an array of (say) pin numbers:

Instead of:

const byte pinNumbers [5] = { 5, 6, 7, 8, 9 };

Use:

const byte pinNumbers [] = { 5, 6, 7, 8, 9 };

Later you can use ARRAY_SIZE to find how many elements there actually are in the array.


Trap: Doing too much in a constructor

There is a subtle problem with initializing things in a class constructor, particularly if the constructor might be called at the global level (that is, not inside a function).

Example:

myClass::myClass ()  // constructor
  {
  Wire.begin (32);  // initialize I2C library
  whenStarted = millis ();
  Serial.begin (115200);
  Serial.println ("Ready to go.");
  }

The problem is that libraries like Wire, Serial, and the timers may not have been initialized at this time. In other words, your constructor may have been called before their constructor.

You are best off doing minimal things in the class constructor, and making a "begin" function (like Serial.begin, Wire.begin, etc.) and calling that in "setup". Acceptable things to do in a constructor are to store simple types (eg. integers like pin numbers).

http://www.parashift.com/c++-faq/static-init-order.html


Tip: Use "else" rather than testing for the same thing twice

Bad:

if (a == 5)
  {
  // do something
  }
if (a != 5)
  {
  // do something else
  }

Good:

if (a == 5)
  {
  // do something
  }
else
  {
  // do something else
  }

Tip: Use "switch/case" rather than lengthy "if" tests

Bad:

if (command == 'A')
  {
  // do something
  }
else if (command == 'B')
  {
  // do something
  }
else if (command == 'C')
  {
  // do something
  }
else
  {
  // unexpected command
  }

Good:

switch (command)
  {
  case 'A':
        // do something
        break;

  case 'B':
        // do something
        break;

  case 'C':
        // do something
        break;

  default:
        // unexpected command
        break;
  }  // end of switch

Trap: Be careful with recursion

These micro-controllers don't have much memory. Be cautious when using recursive functions (functions that call themselves).

In particular this will fail spectacularly:

void loop ()
  {
  // do stuff
 
  loop ();    //  <--- recursion!
  }

RbSCR:
Maybe mentioning the importance of a consistent style and refering to some style guides as examples ?

Any suggestions?

I've experimented over the years with style, but always find the original K&R style the one I come back to. So a reference to that, perhaps.

If you are aiming for conciseness, I wouldn't go into a lot of stylistic issues, such as camel vs underscores in variable names, use of parenthesis around singleton statements, etc., as this is all to personal taste to a large extent. Consistency is always good, however; encourage choosing a style at the outset and trying to keep to it as much as possible.

I've seen readable and well-written programs in many styles; I actually enjoy seeing something that's new (to me) and will sometimes "try it on" for size. Usually revert to something pretty close the K&R though... it just seems the "right" way to write C, somehow.

ITriedToLoveTheCamelButItNeverHappened.
underscores_and_lower_case_is_somehow_just_more_readable. :slight_smile:

but always find the original K&R style the one I come back to.

int function (param1)
  int param1;
{
 
}

Be careful what you wish for :wink:

AWOL:

but always find the original K&R style the one I come back to.

int function (param1)

int param1;
{

}



Be careful what you wish for ;)

Please, K&R would never have used that indentation!

int function (param1)
int param1;
{
 
}

All fixed now. :wink:

Maybe there can be three parts:

  • Traps
  • Tips
  • Style

Plus maybe:

  • Advanced

However it's probably impossible to cover in a short article all the advanced stuff. I was really hoping to have a reference that we could point people to over "beginner" issues, like the semicolon on the same line as an "if".