It does not matter at all, because compilers don't automatically translate variable declaration to memory or register allocation. The difference between the two samples is that in the first case the variables are visible outside of the loop body, and in the second case they are not. However this difference is at the C level only, and if you don't use the variables outside the loop it will result in the same compiled code.
The compiler has two options for where to store a local variable : it's either on the stack or in a register. For each variable you use in your program, the compiler has to choose where it is going to live. If on the stack, then it needs to decrement the stack pointer to make room for the variable. But this decrementation will not happen at the place of variable declaration, typically it will be done at the beginning of the function : the stack pointer will be decremented only once by an amount sufficient to hold all of the stack-allocated variables. If it's only going to be in a register, no initialization needs to be done and the register will be used as destination when you first do an assignment. The important thing is that it can and will re-use memory locations and registers that were previously used for variables which are now out of scope.
For illustration, I made two test programs. I used 10000 iterations instead of 10 because otherwise the compiler would unroll the loop at high optimization levels. The programs use rand
to make for a quick and portable demo, but it should not be used in production code.
declare_once.c :
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
int main(void) {
srand(time(NULL));
int x, y, i;
for (i = 0; i < 10000; i++) {
x = rand();
y = rand();
printf("Got %d and %d !\n", x, y);
}
return 0;
}
redeclare.c is the same except for the loop which is :
for (i = 0; i < 10000; i++) {
int x, y;
x = rand();
y = rand();
printf("Got %d and %d !\n", x, y);
}
I compiled the programs using Apple's LLVM version 7.3.0 on x86_64 Mac. I asked it for assembly output which I reproduced below, leaving out the parts unrelated to the question.
clang -O0 -S declare_once.c -o declare_once.S :
_main:
## Function prologue
pushq %rbp
movq %rsp, %rbp ## Move the old value of the stack
## pointer (%rsp) to the base pointer
## (%rbp), which will be used to
## address stack variables
subq $32, %rsp ## Decrement the stack pointer by 32
## to make room for up to 32 bytes
## worth of stack variables including
## x and y
## Removed code that calls srand
movl $0, -16(%rbp) ## i = 0. i has been assigned to the 4
## bytes starting at address -16(%rbp),
## which means 16 less than the base
## pointer (so here, 16 more than the
## stack pointer).
LBB0_1:
cmpl $10, -16(%rbp)
jge LBB0_4
callq _rand ## Call rand. The return value will be in %eax
movl %eax, -8(%rbp) ## Assign the return value of rand to x.
## x has been assigned to the 4 bytes
## starting at -8(%rbp)
callq _rand
leaq L_.str(%rip), %rdi
movl %eax, -12(%rbp) ## Assign the return value of rand to y.
## y has been assigned to the 4 bytes
## starting at -12(%rbp)
movl -8(%rbp), %esi
movl -12(%rbp), %edx
movb $0, %al
callq _printf
movl %eax, -20(%rbp)
movl -16(%rbp), %eax
addl $1, %eax
movl %eax, -16(%rbp)
jmp LBB0_1
LBB0_4:
xorl %eax, %eax
addq $32, %rsp ## Add 32 to the stack pointer :
## deallocate all stack variables
## including x and y
popq %rbp
retq
The assembly output for redeclare.c is almost exactly the same, except that for some reason x and y get assigned to -16(%rbp)
and -12(%rbp)
respectively, and i
gets assigned to -8(%rbp)
. I copy-pasted only the loop :
movl $0, -16(%rbp)
LBB0_1:
cmpl $10, -16(%rbp)
jge LBB0_4
callq _rand
movl %eax, -8(%rbp) ## x = rand();
callq _rand
leaq L_.str(%rip), %rdi
movl %eax, -12(%rbp) ## y = rand();
movl -8(%rbp), %esi
movl -12(%rbp), %edx
movb $0, %al
callq _printf
movl %eax, -20(%rbp)
movl -16(%rbp), %eax
addl $1, %eax
movl %eax, -16(%rbp)
jmp LBB0_1
So we see that even at -O0 the generated code is the same. The important thing to note is that the same memory locations are reused for x and y in each loop iteration, even though they are separate variables at each iteration from the C language point of view.
At -O3 the variables are kept in registers, and both programs output the exact same assembly.
clang -O3 -S declare_once.c -o declare_once.S :
movl $10000, %ebx ## i will be in %ebx. The compiler decided
## to count down from 10000 because
## comparisons to 0 are less expensive,
## so it actually does i = 10000.
leaq L_.str(%rip), %r14
.align 4, 0x90
LBB0_1:
callq _rand
movl %eax, %r15d ## x = rand(). x has been assigned to
## register %r15d (32 less significant
## bits of r15)
callq _rand
movl %eax, %ecx ## y = rand(). y has been assigned to
## register %ecx
xorl %eax, %eax
movq %r14, %rdi
movl %r15d, %esi
movl %ecx, %edx
callq _printf
decl %ebx
jne LBB0_1
So again, no differences between the two versions, and even though in redeclare.c we have different variables at each iteration, the same registers are re-used so that there is no allocation overhead.
Keep in mind that everything I said applies to variables that are assigned in each loop iteration, which seems to be what you were thinking. If on the other hand you want to use the same values for all iterations, of course the assignment should be done before the loop.