For-loop-insanity

Started by
26 comments, last by FRex 9 years, 7 months ago

In a careless thought to make the data type as small as possible, the compiler complained (naturally) of me using a constant that was too big for the type I was testing against:


    for (uint8_t j = 0; j < (1 << 8); j++) {
        ...
    }

Which gave me the idea to come up with this masterpiece:


    for (uint8_t test = 0; test <= 255; test++) {
        printf("%i\n", test);
    }
Advertisement

Maybe more to the point -- for a local variable, one that will likely be allocated as a register (or on the stack, at worst), you don't really gain anything at all by making 'test' small -- there might be reasons of correctness that are worthwhile, but optimization is basically a non-factor. Maybe if the loop appears in a deeply-recursive function, or on a small, embedded system with 4k of memory or something, but not usually. Just use the natural word-size of the machine, usually plain old 'int' -- it will be as fast as (faster, more likely) than the smaller value.

throw table_exception("(? ???)? ? ???");

Classic mistake. There is a generic way to make it work, if you really want to, though:


uint8_t j = 0;

do
{
  printf("%i\n", j);
}
while (++j);

Which is pretty readable, since the while loop is clearly going to terminate as soon as j overflows. And like Ravyne said, it doesn't really make anything faster in general, that said I personally don't see this as an optimization tactic, but more as a type correctness thing, in other words, the loop variable is going to be used as an 8-bit unsigned integer, so let's not lie to ourselves, make it an 8-bit unsigned integer. But with this comes the responsibility of watching out for overflow and other low-level things which generally don't come up when simply using a sufficiently large signed integer like int, people will have their own opinions on that matter smile.png

edit: fixed the termination condition error, thanks

“If I understand the standard right it is legal and safe to do this but the resulting value could be anything.”

I think we all have those moments sometimes. Figuring out why those loops would never terminate could possibly make for a very frustrating debug session. biggrin.png

My current game project Platform RPG

Classic mistake. There is a generic way to make it work, if you really want to, though:


uint8_t j = 0;

do
{
  printf("%i\n", j);
}
while (j++);

Which is pretty readable, since the while loop is clearly going to terminate as soon as j overflows. And like Ravyne said, it doesn't really make anything faster in general, that said I personally don't see this as an optimization tactic, but more as a type correctness thing, in other words, the loop variable is going to be used as an 8-bit unsigned integer, so let's not lie to ourselves, make it an 8-bit unsigned integer. But with this comes the responsibility of watching out for overflow and other low-level things which generally don't come up when simply using a sufficiently large signed integer like int, people will have their own opinions on that matter smile.png

Don't you mean ++j?

I just tried it on ARM7, which has only 32-bit registers, and it compiled to a compare with 256, branch if not equal. So it doesn't actually force the wrapping, but did get the logic correct, and no different than using an int and comparing with 256.

Classic mistake. There is a generic way to make it work, if you really want to, though:

uint8_t j = 0;

do
{
printf("%i\n", j);
}
while (j++);

To be picky, this works, but it also leaks the loop counter into the surrounding scope (as a "correctness issue"), and might contribute to register pressure for the duration of the scope (as an "optimization issue", though I would hope a modern compiler would do better). If there's a type correctness issue and also a condition at the boundary of that type (</<= ~255 in this case), then its probably best to use the natural word size as the counter variable, and cast to the correct type where needed.

throw table_exception("(? ???)? ? ???");


Don't you mean ++j?

Yes, he does :)

throw table_exception("(? ???)? ? ???");

Huh. I have ++j in my test source file, must've copied it wrong on the forum. Sorry about that, I will edit.


To be picky, this works, but it also leaks the loop counter into the surrounding scope (as a "correctness issue"), and might contribute to register pressure for the duration of the scope (as an "optimization issue", though I would hope a modern compiler would do better)

About leaking the loop counter in the surrounding scope, I am curious if there is an elegant way to achieve the same result with a for loop, so that the loop counter would be limited to the scope of the loop.

“If I understand the standard right it is legal and safe to do this but the resulting value could be anything.”


I am curious if there is an elegant way to achieve the same result with a for loop, so that the loop counter would be limited to the scope of the loop.

Elegant, I don't think so. This is the least ugly thing I could come up with.

for (uint8_t test = 1; test > 0; test++) {
printf("%i\n", (test - 1));
}

But please don't try this at home :)

You could also just create a scope around the variable definition and do-while in your example. It would limit the scope 'appropriately', but its ugly, non-idiomatic, and another programmer who comes along might be tempted to delete the extra braces and not think anything of it -- likely that just puts you back in the same potentially-sub-optimal boat as before, but it could break a build or change the behavior of the code if the variable name aliased another.

throw table_exception("(? ???)? ? ???");

The elegant way is to just use an int.

CPUs don't have 8bit registers, or don't want to operate on 8bit values these days, which means that when you write this:

uint8_t j = 0; do {...} while (++j);

You're actually asking the compiler to transform it into something like this abomination:
int j = 0; do {...} while( (j=(j+1)&0xFF) != 0 );

...and then also asking the compiler to try and optimize such horrible code for you!

After wasting a bunch of time optimizing your code, it will be the same as if you'd just written what DekuTree posted, which is a perfectly idiomatic loop without any clever obfuscation applied:

for( int i=0; i!=256; ++i ) {...}

If there is a more optimal way of looping on a specific platform, a clever compiler will be able to transform any of the above loops to it, seeing they're all exactly equivalent in behavior!

This topic is closed to new replies.

Advertisement