Programa com resultado inexplicável pra mim [RESOLVIDO]

1. Programa com resultado inexplicável pra mim [RESOLVIDO]

Apprentice X
ApprenticeX

(usa Slackware)

Enviado em 18/11/2021 - 16:41h

O Programa abaixo espero resultado de 3 Registros!

Compilando assim: gcc test.c -o test
Ou Debugando: gcc -g test.c -o test
Ele informa 3 Registros, o que é correto

Compilando assim: gcc test.c -o test -O3 -Wall -pedantic -pedantic-errors -Werror
Ele resulta em 2 Registros porque? O que está mudando na forma acima de compilar?

#include <stdio.h>
struct {
char Name[10];
int Age;
} Contacts[] = { {"Name1", 1}, {"Name2", 2}, {"Name3", 3} };

int main() {
int QtdReg = 0;
for( ; Contacts[QtdReg].Name[0] != '\0'; QtdReg++);
printf("%d Registros\n", QtdReg);
}



  


2. MELHOR RESPOSTA

Paulo
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)

3. Re: Programa com resultado inexplicável pra mim

berghetti
berghetti

(usa Debian)

Enviado em 19/11/2021 - 13:01h

Provavelmente é um bug do GCC que não ocorre no CLANG,

mas seu código tem um erro ao acessar elementos fora do tamanho do array,
ao corrigir isso o comportamento é o esperado.

struct
{
char Name[10];
int Age;
} Contacts[] =
{ {"Name1", 1},
{"Name2", 2},
{"Name3", 3},
{ "\0", 0 } }; // marca fim do array



4. Re: Programa com resultado inexplicável pra mim [RESOLVIDO]

Samuel Leonardo
SamL

(usa XUbuntu)

Enviado em 19/11/2021 - 14:01h


ApprenticeX escreveu:

O Programa abaixo espero resultado de 3 Registros!

Compilando assim: gcc test.c -o test
Ou Debugando: gcc -g test.c -o test
Ele informa 3 Registros, o que é correto

Compilando assim: gcc test.c -o test -O3 -Wall -pedantic -pedantic-errors -Werror
Ele resulta em 2 Registros porque? O que está mudando na forma acima de compilar?

#include <stdio.h>
struct {
char Name[10];
int Age;
} Contacts[] = { {"Name1", 1}, {"Name2", 2}, {"Name3", 3} };

int main() {
int QtdReg = 0;
for( ; Contacts[QtdReg].Name[0] != '\0'; QtdReg++);
printf("%d Registros\n", QtdReg);
}

Esse seu for tá o mesmo que jogar na loteria, você tá contando muito com a sorte!
Deixa eu explicar num passo a passo:
1-o primeiro regsitro é pego e então o Name[0] é diferente de \0, incrementa o QtdReg = 1, tudo OK
2-o segundo regsitro é pego e então o Name[0] é diferente de \0, incrementa o QtdReg, = 2 tudo OK
3-o terceiro regsitro é pego e então o Name[0] é diferente de \0, incrementa o QtdReg, = 3 tudo OK
4-nesse último passo, é contar com a sorte, aqui é pra dar no mínimo falha de segmentação, já que o QtdReg será igual a 3, ou seja, acessando um registro inexistente no vetor de registros. E então o loop termina.
No passo 4 é contar com a sorte porque o QtdReg terá valor igual a 3 e então vai ser usado pra comparar na condição do for e por sorte, teve um byte nulo.
Talvez não deu falha de segmentação no passo 4 por conta de existir posições zeradas um pouco além do vetor, e daí não deu erro.
Pra evitar maiores problemas, você pode fazer assim:
#include <stdio.h>
struct {
char Name[10];
int Age;
} Contacts[] = { {"Name1", 1}, {"Name2", 2}, {"Name3", 3}, {"", -1} };
//observe acima o registro {"", -1}, um registro vazio no final do vetor

int main() {
int QtdReg = 0;
//agora compara o for de forma mais correta
for( ; Contacts[QtdReg].Name[0]; QtdReg++)
printf("name = %s\n", Contacts[QtdReg].Name);
printf("%d Registros\n", QtdReg);
}

A diferença do código acima pro seu anterior, é a garantia total que o QtdReg irá adicionar até o 3, já que na outra versão ele ia até o 2 talvez por conta do gcc colocar um byte nulo na posição 4 do passo 4 ali de cima da execução que escrevi.



5. Re: Programa com resultado inexplicável pra mim [RESOLVIDO]

Paulo
paulo1205

(usa Ubuntu)

Enviado em 21/11/2021 - 16:01h

Ou, se quiser saber de antemão a quantidade de elementos do array, use “sizeof Contacts/sizeof Contacts[0]” (tamanho total do array dividido pelo tamanho de cada elemento, já que todos os elementos têm o mesmo tamanho).


... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)


6. Re: Programa com resultado inexplicável pra mim

Apprentice X
ApprenticeX

(usa Slackware)

Enviado em 22/11/2021 - 08:27h

Obrigado berghetti, SamL, paulo1205 pelas explicações!

Acredito ter entendido o que estava de errado no meu Loop.
Entendi com as explicações de vocês que não é a declaração da Struct com valores que estava errada, e sim a FORMA como eu criei o Loop para varrê-la!
Entendi que pra Eu varrer da forma como fiz, eu teria que criar um registro para simular um término! O que de fato não seria na minha opinião elegante!

A resposta do paulo1205 me fez lembrar que li essa resposta em algum lugar aqui no VOL e anotei também em algum lugar, mas que acabei não achando! Porque eu já conhecia essa forma de calcular qts registros existem em uma Struct usando esse método do sizeof.

Essa minha dúvida surgiu porque esqueçi completamente o uso do sizeof e então tentei criar uma forma pra saber qts registros eu tinha, acabei por confundir ainda mais procurando um caracter terminador, que obviamente não existiria para a Struct neste caso, então claro que fiz um Loop com a proposta errada! O que obviamente nunca daria certo de fato, o que me fez ver melhor com as respostas do berghetti e SamL.

Quero agradecer aos 3 por todas as explicações, pq todas foram muito didáticas, tanto criando soluções para o Loop funcionar, como para me mostrar que o Loop não fazia sentido!

Precisei editar minha resposta porque eu achei onde anotei o sizeof
#include <stdio.h>
struct {
char Name[10];
int Age;
} Contacts[] = { {"Name1", 1}, {"Name2", 2}, {"Name3", 3} };

int main(void) {
printf("%ld Registros\n", sizeof Contacts/sizeof Contacts[0]);
}

Mas essa solução ela possue um problema! porque se eu declarar assim
} Contacts[20] = { {"Name1", 1}, {"Name2", 2}, {"Name3", 3} }; 
Vai me retornar 20 registros e não 3.
Ainda testando, pude perceber que o Loop funciona corretamente se a Struct possuir uma Qtd de Registros maior que a declarada inicialmente, claro que isso se deve ao fato dela já ter o 0 nos registros inexistentes
#include <stdio.h>
struct {
char Name[10];
int Age;
} Contacts[20] = { {"Name1", 1}, {"Name2", 2}, {"Name3", 3} };
//} Contacts[] = { {"Name1", 1}, {"Name2", 2}, {"Name3", 3} };

int main(void) {
printf("%ld Registros\n", sizeof Contacts/sizeof Contacts[0]); // Sempre me retorna o tamanho total da Struct

int QtdReg = 0;
for( ; Contacts[QtdReg].Name[0] != '\0'; QtdReg++);
printf("%d Registros\n", QtdReg); // RESULT 3 Correto
}

Existe alguma forma do sizeof saber qts registros a Struct tem em uso?
Ou Existe alguma forma do Loop funcionar identificando os Registros de forma correta no caso tendo apenas 3 registros e esse for o tamanho da Struct, sem eu ter que criar um 4o Registro?


7. Re: Programa com resultado inexplicável pra mim [RESOLVIDO]

Apprentice X
ApprenticeX

(usa Slackware)

Enviado em 22/11/2021 - 12:34h

Mas observando minha pergunta, eu gostaria de entender porque a forma de compilar muda o resultado? O que isso teria a ver?






Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts