paulo1205
(usa Ubuntu)
Enviado em 20/04/2019 - 06:39h
dalison escreveu:
Pode dizer quais os erros? Gostei da tua resposta.
Uma forma razoável de detectar erros é fazer com que o compilador detecte esses erros para você.
Veja o que aconteceu quando eu copiei seu código (arquivo “x.c”) e mandei compilá-lo com opções que habilitam diagnóstico de código:
$ gcc -Wall -Werror -O2 -pedantic-errors x.cx.c: In function ‘main’:
x.c:19:13: error: variable ‘pointer’ set but not used [-Werror=unused-but-set-variable]
celula *pointer;
^~~~~~~
x.c:28:9: error: ignoring return value of ‘scanf’, declared with attribute warn_unused_result [-Werror=unused-result]
scanf("%d", &tecla);
^~~~~~~~~~~~~~~~~~~
x.c:35:13: error: ignoring return value of ‘scanf’, declared with attribute warn_unused_result [-Werror=unused-result]
scanf("%d", &n1);
^~~~~~~~~~~~~~~~
x.c: In function ‘contaRecursiva’:
x.c:61:14: error: ‘numCel’ may be used uninitialized in this function [-Werror=maybe-uninitialized]
numCel++;
~~~~~~^~
cc1: all warnings being treated as errors
Antes de entrar nos erros em si, uma breve explicação das opções de compilação que eu usei com o GCC (se você usa outro compilador, pode ser que as opções sejam um pouco diferentes, mas possivelmente existem funcionalidades parecidas):
•
-Wall habilita todos (por isso o “all”) os alertas (
warnings , por isso o “W”) sobre situações duvidosas que o compilador conseguir reconhecer no código (situações duvidosas são aquelas em que, embora o código seja sintaticamente válido, ele pode ser semanticamente inválido, ou violar algumas cláusulas de uso esperado de certas variáveis ou funções).
•
-Werror faz com que o compilador trate cada alerta emitido como se fosse um erro (por isso o “error”), impedindo a compilação de prosseguir (sem essa opção, o compilador pode até emitir os alertas, mas como o código possui sintaxe válida, ele assume que o programador sabe o que está fazendo, e deixa a compilação prosseguir, mesmo com alertas).
•
-O2 não propriamente é uma opção de diagnóstico, mas sim de otimização, mas como ela força o compilador a seguir o fluxo de vida de cada variável, a fim de minimizar a quantidade de operações que a envolvem, ela acaba ajudando a detectar casos de variáveis desnecessárias ou de variáveis que podem ser usadas antes de terem seu valor inicial explicitamente definido.
•
-pedantic-errors obriga o compilador a seguir estritamente (ou de modo poderia ser considerado pedante, por isso o “pedantic”) o padrão que define a linguagem, emitindo alertas caso se use alguma extensão exclusiva do GCC, que fuja do padrão. Além disso, trata também esses alertas como se fossem erros (por isso o “error”), não deixando que a compilação prossiga.
O primeiro alerta-erro indica que você criou uma variável na linha 19, mas nunca a usou.
O segundo e o terceiro alertas-erros se referem ao fato de que você não verifica se a leitura com
scanf () foi bem sucedida antes de usar o valores que deveriam ter sido lidos. A forma de evitar tais alertas seria verificar o valor de retorno da função, que indica quantas das conversões especificadas na
string de formatação puderam ser realmente convertidas com sucesso e salvas nos ponteiros para os locais de destino. A forma segura de fazer tais leituras seria parecida com o seguinte (usando como exemplo a leitura na linha 28).
if(scanf("%d", &tecla)!=1){ // String tem uma conversão ("%d"), por isso a comparação com 1.
fprintf(stderr, "Erro de leitura. Abortando o programa.\n");
exit(1);
// Se abortar o programa for muito radical, você pode tentar outras abordagens,
// tentando ver o que causou o erro de leitura, e tentando repetir a operação, se
// a causa do erro não tiver sido grave. Por exemplo, se a leitura tiver falhado porque
// o usuário digitou caracteres não-numéricos, você pode tentar descartar tais carac-
// teres e fazer uma nova leitura “limpa”. Por outro lado, se a falha tiver sido causada
// por uma queda da conexão do usuário, não há como recuperar-se dessa situação,
// e abortar a execução é a melhor coisa a fazer.
}
O quarto alerta-erro indica justamente que a variável que você usou para fazer a contagem estava com um valor inicial indefinido. Desse modo, a operação de incrementar esse valor, que você faz ao percorrer cada nó da lista, pode ficar com um resultado que não representa nenhuma informação válida. [Eu não sei você está acostumado a usar alguma outra linguagem de programação que trate todos os valores iniciais de variáveis como se fossem iguais a zero (ou texto vazio, no caso de strings), mas esse não é o caso do C: o compilador só vai colocar alguma coisa na memória referente a variáveis não-estáticas se você explicitamente pedir para colocar tais valor lá.]
Note que o compilador não indicou o erro que eu apontei em minha primeira resposta, embora aquela fosse realmente a causa da falha de segmentação. Por mais espertos que sejam os compiladores modernos, eles são voltados para fazer análise sintática e léxica do código, a fim de traduzi-lo para linguagem de máquina. Análise semântica feita pelo compilador geralmente pega apenas casos mais triviais, e quase sempre apenas no entorno do código que está sendo sintaticamente analisado naquele momento. A falha de segmentação do seu programa era causada pelo esquecimento de uma inicialização dentro de
main (), mas acontecia na função
contaRecursiva (), com uma passagem de argumento como ponteiro. É quase impossível fazer com que o compilador consiga contexto suficiente para perceber um erro assim.
Felizmente, para isso, existem pessoas com um pouquinho mais de experiência. :)
Mas há, ainda, outros erros, além do apontado originalmente e daqueles que o compilador pode perceber. Abaixo eu listo alguns.
• Você chama
contaRecursiva () de um jeito tal que, no fim das contas, em lugar de imprimir apenas o valor final da contagem, todos os valores intermediários acabam impressos também.
• Se bem que, analisando melhor, que valores intermediários seriam esses? Você chama a função
contaRecusiva () recursivamente com nós sucessivos da lista, o que está certo, mas em nenhum lugar você passa a diante a informação de quantos nós já foram processados até o momento. Parece que você tentou fazer isso ao incrementar a variável local automática
numCel , mas isso não funciona porque cada invocação da função produz uma instância nova de
numCel , distinta das invocações anteriores (e como você esqueceu de lhe atribuir um valor inicial, cada uma dessas instâncias pode ter um valor inicial indefinido diferente dos valores indefinidos das instâncias anteriores).
• A conversão do valor de retorno de
malloc () é desnecessária em C (pois
malloc () retorna um valor do tipo
void * , que pode ser automaticamente convertido em qualquer outro tipo de ponteiro) e acaba atrapalhando a manutenção de código, de modo que é seguro removê-la. (Isso não chega a ser um erro mas, sendo uma redundância, é inútil, e é melhor eliminá-la.) (Tal conversão seria necessária em C++ (que não aceita a conversão automática a partir de
void * ), mas em C++ geralmente se evita o uso de
malloc (), dando-se preferência ao operador
new ou a uma classe de
containers para alocação dinâmica.)
• Ainda nessa mesma linha de código, você fez um acoplamento manual entre o ponteiro que está recebendo a alocação e o tipo de dado a ser usado como argumento de
sizeof , para determinar o tamanho da região a ser alocada. Eu recomendo que, em lugar de informar separadamente o ponteiro e seu tipo, você informe apenas o ponteiro, deixando a linha com o formato abaixo. (De novo, o jeito como você fez não está propriamente errado, mas construções semelhantes em programas grandes ficam mais propensas a erros cometidos por distração ou por eventuais transformações de tipo que os objetos venham a sofrer ao longo do ciclo de vida do programa.)
nova=malloc(sizeof *nova); // Deixo que o compilador infira o tipo de “*nova”, em vez de eu o especificar manualmente.
• A declaração de
main () foge do que o padrão do C prescreve. Você de usar “
int main(void) ” se não quiser receber argumentos do sistema operacional, ou “
int main(int argc, char **argv) ” se quiser recebê-los. O jeito como você fez significa, em C, que a função
main pode ser chamada com uma quantidade qualquer de argumentos de quaisquer tipos. (Não confunda C com C++ ou com Java. Em C++ e Java, “
int main() ” declara que a função não recebe argumentos. Em C, se você quiser que uma função seja proibida de receber argumentos ao ser invocada, você tem de colocar a palavra-chave
void , que significa “vazio”, na lista de parâmetros na hora de declarar a função.)
... “Principium sapientiae timor Domini, et scientia sanctorum prudentia.” (Proverbia 9:10)