Nei titoli e nei testi troverete qualche rimando cinematografico (ebbene si, sono un cinefilo). Se non vi interessano fate finta di non vederli, già che non sono fondamentali per la comprensione dei post...

Di questo blog ho mandato avanti, fino a Settembre 2018, anche una versione in Spagnolo. Potete trovarla su El arte de la programación en C. Buona lettura.

venerdì 27 febbraio 2026

The (Duel)Lists
come usare le Linked List in C - pt.1

Armand d'Hubert: [rivolgendosi a Gabriel Feraud] Sono stato ai tuoi ordini per 15 anni... Secondo le regole dei duelli adesso la tua vita mi appartiene. Dichiarerò che sei morto. Se avrai mai a che fare con me, lo farai da uomo morto. Mi sono piegato alla tua nozione di onore per troppo tempo. Adesso tu ti piegherai alla mia.

(...una premessa: questo post è un remake di un mio vecchio post. Ma, anche se tratta lo stesso argomento, amplia e perfeziona un po' il discorso è mi è sembrato il caso di riproporlo. Leggete e mi direte...)

The Duellists (I duellanti) è uno dei capolavori di Ridley Scott, ed è il suo film d'esordio (e che esordio!); racconta la storia di due ufficiali francesi (interpretati da Keith Carradine e Harvey Keitel) che si affrontano a duello in vari scenari europei durante le campagne napoleoniche, per ben quindici anni. È un film imperdibile! Ah, e visto che siamo in tema: vorrei ricordarvi che se qualcuno parla male delle linked list lo sfido a duello, quindi state attenti... Come avrete già intuito, in questo articolo parleremo di linked list!

...Cosa dicevi delle linked list?...

Da queste parti avevamo già affrontato (di striscio) il tema delle linked list, in due vecchi articoli, qui e qui. Ricordo anche che una volta giustificai l'argomento così:

"Per chi non le avesse mai usate, le linked-list non sono una frivolezza: sono una delle costruzioni più potenti della programmazione in generale (e del C in particolare). Chi lavora intensamente in C su progetti grandi e professionali finirà, prima o poi, con usarle: un motivo in più, quindi, per familiarizzare con la allocazione dinamica della memoria."

Ok, credo che la maniera migliore di presentare l'argomento sia un esempietto semplice semplice (ma, come sempre: funzionante!). Vai col codice!

// linklist.c - un programma di esempio sulle linked list
#include<stdlib.h>
#include<stdio.h>

// nodo di una linked list singola con campo dati
typedef struct snode {
int data;
struct snode *next;
} node_t;

// prototipi locali
void addNode(node_t **head, int data);
void appendNode(node_t **head, int data);

// funzione main
int main()
{
// init lista vuota 1 e inserisce con addNode() 3 nodi con data = indice del loop
node_t *head1 = NULL;
for (int i = 0; i < 3; i++)
addNode(&head1, i);

// scorre la lista e stampa i valori
node_t *myhead1 = head1;
printf("myhead1=%p - metodo ADD\n", (void *)myhead1);
while (myhead1) {
printf("data=%d (myhead1=%p next=%p)\n",
myhead1->data, (void *)myhead1, (void *)myhead1->next);
myhead1 = myhead1->next;
}

// init lista vuota 2 inserisce con appendNode() 3 nodi con data = indice del loop
node_t *head2 = NULL;
for (int i = 0; i < 3; i++)
appendNode(&head2, i);

// scorre la lista e stampa i valori
node_t *myhead2 = head2;
printf("myhead2=%p - metodo APPEND\n", (void *)myhead2);
while (myhead2) {
printf("data=%d (myhead2=%p next=%p)\n",
myhead2->data, (void *)myhead2, (void *)myhead2->next);
myhead2 = myhead2->next;
}

return 0;
}

// addNode - alloca in testa a una lista un node con dati e pointer al prossimo elemento
void addNode(node_t **head, int data)
{
// alloca un nuovo node
node_t *node = malloc(sizeof(node_t));
node->data = data;
node->next = *head;

// assegna head lista al nuovo node
*head = node;
}

// appendNode - alloca in fondo a una lista un node con dati e pointer al prossimo elemento
void appendNode(node_t **head, int data)
{
// alloca un nuovo node
node_t *node = malloc(sizeof(node_t));
node->data = data;
node->next = NULL;

// appende il nuovo node
node_t *current = *head;
if (current == NULL) {
// caso speciale per lista vuota: assegna head lista al nuovo node
*head = node;
}
else {
// scorre la lista per trovare l'ultimo node
while (current->next != NULL)
current = current->next;

// assegna all'ultimo node il nuovo node
current->next = node;
}
}

Cosa dire? Più elementare di così! Eppure funziona... Come si evince dagli abbondanti commenti per fare una linked list basta creare un punto di inizio (un "head") e aggiungere nodi con una semplicissima funzione che ho chiamato (in modo veramente originale) addNode(). Il segreto sta nella struttura del nodo, che deve sempre contenere un pointer al nodo successivo (ma va? Sarà mica per questo che si chiamano linked list?). Dato che c'ero ho anche aggiunto una funzione per appendere nodi, che è ugualmente utile. Ovviamente l'esempio fornito è abbastanza limitato: mancano le funzioni per cancellare i nodi, oppure per gestire la lista in modo doppio (double-linked-list), ecc. Ma sulla (solida) base fornita è possibile costruire, in modo relativamente semplice, tutte le estensioni che vogliamo.

Nel main() ho aggiunto un po' di tracce di log, che ci aiutano, durante l'esecuzione, a capire cosa succede quando aggiungiamo un nodo e quando, in alternativa, lo appendiamo. Il risultato finale è lo stesso (nell'esempio è una lista con tre nodi), ma la disposizione cambia: nel caso append  abbiamo una disposizione più logica dei nodi: la lista cresce in avanti, e ogni nuovo nodo è l'ultimo. Nel caso insert, invece, è la testa della lista che cambia ad ogni inserzione, e quindi, quando leggiamo i dati, li troviamo invertiti rispetto all'ordine di inserimento.

Entrambe le liste hanno usi validi (ad esempio una va meglio per creare code FIFO e l'altra va meglio per creare code LIFO) e, per applicazioni più sofisticate dove bisogna aggiungere/leggere/cancellare nodi in posizioni determinate si può decidere (quasi) indistintamente per una struttura o l'altra. Io normalmente uso il metodo append, che mi sembra più logico, anche se, lo riconosco, la funzione di append è un po' più complicata e meno immediata di quella di insert. Però la lista ordinata in avanti mi sembra più coerente e facile da maneggiare.

Compilando (con gli opportuni flag) ed eseguendo, il programma ci mostrerà questo log:

aldo@Linux $ gcc -Wall -Wextra -pedantic linklist.c -o linklist
aldo@Linux $ ./linklist
myhead1=0x5a2fd71412e0 - metodo ADD
data=2 (myhead1=0x5a2fd71412e0 next=0x5a2fd71412c0)
data=1 (myhead1=0x5a2fd71412c0 next=0x5a2fd71412a0)
data=0 (myhead1=0x5a2fd71412a0 next=(nil))
myhead2=0x5a2fd7141710 - metodo APPEND
data=0 (myhead2=0x5a2fd7141710 next=0x5a2fd7141730)
data=1 (myhead2=0x5a2fd7141730 next=0x5a2fd7141750)
data=2 (myhead2=0x5a2fd7141750 next=(nil))

che mi sembra inutile spiegare (parla da solo!). Come già accennato sopra, il codice presentato è solo un "buon inizio": in una applicazione reale bisognerà aggiungere un po' di funzioni accessorie per cercare, inserire e cancellare nodi. E, visto che usiamo delle malloc(3), bisognerà anche aggiungere, quando servono, le corrispondenti free(3)... Ma anzi, crepi l'avarizia! Farò una seconda parte dell'articolo aggiungendo quello che manca, ma sempre in maniera semplificata e ben comprensibile, eh!

Ok, ci sentiremo prossimamente con la seconda parte dell'articolo e, come sempre, vi ricordo di non trattenere il respiro nell'attesa (può nuocere gravemente alla salute...).

Ciao e al prossimo post! 

martedì 27 gennaio 2026

Warning Peaks
perché i warning sono importanti in C - pt.2

Dale Cooper: Harry, ti dirò un piccolo segreto. Ogni giorno, una volta al giorno, fatti un regalo. Non pianificarlo, non aspettarlo... fallo solo succedere.

Ok, è già ora di tornare sul pezzo. Dove eravamo rimasti? Ah, si: nell'ultimo articolo avevo parlato dell'importanza, spesso sottovalutata, dei warning, e avevo anche mostrato qualche (spero) interessante esempio. Avevo poi concluso dicendo che, nella seconda parte avremmo visto una panoramica dei flag consigliati per scrivere una applicazione impeccabile ("a prova di bomba"). E allora, come diceva (qui sopra) il buon Dale Cooper nel mitico Twin Peaks, oggi tocca auto-regalarsi qualcosa di molto utile... come avrete già intuito, anche in questo articolo parleremo di warning!

...Harry, regalati un warning e risolvilo. La tua applicazione ti ringrazierà...

Avevo già anticipato nello scorso articolo che alcuni warning appaiono di base, mente altri appaiono usando il flag di compilazione -Wall. È quindi venuto il momento di mostrare una lista degli altri flag che si possono usare, anche se vedremo solo i più significativi per lo scopo che ci siamo prefissi. I flag disponibili con GCC sono tantissimi, e alcuni sono dei "flag di gruppo" (come il -Wall) che raggruppano molte opzioni in una botta sola. Vedremo, quindi, i flag di gruppo più utili e anche alcuni flag semplici da usare separatamente. Vai con la lista!

Flag di gruppo

  • -Wall: abilita i warning più comunemente utili, tra cui variabili non inizializzate, parametri inutilizzati e costrutti sospetti. Include ben 81 flag semplici che si possono usare separatamente (-Waddress, -Warray-compare -Warray-parameter=2, ecc.), ed è il flag di gruppo "principe", quello assolutamente da usare.
  • -Wextra: aggiunge warning più completi rispetto a -Wall, come parametri di funzione inutilizzati, variabili oscurate e dichiarazioni di funzione implicite. Include ben 29 flag semplici che si possono usare separatamente (-Walloc-size, -Wcalloc-transposed-args, -Wcast-function-type, ecc.), ed è il flag di gruppo "principe" n.2, da usare sempre in coppia con -Wall (o anche da solo, ma in coppia è meglio... scusate il doppio senso, ah ah ah).
  • -Wformat=2: combina i flag semplici -Wformat, -Wformat-nonliteral, -Wformat-security e -Wformat-y2k per rilevare l'uso non sicuro delle stringhe di formato. È un flag utile ma solo per usi avanzati e restrittivi, normalmente è sufficiente usare il -Wformat=1 che è incluso nel -Wall.

Flag semplici

  • -pedantic (oppure -Wpedantic): impone la stretta conformità agli standard ISO C/C++, segnalando le estensioni non standard e il codice non conforme. È un flag altamente raccomandabile.
  • -Werror: tratta tutti i warning come errori, impedendo la compilazione fino alla risoluzione dei problemi, ideale per il codice di produzione. È un flag utile ma non strettamente indispensabile; comunque è importante sapere che è disponibile.
  • -Wshadow: avvisa quando una variabile locale oscura una variabile da uno scope esterno. È un flag altamente raccomandabile.
  • -Wnull-dereference: avvisa in merito a percorsi che potrebbero portare a referenze di puntatori nulli. È un flag utile ma solo per usi avanzati e restrittivi.
  • -Wcast-align=strict: rileva conversioni di pointer non sicure che potrebbero causare problemi di disallineamento su determinate architetture. È un flag utile ma solo per usi avanzati e restrittivi.

Che vi sembra la lista? È, ovviamente, una selezione molto ponderata, ma abbastanza completa; e comunque, se volete conoscere tutti il flag disponibili, potete consultarli qui (ma mettetevi comodi, è una lista lunghissima!).

A questo punto, magari, vi può anche interessare cosa usa, normalmente, il vostro C-blogger preferito (io, no? ah ah ah) quando scrive codice di produzione... ecco la linea!

gcc -Wall -Wextra -pedantic -Wshadow

a volte aggiungo, poi, anche alcuni flag semplici  in base alle esigenze del momento, ma normalmente la linea di compilazione che uso è questa, e permette di produrre applicazioni molto affidabili, ve lo garantisco! Provare per credere...

E direi di finire con una altro esempietto, perché un articolo di programmazione senza un esempio reale è un articolo incompiuto... Vediamo un po': il codice seguente ha un problema, ma compilando senza warning, o anche usando -Wall non segnala niente (!). Ci vorrà mica il -Wextra? Vai col codice!

// typelimits.c
#include <stdio.h>

// prototipi locali
int isGreaterThan200(char c);

// funzione main
int main()
{
int i = 250;
printf("arg = %d --> %s\n",
i, isGreaterThan200(i) ? "è maggiore di 200" : "non è maggiore di 200");
return 0;
}

// isGreaterThan200 - valuta se l'argomento è più grande di 200
int isGreaterThan200(char arg)
{
if (arg > 200) // 200 è troppo grande per un char: la comparazione è sempre falsa
return 1;
else
return 0;
}

compiliamo con -Wall ed eseguiamo:

aldo@Linux $ gcc -Wall typelimits.c -o typelimits
aldo@Linux $ ./typelimits
arg = 250 --> non è maggiore di 200

Ma che strano, è un programma molto semplice ed eppure funziona così male! E il compilatore non ci segnala nulla! Proviamo allora a compilare con -Wextra:

aldo@Linux $ gcc -Wextra typelimits.c -o typelimits
typelimits.c: In function ‘isGreaterThan200’:
typelimits.c:20:13: warning: comparison is always false due to limited range of data type [-Wtype-limits]
20 | if (arg > 200) // 200 è troppo grande per un char: la comparazione è sempre falsa
| ^
aldo@Linux $ ./typelimits
arg = 250 --> non è maggiore di 200

Ok, l'errore di esecuzione è rimasto (e ci mancherebbe solo!), ma almeno sappiamo a cosa è dovuto e possiamo correggerlo facilmente. Questo è solo un esempio dei warning che può segnalare il -Wextra: spesso è veramente utile!

E, prima di chiudere, vorrei ricordarvi che tutti i flag semplici  hanno un flag parallelo che serve a negare (sopprimere) il warning in oggetto, ed è lo stesso flag però con un no- davanti. Quindi, se vogliamo sopprimere uno di quelli inclusi nel -Wall  (ad esempio il -Waddress) possiamo cambiare la linea di compilazione (che ho passato sopra) in questa maniera:

gcc -Wall -Wextra -pedantic -Wshadow -Wno-address

Questa linea modificata aggiungendo un -Wno-address ci permette di compilare segnalando tutti i warning utili ma senza mostrare quelli di tipo -Waddress; e a cosa serve questo? Beh, il compilatore GCC è un gran strumento, ma a volte può sbagliare anche lui offrendoci dei falsi negativi, oppure scambiando per errore cose che abbiamo fatto volutamente, oppure mostrandoci delle segnalazioni che riteniamo completamente ininfluenti: in questo caso possiamo "pulire" l'uscita della compilazione dai messaggi che non ci interessano.

E vabbè, penso che è giunto il momento di chiudere questa larga parentesi sui warning. Non so cosa ci riserverà il futuro... ma sarà sicuramente molto interessante! Restate sintonizzati!

Ciao, e al prossimo post!