The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NOME

perlpacktut - tutorial su pack e unpack

DESCRIZIONE

pack e unpack sono due funzioni adatte a trasformare dati secondo un modello definito dall'utente, partendo dal modo vincolato in cui Perl immagazzina i valori verso una qualche rappresentazione ben definita che potrebbe essere richiesto nell'ambito di un programma Perl. Sfortunatamente, sono anche due delle funzioni peggio comprese e spesso ignorate, disponibili in Perl. Questo tutorial provvederà a demistificarvele.

Il Principio Base

La maggior parte dei linguaggi di programmazione non protegge la memoria in cui sono immagazzinate le variabili. In C, ad esempio, potete prendere l'indirizzo di una variabile, e l'operatore sizeof vi dirà quanti ottetti le sono allocati. Utilizzando indirizzo e dimensione, potete accedere la memoria che la immagazzina come vi pare e piace.

In Perl, non potete accedere la memoria così a casaccio, ma la conversione di struttura e di rappresentazione fornita da pack e unpack rappresenta un'eccellente alternativa. La funzione pack converte i valori verso una sequenza di ottetti che contiene delle rappresentazioni in base ad una specifica prestabilita, il cosiddetto argomento "modello". unpack è il processo inverso, in cui si derivano alcuni valori a partire dal contenuto di una stringa di ottetti. (Fate attenzione, comunque, perché non tutto ciò che viene impacchettato può essere spacchettato in maniera pulita - un'esperienza piuttosto comune che i viaggiatori esperti possono facilmente confermare).

Perché, potreste chiedere, si dovrebbe avere bisogno di avere un pezzo di memoria che contiene valori in rappresentazione binaria? Una buona ragione riguarda l'accesso in ingresso ed uscita ad un file, un dispositivo o una connessione di rete, laddove questa rappresentazione binaria è obbligatoria o vi porta qualche beneficio in termini di elaborazione. Un'altra ragione può essere il passaggio di dati ad una chiamata di sistema che non è disponibile come funzione Perl: syscall esige che forniate i parametri nel modo in cui si fa in un programma C. Anche l'elaborazione di testi (come vedremo nella prossima sezione) può risultare più semplice con un uso oculato di queste due funzioni.

Per vedere come funziona l'impacchettamento (o lo spacchettamento), cominciamo con un modello semplice dove la conversione è basilare: tra il contenuto di una sequenza di ottetti ed una stringa di caratteri esadecimali. Utilizziamo unpack, poiché è facile che vi ricordi un programma di dump [termine usualmente utilizzato per indicare la stampa o il salvataggio di un'area di memoria, NdT], o qualche ultimo disperato messaggio quale sono soliti lanciarvi gli sfortunati programmi appena prima di volare in paradiso. Assumendo che la variabile $mem contenga una sequenza di ottetti che vorremmo ispezionare senza fare alcuna ipotesi sul suo significato [ossia, sul suo contenuto, NdT], possiamo scrivere

   my( $esadecimale ) = unpack( 'H*', $mem );
   print "$esadecimale\n";

dove potremmo ritrovarci qualcosa come la seguente, con ciascuna coppia di cifre esadecimali corrispondente ad un singolo ottetto:

   41204d414e204120504c414e20412043414e414c2050414e414d41

Cosa c'era in quella zona di memoria? Numeri, caratteri o una mistura dei due? Assumendo di trovarci su un computer dove viene utilizzata la codifica ASCII (o una simile): i valori esadecimali nell'intervallo 0x40 - 0x5A indicano una lettera maiuscola, 0x20 codifica uno spazio. Possiamo quindi assumere che si tratti di un brano di testo, che alcuni sono in grado di leggere come un giornale; altri, invece, hanno bisogno di mettere le mani su una tabella ASCII e rivivere quella sensazione da pivelli. Senza preoccuparci troppo di come riuscire a leggerlo, osserviamo che unpack con il codice modello H converte il contenuto di una sequenza di ottetti nella notazione esadecimale. Poiché "una sequenza di" è un'indicazione piuttosto vaga della quantità di ottetti, H è definito per la conversione di un'unica cifra esadecimale, a meno che non sia seguito da un contatore di ripetizione. Un asterisco come contatore di ripetizione significa utilizzare qualsiasi carattere resti.

L'operazione inversa - impacchettare un contenuto in ottetti da una stringa di cifre esadecimali - si scrive altrettanto facilmente. Ad esempio:

   my $s = pack( 'H2' x 10, map { "3$_" } ( 0..9 ) );
   print "$s\n";

Poiché forniamo a pack una lista di dieci stringhe esadecimali da due caratteri l'una, il modello di impacchettamento dovrebbe contenere dieci codici. Se lo eseguiamo su un computer con la codifica dei caratteri ASCII, stamperà 0123456789.

Impacchettare Testo

Supponiamo che dobbiate leggere un file di dati come questo:

    Data      |Descrizione                |Entrate|Uscite
    01/24/2001 Emporio Cammelli di Ahmed               1147.99
    01/28/2001 Spray per le pulci                        24.99
    01/29/2001 Giro cammello per turisti   1235.00

Come farlo? Per iniziare, potreste pensare di utilizzare split; comunque, poiché split raggruppa campi vuoti, non saprete mai se un record era relativo alle entrate o alle uscite. Oops. Bene, potreste sempre utilizzare substr:

    while (<>) { 
        my $data    = substr($_,  0, 11);
        my $descr   = substr($_, 12, 27);
        my $entrate = substr($_, 40,  7);
        my $uscite  = substr($_, 52,  7);
        ...
    }

Non è proprio da ridere, eh? Infatti, è peggio di quanto possa sembrare: chi ha la vista aguzza può aver notato che il primo campo è ampio solo 10 caratteri, e che l'errore si è propagato attraverso gli altri valori - che dobbiamo contare a mano. Quindi, risulta soggetto ad errore così come risulta terribilmente ostile.

Potremmo anche utilizzare alcune espressioni regolari:

    while (<>) { 
        my($data, $descr, $entrate, $uscite) = 
            m|(\d\d/\d\d/\d{4}) (.{27}) (.{7})(.*)|;
        ...
    }

Urgh. Va un po' meglio ma - beh, vi piacerebbe doverci fare manutenzione?

Ehi, ma Perl non dovrebbe semplificare questo genere di cose? Beh, lo fa se utilizzate gli strumenti adatti. pack e unpack sono progettati per aiutarvi quando avete a che fare con dati a lunghezza fissa come quelli nell'esempio dato. Diamo un'occhiata alla soluzione con unpack:

    while (<>) { 
        my($data, $descr, $entrate, $uscite) = unpack("A10xA27xA7A*", $_);
        ...
    }

Sembra già meglio, ma dobbiamo smontare quello strano modello. Da dove l'ho tirato fuori?

OK, diamo di nuovo un'occhiata ad un po' dei dati di origine; di fatto, includeremo le intestazioni, ed un comodo righello in modo da tenere traccia di dove ci troviamo.

             1         2         3         4         5        
    1234567890123456789012345678901234567890123456789012345678
    Data      |Descrizione                |Entrate|Uscite     
    01/24/2001 Emporio Cammelli di Ahmed               1147.99
    01/28/2001 Spray per le pulci                        24.99

Da questo possiamo vedere che la colonna della data si estende dalla colonna 1 alla colonna 10 - è larga 10 caratteri. "Carattere", in gergo pack, corrisponde a A, e dieci caratteri sono quindi A10. Se volessimo dunque estrarre le sole date, potremmo scrivere:

    my($data) = unpack("A10", $_);

OK, che viene dopo? Tra la data e la descrizione c'è una colonna vuota; vogliamo saltarla. Il modello x significa "fai un salto in avanti", per cui ce ne serve uno. Di seguito, abbiamo un altro po' di caratteri, dal 12 al 38. In tutto sono 27 caratteri, da cui A27. (Non commettete l'errore sui bordi - ci sono 27 caratteri tra 12 e 38, non 26. Contateli!)

Ora saltiamo un altro carattere e prendiamo i 7 caratteri successivi:

    my($data,$descrizione,$entrate) = unpack("A10xA27xA7", $_);

Ora arriviamo alla furbata. Le righe nella nostra tabella che contengono solo entrate e non spese potrebbero terminare alla colonna 46. Per questo motivo, non possiamo dire al nostro modello unpack che abbiamo bisogno di trovare altri 12 caratteri; diremo solamente che "se avanza qualcosa, prendilo". Come potete immaginare conoscendo le espressioni regolari, questo è quello che * significa: "usa qualunque cosa rimanga".

  • Fate attenzione che, diversamente dalle espressioni regolari, se il modello unpack non corrisponde ai dati in ingresso, Perl comincerà ad urlare e terminerà con die.

Da qui, mettendo tutto insieme:

    my($data,$descriz,$entrate,$uscite) = unpack("A10xA27xA7xA*", $_);

Ecco infine che i nostri dati sono interpretati. Suppongo che ciò che potremmo desiderare di fare a questo punto sia di trovare il totale di entrate ed uscite, per aggiungere un'altra riga alla fine del nostro prospetto - nello stesso formato - indicando quanto abbiamo ricavato e quanto abbiamo speso:

    while (<>) {
        my($data, $descr, $entrate, $uscite) = unpack("A10xA27xA7xA*", $_);
        $tot_entrate += $entrate;
        $tot_uscite += $uscite;
    }

    $tot_entrate = sprintf("%.2f", $tot_entrate); # Trasformazione in
    $tot_uscite  = sprintf("%.2f", $tot_uscite);  # formato "finanziario"

    $data = POSIX::strftime("%m/%d/%Y", localtime); 

    # OK, possiamo andare

    print pack("A10xA27xA7xA*", $data, "Totali", $tot_entrate, $tot_uscite);

Oh, hmm. Non ha proprio funzionato. Vediamo cos'è successo:

    01/24/2001 Emporio Cammelli di Ahmed               1147.99
    01/28/2001 Spray per le pulci                        24.99
    01/29/2001 Giro cammello per turisti   1235.00
    03/23/2001Totali                     1235.001172.98

OK, è un inizio, ma che è successo agli spazi? Abbiamo messo x, no? Non dovrebbe fare un salto in avanti? Diamo un'occhiata a cosa dice "pack" in perlfunc:

    x   A null byte. [Un ottetto nullo, NdT]

Urgh. Nessuna meraviglia che non abbia funzionato. C'è una gran differenza fra "un ottetto nullo", ossia il carattere zero, ed "uno spazio", ossia il carattere 32. Perl ha inserito qualcosa fra data e descrizione - ma sfortunatamente non possiamo vederlo!

Quello di cui abbiamo veramente bisgogno è di espandere la larghezza dei campi. Il formato A aggiunge spazi al posto dei caratteri che non esistono, di modo che possiamo utilizzare gli spazi addizionali per allineare i nostri campi, come:

    print pack("A11 A28 A8 A*", $data, "Totali", $tot_entrate, $tot_uscite);

(Osservate che potete inserire spazi nel modello per renderlo più leggibile, ma che questi non si traducono in spazi in uscita). Ecco quel che abbiamo ottenuto questa volta:

    01/24/2001 Emporio Cammelli di Ahmed               1147.99
    01/28/2001 Spray per le pulci                        24.99
    01/29/2001 Giro cammello per turisti   1235.00
    03/23/2001 Totali                      1235.00 1172.98

Va già meglio, ma abbiamo ancora l'ultima colonna che va spostata un po' più in là. C'è un modo semplice per risolvere questa situazione: sfortunatamente, non possiamo chiedere a pack di giustificare i campi sulla destra, ma possiamo usare sprintf:

    $tot_entrate = sprintf("%.2f", $tot_entrate); 
    $tot_uscite  = sprintf("%12.2f", $tot_uscite);
    $data = POSIX::strftime("%m/%d/%Y", localtime); 
    print pack("A11 A28 A8 A*", $data, "Totali", $tot_entrate, $tot_uscite);

Questa volta otteniamo la risposta corretta:

    01/24/2001 Emporio Cammelli di Ahmed               1147.99
    01/28/2001 Spray per le pulci                        24.99
    01/29/2001 Giro cammello per turisti   1235.00
    03/23/2001 Totali                      1235.00     1172.98

Ecco dunque come possiamo leggere e scrivere dati a larghezza fissa. Ricapitolando quel che abbiamo visto di pack e unpack fino ad ora:

  • Utilizzate pack per andare da una moltitudine di dati ad una versione a larghezza fissa; utilizzate unpack per trasformare un formato a larghezza fissa in una molteplicità di dati.

  • Il formato di impacchettamento A significa "un qualsiasi carattere"; se state impacchettando con pack e avete finito le cose da impacchettare, pack riempirà il resto di spazi.

  • x significa "saltare un ottetto" con unpack; con pack, significa "inserire un ottetto nullo" - che probabilmente non è quello che vi serve quando avete a che fare con del semplice testo.

  • Potete aggiungere dei numeri ai formati per specificare quanti caratteri dovrebbero essere coinvolti da quel dato formato: A12 significa "prendere 12 caratteri"; x6 significa "saltare 6 ottetti" o "carattere 0, per 6 volte".

  • Invece di un numero, potete utilizzare * per intendere "leggi tutto ciò che è rimasto".

    Attenzione: quando impacchettate una molteplicità di dati, * significa solo "leggi tutto quanto avanza del dato corrente". Si intende cioè che:

        pack("A*A*", $uno, $due)

    impacchetta tutto $uno nel primo A* e poi tutto $due nel secondo. Questo è un principio generale: ciascun carattere di formato corrisponde ad una porzione di dati da impacchettare con pack.

Impacchettare Numeri

Con i dati testuali abbiamo finito. Arriviamo allora alla "ciccia" vera, dove pack e unpack danno il loro meglio: gestire formati binari per i numeri. Ovviamente, non esiste un solo formato binario - la vita sarebbe troppo semplice - ma Perl farà tutto il lavoro certosino al posto vostro.

Interi

Impacchettare ed estrarre numeri implica una conversione verso, e da, una qualche rappresentazione binaria specifica. Lasciando perdere i numeri a virgola mobile per il momento, le proprietà veramente importanti di qualunque rappresentazione del genere sono:

  • il numero di ottetti utilizzati per immagazzinare l'intero,

  • se il contenuto va interpretato come numero con segno o privo di segno,

  • l'ordine degli ottetti, ossia se il primo ottetto rappresenta il byte più significativo o il meno significativo (o anche, rispettivamente, big-endian [finale grande, NdT] o little-endian [finale piccolo, NdT]).

Quindi, ad esempio, per impacchettare il numero 20302 in un intero a 16 bit con segno, nella rappresentazione del vostro computer, dovete scrivere

   my $ps = pack( 's', 20302 );

Di nuovo, il risultato è una stringa, che ora contiene due ottetti. Se stampate tale stringa (il che, in generale, non è consigliabile) potreste vedere ON oppure NO (in base all'ordinamento degli ottetti nel vostro sistema) - o qualcosa di completamente differente se il vostro computer non utilizza la codifica ASCII. Utilizzare unpack su $ps con lo stesso modello restituisce il valore intero originale:

   my( $s ) = unpack( 's', $ps );

Tutto ciò vale per tutti i codici di modello numerici. Non vi aspettate miracoli, comunque: se il valore impacchettato eccede la capacità in ottetti assegnata, i bit più significativi sono scartati senza colpo ferire, ed unpack certamente non ve li ritirerà fuori dal cilindro. Inoltre, quando impacchettate utilizzando un codice di modello come s, un valore in eccesso potrebbe avere come conseguenza che il bit di segno venga impostato, per cui lo spacchettamento vi tirerà fuori - molto intelligentemente - un valore negativo.

16 bit non vi porteranno molto lontano con gli interi, ma ci sono sempre l e L per gli interi con e senza segno a 32 bit. E se non vi bastassero nemmeno questi, ed aveste a disposizione interi a 64 bit nel vostro sistema, potete spingere i limiti più vicino all'infinito con i codici q e Q di pack. Un'eccezione degna di nota si ha con i codici i e I per gli interi con e senza segno della varietà "locale e personalizzata": un intero che occupa tanti ottetti quanto il compilatore C locale si aspetta con sizeof(int), ma utilizzerà almeno 32 bit.

Ciascuno dei codici per interi sSlLqQ dà come risultato un numero fisso di ottetti, indipendentemente da dove eseguiate il vostro programma. Questo fatto può risultare utile per alcune applicazioni, ma non è un modo portabile per passare strutture dati tra Perl ed i programmi C (il che succede di sicuro quando chiamate estensioni XS o la funzione Perl syscall), o quando leggete o scrivete file binari. Ciò di cui avete bisogno in questo caso sono dei codici di modello che rispecchino quello che viene utilizzato dal vostro compilatore C locale quando usate short o unsigned long, ad esempio. Tali codici e le relative lunghezze in ottetti sono mostrate nella tabella che segue. Poiché lo standard C lascia molta libertà riguardo alle dimensioni relative di questi tipi di dato, i valori effettivi possono variare, e questo è il motivo per cui i valori sono indicati attraverso espressioni in C ed in Perl. (Se volete utilizzare i valori da %Config nel vostro programma dovete importarla con use Config).

   con segno  senza segno  otteti, in C       ottetti, in Perl       
     s!            S!      sizeof(short)      $Config{shortsize}
     i!            I!      sizeof(int)        $Config{intsize}
     l!            L!      sizeof(long)       $Config{longsize}
     q!            Q!      sizeof(long long)  $Config{longlongsize}

I codici i! e I! non sono differneti da i e I, sono solo inclusi per completezza.

Spacchettare un Frame dello Stack

[lo Stack è la pila di attivazione di un programma, composta da tanti "mattoni" chiamati Frame, NdT]

Avere bisogno di un particolare ordinamento degli ottetti potrebbe esservi necessario quando lavorate con dati binari che provengono da una qualche architettura specifica, laddove il vostro programma potrebbe trovarsi a lavorare in un sistema completamente differente. Come esempio, immaginate di avere 24 ottetti che contengono uno stack frame come avviene in un 8086 Intel:

      +---------+        +----+----+               +---------+
 TOS: |   IP    |  TOS+4:| FL | FH | FLAGS  TOS+14:|   SI    |
      +---------+        +----+----+               +---------+
      |   CS    |        | AL | AH | AX            |   DI    |
      +---------+        +----+----+               +---------+
                         | BL | BH | BX            |   BP    |
                         +----+----+               +---------+
                         | CL | CH | CX            |   DS    |
                         +----+----+               +---------+
                         | DL | DH | DX            |   ES    |
                         +----+----+               +---------+

Prima di tutto, osserviamo che questa veneranda CPU a 16 bit utilizza un ordinamento a finale piccolo [little-endian, NdT], e che questo è il motivo per cui l'ottetto di ordine basso è immagazzinato all'indirizzo più basso. Per spacchettare un intero short (con segno) di tal fatta dovremo utilizzare il codice v. Un contatore di ripetizione ci consente di spacchettare tutti e 12 gli interi short:

   my( $ip, $cs, $flags, $ax, $bx, $cd, $dx, $si, $di, $bp, $ds, $es ) =
     unpack( 'v12', $frame );

In alternativa, avremmo potuto utilizzare C per spacchettare i registri byte FL, FH, AL, AH, ecc., individualmente accessibili:

   my( $fl, $fh, $al, $ah, $bl, $bh, $cl, $ch, $dl, $dh ) =
     unpack( 'C10', substr( $frame, 4, 10 ) );

Sarebbe carino se potessimo farlo in un solo passo: spacchettare uno short, tornare indietro un po', e poi spacchettare 2 ottetti. Visto che Perl è carino, mette a disposizione il codice di modello X per tornare indietro di un ottetto. Mettendo tutto insieme, possiamo allora scrivere:

   my( $ip, $cs,
       $flags,$fl,$fh,
       $ax,$al,$ah, $bx,$bl,$bh, $cx,$cl,$ch, $dx,$dl,$dh, 
       $si, $di, $bp, $ds, $es ) =
   unpack( 'v2' . ('vXXCC' x 5) . 'v5', $frame );

(La costruzione astrusa del modello può essere evitata - leggete!)

Ci siamo presi la briga di costruire il modello in modo che corrisponda al contenuto del nostro frame buffer. Altrimenti o avremmo ottenuto valori indefiniti, o unpack non avrebbe potuto fare proprio niente. Se pack rimane a corto di elementi, inserirà stringhe vuote (che sono trasformate in zeri laddove il codice di impacchettamento lo richiede).

Come Mangiare un Uovo su una Rete

Il codice di impacchettamento per il finale grande [big-endian, NdT] (ottetto di ordine alto all'indirizzo più basso) è n per interi a 16 bit e N per quelli a 32 bit. Utilizzate questi codici se sapete che i vostri dati arrivano da un'architettura compatibile ma, abbastanza sorprendentemente, dovreste anche utilizzare questi codici se scambiate dati binari, in una rete, con qualche sistema di cui non sapete niente. La ragione più semplice è che questo ordine è stato scelto come ordine di rete, e tutti i bravi programmi timorati degli standard dovrebbero seguire questa convenzione. (Questo è, chiaramente, un saldo supporto per uno dei partiti Lillipuziani, e può ben influire sugli sviluppi politici di Lilliput). Quindi, se il protocollo prevede che mandiate un messaggio inviando prima di tutto la sua lunghezza, seguita da quel tal numero di ottetti, potreste scrivere:

   my $buf = pack( 'N', length( $msg ) ) . $msg;

o persino:

   my $buf = pack( 'NA*', length( $msg ), $msg );

e passare $buf alla vostra routine di invio. Alcuni protocolli richiedono che il conteggio includa anche la lunghezza del contatore stesso: in questo caso basterà aggiungere 4 alla lunghezza dei dati. (Ma assicuratevi di leggere "Lunghezze e Larghezze" prima di fare una cosa del genere!)

Numeri a Virgola Mobile

Per impacchettare numeri in virgola mobile dovete scegliere fra i codici f e d, che impacchettano (o spacchettano) in rappresentazioni a singola precisione o doppia precisione, così come fornite dal vostro sistema. (Non esiste una vera rappresentazione di rete per i reali, quindi se volete inviare i vostri numeri reali oltre i confini di un computer, è meglio se vi attenete ad una rappresentazione ASCII, a meno che non siate assolutamente sicuri di cosa trovate dall'altra parte della linea).

Modelli Esotici

Stringhe di Bit

I bit sono gli atomi del mondo della memoria. L'accesso ai bit individuali potrebbe essere necessario o come ultima spiaggia o perché è il modo più conveniente di trattare i vostri dati. L'impacchettamento (o lo spacchettamento) in stringhe di bit converte fra stringhe che contengono una serie di caratteri 0 ed 1 e una sequenza di ottetti, ognuna contenente un gruppo di 8 bit. È proprio semplice come sembra, ad eccezione del fatto che ci sono due modi in cui il contenuto di un ottetto può essere scritto come stringa di bit. Diamo un'occhiata ad un ottetto annotato:

     7 6 5 4 3 2 1 0
   +-----------------+
   | 1 0 0 0 1 1 0 0 |
   +-----------------+
    MSB           LSB

[MSB sta per Most Significant Bit, ossia il bit più significativo, quello di peso più alto; analogamente, LSB sta per "Least Significant Bit", ossia bit meno significativo. NdT]

Si tratta di nuovo di mangiar uova: alcuni pensano che, come stringa di bit, questo ottetto debba essere scritto come "10001100", ossia a partire dal bit più significativo, mentre altri insistono per avere "00110001". Bene, Perl non fa preferenze, per cui abbiamo due codici per le stringhe di bit:

   $byte = pack( 'B8', '10001100' ); # inizia con MSB
   $byte = pack( 'b8', '00110001' ); # inizia con LSB

Non è possibile impacchettare o spacchettare campi di bit - solo ottetti interi. pack parte sempre dal bordo dell'ottetto successivo, ed "arrotonda" al multiplo successivo di 8 aggiungendo degli zero se necessario. (Se proprio volete i campi di bit, esiste sempre la funzione "vec" in perlfunc. In alternativa, potreste implementare la gestione del campo di bit a livello di stringa di caratteri, utilizzando split, substr e la concatenazione su stringhe di bit spacchettati).

Per illustrare lo spacchettamento di stringhe di bit, effettueremo la decomposizione di un semplice registro di stato (un carattere "-" indica un bit "riservato"):

   +-----------------+-----------------+
   | S Z - A - P - C | - - - - O D I T |
   +-----------------+-----------------+
    MSB           LSB MSB           LSB

La conversione di questi due ottetti in una stringa può essere fatta con il modello di spacchettamento 'b16'. Per ottenere i valori dei singoli bit dalla stringa di bit utilizziamo split con il pattern di separazione vuoto, che suddivide la stringa nei singoli caratteri. I valori dei bit dalle posizioni "riservate" sono assegnate semplicemente ad undef, una notazione piuttosto comoda per indicare "Non mi importa di che fine faccia questo".

   ($carry, undef, $parity, undef, $auxcarry, undef, $zero, $sign,
    $trace, $interrupt, $direction, $overflow) =
      split( //, unpack( 'b16', $status ) );

Avremmo anche potuto utilizzare un modello di spacchettamento 'b12', poiché gli ultimi 4 bit possono essere ignorati.

Uuencode

Un altro strano tipo nell'alfabeto dei modelli di impacchettamento e spacchettamento è u, che impacchetta una "stringa con uuencode". ("uu" è l'abbreviazione di Unix-to-Unix [da Unix a Unix, NdT]). Ci sono buone probabilità che non avrete mai bisogno di questa tecnica di encoding, che fu inventata per superare le limitazioni di mezzi trasmissivi datati che non supportano altro che semplici dati ASCII. La ricetta essenziale è semplice: prendete tre ottetti, o 24 bit. Divideteli in 4 pacchetti da sei bit l'uno, aggiungendo uno spazio (0x20) a ciascuno. Ripetete finché non avete mescolato tutti i dati. Ripiegate gruppi di 4 ottetti in righe non più lunghe di 60 ottetti, e guarnite aggiungendo all'inizio il conteggio degli ottetti originali (incrementati con 0x20), e un "\n" alla fine. - Lo chef di pack preparerà tutto questo al posto vostro, espresso, quando selezionate il codice di impacchettamento u dal menu:

   my $uubuf = pack( 'u', $bindat );

Un indicatore di ripetizione dopo u imposta il numero di ottetti da inserire in una riga dopo l'encoding uu, che è il massimo di 45 per default, ma potrebbe essere impostato ad un qualsiasi multiplo intero di tre inferiore a 45. unpack ignora l'indicatore di ripetizione.

Fare le Somme

Un codice di modello persino più strano è %<number>. Prima di tutto, perché viene utilizzato come prefisso per qualche altro codice di modello. Secondo poi, perché non può mai essere utilizzato in pack, e terzo, in unpack, non restituisce il dato definito dal codice di modello che lo segue. Invece, vi darà un intero contenente il numero di bit che risulta dal valore del dato mediante delle somme. Per codici di spacchettamento numerici, non si ha nessuna caratteristica particolare:

    my $buf = pack( 'iii', 100, 20, 3 );
    print unpack( '%32i3', $buf ), "\n";  # stampa 123

Per i valori stringa, % restituisce la somma dei valori degli ottetti, levandovi dall'impaccio di effettuare un ciclo di somma con substr e ord

    print unpack( '%32A*', "\x01\x10" ), "\n";  # prints 17

Sebbene la documentazione del codice % riporti che esso restituisce un "checksum" [una somma di controllo, N.d.T.]: non vi fidate di questo valore! Anche quando applicato ad un esiguo numero di ottetti, non vi garantirà una distanza di Hamming notevole.

In connessione con b o B, % non fa altro che aggiungere bit, e questo può essere utilizzato per contare insiemi di bit in maniera efficiente:

    my $conteggiobit = unpack( '%32b*', $mask );

Ed un bit di parità può essere determinato come segue:

    my $parita = unpack( '%1b*', $mask );

Unicode

Unicode è un insieme di caratteri che può rappresentare la maggior parte dei caratteri nella maggior parte delle lingue al mondo, avendo spazio per più di un milione di caratteri differenti. Unicode 3.1 specifica 94140 caratteri: i caratteri Basic Latin [Latino base, NdT] sono posizionati ai numeri 0 - 127. Il Supplemento Latin-1, contenente caratteri che sono utilizzati in molti linguaggi europei, si trovano nell'intervallo successivo, fino a 255. Dopo qualche altra estensione Latin troviamo gli insiemi di caratteri da linguaggi che utilizzano alfabeti non Romani, intervallati con una varietà di insiemi di simboli come i segni per le monete, Zapf Dingbat e Braille. (Potreste sempre fare una visita su www.unicode.org per dare un'occhiata a qualcuno - i miei preferiti sono Telugu e Kannada).

L'insieme di caratteri Unicode associa caratteri ed interi. La codifica di questi numeri in un numero equivalente di ottetti avrebbe l'effetto di più che raddoppiare i requisiti di immagazzinamento di testi scritti con alfabeti latini. La codifica UTF-8 evita questo spreco immagazzinando i caratteri più comuni (da un punto di vista del mondo occidentale) in un singolo ottetto, mentre i più rari sono immagazzinati in tre o più ottetti.

Dunque, che cosa ha a che fare tutto ciò con pack? Bene, se volete convertire fra un numero Unicode e la sua rappresentazione UTF-8, potete farlo utilizzando il codice U. Come esempio, generiamo la rappresentazione UTF-8 del simbolo dell'Euro (codice 0x20AC):

   $UTF8{Euro} = pack( 'U', 0x20AC );

Andando a vedere $UTF8{Euro} possiamo notare che contiene tre ottetti: "\xe2\x82\xac". Il giro può essere completato con unpack:

   $Unicode{Euro} = unpack( 'U', $UTF8{Euro} );

Di solito vorrete impacchettare o spacchettare intere stringhe UTF-8:

   # pack e unpack dell'alfabeto Ebraico
   my $alefbeto = pack( 'U*', 0x05d0..0x05ea );
   my @ebraico = unpack( 'U*', $utf );

Un'altra Codifica Binaria Portabile

Il codice di impacchettamento w è stato aggiunto per supportare uno schema di codifica che va al di là dei semplici interi. (Potete trovare maggiori dettagli su Casbah.org, il progetto Scarab). Un intero senza segno compresso BER (Binary Encoded Representation [Rappresentazine Binaria Codificata, NdT] contiene cifre in base 128, con la cifra più significativa per prima, utilizzando meno cifre possibile. L'ottavo bit (quello più significativo) è impostato ad 1 su tutti gli ottetti eccetto l'ultimo. Non esiste un limite di dimensione per la codifica BER, ma Perl non si spingerà fino agli estremi.

   my $berbuf = pack( 'w*', 1, 128, 128+1, 128*128+127 );

Una stampa esadecimale di $berbuf, con spazi inseriti nei posti giusti, è 01 8100 8101 81807F. Poiché l'ultimo ottetto è sempre inferiore a 128, unpack sa dove fermarsi.

Raggruppamento di Modelli

Prima di Perl 5.8, la ripetizione dei modelli doveva esser fatta con l'utilizzo dell'operatore x sulle stringhe dei modelli stessi. Ora esiste un modo migliore, perché possiamo utilizzare i codici ( e ) in combinazione con un contatore di ripetizione. Il modello unpack dall'esempio sullo Stack Frame può scriversi semplicemente così:

   unpack( 'v2 (vXXCC)5 v5', $frame )

Diamo un'occhiata più da vicino a questa opportunità. Cominciamo con l'equivalente di

   join( '', map( substr( $_, 0, 1 ), @str ) )

che restituisce una stringa composta del primo carattere di ciascuna stringa. Utilizzando pack, possiamo scrivere

   pack( '(A)'.@str, @str )

o, viso che il contatore di ripetizione * significa "ripeti quanto basta", semplicemente

   pack( '(A)*', @str )

(Osservate che il modello A* abrebbe solo impacchettato $str[0] a lunghezza piena).

Per impacchettare le date immagazzinandole come triplette (giorno, mese, anno) da un array @date in una sequenza di ottetto, otteto, short [intero breve, costituito da 2 ottetti, NdT] possiamo scrivere

   $pd = pack( '(CCS)*', map( @$_, @date ) );

Per invertire coppie di caratteri in una stringa (di lunghezza pari) possono essere utilizzate varie tecniche. Per prima cosa, utilizziamo x e X per saltare avanti ed indietro:

   $s = pack( '(A)*', unpack( '(xAXXAx)*', $s ) );

Possiamo anche utilizzare @ per saltare ad un determinato scostamento nella stringa, con 0 che rappresenta la posizione dove ci trovavamo l'ultima volta che si è encontrato (:

   $s = pack( '(A)*', unpack( '(@1A @0A @2)*', $s ) );

Infine, esiste un approccio completamente differente basato sullo spacchettamento di interi short a finale grande e nel successivo re-impacchettamento in ordine di ottetti invertiti:

   $s = pack( '(V)*', unpack( '(n)*', $s );

Lunghezze e larghezze

Lunghezza delle stringhe

Nella sezione precedente abbiamo visto un messaggio di rete costruito mettendo la lunghezza del messaggio binario come prefisso al messaggio vero e proprio. Vi capiterà di trovare che l'impacchettamento di una lunghezza seguita da quel certo numero di ottetti di dati è una ricetta utilizzata comunemente, perché aggiungere un ottetto nullo in fondo non funziona correttamente se gli ottetti nulli possono far parte dei dati stessi. Ecco un esempio dove vengono utilizzate entrambe le tecniche: dopo due stringhe terminate con ottetti nulli, viene mandato uno Short Message (ad un terminale mobile) dopo un ottetto che indica la lunghezza:

   my $msg = pack( 'Z*Z*CA*', $src, $dst, length( $sm ), $sm );

Lo spacchettamento di questo messaggio può essere effettuato con lo stesso modello:

   ( $src, $dst, $len, $sm ) = unpack( 'Z*Z*CA*', $msg );

C'è una sottile trappola che si aggira: aggiungere un altro campo dopo lo Short Message (nella variabile $sm) va bene in fase di impacchettamento, ma non può essere spacchettato alla leggera:

   # Impacchetta un messaggio
   my $msg = pack( 'Z*Z*CA*C', $src, $dst, length( $sm ), $sm, $prio );

   # unpack fallisce - $prio rimane non definito!
   ( $src, $dst, $len, $sm, $prio ) = unpack( 'Z*Z*CA*C', $msg );

Il codice di impacchettamento A* cattura tutti gli ottetti rimanenti, e $prio rimane non definito! Prima di lasciarci abbattere il morale: Perl ha l'asso per fare anche questo, solo che sta un po' più su... nella manica. Guardate qui:

   # impacchetta un messaggio: ASCIIZ, ASCIIZ, lunghezza/stringa, ottetto
   my $msg = pack( 'Z* Z* C/A* C', $src, $dst, $sm, $prio );

   # spacchetta
   ( $src, $dst, $sm, $prio ) = unpack( 'Z* Z* C/A* C', $msg );

Combinando due codici con una barra (/) si associano ad un singolo valore dalla lista degli argomenti. In pack, la lunghezza dell'argomento viene presa ed impacchettata in accordo al primo codice, mentre l'argomento stesso viene aggiunto con la conversione del codice dopo la barra. Il tutto ci risparmia la chiamata a length, ma in unpack che abbiamo il vero vantaggio: il valore dell'ottetto della lunghezza segna la fine della stringa che deve essere estratta dal buffer. Questa combinazione non ha senso eccetto che nei casi in cui il secondo codice di impacchettamento sia a*, A* o Z*.

Il codice di impacchettamento che precede / può essere uno qualsiasi fra quelli adatti a rappresentare un numero: tutti i codici di impacchettamento binario dei numeri, e perfino codici testuali come A4 o Z*:

   # pack/unpack di una stringa preceduta dalla sual lunghezza in ASCII
   my $buf = pack( 'A4/A*', "Humpty-Dumpty" );
   # unpack $buf: '13  Humpty-Dumpty'
   my $txt = unpack( 'A4/A*', $buf );

/ non è presente nelle versioni di Perl precedenti a 5.6, per cui se il vostro codice deve essere eseguito in Perl più vecchi avrete bisogno di utilizzare unpack( 'Z* Z* C' ) per prendere la lunghezza, e poi utilizzare questa per costruire una nuova stringa di spacchettamento. Ad esempio

   # impacchetta un messaggio: ASCIIZ, ASCIIZ, lunghezza, stringa, ottetto
   # (compatibile con la versione 5.005 di Perl)
   my $msg = pack( 'Z* Z* C A* C', $src, $dst, length $sm, $sm, $prio );

   # spacchetta
   ( undef, undef, $len) = unpack( 'Z* Z* C', $msg );
   ($src, $dst, $sm, $prio) = unpack ( "Z* Z* x A$len C", $msg );

Con il secondo unpack stiamo correndo un po' troppo. Non sta utilizzando una semplice stringa letterale come modello. Per questo motivo, forse è il caso di presentarvi...

Modelli Dinamici

Fino ad ora, abbiamo visto modelli composti di soli letterali. Se la lista degli elementi di impacchettamento non ha una lunghezza prefissata, c'è bisogno di un'espressione che costruisca il modello (in tutti quei casi in cui, per qualche motivo, non sia possibile utilizzare ()*). Ecco un esempio: per immagazzinare stringhe con un nome associato in modo che possano essere facilmente comprese da un programma C, creiamo una sequenza di nomi e stringhe ASCII terminate da un ottetto nullo, con = utilizzato come separatore fra nome e valore, seguito da un ottetto nullo addizionale. Ecco come:

   my $env = pack( '(A*A*Z*)' . keys( %Env ) . 'C',
                   map( { ( $_, '=', $Env{$_} ) } keys( %Env ) ), 0 );

Diamo un'occhiata ai dentini di questo ingranaggio, uno ad uno. C'è la chiamata a map, che crea gli elementi che vogliamo infilare nel buffer $env: per ciascuna chiave (in $_) aggiunge il separatore = ed il valore nella hash. Ciascuna tripletta viene impacchettata con la sequenza di modello A*A*Z* che viene ripetuta in base al numero di chiavi. (Esatto, è ciò che la funzione keys restituisce in contesto scalare). Per avere l'ultimo ottetto nullo di conclusione, aggiungiamo un 0 alla fine della lista di pack, per impacchettarlo con C. (I lettori più attenti potranno aver notato che avremmo potuto anche risparmiarci lo 0).

Per l'operazione inversa, dovremo determinare il numero di elementi nel buffer prima di lasciare che unpack li tiri fuori:

   my $n = $env =~ tr/\0// - 1;
   my %env = map( split( /=/, $_ ), unpack( "(Z*)$n", $env ) );

La chiamata a tr conta gli ottetti nulli. La chiamata a unpack restituisce una lista di coppie nome-valore, ciascuna delle quali viene separata nel blocco map.

Contare le ripetizioni

Piuttosto che aggiungere una sentinella alla fine di un dato (o di una lista di elementi), potremmo precedere i dati con un conteggio. Di nuovo, impacchettiamo chiavi e valori di una hash, precedendo ciascuno con un contatore di lunghezza di tipo intero corto senza segno [unsigned short, NdT], ed all'inizio immettiamo il numero di coppie:

   my $env = pack( 'S(S/A* S/A*)*', scalar keys( %Env ), %Env );

Questo semplifica l'operazione inversa, poiché il numero di ripetizioni può essere estratto con il codice /:

   my %env = unpack( 'S/(S/A* S/A*)', $env );

Osservate che questo è uno dei rari casi nei quali non potete utilizzare lo stesso modello per pack e unpack perché pack non è in grado di determinare un contatore di ripetizione per il gruppo ().

Impacchettare e Spacchettare Strutture C

Nelle sezioni precedenti abbiamo visto come impacchettare numeri e stringhe di caratteri. Se non fosse per un paio di trappole potremmo concludere qui, con una nota chiara che le strutture C non contengono nient'altro, e che perciò già sapete tutto quello che c'è da sapere. Spiacenti, non è così: leggete e capirete.

La Fossa dell'Allineamento

In un confronto fra requisiti velocità e memoria la bilancia è stata spostata verso un'esecuzione più rapida. Cià ha influenzato il modo in cui i compilatori C allocano la memoria per le strutture: su architetture nelle quali operandi a 16 e 32 bit possono essere spostati più rapidamente fra locazioni di memoria, oppure da/verso registri della CPU, quando questi siano allineati ad indirizzi pari, o multipli di quattro o persino multipli di otto ottetti, un compilatore vi darà questo beneficio di velocità inserendo ottetti extra nelle strutture. Se non attraversate la banchina del C non vi darà problemi (sebbene dobbiate aver cura quando progettate strutture dati particolarmente grandi, o volete che il vostro codice sia portabile fra le varie architetture (e voi lo volete, giusto?)).

Per vedere come questo influisca su pack e unpack, confronteremo le seguenti strutture C:

   typedef struct {
     char     c1;
     short    s;
     char     c2;
     long     l;
   } groviera_t;

   typedef struct {
     long     l;
     short    s;
     char     c1;
     char     c2;
   } densa_t;

Tipicamente, un compilatore C alloca 12 ottetti per una variabile groviera_t, ma richiede solo 8 ottetti per una densa_t. Dopo un po' di studio, possiamo disegnare le mappe di memoria, che mostrano dove sono nascosti questi 4 ottetti aggiuntivi:

   0           +4          +8          +12
   +--+--+--+--+--+--+--+--+--+--+--+--+
   |c1|xx|  s  |c2|xx|xx|xx|     l     |    xx = ottetto di riempimento
   +--+--+--+--+--+--+--+--+--+--+--+--+
   groviera_t

   0           +4          +8
   +--+--+--+--+--+--+--+--+
   |     l     |  h  |c1|c2|
   +--+--+--+--+--+--+--+--+
   densa_t

E qui è dove colpisce la prima stranezza: i modelli di pack e unpack devono essere riempiti con codici x per arrivare a questi ottetti di riempimento addizionali.

L'ovvia domanda: "Perché Perl non compensa da solo questi buchi?" merita una risposta. Una buona ragione è che il compilatore C potrebbe fornire delle estensioni (non ANSI) che consentono tutte le varietà di controllo sul modo in cui le strutture vengono allineate, persino a livello di singolo campo della struttura. E, se non fosse già abbastanza, esiste una cosa insidiosa chiamata union dove il numero di ottetti di riempimento non può essere dedotta solo dall'allineamento dell'elemento del prossimo elemento.

OK, via il dente, via il dolore. Ecco un modo per sistemare l'allineamento inserendo i codici di modello x, che non prendono elementi dalla lista:

  my $gappy = pack( 'cxs cxxx l!', $c1, $s, $c2, $l );

Osservate il ! dopo l: vogliamo essere sicuri che impacchettiamo un intero lungo [long integer, NdT] come fatto dal nostro compilatore C. Persino ora, però, funzionerà solamente nelle piattaforme dove il compilatore allinea come descritto in precedenza. E qualcuno, da qualche parte, usa una piattaforma che non lo fa. (Probabilmente un Cray, dove short, int e long sono tutti di 8 ottetti :-)).

Contare gli ottetti e star dietro agli allineamenti in strutture corpose è destinato ad essere un peso. Non esiste un modo con cui possiamo creare un modello utilizzando un semplice programma? Ecco un programmino C che fa questo trucco:

   #include <stdio.h>
   #include <stddef.h>

   typedef struct {
     char     fc1;
     short    fs;
     char     fc2;
     long     fl;
   } groviera_t;

   #define Pt(struttura,campo,tchar) \
     printf( "@%d%s ", offsetof(struttura,campo), # tchar );

   int main() {
     Pt( groviera_t, fc1, c  );
     Pt( groviera_t, fs,  s! );
     Pt( groviera_t, fc2, c  );
     Pt( groviera_t, fl,  l! );
     printf( "\n" );
   }

La linea stampata in uscita può essere utilizzata come modello nella chiamata a pack o unpack:

  my $groviera = pack( '@0c @2s! @4c @8l!', $c1, $s, $c2, $l );

Cribbio, un altro codice di modello - come se non ne avessimo abbastanza. Ma @ ci salva la giornata dandoci modo di specificare gli scostamenti dall'inizio del buffer di pack fino all'elemento successivo: questo è appunto il valore restituito dalla macro offsetof (definita in <stddef.h>) quando le viene dato un tipo struttura ed uno dei nomi dei suoi campi (in linguaggio standard C il member-designator [indicatore di membro, NdT]).

Né l'utilizzo degli scostamenti né l'aggiunta di x è soddisfacente per riempire i buchi. (Provate solo ad immaginare cosa succederebbe se cambiasse la struttura). Ciò di cui abbiamo realmente bisogno è un sistema per dire "salta quel tanto di ottetti che basta per avere il prossimo multiplo di N". In Modellese fluente, potete dirlo con x!N, ove N va rimpiazzato con il valore appropriato. Ecco una nuova versione del nostro sistema di impacchettamento per strutture:

  my $groviera = pack( 'c x!2 s c x!4 l!', $c1, $s, $c2, $l );

Andiamo indubbiamente meglio, ma dobbiamo ancora sapere quanto sono lunghi gli interi, e siamo lontani dalla portabilità. Piuttosto che 2, ad esempio, vogliamo esprimere "qualsiasi lunghezza sia uno short". Ma questo possiamo farlo racchiudendo il codice di impacchettamento appropriato fra parentesi quadre: [s]. Quindi, ecco il meglio che possiamo fare:

  my $groviera = pack( 'c x![s] s c x![l!] l!', $c1, $s, $c2, $l );

Allineamento, seconda ripresa

Ho paura che non abbiamo ancora finito con questa storia dell'allineamento. L'idra solleva un'altra brutta testaccia quando impacchettate array di strutture:

   typedef struct {
     short    conteggio;
     char     glifo;
   } cella_t;

   typedef cella_t buffer_t[BUFLEN];

Dove sta la trappola? Il padding [l'operazione di aggiunta di ottetti per raggiungere una determinata lunghezza, NdT] non è richiesto né prima del primo campo counteggio, né fra questo ed il campo glifo successivo, quindi perché non possiamo impacchettare semplicemente come segue:

   # something goes wrong here:
   pack( 's!a' x @buffer,
         map{ ( $_->{conteggio}, $_->{glifo} ) } @buffer );

Questo impacchetta 3*@buffer ottetti, ma alla fine abbiamo che la dimensione di buffer_t è quattro volte BUFLEN! Il morale della storia è che l'allineamento richiesto per una struttura o un array viene propagato al livello superiore successivo dove dobbiamo considerare di effettuare padding anche alla fine di ciascun componente. Per questo motivo, il modello corretto è:

   pack( 's!ax' x @buffer,
         map{ ( $_->{conteggio}, $_->{glifo} ) } @buffer );

Alignment, terza ripresa

Ed anche tenendo tutto questo in conto, ANSI consente ancora che questo:

   typedef struct {
     char     pippo[2];
   } pippo_t;

possa avere dimensione variabile. Il vincolo di allineamento della struttura può essere maggiore di ciascuno dei suoi elementi. (E se pensate che questo non abbia impatti su niente di comune, aprite il prossimo telefono cellulare che vedete. Molti hanno processori ARM, e le regole di struttura ARM sono tali che sizeof(pippo_t) == 4).

Puntatori per Come Utilizzarli

Il titolo di questa sezione indica il secondo problema in cui potete imbattervi prima o poi, quando impacchettate strutture C. Se la funzione che intendete chiamare si aspetta di ricevere, diciamo, un valore void *, non potete prendere semplicemente un riferimento ad una variabile Perl. (Sebbene il valore sia di sicuro un indirizzo di memoria, non è l'indirizzo dove sono immagazzinati i contenuti della variabile).

Il codice di modello P si impegna ad impacchettare un "puntatore ad una stringa di lunghezza fissa". Non è quel che vogliamo? Proviamo:

    # alloca un po' di spazio ed impacchetta un puntatore ad esso
    my $memoria = "\x00" x $dimensione;
    my $memptr = pack( 'P', $memoria );

Aspettate: pack non restituisce semplicemente una sequenza di ottetti? Come facciamo a passare questa stringa di ottetti ad un qualche codice C che si aspetta un puntatore che, dopo tutto, altro non è che un numero? La risposta è semplice: otteniamo l'indirizzo numerico dagli ottetti restituiti da pack.

    my $ptr = unpack( 'L!', $memptr );

Ovviamente si sta assumendo che sia possibile effettuare una forzatura di tipo da puntatore ad intero lungo senza segno, e viceversa, il che funziona di frequente ma che non può essere preso come legge universale. Ora che abbiamo questo puntatore la prossima domanda è: come utilizzarlo per bene? Abbiamo bisogno di chiamare una funzione C che si aspetta di ricevere un puntatore. Ci viene in mente la chiamata di sistema read(2):

    ssize_t read(int fd, void *buf, size_t count);

Dopo aver letto come utilizzare syscall in perlfunc, possiamo scrivere questa funzione Perl che copia un file sullo standard output:

    require 'syscall.ph';
    sub cat($){
        my $percorso   = shift();
        my $dimensione = -s $path;
        my $memoria    = "\x00" x $dimensione;  # alloca un po' di memoria
        my $ptr = unpack( 'L', pack( 'P', $memoria ) );
        open( F, $percorso ) || die( "$path:  errore open ($!)\n" );
        my $fd = fileno(F);
        my $res = syscall( &SYS_read, fileno(F), $ptr, $dimensione );
        print $memoria;
        close( F );
    }

Non è né un esempio di semplicità né un paragone di portabilità, ma descrive bene la situazione: siamo in grado di scivolare dietro le quinte per accedere alla altrimenti ben sorvegliata memoria di Perl! (Una nota importante: la funzione syscall di Perl non vi richiede di costruire i puntatori in questa maniera complicata. Passate semplicemente una variabile stringa, e Perl fa il resto inoltrando il suo indirizzo).

Come funziona unpack con P? Immaginate un puntatore qualsiasi nel buffer che si sta per spacchettare: se non si tratta del puntatore nullo (che molto intelligentemente produrrà un valore undef) abbiamo un indirizzo di partenza - ma poi? Perl non ha modo di sapere quanto sia lunga questa "stringa di lunghezza fissa"), per cui sta a voi specificare la reale dimensione come lunghezza esplicita dopo P.

   my $mem = "abcdefghijklmn";
   print unpack( 'P5', pack( 'P', $mem ) ); # stampa "abcde"

Di conseguenza, pack ignora qualsiasi numero o * dopo P.

Ora che abbiamo visto P al lavoro, potremmo fare un giro su p. Perché abbiamo bisogno di un secondo codice di modello per impacchettare i puntatori? La risposta giace dietro il semplice fatto che una unpack con p si impegna a restituire una stringa terminata da un ottetto nullo a partire dall'indirizzo preso dal buffer, e questo implica una lunghezza per l'elemento di dati che deve essere restituito:

   my $buf = pack( 'p', "abc\x00efhijklmn" );
   print unpack( 'p', $buf );    # stampa "abc"

In ogni caso questo porta a della confusione: come risultato del fatto che la lunghezza è conseguenza della lunghezza della stringa, un numero dopo il codice di impacchettamento p è un contatore di ripetizione, non una lunghezza come dopo P.

L'utilizzo di pack(..., $x) con P o p per ottenere l'indirizzo dove $x è effettivamente immagazzinata, deve essere effettuato con circospezione. Il codice interno di Perl considera la relazione fra una variabile e quell'indirizzo un qualcosa di veramente privato, e si disinteressa se ne abbiamo ottenuto una copia. Perciò:

  • Non utilizzate pack con p o P per ottenere l'indirizzo di una variabile che finirà fuori dal campo di visibilità [scope, NdT] (e la cui memoria sarà pertanto liberata) prima che abbiate terminato di utilizzare la memoria a quell'indirizzo.

  • State molto attenti alle operazioni Perl che cambiano il valore della variabile. Aggiungere qualcosa alla variabile, ad esempio, potrebbe richiedere una riallocazione, lasciandovi con un puntatore verso la terra di nessuno.

  • Non pensate di poter accedere al'indirizzo di una variabile Perl quando è immagazzinata come numero intero o a doppia precisione! pack('P', $x) forzerà la rappresentazione interna della variabile su una stringa, proprio come se aveste scritto qualcosa tipo $x .= ''.

In ogni caso, è sicuro utilizzare P o p per impacchettare una stringa letterale, perché Perl alloca semplicemente una variabile anonima.

Ricette di Impacchettamento

Ecco un po' di ricette preconfezionate (possibilmente) utili per pack e unpack:

    # Converti indirizzi IP per le funzioni sui socket
    pack( "C4", split /\./, "123.4.5.6" ); 

    # Conta i bit in un segmento di memoria (per esempio un vettore select)
    unpack( '%32b*', $mask );

    # Determina il tipo di finale del vostro sistema
    $a_finale_piccolo = unpack( 'c', pack( 's', 1 ) );
    $a_finale_granden = unpack( 'xc', pack( 's', 1 ) );

    # Determina il numero di bit in un intero nativo
    $numero_bit = unpack( '%32I!', ~0 );

    # Prepara il parametro per la chiamata di sistema nanosleep
    my $specifica_temporale = pack( 'L!L!', $secondi, $nanosecondi );

Per una semplice stampata della memoria spacchettiamo alcuni ottetti in un numero corrispondente di coppie di cifre esadecimali, ed utilizziamo map per gestire la spaziatura tradizionale - 16 ottetti per riga:

    my $i;
    print map( ++$i % 16 ? "$_ " : "$_\n",
               unpack( 'H2' x length( $mem ), $mem ) ),
          length( $mem ) % 16 ? "\n" : '';

Sezione Amenità

    # Estraiamo cifre dal nulla...
    print unpack( 'C', pack( 'x' ) ),
          unpack( '%B*', pack( 'A' ) ),
          unpack( 'H', pack( 'A' ) ),
          unpack( 'A', unpack( 'C', pack( 'A' ) ) ), "\n";

    # Eccone una utile per la strada ;-)
    my $consiglio = pack( 'tutto quel che puoi nel furgoncino' );

Autori

Simon Cozens e Wolfgang Laun.

TRADUZIONE

Versione

La versione su cui si basa questa traduzione è ottenibile con:

   perl -MPOD2::IT -e print_pod perlpacktut

Per maggiori informazioni sul progetto di traduzione in italiano si veda http://pod2it.sourceforge.net/ .

Traduttore

Traduzione a cura di Flavio Poletti.

Revisore

Revisione a cura di dree.