Declare variables in the loop initialization
Why?
The following program should create a matrix with some values and then show the matrix on the screen. The program compiles without errors, but it does not work. Can you get what is wrong?
#include <stdio.h>
#define L 4
#define C 8
int main(){
int mat[L][C];
size_t i,j;
for(i=0; i<L; ++i){
for(j=0; j<C; ++j){
mat[i][j] = j;
}
}
for(i=0; i<L; ++j){
for(j=0; j<C; ++j){
printf("%d ", mat[i][j]);
}
putchar('\n');
}
return 0;
}
Got it? It is in line 16. It should be for(i=0; i<L; ++i). This rookie error could be warned by the compiler if we declared the variables i and j inside the loop. There are many reasons to declare the variables in the loop initialization. The first obvious reason is that in the example, the variables i and j are used only to control the loops, so why should these variables exist after the loop termination? By declaring the variable in the loop initialization, you also make clear that the variable is used only to control the loop.
Keep in mind: a variable should exist only in the scope where it is necessary. So, a better (and corrected) version of the program is:
#include <stdio.h>
#define L 4
#define C 8
int main(){
int mat[L][C];
for(size_t i=0; i<L; ++i){
for(size_t j=0; j<C; ++j){
mat[i][j] = j;
}
}
for(size_t i=0; i<L; ++i){
for(size_t j=0; j<C; ++j){
printf("%d ", mat[i][j]);
}
putchar('\n');
}
return 0;
}
This approach's only drawback is that the program might spend additional time creating and destroying the variables in the stack, although this is frequently not a concern for modern compilers. By declaring the variables in the loop initialization we may even save memory. We guarantee the compiler that the variable should exist only when executing the loop.
Is there any overhead?
Let us compare a simple example using the variables declared in the loop initialization and outside it.
#include <stdio.h>
#define M 4
//declaring the variable inside the loop initialization
int main(){
int array[M];
int sum = 0;
for(size_t i=0; i<M; ++i)
array[i] = i;
for(size_t i=0; i<M; ++i){
printf("%d ", array[i]);
sum += array[i];
}
printf("\nSum: %d\n", sum);
return 0;
}
#include <stdio.h>
#define M 4
//declaring the variable outside the loop
int main(){
int array[M];
int sum = 0;
size_t i;
for(i=0; i<M; ++i)
array[i] = i;
for(i=0; i<M; ++i){
printf("%d ", array[i]);
sum += array[i];
}
printf("\nSum: %d\n", sum);
return 0;
}
Let us compare the assembly (we are fearless C programmers who are not intimidated by some simple assembly) generated for these two snippets using gcc -S file.c.
The first line highlighted shows that the allocation of the variables in the loop initialization took 16 extra bytes in the stack. If you are wondering why we needed 16 extra bytes and not 8 as expected by a size_t (translated to an unsigned long in an x86-64), it is because the x86-64 stack can only grow in a multiple of 16 bytes due to alignment restrictions.
But with a simple compiler optimization, we can remove this extra memory needed. To see that, recompile the programs using gcc -O1 -S file.c.
Now the generated assembly is identical for both programs, and we have no overhead.
References
Seacord, R. C. Effective C: An Introduction to Professional C Programming. No Starch Press. 2020.
C ISO Standard. ISO/IEC 9899:2018, 2018.
Plantz. Introduction to Computer Organization: A Guide to X86-64 Assembly Language and GNU/Linux. 2011.