Deep C Dives: The Brilliant But Evil for
Written by Mike James   
Tuesday, 19 November 2024
Article Index
Deep C Dives: The Brilliant But Evil for
Conditional Loops
Break and Continue
The Evil Parts

Conditional Loops

A conditional loop has one of a number of possible forms but a common one is:

initial set up
test to see if loop should continue
   body of loop
   update test condition
end of loop

For example using C to implement this sort of loop directly:

    int i = 0;     		← initial set up
loopStart:
    if (i >= 10) 		← test to see if loop 
should continue

goto loopEnd; printf("%d\n", i); ← body of loop i++; ← update test condition
goto loopStart; loopEnd: printf("end of loop"); ← end of loop

You can clearly see the different parts of the loop and the way that gotos are used to jump to the appropriate points in the code. Not long ago all programming was like this and it mirrors the way that a conditional loop is implemented in assembler. Today we prefer to use high-level language constructs that bundle all of the sections up into fixed parts of the construct so you don’t have to waste time building your own from scratch.

The C for Loop

The conditional loop in the previous section is in fact an enumeration loop and could more easily be expressed in Basic as:

for i = 0 to 9
   print(i)
next i

or in Python

for i in range(0,10):
    print(i)

C takes the approach that it is better to package the parts of a general conditional loop rather than invent a proper enumeration loop. The C for loop, and it would be better called a conditional loop, is:

for(initial set up; test if loop should continue;
                             update test condition){
   body of loop
}

When you write a C for loop the compiler uses the expressions to construct a conditional loop. For example:

for(i = 0; i < 10; i++){
   printf("%d\n", i);
}

is equivalent to the previous conditional loop:

    int i = 0;  
loopStart:
    if (i >= 10)  goto loopEnd; 
    printf("%d\n", i); 	
    i++;	
    goto loopStart;
loopEnd:
    printf("end of loop"); 

The i = 0 is performed before the loop starts, the i < 10 is evaluated at the start of the loop and the body of the loop only executes if it is true and the i+⁠+ is performed at the end of the loop before the next potential repeat. Notice that the condition in the for loop has to be true for the loop to continue. In the example conditional loop the condition has to be true for the loop to end but this is a minor difference.

In this form the C for loop is perfectly OK as an enumeration loop. All C programmers learn this form as an idiom and read it as a loop that repeats for i from 0 to 9. It is as if the C for loop is frozen in a restricted but useful form:

for(index=startvalue; index<endvalue; index++){ 

and the only things you ever change are the startvalue and endvalue and the loop repeats for index from the startvalue to one less than the endvalue. In this form it really is an easy to use enumeration loop.

From here most C programmers learn to vary it slightly to get different effects.

For example:

for(index = startvalue; index <= endvalue; index++){

gives an enumeration loop that runs from startvalue to, and including, endvalue, whereas:

for(index = startvalue; index >= endvalue; index--){

gives an enumeration loop that runs from startvalue down to endvalue assuming startvalue >endvalue and so on.

These idioms are what makes the C for loop usable for beginners and non-beginners alike. It is a for loop with direct mapping to the underlying assembly language that it is compiled to and as such it is brilliant. But it has its evil side – it is too flexible.

While and Until

The for loop is very general, but it is limited to having its exit point at the start of the loop. This makes it a while loop.

Loops differ in where they place their exit points, i.e. where the test for the end of the loop is. This is another distinguishing feature in the loop zoo – loops differ in both the number of exit points they have and where they are placed.

While loops test at the start and until loops test at the end.

It should be clear that the difference is that a while loop can exit without ever executing the body of the loop, but an until loop has to execute the body of the loop before making a test. You can summarize this as a while loop repeats 0 to n times and an until loop repeats 1 to n times. In other words, an until loop has to execute at least once.

C has a while loop, but it is entirely equivalent to a particular form of the for loop. The while loop:

while(condition){
}

is exactly the same as:

for(;condition;){
}

and, yes, you can leave out any part of the for loop as long as it makes sense. Indeed:

for(;;)

is an infinite loop, often used in IoT applications and in constructing more general loops. It never ends because there is no condition specified.

C also has an until loop and this is one that is more difficult to convert into a for loop:

do{
  body of loop
}while(condition)

If this is an until loop, why does it end with while?

The answer is that in a conventional until loop the condition is for the loop to end. In a conventional while loop the condition is for it to continue. You can see that until = !while in that until ends the loop and while continues it.

So even though the C until loop has its exit point at the end it uses a condition for it to continue – hence the use of while. It’s a historical accident caused by the desire to reuse the compiler's handling of the while condition i.e. to avoid introducing an until(condition).

You can convert this to a for loop, but only if you are prepared to repeat the body of the loop:

body of loop
for(condition){
  body of loop;
}

Cdive180

 



Last Updated ( Tuesday, 19 November 2024 )