Resultado diferente mudando parâmetro de compilação? [RESOLVIDO]

1. Resultado diferente mudando parâmetro de compilação? [RESOLVIDO]

Apprentice X
ApprenticeX

(usa FreeBSD)

Enviado em 29/01/2023 - 22:23h

Boa Noite a todos!

Porque esse comportamento estranho, apenas porque troquei os parâmetros para compilar?

Fui orientado a compilar assim: gcc test.c -o test -O3 -Wall -pedantic -pedantic-errors -Werror
Pq dessa forma eu conheceria todos os erros que eu preciso consertar e ainda estaria otimizando o programa! Resumo, seria a melhor forma e a mais correta de compilar um programa!

Acontece que é estranho o comportamento do código abaixo, e isso bagunça tudo!
#include <stdio.h>

int main(void) {
struct {
char Col1[5],
Col2[5],
Col3[5];
} Database1[] = {{"reg1", "a", "b"},
{"reg2", "c", "d"},
{"reg3", "e", "f"},
{"reg4", "g", "h"},
{"reg5", "i", "j"}};

int QtdReg = 0;

////////////////////////////////////////////////////////////////////////////////////////////////////
// Porque conta 5 registros compilando assim: gcc test.c -o test
// E 4 registros compilando assim: gcc test.c -o test -O3 -Wall -pedantic -pedantic-errors -Werror
////////////////////////////////////////////////////////////////////////////////////////////////////
for( ; Database1[QtdReg].Col1[0]; QtdReg++);
printf("%d registros\n", QtdReg);

QtdReg = 0;
for( ; Database1[QtdReg].Col1[0] != '\0'; QtdReg++);
printf("%d Registros\n", QtdReg);
////////////////////////////////////////////////////////////////////////////////////////////////////

QtdReg = 0;

////////////////////////////////////////////////////////////////////////////////////////////////////
// Pq imprimindo na tela ele conta corretamente, independente da forma como compilo?
////////////////////////////////////////////////////////////////////////////////////////////////////
for( ; Database1[QtdReg].Col1[0]; puts(Database1[QtdReg].Col1), QtdReg++);
printf("%d Registros\n", QtdReg);

QtdReg = 0;
for( ; Database1[QtdReg].Col1[0] != '\0'; puts(Database1[QtdReg].Col1), QtdReg++);
printf("%d Registros\n", QtdReg);
}

Existe explicação para isso?


  


2. MELHOR RESPOSTA

Paulo
paulo1205

(usa Ubuntu)

Enviado em 30/01/2023 - 12:04h

ApprenticeX escreveu:

Boa Noite a todos!

Porque esse comportamento estranho, apenas porque troquei os parâmetros para compilar?


Não sei se você percebeu, mas seu programa está errado. Já mostro onde, mas já adianto que, na minha máquina, com a otimização ligada, a versão sem impressão de cada registro “contou” 4 registros, mas a versão com impressão gerou lixo na tela e “contou” 7 registros. Ambas erradas, portanto, e com diferenças em relação aos comentários que você colocou no código.

Se quiser, antes de continuar lendo, use essa informação e tente achar o erro no seu programa. Depois você continua lendo e confere se sua conclusão bate com o que eu vou dizer.

Fui orientado a compilar assim: gcc test.c -o test -O3 -Wall -pedantic -pedantic-errors -Werror
Pq dessa forma eu conheceria todos os erros que eu preciso consertar e ainda estaria otimizando o programa! Resumo, seria a melhor forma e a mais correta de compilar um programa!


Possivelmente fui eu quem o orientou nesse sentido (ou parecido com isso, pois eu raramente uso -O3, geralmente uso apenas -O2). No entanto, eu mudei minha recomendação de alguns meses para cá, porque me dei conta que -Wall, ao contrário do que o nome sugere, não produz todos os possíveis diagnósticos. Agora eu venho usando e recomendando -Wextra.

Infelizmente, no entanto, mesmo -Wextra no lugar de -Wall não muda nada com relação ao fato de o compilador deixar de detectar o erro no seu programa.

Acontece que é estranho o comportamento do código abaixo, e isso bagunça tudo!
#include <stdio.h>

int main(void) {
struct {
char Col1[5],
Col2[5],
Col3[5];
} Database1[] = {{"reg1", "a", "b"},
{"reg2", "c", "d"},
{"reg3", "e", "f"},
{"reg4", "g", "h"},
{"reg5", "i", "j"}};

int QtdReg = 0;

////////////////////////////////////////////////////////////////////////////////////////////////////
// Porque conta 5 registros compilando assim: gcc test.c -o test
// E 4 registros compilando assim: gcc test.c -o test -O3 -Wall -pedantic -pedantic-errors -Werror
////////////////////////////////////////////////////////////////////////////////////////////////////
for( ; Database1[QtdReg].Col1[0]; QtdReg++);


A lógica aqui está errada pois você não indica, entre os elementos do array Database1, nenhum elemento que contenha um byte nulo na primeira posição do campo Col1, e portanto não há como garantir que a condição de parada desse suposto contador da quantidade de elementos será atingida em algum momento.

Se o programa chegou a funcionar em tempo finito e sem produzir uma falha de segmentação, foi por AZAR (não por sorte, porque eu considero que algo que deixa um bug importante sem diagnóstico claro é algo negativo, não positivo). O azar só não foi maior porque, com a otimização ligada, você conseguiu perceber que a contagem estava diferente do que você gostaria, e porque calhou de o laço de repetição parar antes do que você achava que deveria.


O que explica essa parada antes do ponto é o fato de que, ao acionar a otimização máxima (opção -O3), você habilitou a otimização agressiva de laços de repetição, que leva o compilador a expandir as iterações do laço como se não houvesse laço nenhum, mas sim uma sequência linear de instruções que supostamente produziriam o mesmo efeito final.

Entretanto, como parte dessa otimização agressiva, o compilador assume que sua condição de parada será satisfeita em algum momento, e usa suas heurísticas de otimização tomando essa assunção como verdadeira. No seu caso, ela não o é, então a heurística de otimização falha fragorosamente, “contando” menos do que você gostaria.

   printf("%d registros\n", QtdReg);

QtdReg = 0;
for( ; Database1[QtdReg].Col1[0] != '\0'; QtdReg++);
printf("%d Registros\n", QtdReg);
////////////////////////////////////////////////////////////////////////////////////////////////////

QtdReg = 0;

////////////////////////////////////////////////////////////////////////////////////////////////////
// Pq imprimindo na tela ele conta corretamente, independente da forma como compilo?
////////////////////////////////////////////////////////////////////////////////////////////////////
for( ; Database1[QtdReg].Col1[0]; puts(Database1[QtdReg].Col1), QtdReg++);
printf("%d Registros\n", QtdReg);


Por AZAR (de novo, hehehe...)!

A lógica de detecção de condição de parada continua errada para o conjunto de dados em questão, mas o fato de haver uma chamada de função envolvendo parte dos dados usados na avaliação da condição de parada foi suficiente, na sua máquina e com certas condições de execução do momento em que você executou, para provocar a coincidência de parar no lugar que você gostaria.

No meu micro, com a minha versão de compilador e com meu SO e na manhã de hoje, o resultado final foi de 7 registros, sendo que a tentativa de imprimir o 6º e o 7º registros produziu caracteres espúrios no meu terminal.


QtdReg = 0;
for( ; Database1[QtdReg].Col1[0] != '\0'; puts(Database1[QtdReg].Col1), QtdReg++);
printf("%d Registros\n", QtdReg);
}


Existe explicação para isso?


Como se viu acima, trata-se de bug no seu código. Em todos os laços de repetição, a condição de parada assume a presença de bytes nulos para os quais não há qualquer provisão explícita na declaração e definição de Database1.

Que tenha funcionado sem a otimização ligada é outro exemplo de puro azar. Provavelmente o que aconteceu nesse caso é que o compilador deixou um espaço de padding após o final do array, e esse espaço foi casualmente preenchido com zeros, o que permitiu aos laços de repetição encontrar um byte nulo onde você gostaria que ele estivesse. Com otimização agressiva ligada, que inclui otimização de espaço ocupado, no entanto, não houve padding, e não havia byte nulo na posição correspondente ao sexto elemento elemento do array.

A forma de corrigir totalmente o programa sem mudar as lógicas de parada dos laços de repetição é incluir no array um elemento que explicitamente caracterize o “fim dos dados válidos”. Por exemplo:
struct {
char Col1[5],
Col2[5],
Col3[5];
} Database1[] = {{"reg1", "a", "b"},
{"reg2", "c", "d"},
{"reg3", "e", "f"},
{"reg4", "g", "h"},
{"reg5", "i", "j"},
{"", "", ""}};


Fazendo desse modo funciona com otimização ligada ou não, imprimindo ou sem imprimir.

Entretanto, a verdade é que agora o array tem seis elementos, em vez de cinco, e o sexto elemento funciona somente como marcador de fim.


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

3. Re: Resultado diferente mudando parâmetro de compilação? [RESOLVIDO]

Apprentice X
ApprenticeX

(usa FreeBSD)

Enviado em 29/01/2023 - 22:26h

alexabolada escreveu: ao adicionar os parâmetros avançados, como -O3 -Wall -pedantic -pedantic-errors -Werror, você está avisando ao compilador para realizar todos os testes possíveis, e nesse caso, foi detectado um erro, como por exemplo, que você está declarando \0, quando na verdade pode ser somente 0, assim, o compilador encerra a contabilização e traz um resultado diferente.

Esse erro de sintaxe deve ser corrigido, e tudo volta ao normal, então com os parâmetros avançados, o programa consegue verificar possíveis erros e otimizações, e assim, executar melhor o seu código.

O Compilador não informa nenhum erro de sintaxe! Não pede pra que nada seja corrigido!




4. Re: Resultado diferente mudando parâmetro de compilação?

Paulo
paulo1205

(usa Ubuntu)

Enviado em 30/01/2023 - 11:02h

Mais uma vez essa AlexaBolada falando pura porcaria sem qualquer nexo com a realidade ou com a pergunta feita pelo autor do tópico. Excluí, como avisei que o faria.

ApprenticeX, por acaso você deixou marcada a opção que habilita respostas automáticas? Foi só pela curiosidade de ver que tipo de resposta viria?


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


5. Re: Resultado diferente mudando parâmetro de compilação? [RESOLVIDO]

Apprentice X
ApprenticeX

(usa FreeBSD)

Enviado em 30/01/2023 - 15:04h

paulo1205 escreveu:ApprenticeX, por acaso você deixou marcada a opção que habilita respostas automáticas? Foi só pela curiosidade de ver que tipo de resposta viria?

Sim, eu sempre deixo marcado! Por curiosidade em ver as respostas, e sim, ela não entendeu o contexto neste caso!


6. Re: Resultado diferente mudando parâmetro de compilação? [RESOLVIDO]

aguamole
aguamole

(usa KUbuntu)

Enviado em 30/01/2023 - 15:06h

@paulo1205 a otimização máxima é o parâmetro -Ofast com o -march=native.
O -Ofast tem todas as otimizações do -O3 e e ainda adiciona outras otimizações.


7. Re: Resultado diferente mudando parâmetro de compilação? [RESOLVIDO]

Apprentice X
ApprenticeX

(usa FreeBSD)

Enviado em 30/01/2023 - 15:16h

paulo1205 escreveu: Possivelmente fui eu quem o orientou nesse sentido (ou parecido com isso.
Sim no fórum ao longo de respostas suas tanto para mim qto para outras pessoas, aprendi a importância dos Warnings do compilador, tanto que me esforçei para vencê-los sempre, ou seja, não ignoro os warnings, procuro resolvê-los, também sei como desabilitá-los qdo for apenas para ver um resultado.

eu raramente uso -O3, geralmente uso apenas -O2)
Não sei se achei melhor usar o -03 em relação ao -02, não lembro o motivo agora, mas vc recomenda o 3 ou o 2?

No entanto, eu mudei minha recomendação de alguns meses para cá, porque me dei conta que -Wall, ao contrário do que o nome sugere, não produz todos os possíveis diagnósticos. Agora eu venho usando e recomendando -Wextra. Infelizmente, no entanto, mesmo -Wextra no lugar de -Wall não muda nada com relação ao fato de o compilador deixar de detectar o erro no seu programa.
Gostei de saber, vou usar também e re-examinar mais a documentação!

A lógica aqui está errada pois você não indica, entre os elementos do array Database1, nenhum elemento que contenha um byte nulo na primeira posição do campo Col1, e portanto não há como garantir que a condição de parada desse suposto contador da quantidade de elementos será atingida em algum momento.
Eu sempre meio que soube disso, pois já pensei que estaria faltando de fato um \0 ali. Não sei porque me perdi, entre o funcionar em determinadas, situações, talvez eu tenha imaginado que ele encontrava um final de alguma forma, mesmo isso meio que sendo ilógico!

A forma de corrigir totalmente o programa sem mudar as lógicas de parada dos laços de repetição é incluir no array um elemento que explicitamente caracterize o “fim dos dados válidos”.
{"", "", ""}}; 
Isso eu meio que já sabia, creio que tenha sido mais como uma confusão mental imaginativa de por um dado momento acreditar em algo que de fato nunca esteve lá!

De qualquer forma obrigado, por redirecionar novamente para o caminho lógico, pq meio que sai da estrada nessa!



8. Re: Resultado diferente mudando parâmetro de compilação? [RESOLVIDO]

Apprentice X
ApprenticeX

(usa FreeBSD)

Enviado em 30/01/2023 - 15:17h

aguamole escreveu: @paulo1205 a otimização máxima é o parâmetro -Ofast com o -march=native.
O -Ofast tem todas as otimizações do -O3 e e ainda adiciona outras otimizações.
Bom saber disso também, agrega conhecimento!




9. Re: Resultado diferente mudando parâmetro de compilação? [RESOLVIDO]

Heitor Costa
Heitor.rj

(usa Slackware)

Enviado em 30/01/2023 - 19:38h

A pergunta já foi respondida, mas para enriquecer o post sobre as opções de compilação!
Saber como compilar seu programa é uma etapa importante do aprendizado, exatamente como enfatiza o Paulo sobre a importância de ativar os warnings para auxiliar a verificação do seu código!

O gcc pode otimizar o seu programa de modo a aumentar seu desempenho e/ou diminuir o tamanho do código de máquina gerado. Por omissão, o gcc não realiza nenhuma otimização. Existem vários níveis de otimização: 1, 2, 3... Quanto maior o nível, maior deve ser a melhora no desempenho; mas também deve ser maior o tempo de compilação.

Para ativar a otimização, use as opções -O1, -O2 ou -O3 (É letra O, não número zero). Também existe a opção -Os, que realiza as mesmas otimizações de -O2, excetuando as que costumam aumentar o tamanho do executável gerado.

-O2 = Ativa otimização no nível 2, informa variáveis usadas sem inicialização, declaradas ou inicializadas mas nunca usadas, e código que nunca será executado, entre outros problemas
-Ofast = Faz tudo da -O3 e um pouco mais

  OPTION  | OPTIMIZATION LEVEL                    | EXECUTION TIME | CODE SIZE | MEMORY USAGE | COMPILE TIME
-O0 | for compilation time (default) | + | + | - | -
-O1 or -O | for code size and execution time | - | - | + | +
-O2 | more for code size and execution time | -- | | + | ++
-O3 | more for code size and execution time | --- | | + | +++
-Os | for code size | | -- | | ++
-Ofast | O3 with fast none accurate | --- | | + | +++
| math calculations
+increase ++increase more +++increase even more -reduce --reduce more ---reduce even more

Mais informações nos links abaixo
https://gcc.gnu.org/onlinedocs/gcc-9.3.0/gcc/Optimize-Options.html#Optimize-Options
https://gcc.gnu.org/onlinedocs/gcc-9.3.0/gcc/Warning-Options.html#Warning-Options
https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html






Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts