This is probably just a compiler quirk or a benchmarking error. Code segments that produce identical results under all circumstances should theoretically compile to the same assembly. Usually the compiler fails to optimize if parts of the code are obfuscated (eg in different translation units) or if the code segment is complex enough that the optimizer fails to see the equivalency.
In this particular case, there should be no problem. Indeed, GCC compiles these segments to the same assembly.
struct P {
P* next;
};
P* func1(unsigned int k, P* p) {
k += 1;
while (--k) {
p = p->next;
}
return p;
}
P* func2(unsigned int k, P* p) {
while (k) {
p = p->next;
--k;
}
return p;
}
The assembly output is
func1(unsigned int, P*):
movq %rsi, %rax
testl %edi, %edi
je .L2
.L3:
movq (%rax), %rax
subl $1, %edi
jne .L3
.L2:
ret
func2(unsigned int, P*):
movq %rsi, %rax
testl %edi, %edi
je .L10
.L11:
movq (%rax), %rax
subl $1, %edi
jne .L11
.L10:
ret
Jump labels aside, the assembly for those functions is identical. You can view it in godbolt here.