Programming in C for pets: Loops
2026-04-03
Why do we start learning programming from loops? Because thanks to loops, programs do what they do best: hang.
There are three loop statements in C: while, do-while, and for.
Another way to loop is the goto statement, but mind your karma.
Let's start from the for loop.
Here's an updated example that prints all command line arguments:
#include <stdio.h>
int main(int argc, char* argv[])
{
for (int i = 0; i < argc; i++) {
puts(argv[i]);
}
return 0;
}Everything should be clear intuitively:
foris a keyword that tells the compiler that the programmer is going to write a loop and it needs to prepare for the worst.- There's a control statement in parentheses that consists of three parts separated by comma:
int i = 0- this is an init statement executed before enering the loopi < argc- this part is a condition statement executed each time before the loop body. If this condition is true, the loop body is executed. If not, the the loop completes.i++- this part is executed each time after the loop body. Two pluses after the variable stand for increment. This is a short form ofi = i + 1and this form is used everywhere in C. And they used it for C++. Just for fun.
- There's a loop body in curly brackets. In our example it's a single statement
puts(argv[i])that prints i-th argument. In C, elements are enumerated from zero soiruns from 0 toargc - 1.
This is an idiomatic iterator construct in C. Same as in linguistics, there are idiomatic constructs in programming languages. Their meaning is not always obvious.
Why humans invented such a construct?
To answer this question lets study while loop.
Here's how our example would look:
#include <stdio.h>
int main(int argc, char* argv[])
{
int i = 0;
while (i < argc) {
puts(argv[i]);
i++;
}
return 0;
}What's the difference from for?
Same logic, more lines.
However, for was invented not for brevity.
Before invention of C, programs looked pretty awfully,
but even with such an awesome tool as C, programmers continued writing smelly code as they used to
(they still do that way).
The loop body grew up to a few pages and with while the critical statement i++
went out of sight very quickly.
In for loop all critical operations are grouped together and a programmer can glance
at all of them by a single look and see that everything is correct. Or not.
Nevertheless, in programming it's important to keep the loop body small. Ideally no more than 20 lines. Each pet has its own ideal, mine is as this. Not always reachable, but that's the ideal to be unreachable. When the entire loop body fits the screen, the program is easily readable and all mistakes do not remain unnoticed.
Well, it's time to compile and run our program. Any variant.
cc --std=gnu23 -Wall -Wextra -pedantic -Werror -Wno-unused-parameter -O3 -o myprogram myprogram.c
./myprogram one two three
./myprogram
one
two
threeDo you see the output?
./myprogram
one
two
threeWhy ./myprogram is at the first place?
Because the operating system, when it spawns a process, passes its file name as the first argument. Why? - Well, there are a few reasons for that, let consider them later. For now we only need to know the argument zero is present and it's always present.
Thus, if we want to print only arguments we typed, we must start from 1, not from zero:
#include <stdio.h>
int main(int argc, char* argv[])
{
for (int i = 1; i < argc; i++) {
puts(argv[i]);
}
return 0;
}Now let's study the remaining do-while loop.
If we rewrite our example it will look like this:
#include <stdio.h>
int main(int argc, char* argv[])
{
int i = 0;
do {
puts(argv[i]);
i++;
} while (i < argc);
return 0;
}See the difference?
In the plain while loop the body may never run if condition is initially false.
In do-while it is guaranteed to run al least once.
This means argv should contain at least one element.
But we already know there's always at least one element there. It's the file name of the program.
This also means that do-while is not suitable to print arguments starting from i = 1.
Let's try:
#include <stdio.h>
int main(int argc, char* argv[])
{
int i = 1;
do {
puts(argv[i]);
i++;
} while (i < argc);
return 0;
}cc --std=gnu23 -Wall -Wextra -pedantic -Werror -Wno-unused-parameter -O3 -o myprogram myprogram.c
./myprogram one two three
one
two
threeSo far so good. But if we run the program without arguments, it crashes:
./myprogram
Segmentation faultThat's because there's the only element in argv at index 0,
but we want to get a nonexistent element at index 1.
C does not check the correctness of indexes, that the programmer who must do that. And the compiler emits no warnings. Because of this feature programmers have been whinning for 50 years how difficult to write in C.
Infinite loops
The most popular form of an infinite loop is forever:
for (;;) {
}All the rest variants are a bit longer:
while (1) {
}
do {
} while (1)break and continue
The break operator allows early exit from the loop.
For example, if we want to print no more than five arguments we can write:
#include <stdio.h>
int main(int argc, char* argv[])
{
for (int i = 1; i < argc; i++) {
puts(argv[i]);
if (i == 5) {
break;
}
}
return 0;
}The continue operator skips the remaining loop body.
For example, if we don't want to print the second argument we can write:
#include <stdio.h>
int main(int argc, char* argv[])
{
for (int i = 1; i < argc; i++) {
if (i == 2) {
continue;
}
puts(argv[i]);
}
return 0;
}Stylistics
Finally, let's take a look at formatting. Some programmers prefer to omit curly brackets if the loop body consists of a single statement:
for (int i = 1; i < argc; i++)
puts(argv[i]);It's quite acceptable in C, but pet does not welcome this. It may lead to errors when you don't notice missing brackets in a hurry. It's better to always write them regardless of the length of the loop body. Besides, the closing curly bracket visually separates the loop from the next statement. That contributes to readability a lot.
In contrast to functions, the opening curly bracket is placed on the same line with the loop statement. This looks somewhat weird. On the one hand the loop statement is blended with the loop body. But on the other hand less empty lines in simple constructs is better. If we need to visually separate the loop body, we could insert an empty line, but it's much better to use comments. Let's compare:
for (int i = 0; i < argc; i++) {
puts(argv[i]);
}
for (int i = 0; i < argc; i++) {
// print the argument
puts(argv[i]);
}
for (int i = 0; i < argc; i++) {
/*
* print the argument
*/
puts(argv[i]);
}
for (int i = 0; i < argc; i++)
{
puts(argv[i]);
}
for (int i = 0; i < argc; i++) {
puts(argv[i]);
}A space between for and the opening round bracket is optional.
Some programmers prefer this style:
for( int i = 0; i < argc; i++ ) {
puts( argv[i] );
}That's quite readable and looks pretty good too. Better than without spaces at all:
for(int i=0;i<argc;i++){
puts(argv[i]);
}There are no dogmas in styling. Which style to use is a personal matter. But when you participate in an existing project you should stick to the style which other participants stick to.
Meow.
Pet has limited explanatory capabilities and might miss something. Litte kitties may ask questions on Mastodon and pet will improve this article.