Linguaggio C

Errori Debug

Errori di sintassi, warning, errori a runtime ed errori logici: come leggere i messaggi di gcc e correggere un programma passo dopo passo.

Primi Passi | Errori Debug
Competenze previste
  • Classificare gli errori: sintassi, warning, runtime e logici.
  • Leggere e interpretare i messaggi di gcc.
  • Usare -Wall e -Wextra per individuare codice sospetto.
  • Applicare un metodo ordinato per correggere un programma.

I tipi di errore

Concetto: bug   |   Obiettivo: riconoscere e correggere

Chi programma sbaglia di continuo: è normale. Conta saper riconoscere il tipo di errore, perché da quello dipende dove cercare.

TipoQuando si manifestaEsempio
Errore di compilazioneIl compilatore si ferma e non crea l'eseguibile.punto e virgola mancante, variabile non dichiarata
WarningIl programma si compila, ma il codice è sospetto.formato %d usato con un float
Errore del linkerDopo la compilazione, durante il collegamento.funzione usata ma mai definita (undefined reference)
Errore a runtimeIl programma parte e poi si interrompe.divisione per zero
Errore logicoIl programma funziona ma dà risultati sbagliati.precedenza degli operatori sbagliata
Idea chiave: gli errori più pericolosi sono quelli logici: nessun messaggio ti avverte, devi accorgertene confrontando il risultato con quello atteso.

Leggere i messaggi del compilatore

Formato: file:riga:colonna: tipo: messaggio   |   Regola: si parte dal primo

Un punto e virgola dimenticato:

#include <stdio.h>

int main(void)
{
    int x = 10
    printf("%d\n", x);
    return 0;
}
err1.c: In function 'main':
err1.c:6:5: error: expected ',' or ';' before 'printf'
    6 |     printf("%d\n", x);
      |     ^~~~~~

Il messaggio indica file (err1.c), riga (6), colonna (5), il tipo (error) e la spiegazione. Nota che il compilatore segnala la riga 6, mentre il punto e virgola manca alla riga 5: l'errore si scopre solo quando arriva l'istruzione successiva. Se la riga indicata sembra corretta, guarda quella precedente.

Un nome scritto male:

#include <stdio.h>

int main(void)
{
    int numero = 4;
    printf("Il doppio vale %d\n", nmero * 2);
    return 0;
}
err2.c: In function 'main':
err2.c:6:35: error: 'nmero' undeclared (first use in this function); did you mean 'numero'?
    6 |     printf("Il doppio vale %d\n", nmero * 2);
      |                                   ^~~~~
      |                                   numero

nmero non è stato dichiarato; gcc propone anche il nome simile numero. I messaggi qui riportati sono quelli di gcc 13 in inglese; il testo può variare leggermente tra versioni.

Metodo
  • Leggi il primo errore: gli altri possono esserne una conseguenza.
  • Vai alla riga indicata e a quella precedente.
  • Correggi un solo problema e ricompila.
  • Ripeti finché non restano errori.

I warning e le opzioni -Wall -Wextra

Opzioni: -Wall -Wextra   |   Regola: mai ignorare un warning

#include <stdio.h>

int main(void)
{
    float prezzo = 9.5f;
    printf("Prezzo: %d\n", prezzo);
    return 0;
}
err3.c: In function 'main':
err3.c:6:22: warning: format '%d' expects argument of type 'int', but argument 2 has type 'double' [-Wformat=]
    6 |     printf("Prezzo: %d\n", prezzo);
      |                     ~^     ~~~~~~
      |                      |     |
      |                      int   double
      |                     %f

Il programma si compila, ma %d con un float stampa un valore privo di senso. Il warning ti ha avvisato prima ancora di eseguire il programma: per questo si compila sempre con -Wall -Wextra e si tratta ogni warning come un errore da correggere.

Errori a runtime ed errori logici

Runtime: il programma si interrompe   |   Logici: risultato sbagliato

Errore a runtime. Questo programma si compila senza problemi, ma se l'utente inserisce 0 come divisore si interrompe:

#include <stdio.h>

int main(void)
{
    int a, b;

    printf("Dividendo: ");
    scanf("%d", &a);
    printf("Divisore: ");
    scanf("%d", &b);
    printf("Risultato: %d\n", a / b);
    return 0;
}

Il C non controlla i dati al posto tuo: la divisione intera per zero termina il programma (su Linux la shell mostra un messaggio come Floating point exception). Imparerai a prevenire il problema con le condizioni.

Errore logico. Qui la media di 8 e 6 dovrebbe valere 7:

#include <stdio.h>

int main(void)
{
    int a = 8, b = 6;
    float media = a + b / 2;

    printf("Media: %.1f\n", media);
    return 0;
}
Media: 11.0

Nessun avviso, ma il risultato è sbagliato: b / 2 viene calcolato prima della somma. La correzione è (a + b) / 2.0.

Come si cercano gli errori logici:

  • inserisci dati di cui conosci già il risultato e confronta;
  • aggiungi printf di controllo che mostrino i valori intermedi delle variabili;
  • spiega il codice a voce, riga per riga (rubber duck debugging);
  • usa il debugger di VS Code: un breakpoint ferma il programma e ti fa vedere le variabili passo dopo passo.

Verifica dei concetti

Tipo: autovalutazione   |   Argomento: Errori e debug

  1. Qual è la differenza tra un errore di compilazione e un errore logico?
  2. Perché conviene correggere prima il primo errore segnalato?
  3. Perché il compilatore può segnalare una riga diversa da quella in cui hai sbagliato?
  4. A che cosa servono le opzioni -Wall e -Wextra?
  5. Quali tecniche puoi usare per trovare un errore logico?
In Sintesi
  • Gli errori possono essere di compilazione, di collegamento, a runtime o logici; i warning segnalano codice sospetto.
  • I messaggi di gcc indicano file, riga, colonna e causa.
  • Si corregge un errore alla volta, partendo dal primo, e si ricompila.
  • Gli errori logici si scoprono confrontando il risultato con quello atteso.
Cosa hai imparato
  • Hai classificato gli errori per tipo.
  • Hai letto i messaggi del compilatore.
  • Hai usato i warning per prevenire errori.
  • Hai corretto un programma con un metodo ordinato.

Parole chiave

I termini principali introdotti in questa guida sono:

Bug Errore di sintassi Warning Runtime Errore logico Debug Breakpoint -Wall