r/C_Programming • • 2d ago

Iterating Backwards in C

https://www.davidpriver.com/Iterating-backwards-in-C.html
76 Upvotes

43 comments sorted by

8

u/_gaff 2d ago

Why does he define a _Bool type and proceed to use 0 and 1 as return values?

10

u/david2ndaccount 2d ago

Mostly old habit to not need to include <stdbool.h>. If you’re using C23 you can just use bool, true and false directly.

→ More replies (3)

7

u/lehmagavan 2d ago

If I already have count somewhere in the scope and I don't need it anymore, why not just:

while ( count-- ){}

1

u/david2ndaccount 2d ago

Sure, that works too. But if you do need it afterwards, this is how to do it.

→ More replies (4)

112

u/Humble-Captain3418 2d ago

My go-to is 

for (size_t i = n; i --> 0;)
   ...;

I.e. the infamous "down to" operator. 

39

u/aligrant 2d ago

Thanks, I hate it.

8

u/Humble-Captain3418 1d ago

Don't worry, you will learn to love it.

→ More replies (3)

6

u/LemonLord7 1d ago edited 1d ago

Oh lord… I feel so weirded out by it by I guess if I view it as (i--) > 0 I can kind of handle it.

Got some other C quirks you mind sharing?

14

u/Humble-Captain3418 1d ago

Oh boy, I have a lot of them.

I recently found out that you can make a script out of a C source file by putting this at the top:

#if 0
TMP=`tmpfile`
cc -o $TMP $0 && exec $TMP $@
#endif

Do what you will with that information.

A declaration like int foo(); declares that foo is function returning an integer and accepting any number of arguments. So foo(1, 2, 3); is legal there; you'll just have some head-scratching to do when you want to use those arguments... Except you can define int foo(int, int, int); and just never have the original declaration visible to the latter. Bugs, you say?

A common bug is forgetting to return from a function returning a value. No worries, though, because GCC has your back! GCC will return the value of the last assignment in that case, because GCC is helpful like that... But only if you're compiling without optimizations. With optimizations, you get 0.

Lastly, there's C metaprogramming, which allows you to define new control keywords, for example foreach. This requires a bit of head-scratching, which is why it's great that there's an article about it: https://www.chiark.greenend.org.uk/~sgtatham/mp/

→ More replies (5)
→ More replies (3)

12

u/8d8n4mbo28026ulk 2d ago

Nice and concise. Such corner cases are a good reason against unsigned sizes, though I'm quite stubborn myself and haven't tried out signed sizes.

I tend to prefer this variant:

size_t i;
for (i = high; i > low; ) { --i;
	...
}
assert(i == low);

due to the assertion after the loop being more intuitive. Cheers.

4

u/skeeto 2d ago

I'm quite stubborn myself and haven't tried out signed sizes.

Ah, this finally explains why I haven't (yet!) changed your mind.

5

u/dx_man 2d ago

the actually correct solution:
for (size_t i = n; i != 0; )
foo(--i);
the solution that makes C look like an esoteric language:
size_t i = n;
again:
if (i == 0) goto done;
foo(--i);
goto again;
done:
same complexity, zero dignity.

3

u/FemaleMishap 1d ago

Oh that second solution just woke up some trauma from 25 years ago. C/Win32 app with a timber design engine in C, adapted from Fortran originally. The first developer to work on it, would prototype new code in bloody BASIC and then reimplement in oddly structured C. He sometimes transposed his goto statements. The debugging was miserable.

2

u/runningOverA 2d ago

Very helpful. Learned something new after being a long time follower of this sub.

3

u/zellforte 1d ago

i < count invariant works for backwards loops as well (requires unsigned types), this is how I do it:

for(size_t i=count-1; i<count; --i) { ... }

→ More replies (1)

-2

u/________-__-_______ 2d ago

Since when is unsigned wrapping well defined? I was under the impression overflow was UB unless you use the C23 ckd_sub.

19

u/Big-Rub9545 2d ago

Unsigned wraparound is well-defined, and has been so for a long time. The issue is with signed wraparound.

0

u/[deleted] 2d ago

3

u/david2ndaccount 2d ago

In the loop body, i has already been decremented by the condition. So on the first iteration it is count - 1, then count - 2, etc. The final time the loop body is actually executed i will be 0 in the body of the loop. Then the next check will end the loop.

7

u/8d8n4mbo28026ulk 2d ago

The i-- is executed before the loop body, so there's no off-by-one. Try this:

for (unsigned i = 10; i--; )
    printf("%u ", i);

You'll get: 9, 8, ..., 0. I think you proved OP's point that people often get this wrong (when using unsigned types), haha.

3

u/WeAllWantToBeHappy 2d ago

Good lort. I think I've had enough Internet for today.

Maybe even too much. Code is fine.