Is the size of the datatype used for a for loop iterator important?

Most of the time I find for-loops only iterate a handful of times.

I've taken to using Byte or uint8_t as the iterator datatype as general practice.
The thinking being that we should care wherever possible about datatype size in embedded systems.
But is it possible there's no point doing this?

e.g would the compiler make Ints into Bytes anyway if it knows the upper limit from the loop definition?

According to godbolt using uint8_t is five machine instructions less than using uint16_t.

A smart compiler could do so...
It sometimes does pretty clever things.
But why not take your faith in your own hands?

Using a "uint8_t" on a 32-bit processor might require more code than a normal 32-bit "int".

When I write code for beginners, then I use more brackets '{' and '}' and I prefer "int" in a for-loop.
Suppose you have this:

int myArray[10];
for (uint8_t i=0; i<10; i++)
{
  Serial.println(myArray[i]);
}

and then decide that you need the numbers in reverse:

int myArray[10];
for (uint8_t i=9; i>=0; i++)   // bug
{
  Serial.println(myArray[i]);
}

then you have a bug.

I think that a "int" in the source code is easier to read. The optimization for "uint8_t" is for a ATtiny or ATmega8. But there is no golden rule for this.

Yes int is more readable for sure, and if the compiler make the optimization in a smart way most of the time, then I think Int is probably the way to go.

I can't help but bring up the philosophical argument for code optimisation here.
Coming from Amiga 500 days where a crack group could crunch down 5 full games with gfx and audio assets onto a single 1.4mb floppy.
To today when e.g Apple's Numbers spreadsheet, a glorified table that does some math, containing no gfx or sound assets takes up ~580MB....
I can't help but think trends away from being memory footprint aware are starting to get out of hand.
I guess with SoC's getting cheaper with more ram and flash allows us to be sloppy.
...digression over...

and then decide that you need the numbers in reverse:

int myArray[10];
for (uint8_t i=9; i>=0; i++) // bug
{
Serial.println(myArray[I]);
}

Is this only a bug because though because it's still i++ instead of i--?

Even with i--, you get issues because it's not possible for unsigned i to fall below zero. It will underflow(?) to 255, and the loop will run forever.

Nope. A uint will ALWAYS be greater than or equal to zero, so it's an infinite loop.

There are types int_fast8_t defined at the same time as uint8_t and etc. "use the fastest unsigned integer that is at least 8 bits wide." They should probably be used more often, but they're sort of ugly and hard to type :slight_smile:

3 Likes

That would depend very much on what processor you use.

On an 8-bit processor, handling 8-bit data is always going to be more "efficient" than larger sizes.

On larger processors, as @Koepel said, using a smaller type might actually be less efficient.

TBH I figured the point was to make an example that uints can't go negative.
But I'm confused by that particular example.
It would appear to me to start at 9 and (at least if it were using i--) decrement until it equals 0 and exit the loop, never encountering potential to go negative anyway.
Am I missing something?

It will run so long as i >= 0 is true.

When i is 0: i >= 0 is true.
Minus one, i is now 255, loop carries on.

Ahh ok thanks, I get it now.

Ok interesting.
So then, If I'm using an ESP32 variant or similar (increasingly common), then I guess I really should just use int then and let the compiler take care of it?

Does this then apply to all integers on a 32bit SoC?
Just use Int everywhere, even for small numbers?

It depends.

eg, using a smaller type may or may not save RAM - depending on how the processor likes data to be aligned.

The processor might have special opcodes optimised for 8 bits - so you might get better performance...

I would not do that for 8 bit RGB images...
Or 8 bit raw sound files...

Sorry, I meant "i--".
I should have tested it in Wokwi but I didn't :face_with_raised_eyebrow:
A down counting loop for an array stops at -1, so the loop variable must be signed.

Hello TijuanaKez

Try a so called "range-based for loop" and the compiler will do all the work.

constexpr uint16_t myArray[] {1,2,3,4,5,6,7,8,9};

void setup() 
{
  Serial.begin(115200);
  for (auto number:myArray) Serial.print(number);
  Serial.println(); 
}

void loop() 
{
  
}

Have a nice day and enjoy coding in C++.

How does that compare on speed of execution and code size? :thinking:

Or should that be uint8_t ... ? :stuck_out_tongue_winking_eye:

It depends on the target platform, the compiler, the compiler optimizations, the number of pizza slices in your fridge, I don't know, but it depends.

Normal for-loop:

#include <stdio.h>
#include <stdint.h>

const uint16_t myArray[] {1,2,3,4,5,6,7,8,9};

int main() 
{
  for( int i=0; i<sizeof(myArray)/sizeof(myArray[0]); i++)
    printf("%d", myArray[i]);
}

Range based for-loop:

#include <stdio.h>
#include <stdint.h>

const uint16_t myArray[] {1,2,3,4,5,6,7,8,9};

int main() 
{
  for (auto number:myArray) 
    printf("%d", number);
}

Tested at https://godbolt.org

AVR gcc 13.2.0:

  • normal for-loop: 6919 bytes (6922 bytes with uint8_t as loop variable)
  • range based: 7872 bytes

RISC-V (32-bits) gcc 13.2.0

  • normal for-loop: 6651 bytes (6847 bytes with uint8_t as loop variable)
  • range based: 7376 bytes

Conclusion: There is no conclusion, it depends.

:+1: :clap: :clap: :clap:

Also note that some processors are better at counting down than up; in particular, counting down to zero ...

Using Byte or uint8_t as the iterator datatype in for-loops is a good practice when working with embedded systems to save memory, as it's essential to be mindful of datatype sizes. However, it's important to note that modern compilers are generally quite efficient at optimizing code. If the compiler can determine that a loop's upper limit is small, it may automatically optimize the iterator to a smaller datatype, like uint8_t, even if you initially declared it as an int. So, while specifying the smaller datatype explicitly is a good practice for clarity and safety, the compiler might perform similar optimizations if it can infer the loop's characteristics. Nevertheless, it's a good habit to be explicit in your code to ensure you're using the appropriate datatype, especially in resource-constrained embedded systems.