💾 Archived View for it.omarpolo.com › articoli › eleganza-puntatori.gmi captured on 2022-01-08 at 13:49:59. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2021-11-30)
-=-=-=-=-=-=-
Uno dei concetti considerati di più difficile comprensione nell’ambito della programmazione credo sia l’uso dei puntatori, seguito solo dalla gestione manuale della memoria, entrambi caratteristiche di C.
È, ovviamente, anche la mia opinione. Ho ancora ben in mente le difficoltà di uno yumh più giovane nel risolvere gli esercizi in assembly e non capire perché non funzionassero le soluzioni.
La risposta classica alla domanda “cos’è un puntatore?” è abbastanza banale quanto poco utile in effetti: “un indirizzo di memoria” (soprattutto perché da lì è semplice passare a puntatori a puntatori e via dicendo…). È poco utile perché nonostante tecnicamente corretta, non è in grado di fornire al novizio un’idea di cosa stia sbagliando; serve un po’ di pratica per quello, credo. Fortunatamente dopo poco ho “capito” i puntatori e sono andato avanti.
Una volta che si ha un’idea di come lavorare con i puntatori, tutta una classe di problemi diventano, secondo me, non solo più semplici da risolvere, ma addirittura più eleganti e semplici che in linguaggi senza puntatori (o senza aritmetica sui puntatori, come Go.)
Un esempio che voglio mostrare è quello di una routine che effettua il parsing degli oggetti “tree” all’interno di un repository git. Si tratta di un file binario con una struttura semplice e regolare: una sequenza di record col seguente formato, uno dopo l’altro, ognuno rappresentante un file in un repository:
<permessi> <spazio> <nome del file> <NUL byte> <20 byte di SHA1>
In linguaggi di alto livello se si ha a che fare con stringhe del genere occorre tenere traccia degli indici. Si inizia con ‘i = 0’ e pian piano si incrementa l’indice man mano che si prosegue nel parsing della stringa. In casi come questi, trovo che dover tener traccia di indici sia uno spreco di facoltà mentali: l’immagine mentale che ho è quella di dover lavorare su una serie di elementi, uno dopo l’altro, come fosse una catena di montaggio. Tenere il conto del numero a cui siamo arrivati non aiuta, tutto quello che serve sapere è dove ci troviamo *adesso* e alla peggio qual è il prossimo elemento.
Quindi, nonostante sia risaputo che lavorare sulle stringhe in C non sia piacevole, trovo difficile pensare di riuscire a scrivere codice più elegante ed espressivo del seguente in altri linguaggi di più alto livello:
void parsetree(const char *buf, size_t len) { const char *t, *end, *fname; char sha[20]; int mode; end = buf + len; while (buf < end) { /* leggi il tipo */ mode = 0; while (*buf >= '0' && *buf <= '7' && buf < end) mode = mode * 8 + *buf++ - '0'; if (buf == end) error(); /* poi uno spazio */ if (*buf++ != ' ') error(); /* poi il path, seguito da NUL */ t = buf; while (t < end && *t != '\0') t++; if (t == end) error(); fname = buf; buf = ++t; /* infine 20 byte di sha1 */ if (end - buf < 20) error(); memcpy(sha, buf, 20); buf += 20; } }
Inoltre, se si è disposti ad usare memmem(3) — non standard ma largamente diffusa — allora il while intermedio diventa inutile:
/* poi il path, seguito da NUL */ if ((t = memmem(buf, end - buf, "", 1)) == NULL) error(); fname = buf; buf = ++t;
Un secondo esempio, più complesso ma di gran lunga migliore, è l’implementazione di un matcher per espressioni regolari scritto da Rob Pike per il libro “The Practice of Programming”. Non ho mai letto il libro, ma c’è una versione commentata da Brian Kernighan:
“A Regular Expression Matcher”
La conclusione credo sia abbastanza scontanta, ovvero usare le funzionalità di un linguaggio bene produce codice compatto e leggibile, ma spero anche di aver mostrato in parte come l’aritmetica sui puntatori non sia voodoo e che lavorare con le stringhe in C possa essere piacevole.
$BlogIt: eleganza-puntatori.gmi,v 1.1 2021/10/20 07:41:39 op Exp $