paulo1205
(usa Ubuntu)
Enviado em 23/11/2021 - 00:43h
A rigor, dado que o programa original tinha um erro de construção, os dois resultados são errados, e a aparência de que o primeiro deles funcionou foi um
azar (alguns diriam que foi sorte, mas eu prefiro dizer que um
bug que se manifesta logo é sempre melhor do que outro que fica latente, como que à espera do pior momento para finalmente vir à luz).
Mas o que explica a diferença de comportamento é a otimização, mesmo. Se você usar as opções que geram o código
assembly correspondente ao programa em C (e.g.
-c -g -S -fverbose-asm ) e examinar o código gerado, possivelmente verá, em comentários perto do topo do programa, que o compilador usou, por implicação de ter habilitado a otimização, a diretiva de compilação
-faggressive-loop-optimization . E veja que interessante a descrição dessa diretiva.
This option tells the loop optimizer to use language constraints to derive bounds for the number of iterations of a loop. This assumes that loop code does not invoke undefined behavior by for example causing signed integer overflows or out-of-bound array accesses . The bounds for the number of iterations of a loop are used to guide loop unrolling and peeling and loop exit test optimizations. This option is enabled by default.
Ou seja, o compilador tenta otimizar laços de repetição por meio de uma estimativa do número de iterações, e, para isso, assume que o programa não vai exceder os limites do
array , o que não era verdadeiro no caso do seu programa na postagem original.
No seu caso, ele vê que o número de elementos do
array é 3, e infere que você testa a condição de parada quando chega ao último elemento do
array , que é o terceiro elemento. Logo, se o
loop para quando vai testar o terceiro elemento, então ele só executou duas iterações completas. E como o número de iterações é justamente o que o
loop está calculando e guardando em
QtdReg , ele substitui todo o laço de repetição por uma atribuição do valor
2 a tal variável. De fato, é isso que acontece no código
assembly abaixo, correspondente ao
loop (compilando com otimização na minha máquina).
# p.c:9: for( ; Contacts[QtdReg].Name[0] != '\0'; QtdReg++); // Linha do código fonte original em C.
.loc 1 9 0
cmpb $0, Contacts(%rip) #, Contacts[0].Name // Como colher de chá, até vê se o loop já termina antes da primeira iteração (testando se 0==Contacts[0].Name[0]).
je .L2 #, // Se for igual, desvia para o ponto após o loop (mas não é o caso).
.LVL1:
# // “Dentro” do loop.
cmpb $1, 16+Contacts(%rip) #, Contacts[1].Name // Testa valor de Contacts[1].Name[0]-1 (se for negativo faz CF=1; caso contrário, CF=0).
sbbl %edx, %edx # QtdReg // Faz QtdReg-=(QtdReg+CF) (ou seja: se CF==0, QtdReg=0; se não, QtdReg=-1).
addl $2, %edx #, QtdReg // Faz QtdReg+=2.
.LVL2:
# // Fim do loop (note que não houve nenhum desvio desde que entrou no loop).
.L2:
Conclusão: compilar com otimização ligada habilitou uma otimização agressiva de laços de repetição, que assumia que seu programa não continha um determinado tipo de erro que, na verdade, o programa realmente continha.
Um bom exemplo de por que eu costumo dizer que
bug que fica latente durante parte do desenvolvimento faz o programa funcionar por
azar , e não por sorte.
... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)