Menu

Erros comuns em C: os deslizes de iniciante e como corrigi-los

Os erros que todo programador C comete pelo menos uma vez - ponto e vírgula faltando, = no lugar de ==, declaração implícita, scanf sem &, comparar strings com ==, divisão inteira, variáveis não inicializadas e deslocamento por um - cada um como sintoma, causa e solução.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Todo programador C encontra a mesma listinha de erros, geralmente na primeira semana e ocasionalmente uma década depois. O que os torna dignos de catálogo é que a maioria produz mensagens de erro que não os descrevem - ou, pior, nenhuma mensagem.

Cada entrada abaixo é um sintoma, a causa por trás dele e a solução.

Ponto e vírgula faltando e a cascata

Sintoma: uma parede de erros, todos reportados em linhas que parecem corretas.

program.c:6:5: error: expected ';' before 'printf'
program.c:7:5: error: expected declaration specifiers before 'return'

Causa: C termina instruções com ;. Deixe um de fora e o compilador cola a próxima linha na atual, e depois reporta a confusão no ponto em que o texto combinado deixa de fazer sentido - tipicamente na linha seguinte.

int main(void) {
    int x = 5      /* ponto e vírgula faltando */
    printf("%d\n", x);
    return 0;
}

Solução: quando aparecer um monte de erros, corrija só o primeiro e recompile. Tudo depois dele pode ser consequência. E confira sempre a linha anterior à que o compilador aponta.

A mesma cascata vem de uma chave não fechada ou de um comentário /* não encerrado, casos em que os erros podem cair dezenas de linhas adiante.

Uma observação extra: nada de ponto e vírgula depois do cabeçalho de um if, for ou while, nem depois da chave de fechamento da definição de uma função. if (x > 0); compila normalmente e não faz nada - o ; é o corpo inteiro.

Atribuição no lugar de comparação

Sintoma: uma condição sempre verdadeira, ou uma variável que muda misteriosamente.

int x = 5;
if (x = 10) {              /* atribui 10 e depois testa 10 -> verdadeiro */
    printf("x é dez\n");   /* imprime sempre; x agora é 10 */
}

Causa: = atribui, == compara. O valor da atribuição é o valor atribuído, então if (x = 10) testa 10, que é diferente de zero, o que é verdadeiro. O compilador aceita porque de vez em quando é isso que as pessoas querem.

Solução: use == em toda condição e ligue o -Wall para o compilador avisar:

warning: suggest parentheses around assignment used as truth value

Se você realmente quer uma atribuição dentro de uma condição - comum em while ((c = getchar()) != EOF) - os parênteses extras dizem isso e silenciam o aviso.

Declaração implícita de uma função

Sintoma:

warning: implicit declaration of function 'printf'
warning: implicit declaration of function 'malloc'

...seguido às vezes de resultados estranhos em tempo de execução ou de um erro de ligação.

Causa: o compilador encontrou uma chamada a uma função para a qual não tem declaração. Em dialetos anteriores ao C99, ele supunha que a função retornava int e seguia em frente; em sistemas de 64 bits essa suposição trunca um ponteiro retornado para 32 bits, que é como um <stdlib.h> esquecido transforma o malloc num travamento.

Solução: inclua o cabeçalho certo.

FunçãoCabeçalho
printf, scanf, fopen<stdio.h>
malloc, free, atoi, rand, exit<stdlib.h>
strlen, strcpy, strcmp<string.h>
sqrt, pow, fabs<math.h>
isdigit, toupper<ctype.h>
time<time.h>

Para funções suas, o mesmo aviso significa que você chamou uma antes de defini-la. Coloque um protótipo acima de main:

scanf sem o &

Sintoma: o programa trava na entrada, ou não lê nada e deixa a variável intacta.

int age;
scanf("%d", age);     /* falta o & : passa o valor, não o endereço */

Causa: scanf escreve dentro da sua variável, então precisa do endereço dela. Passar age entrega o lixo que estava lá dentro, e o scanf trata isso como um lugar para escrever, o que normalmente é uma falha de segmentação.

Solução: & antes da variável - para todo tipo, exceto um array, que já é um endereço:

Duas armadilhas vizinhas da mesma família: use %lf para um double no scanf (%f ali significa float, e escrever o tanto de bytes de um float dentro de um double o deixa errado) e sempre limite um %s com uma largura - %49s para um buffer de 50 bytes - ou uma palavra longa o estoura.

Melhor ainda: leia uma linha inteira com fgets e a analise, o que não pode transbordar e não deixa entrada perdida no buffer. Mais sobre os dois em scanf.

Comparar strings com ==

Sintoma: duas strings que claramente coincidem são comparadas como diferentes.

char a[] = "hello";
char b[] = "hello";

if (a == b) {              /* comparando dois endereços: falso */
    printf("iguais\n");
}

Causa: uma string em C não é um valor, é um ponteiro para o primeiro caractere. == compara os ponteiros. Dois arrays com o mesmo texto vivem em endereços diferentes, então o teste é falso. (De forma confusa, comparar dois literais idênticos às vezes dá verdadeiro, porque o compilador pode guardar só uma cópia - o que torna o bug intermitente.)

Solução: strcmp, e lembre que ele retorna 0 para igual:

A leitura invertida também pega gente: if (strcmp(a, b)) é verdadeiro quando as strings diferem, já que um resultado diferente de zero significa "não são iguais". Escreva sempre o == 0 explicitamente.

Divisão inteira

Sintoma: uma média de 0, uma porcentagem que é sempre 0 ou 100, uma razão que perdeu a fração.

int correct = 7, total = 10;
double score = correct / total;      /* 0.0, e não 0.7 */

Causa: os dois operandos são int, então C faz divisão inteira e trunca antes de o resultado ser atribuído a um double. 7 / 10 é 0; converter 0 para double é 0.0.

Solução: torne um dos operandos de ponto flutuante antes da divisão:

Converter um operando promove o outro automaticamente. Converter o resultado é tarde demais - o truncamento já aconteceu. Veja conversão de tipos.

A mesma armadilha se esconde em expressões como (a + b) / 2 para um ponto médio e 1 / 2 * x, que é sempre 0 independentemente de x.

return ausente ou errado

Sintoma: uma função retorna um número errado com cara de plausível, diferente a cada execução ou a cada compilação.

int add(int a, int b) {
    int sum = a + b;
    /* nenhuma instrução return */
}

Causa: chegar ao fim de uma função não-void sem retornar dá um valor não especificado - na prática, o que por acaso estivesse no registrador de retorno. É comportamento indefinido se quem chamou usar esse valor.

A forma mais sorrateira retorna em alguns caminhos e em outros não:

int classify(int n) {
    if (n > 0) return 1;
    if (n < 0) return -1;
    /* n == 0 escorrega até o fim */
}

Solução: retorne em todo caminho e compile com -Wall - o "control reaches end of non-void function" do GCC pega as duas versões.

Variáveis não inicializadas

Sintoma: saída com lixo, ou resultados que mudam entre execuções e entre níveis de otimização.

int total;                       /* contém o que estava na pilha */
for (int i = 1; i <= 5; i++) {
    total += i;                  /* somando em cima de lixo */
}
printf("%d\n", total);           /* algum número enorme */

Causa: variáveis locais não são zeradas. Uma variável global ou static é posta em zero automaticamente; uma local começa com os bytes que já estavam naquele endereço da pilha.

Solução: inicialize no ponto da declaração. Não custa nada e elimina a classe inteira de bug:

-Wall -Wextra avisa sobre muitos desses casos ("may be used uninitialized"), e -fsanitize=memory ou o valgrind pegam o resto. Esse é especialmente cruel porque um ponteiro não inicializado leva direto a uma falha de segmentação.

Deslocamento por um (off-by-one)

Sintoma: o último item é perdido, ou um elemento a mais é tocado e o programa se comporta mal depois.

int arr[5];
for (int i = 0; i <= 5; i++) {   /* toca arr[5], que não existe */
    arr[i] = i;
}

Causa: um array de n elementos tem índices de 0 a n - 1. <= executa uma passada a mais.

Solução: o padrão i < n, e calcule n a partir do array em vez de escrever o número duas vezes:

A versão com strings - esquecer espaço para o '\0' - é o mesmo erro com outra roupa, e char word[5] com "hello" dentro é um estouro de buffer.

Dois menores que vale conhecer

Ponto e vírgula depois do cabeçalho de um laço. for (int i = 0; i < 10; i++); seguido de um bloco entre chaves executa o laço dez vezes sem fazer nada e depois executa o bloco uma vez. Compila sem reclamar.

sizeof num ponteiro. Dentro de uma função, um parâmetro de array é um ponteiro, então sizeof(arr) é o tamanho do ponteiro (8 bytes), não o do array. Passe o comprimento como um argumento separado:

O hábito que evita a maior parte disso

Compile com os avisos ligados, desde o primeiro programa:

gcc -Wall -Wextra -g program.c -o program

-Wall -Wextra pega a atribuição dentro de condição, o return faltando, a leitura de variável não inicializada, a variável não usada e o especificador do printf que não bate com o argumento. Acrescentar -fsanitize=address,undefined durante o desenvolvimento pega quase todo o resto no momento em que acontece.

Trate todo aviso como um erro que você ainda não encontrou. Um programa em C que compila sem avisos não tem correção garantida - mas quase todo programa em C que trava estava avisando sobre alguma coisa antes.

Perguntas frequentes

O que significa 'implicit declaration of function' em C?

O compilador encontrou uma chamada a uma função que ele nunca viu declarada. Quase sempre você esqueceu um #include - printf precisa de <stdio.h>, malloc precisa de <stdlib.h>, strlen precisa de <string.h>. Também pode significar que você chamou uma função sua antes de defini-la, o que um protótipo acima de main resolve.

Por que meu programa em C acusa erro numa linha que parece correta?

Normalmente porque o erro de verdade está na linha anterior. Um ponto e vírgula faltando, uma chave não fechada ou um comentário não encerrado fazem o compilador ler suas duas linhas como uma só, então ele reporta a confusão onde o texto finalmente deixa de fazer sentido. Confira sempre a linha acima da que foi apontada.

Por que não posso comparar strings com == em C?

Porque uma string em C é um char * - um ponteiro - então == compara dois endereços, não os caracteres para os quais eles apontam. Duas strings idênticas guardadas em lugares diferentes dão false. Use strcmp(a, b) == 0, de <string.h>, que retorna 0 quando o conteúdo coincide.

Por que 5 / 2 dá 2 em C?

Porque os dois operandos são inteiros, então C faz divisão inteira e descarta a fração. Torne um dos lados um valor de ponto flutuante para obter 2.5: 5.0 / 2, ou (double)a / b quando estiver trabalhando com variáveis. Converter o resultado é tarde demais - (double)(5 / 2) é 2.0.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR