Erros comuns em C

Essa página é uma coleção de erros comuns (com exemplos de código) que ocorrem quando estamos aprendendo a programar em C. A página foi feita em conjunto com Tales Lelo da Aparecida.

1. Variáveis, Atribuições e Estrutura Básica de um Programa

A. Armazenar um valor muito grande em um int

#include <stdio.h>

int main() {
    int n = 4000000000;
    printf("%d\n", n); /* Imprime -294967296. */
    return 0;
}

Neste caso, podemos usar o long:

#include <stdio.h>

int main() {
  long n = 4000000000;
  printf("%ld\n", n);
  return 0;
}

B. Não declarar uma variável antes de usá-la

#include <stdio.h>

int main() {
  printf("%d\n", a);
  int a;
  return 0;
}

Neste caso, ocorre um erro de compilação:

exemplo1.c: In function 'main':  
exemplo1.c:4:18: error: 'a' undeclared (first use in this function)  
   printf("%d\n", a);  
                  ^  
exemplo1.c:4:18: note: each undeclared identifier is reported only once for each function it appears in

2. Escrita, Leitura e Operações Aritméticas

A. Imprimir uma informação usando printf com o tipo errado

Exemplo:

#include <stdio.h>

int main() {
  printf("%d\n", 10.0); /*Imprime 1490196704*/
  return 0;
}

Esse programa gera exibe a seguinte mensagem ao ser compilado com a opção -Wall:

main.c: In function'main':                                                        
main.c:4:14: warning: format '%d' expects argument of type 'int', but argument 2 has type 'double' [-Wformat=]
     printf("%d\n", 10.0);

Solução 1: imprimir como float:

#include <stdio.h>

int main() {
  printf("%f\n", 10.0); /* Imprime 10.000000 */
  return 0;
}

Solução 2: informar ao C que ele precisa converter o tipo

#include <stdio.h>

int main() {
  printf("%d\n", (int)10.0); /* Imprime 10 */
  return 0;
}

B. Imprimir dois números na sequência sem dar um espaço ou pular uma linha

Exemplo:

#include <stdio.h>

int main() {
  printf("%d", 4);
  printf("%d", 2);
  return 0;
}

Esse programa imprime:
42

Solução: adicionar um \n para quebrar a linha ou espaço em branco.

#include <stdio.h>

int main() {
  printf("%d\n", 4);
  printf("%d", 2);
  return 0;
}

Esse programa imprime:
4
2

C. Esquecer de colocar um & antes do nome da variável no scanf

#include <stdio.h>

int main() {
  int a;
  scanf("%d", a);
  printf("%d", a);
  return 0;
}

Neste caso, o programa compila, mas quando o executamos, o erro segmentation fault ocorre.

D. Usar o valor de uma variável sem antes ter definido esse valor no código ou à partir do scanf

Exemplo:

#include <stdio.h>

int main() {
  int a;
  printf("%d", a); /* valor de 'a' não está definido, imprime qualquer número */
  return 0;
}

E. Colocar uma expressão do lado esquerdo do sinal de =

Devido ao hábito criado com a sintaxe matemática, quando se inicia a programar pode surgir esta confusão. O operador = é utilizado para definir um novo valor a uma variável. A linguagem não é capaz de resolver cálculos algébricos com dois lados de uma igualdade, isto é, o exemplo abaixo é inválido:

#include <stdio.h>

int main() {
  int y;
  4*y - 2 = 3; // Esta linha gera o erro de compilação: "error: lvalue required as left operand of assignment"
  printf("Valor de y: %d", y);

  return 0;
}

Assim, para não sofrer com este problema basta organizar o código de modo que todo cálculo fique do lado direito de =, resultando, no exemplo, em y = (3+2)/4

3. Expressões Relacionais, Lógicas e Comandos Condicionais

A. Usar expressões do tipo a <= x <= b

Exemplo:

#include <stdio.h>

int main() {
  int num;
  scanf("%d", &num);
  if(5 <= num <= 15) {
    printf("ok\n");
  }
  return 0;
}

Esse programa apresenta os seguintes warnings quando compilamos:

exemplo.c: In function 'main':
exemplo.c:6:17: warning: comparison of constant '15' with boolean expression is always true [-Wbool-compare]
   if(5 <= num <= 15) {
                 ^
exemplo.c:6:8: warning: comparisons like 'X<=Y<=Z' do not have their mathematical meaning [-Wparentheses]
   if(5 <= num <= 15) {
        ^

Ou seja, nós somos avisados que comparações deste tipo não têm o mesmo significado da matemática.

Se rodarmos esse programa, ok será impresso independentemente do valor digitado. Isso porque o C avalia a expressão 5 <= num <= 15 como (5 <= num) <= 15 e a expressão 5 <= num vale 0 ou 1, dependendo do valor da variável num. Assim, a expressão (5 <= num) <= 15 é sempre verdadeira, já que tanto 0 quanto 1 são menores ou iguais a 15.

B. Confusão entre = e ==

Em C temos, assim como muitas linguagens, temos símbolos que são reaproveitados, devido ao número limitado de caracteres que o teclado nos proporciona. Assim, possuímos duas utilidades para o =: uma na atribuição, como em x = 2; e uma na comparação, como (x == 2).

É importante entender a diferença entre esses dois usos, pois ao confundí-los pode ocorrer erros como:

#include <stdio.h>
  
int main() {
  int x, y;
  scanf("%d %d", &x, &y);
  if (x = 2) /* Atribui 2 a variavel x, entrando no if, pois 2 e um valor diferente de zero */
    printf("Voce digitou 2.");
    
  y == 4; /* Compara y com 4 e descarta o resultado, ja que nao e usado dentro de um if ou while, nem guardado */

  return 0;
}

Este programa gera a seguinte saída, quando compilado com -Wall:

main.c:6:2: warning: suggest parentheses around assignment used as truth value [-Wparentheses] 
if (x = 2) /* Atribui 2 a variavel x, entrando no if, pois 2 e um valor diferente de zero */
      ^
main.c:9:2: warning: statement with no effect [-Wunused-value]
y == 4; /* Compara y com 4 e descarta o resultado, ja que nao e usado dentro de um if ou while, nem guardado */
  ^

Para não enfrentar tais problemas basta utilizar o operador = para atribuições e o == para comparações.

4. Comandos Condicionais

A. Não usar chaves no comando if quando temos mais de uma linha a ser executada se a condição for verdadeira:

#include <stdio.h>

int main() {
  int n;
  scanf("%d", &n);
  if(n < 10 && n % 2 == 0) /*testa se é menor que 10 e é par*/
    printf("O número é menor que 10\n");
    printf("O número é par\n");
  return 0;
}

Quando digitamos 8, a saída é:

O número é menor que 10
O número é par

Mas quando digitamos 13, a saída é:

O número é par

Solução: Usar as chaves corretamente, já que não basta a identação.

#include <stdio.h>

int main() {
  int n;
  scanf("%d", &n);
  if(n < 10 && n % 2 == 0) { /*testa se é menor que 10 e é par*/
    printf("O número é menor que 10\n");
    printf("O número é par\n");
  }
  return 0;
}

B. Não agrupar ifs e elses corretamente com as chaves (mesmo quando a identação está correta):

#include <stdio.h>

int main() {
  int n;
  scanf("%d", &n);
  if(n % 2 == 0)
    if(n % 4 == 0)
      printf("O número é par e divisível por 4\n");
  else
    printf("O número é impar\n");
  return 0;
}

Quando a digitamos 12, a saída é:

O número é par e divisível por 4

Mas quando digitamos 6, a saída é:

O número é impar

O erro ocorre porque o else está associado ao segundo if e não ao primeiro, independentemente da identação.

Solução: Usar corretamente as chaves para dar o significado que você deseja.

#include <stdio.h>

int main() {
  int n;
  scanf("%d", &n);
  if(n % 2 == 0) {
    if(n % 4 == 0)
      printf("O número é par e divisível por 4\n");
  } else
    printf("O número é impar\n");
  return 0;
}

C. Esquecer do comando break quando utilizamos um switch

Exemplo:

#include <stdio.h>

int main() {
  int op1, op2;
  char operador;
  printf("Digite a operação\n");
  scanf("%c", &operador);
  printf("Digite os dois operandos\n");
  scanf("%d %d", &op1, &op2);
  switch(operador) {
    case '+':
      printf("%d + %d = %d\n", op1, op2, op1 + op2);
    case '-':
      printf("%d + %d = %d\n", op1, op2, op1 - op2);
    case '*':
      printf("%d + %d = %d\n", op1, op2, op1 * op2);
    case '/':
      printf("%d + %d = %d\n", op1, op2, op1 / op2);
    case '%':
      printf("%d + %d = %d\n", op1, op2, op1 % op2);
    default: 
      printf("Operação não reconhecida\n");
  }
  return 0;
}

Se no programa acima digitamos o caracter ’+’ e os números 2 e 6, então será impresso:

2 + 4 = 6
2 + 4 = -2
2 + 4 = 8
2 + 4 = 0
2 + 4 = 2
Operação não reconhecida

Isso se deve a não colocar o comando break no final de cada case. Neste caso, o programa continua a execução do programa seguindo para os próximos cases até terminar o bloco ou até encontrar um break.

Solução: Usar break corretamente para indicar que o programa não deve continuar executando os próximos comandos.

#include <stdio.h>

int main() {
  int op1, op2;
  char operador;
  printf("Digite a operação\n");
  scanf("%c", &operador);
  printf("Digite os dois operandos\n");
  scanf("%d %d", &op1, &op2);
  switch(operador) {
    case '+':
      printf("%d + %d = %d\n", op1, op2, op1 + op2);
      break;
    case '-':
      printf("%d + %d = %d\n", op1, op2, op1 - op2);
      break;
    case '*':
      printf("%d + %d = %d\n", op1, op2, op1 * op2);
      break;
    case '/':
      printf("%d + %d = %d\n", op1, op2, op1 / op2);
      break;
    case '%':
      printf("%d + %d = %d\n", op1, op2, op1 % op2);
      break;
    default: 
      printf("Operação não reconhecida\n");
  }
  return 0;
}

D. Repetir trechos em blocos mutuamente exclusivos

Se algum trecho se repete, por exemplo, em um if e seu else correspondente, muitas vezes é possível reescrever o código sem repetição.

Exemplo:

...
if (a != 0) {
  printf("O numero escolhido foi: %d", a); /* Observe que esta linha se repete. */
  scanf("%d", &a);
} else {
  printf("O numero escolhido foi: %d", a);
  printf("Saindo do programa!");
}
...

Solução: Neste caso, basta colocar a linha fora dos blocos.

...
printf("O numero escolhido foi: %d", a); /* Observe a alteração */
if (a != 0) {
  scanf("%d", &a);
} else {
  printf("Saindo do programa!");
}
...

E. Não fazer o else if de condições mutuamente exclusivas

Sempre que possível utilize else if pois esta estrutura evite que o computador avalie um if que só pode ser falso, afinal um if de condição oposta foi verdadeiro.

Exemplo:

...
if (a > 0) {
  printf("Maior que 0.\n");
} else if (a < 0) { /* Aqui, caso não seja colocado o **else** o programa continua correto, mas, no caso de **a>0** ser verdade, é desnecessário testar se **a<0**, logo, o else gera eficiência */
  printf("Menor que 0.\n");
}
...

Além de otimizar o código, há casos onde isto é necessário, como, por exemplo, quando as condições não são mutuamente exclusivas, mas só um bloco é desejado para executar. Por exemplo, um programa que avalia entre dois números se são uma PA de razão 1 ou simplesmente uma sequência crescente.

...
if (atual == anterior+1) {
  printf("Seguinte ao anterior.\n");
}
if (atual > anterior) { /* Observe a ausência do else */
  printf("Maior que o anterior.\n");
}
...

Neste exemplo, como não há else antes do segundo if, caso o número atual seja seguinte ao anterior, vai ser exibido:
Seguinte ao anterior.
Maior que o anterior.

Assim, neste caso, é necessário para o funcionamento correto do programa a estrutura else if.

5. Comandos Repetitivos

A. Usar ponto-e-vírgula incorretamente no for e no while

Exemplo:

#include <stdio.h>

int main() {
  int i;
  for(i = 1; i <= 10; i++); /*Note o ponto-e-virgula nesta linha*/
    printf("%d\n", i);
  return 0;
}

Esse código imprime 11 ao invés de imprimir os números entre 1 e 10.

Isto acontece por causa do ; no final da linha do for. Neste caso, o comando for executa um comando vazio enquanto i for menor ou igual a 10. Quando o for termina de ser executado, a variável i vale 11 e o programa segue para a próxima linha, que imprime o valor de i.

Para corrigir, basta remover o ; no final da linha do for.

B. Esperar que o loop seja quebrado na mudança da variável condicional

É importante entender como funciona o while e os demais loops.

Exemplo:

#include <stdio.h>

int main() {
  int a=0;
  while(a < 9) {
    a++;
    
    printf("%d", a);
  }
  return 0;
}

Quando o código acima é executado, a saída é: 0123456789. Pode parecer trivial, mas é importante notar que ao exibir o algarismo 9, a variável a já possui um valor que faz a condição do while ser falsa, contudo, até que a condição seja atingida, que é após o printf, o loop vai continuar a rodar. Caso o comportamento desejado seja sair abruptamente do loop, apesar de não recomendado, é utilizado a comando break. Em nosso caso poderíamos fazer:

#include <stdio.h>

int main() {
  int a=0;
  while(a < 9) {
    a++;
    if(a >= 9)
      break;
    
    printf("Valor de a: %d", a);
  }
  return 0;
}

Assim, a nova saída seria 012345678.

C. Sobreescrever a última leitura

Certas vezes precisamos ler dados em um loop. E em casos mais específicos podemos desejar ler um dado fora, antes dos demais.

...
scanf("%c", &entrada);
while (c != 's') {
    scanf("%c", &entrada); /* Perceba que ao entrar no loop, o valor de 'entrada' é substituido */
    switch(entrada) {
        ... /* Bloco que utiliza devidamente a entrada */
    }
}
...

Solução: Existem diversas maneiras de contornar este problema. Neste nosso trecho é possível usar o do/while, que permitiria que a leitura de dados aparecesse somente dentro do loop, onde o caso (entrada == ‘s’) seria tratado no switch. Mas, em outros casos, é possível passar o scanf para a última linha do while, e mantendo a leitura externa.

Dicas relacionadas

1. Funcionamento do comando diff

Ao submeter no susy(ou outro sistema de submissão de algoritmos), é executado o diff, um comando que pode ser executado no terminal da seguinte maneira:

diff [arquivo1] [arquivo2]

Ele ira analisar ambos arquivos e exibir suas diferenças seguindo um padrão para cada trecho de diferenças. Na 1ª linha é exibido o <Número><Letra><Número>. Os números são as linhas do primeiro e segundo arquivo, respectivamente, onde começa o bloco de diferenças. A letra será a em caso de adição de conteúdo, d para deleções e c para modificações.

A partir daí, as linhas com < marcam o que está no arquivo 1; > o que está no 2 e entre os dois há uma linha que os separa, para facilitar a leitura, apenas. Pode aparecer um =, que significará que a linha está igual em ambos arquivos.

Quando não há diferença entre as saídas, nem mesmo caracteres maiúsculos em um e minúsculos em outro, mas ainda aparecem ”>” ou ”<”, pode ser o caso de existir caracteres não legíveis, como o espaço ou o \n em um dos arquivos.

Agora, quando os arquivos realmente são identicos, ao executar o comando diff, não será exibido texto algum.