[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico] [home] [volume] [parte]


Capitolo 597.   C: «stdlib.h»

Il file stdlib.h della libreria standard definisce alcuni tipi di dati, varie funzioni di utilità generale e alcune macro-variabili. Viene proposto un esempio di questo file, e di alcune delle funzioni a cui si riferisce, indicando per le altre solo i prototipi.

597.1   Tipi di dati speciali

I tipi di dati che il file stdlib.h definisce sono size_t e wchar_t, già descritti nel file stddef.h (capitolo 601), oltre a div_t, ldiv_t e lldiv_t. I tipi ...div_t sono delle strutture il cui scopo è quello di contenere il risultato di una divisione, espresso come quoziente e resto. Questi tipi di dati si usano per contenere il valore restituito dalle funzioni div(), ldiv() e lldiv(). La distinzione tra i tre tipi deriva dalla capienza dei membri della struttura. Ecco come potrebbero essere dichiarati:

typedef struct {int quot; int rem;} div_t;
typedef struct {long int quot; long int rem;} ldiv_t;
typedef struct {long long int quot; long long int rem;} lldiv_t;

597.2   Macro-variabili

Il file stdlib.h dichiara nuovamente la macro-variabile NULL, come già avviene nel file stddef.h (capitolo 601); inoltre definisce quelle seguenti:

Macro Descrizione
EXIT_SUCCESS
EXIT_FAILURE
Rappresentano un numero intero che possa essere usato come argomento della funzione exit(), per rappresentare il successo o l'insuccesso dell'attività svolta. Normalmente, i valori in cui si espandono le due macro-variabili sono rispettivamente zero e uno.
RAND_MAX
Rappresenta il valore massimo che possa essere generato dalla funzione rand() e generalmente corrisponde al valore massimo che può assumere un numero intero di tipo int.
MB_CUR_MAX
Rappresenta la quantità massima di byte che possono essere utilizzati in una sequenza multibyte, in base alla configurazione locale. Questo valore è un intero che deve essere di tipo size_t è non può superare il limite rappresentato dalla macro-variabile MC_LEN_MAX (dichiarata nel file limits.h, descritto nel capitolo 591).

Merita un po' di attenzione la macro-variabile MB_CUR_MAX. La sequenza multibyte è una sequenza di byte che, in base alla configurazione locale, deve essere interpretata come un carattere singolo. Per esempio, questo meccanismo si utilizza nella codifica UTF-8 e in altre; ma proprio perché esistono più metodi alternativi, per quanto superati possano essere rispetto a UTF-8, la configurazione locale stabilisce le regole particolari per interpretare tali sequenze e i limiti rispetto a queste. Pertanto, la macro-variabile MB_CUR_MAX dovrebbe espandersi in una funzione che restituisce il valore desiderato, in relazione alla configurazione locale che si trova a essere attiva in un certo momento. Per semplicità, nell'esempio che viene proposto si associa il valore di questa macro-variabile a quello massimo accettabile in assoluto.

#define EXIT_FAILURE    1
#define EXIT_SUCCESS    0

#define RAND_MAX        INT_MAX

#define MB_CUR_MAX      ((size_t) MB_LEN_MAX)  // Sarebbe meglio una funzione.

Nell'esempio proposto viene usata la macro-variabile MB_LEN_MAX, pertanto, in questo modo si rende necessaria l'inclusione del file limits.h che deve contenere la sua dichiarazione.

597.3   Conversioni numeriche

Un gruppo di funzioni del file stdlib.h permette di convertire una stringa in un valore numerico. In particolare, le funzioni con nomi del tipo ato...() (ASCII to ...) non eseguono controlli particolari e non modificano la variabile errno (capitolo 593); invece, le funzioni con nomi strto...() (string to ...) sono più sofisticate.

Le funzioni ato...() interpretano una stringa e convertono il suo contenuto in un numero intero o in un numero a virgola mobile. Le funzioni sono atoi(), atol(), atoll() e atof(), che convertono rispettivamente in un tipo int, long int, long long int e double. Ecco i prototipi:

int           atoi  (const char *nptr);
long int      atol  (const char *nptr);
long long int atoll (const char *nptr);
double        atof  (const char *nptr);

Viene proposta una soluzione per queste funzioni di conversione:

#include <stdlib.h>
#include <ctype.h>
int
atoi (const char *nptr)
{
    int i;
    int sign = +1;
    int n;
    
    for (i = 0; isspace (nptr[i]); i++)
      {
        ;       // Si limita a saltare gli spazi iniziali.
      }

    if (nptr[i] == '+')
      {
        sign = +1;
        i++;
      }
    else if (nptr[i] == '-')
      {
        sign = -1;
        i++;
      }

    for (n = 0; isdigit (nptr[i]); i++)
      {
        n = (n * 10) + (nptr[i] - '0');         // Accumula il valore.
      }

    return sign * n;
}

Logicamente, le funzioni atol() e atoll() sono praticamente uguali, con la differenza che la variabile automatica n deve essere dello stesso tipo restituito dalla funzione; pertanto si passa alla soluzione proposta per la funzione atof():

#include <stdlib.h>
#include <ctype.h>
double
atof (const char *nptr)
{
    int i;
    int sign = +1;
    double n;           // Il risultato sarà:  n / d.
    double d;           //
    
    for (i = 0; isspace (nptr[i]); i++)
      {
        ;       // Si limita a saltare gli spazi iniziali.
      }

    if (nptr[i] == '+')
      {
        sign = +1;
        i++;
      }
    else if (nptr[i] == '-')
      {
        sign = -1;
        i++;
      }

    for (n = 0.0; isdigit (nptr[i]); i++)
      {
        n = (n * 10.0) + (nptr[i] - '0');       // Accumula il valore.
      }

    if (nptr[i] == '.')
      {
        i++;
      }

    for (d = 1.0; isdigit (nptr[i]); i++)
      {
        // La variabile "d" viene inizializzata in ogni caso.
      
        n = (n * 10.0) + (nptr[i] - '0');
        d = d * 10.0;   // Tiene conto di quanto dovrà essere diviso
                        // il risultato.
      }

    return sign * n / d;
}

Le funzioni strto...() sono più complesse rispetto a quelle ato...(). Per dare una descrizione sommaria, si può osservare che, oltre alla stringa da scandire ricevono un puntatore di puntatore all'ultimo elemento utile di tale stringa;(1) se poi questo è nullo, la scansione avviene normalmente nella stringa, entro il limite del carattere nullo di terminazione. Se fallisce il riconoscimento del valore da tradurre, il puntatore all'inizio della stringa viene copiato nell'area di memoria a cui punta il puntatore di puntatore, a meno che questo, inizialmente, sia già nullo (potrebbe essere nullo il puntatore principale o il contenuto dell'area a cui punta).

In caso di errore nell'interpretazione del valore, queste funzioni utilizzano la variabile errno per annotare il tipo di problema riscontrato.

I valori che possono essere convertiti sono esprimibili in notazione decimale o esadecimale; inoltre, le funzioni che convertono in valori a virgola mobile, accettano una notazione esponenziale e delle parole chiave per rappresentare l'infinito e NaN (Not a number). Nel caso particolare delle funzioni che convertono in un numero intero, esiste un terzo parametro per specificare la base di numerazione attesa.

Vengono presentati solo i prototipi di queste funzioni:

float                  strtof   (const char * restrict nptr, char ** restrict endptr);
double                 strtod   (const char * restrict nptr, char ** restrict endptr);
long double            strtold  (const char * restrict nptr, char ** restrict endptr);

long int               strtol   (const char * restrict nptr, char ** restrict endptr, int base);
long long int          strtoll  (const char * restrict nptr, char ** restrict endptr, int base);
unsigned long int      strtoul  (const char * restrict nptr, char ** restrict endptr, int base);
unsigned long long int strtoull (const char * restrict nptr, char ** restrict endptr, int base);

Per una descrizione completa si vedano le pagine di manuale strtof(3), strtod(3), strtold(3), strtol(3), strtoll(3), strtoul(3) e strtoull(3), oltre alla documentazione standard citata alla fine del capitolo.

L'esempio seguente mostra l'uso delle funzioni ato...():

#include <stdio.h>
#include <stdlib.h>

int main (void)
{
    int             i;
    long int       li;
    long long int lli;
    double          d;
    char          s[] = "  -987654.3210";

    i = atoi (s);
    li = atol (s);
    lli = atoll (s);
    d = atof (s);

    printf ("\"%s\" = %d, %ld, %lld, %f\n", s, i, li, lli, d);

    return 0;
}

Il risultato che ci si attende di visualizzare è questo:

"  -987654.3210" = -987654, -987654, -987654, -987654.321000

597.4   Funzioni per la generazione di numeri in modo pseudo-casuale

La libreria standard deve disporre, nel file stdlib.h, di due funzioni per la generazione di numeri pseudo-casuali. Si tratta precisamente di rand() che restituisce un numero intero casuale (di tipo int, ma sono ammessi solo valori positivi) e di srand() che serve a cambiare il «seme» di generazione di tali numeri. Lo standard prescrive anche che per uno stesso seme, la sequenza di numeri pseudo-casuali sia la stessa e che il seme predefinito iniziale sia pari a uno.

Nella descrizione dello standard si fa riferimento al fatto che il valore che può essere generato deve andare da zero a RAND_MAX, escludendo quindi valori negativi. Considerando che la funzione rand() restituisce un valore di tipo int e che questo non può essere negativo, significa che RAND_MAX deve essere inferiore o uguale al massimo numero positivo rappresentabile con il tipo int.

int  rand  (void);
void srand (unsigned int seed);

Viene proposta una soluzione molto semplice e anche molto scadente sul piano della sequenza casuale generata. Tuttavia garantisce che il valore ottenuto vada effettivamente da zero a RAND_MAX incluso:

#include <stdlib.h>
static unsigned int _srand = 1; // Il rango di «_srand» deve essere
                                // maggiore o uguale a quello di
                                // «RAND_MAX» e di «unsigned int».
int
rand (void)
{
    _srand = _srand * 1234567 + 12345;
    return _srand % ((unsigned int) RAND_MAX + 1);
}

void
srand (unsigned int seed)
{
    _srand = seed;
}

L'esempio seguente consente di verificare sommariamente il lavoro delle funzioni per la generazione di numeri casuali. Per la precisione, si vogliono ottenere valori che vanno da 0 a 99 inclusi:

#include <stdio.h>
#include <stdlib.h>

int main (void)
{
    int r;
    int i;
    int j;
    const int max = 99;

    srand (123);

    for (i = 0; i < 25; i++)
    {    
        for (j = 0; j < 26; j++)
          {
            r = (rand () % (max + 1));
            printf ("%2d ", r);
          }
        printf ("\n");
    }

    return 0;
}

Si dovrebbe ottenere un risultato simile a quello seguente:

86 67 22 15 22 47 94 91 10 99  6 31 22 51 98 11 14 11 22  3 22 11 82  7 54 11 
62 75 10 63 50 75 98 91 90 79 74 87 18 15 34  7 54 71 42 47 10 23 82 67 50 19 
14 31 34 27 58 71 38 43 14 87 38 71 82 95 50 99  2  7 30  7 98 87  2 19 46 71 
78 83 34 43 58 99 70 79 30 75 58 99 46 31 78 91 34 79 98 75 14 99 66 63 82 99 
 2 51 46 11 74 39 90 31 54 63 78 39 14 31 50 55 26 87  2 51 70 15 98 67 54 27 
34 55 14  7 74 71 42  7 30 87 70 67 34 15 26 47 90 91 30 19 66 27 86 15 26 15 
 6 99 46  7 14 51 22 43 54 95 58  7 18 15 50 83 26 71  2 27 38 71 30  7 94 19 
 6 47 62 19 90 39 54 39 42 15 86 91 82 87 38 27 34 87 74 87 50 87 82 67 78 63 
22  3 26 87 86 11 94 71 26 35 10 83 78 71 46 63 82 47 74 63 70 11 54 95 62 31 
74 87 30 71 54 15 18 19 14 79 86 23 58  7 98 47 10 63 46 39 26 19 10 67 54 99 
 6 47 70 11 26  7 66 23 10 51 66 31 58 91 74 87 22 35 74 11  6 39 58 39 26 35 
94 55 82 47 78 67 98 91 30 67 18 75 74 83 18 23 54 51 26 91 38 11 22 23 66 91 
18 99 74 27 82 99 62 35 58 35 54 95  2 95 86 95 82 47 90 51 90 51 54 23 62 91 
90 99 18 19 62 51 10 59 26 39 82 71 94 83 98 27 38  7 22 15 90 79 18 31 58 71 
22  3 58 39 98 63 62 55 38 51 86 51  2  7 86 87 94 43 54 67 62 23 42 31 10  3 
42 47 74 87 26 27 54 43 54 95 18 31 34 27 58 47 66 47 70 75 22 35 82  7 54  7 
62 19 18 91 22 63 94 83 98 27 86 59 78 91 78 35 62 67 90 67 62 35 78 95 86 99 
54 47 14  3 62 19 58 63 74 11 62 99 46 71 86  7 34  3 34 59 18  3 62 43 62 27 
26 95 46  3 22 15 54 35  6 75 86 27 58 51 70 71 82 15 50 39 38 91 14 99 66 67 
66 91 54 31 34  3 30 55 98 27 50  7 38 11 26 11 42 47 26 31 10 91 90 15 54 19 
50 87 70 35 14 79 10 47 74 55 34 51 54 95 58 23 46 59 78 91 38  3 94 51 70  3 
54 51 86 51 14 95 70 55 50 99 70 63 86 15  6 35 98 67 74 91 54 99 30  7 94 35 
10 43 54 55 50 11 58 75 26 27 78 99 98 55 94 79 26 83 34 87 38 47  6 55 74 23 
10 11 26 87  6 59 10 71 86 31 78 55 30 39 66 47 46 11 30 47  2 11 74 55 42 59 
 2 59 42 63 78 71  2 19  6 83 30 23  2 31 54 47 62 31 26 67  6 67 70 63 14 91 

597.5   Funzioni standard per la generazione di numeri pseudo-casuali

Il documento che descrive lo standard descrive una versione della funzione rand() che corrisponde al listato successivo. I valori usati nei calcoli sono tali da essere adatti a un contesto in cui i limiti degli interi sono quelli minimi previsti.

Listato 597.14. Esempio di realizzazione delle funzioni rand() e srand(), tratto dal documento che descrive lo standard del linguaggio.

// RAND_MAX assumed to be 32767

static unsigned long int next = 1;
                
int
rand(void)              
{
      next = next * 1103515245 + 12345;
      return (unsigned int)(next/65536) % 32768;
}

void
srand(unsigned int seed)
{
      next = seed;
}

597.6   Amministrazione della memoria

Un gruppo di funzioni dichiarate nel file strlib.h consente di utilizzare dinamicamente la memoria. Si tratta di malloc(), calloc(), realloc() e free(). Le prime tre funzioni restituiscono un puntatore di tipo void * all'area di memoria allocata, oppure il puntatore nullo nel caso l'operazione di allocazione fallisca; la funzione free() libera un'area di memoria allocata, indicando come argomento il puntatore che inizialmente la rappresentava.

void *malloc  (size_t size);
void *calloc  (size_t nmemb, size_t size);
void *realloc (void *ptr, size_t size);
void free     (void *ptr);

Rispetto ai prototipi mostrati, la funzione malloc() richiede l'allocazione di una quantità di byte espressa dal parametro size; calloc() richiede una quantità di nmemb elementi da size byte (pertanto serve solo a facilitare l'allocazione di uno spazio necessario a un array); realloc() richiede la riallocazione della memoria già allocata precedentemente a partire dall'indirizzo ptr per avere size byte, con l'intento di non perdere le informazioni precedenti (a meno che si tratti una riduzione della dimensione); infine, free() si limita a deallocare la memoria a cui punta ptr.

Nel linguaggio C, la memoria deve essere allocata e liberata espressamente, in quanto non esiste alcun sistema automatico al riguardo.

La gestione della memoria dipende strettamente dal sistema operativo, pertanto la realizzazione delle funzioni non può essere generalizzata. Per i dettagli che riguardano il comportamento di queste funzioni nel proprio sistema operativo vanno consultate le pagine di manuale malloc(3), calloc(3), realloc(3) e free(3). Eventualmente si può trovare nel capitolo 618 un esempio di realizzazione di queste funzioni.

597.7   Conclusione forzata del programma

Alcune funzioni si occupano di interrompere il funzionamento del programma al di fuori della conclusione naturale della funzione main(). In generale si possono distinguono i casi in cui la conclusione del programma viene gestita in modo gentile, oppure viene forzata brutalmente.

Per una conclusione corretta di un programma, è possibile predisporre un elenco di funzioni da eseguire automaticamente nel momento della conclusione. Ciò avviene attraverso la funzione atexit() che accumula un elenco di puntatori a funzione; successivamente, attraverso la chiamata alla funzione exit() si ottiene l'esecuzione delle funzioni dell'elenco, senza argomenti, secondo l'ordine di inserimento. Quindi, la funzione exit() conclude con la chiusura dei file e con la restituzione del valore passatole come argomento.

Una conclusione brutale si ottiene con la funzione _Exit(), che si limita a concludere il programma, ma senza fare nulla altro, soprattutto senza garantire che i file aperti siano chiusi correttamente.

Per ottenere una conclusione brutale del funzionamento di un programma si può usare anche la funzione abort() che però è legata alla gestione dei segnali (capitolo 603) e qui non viene spiegato il suo utilizzo.

int  atexit (void (*func) (void));
void exit   (int status);
void _Exit  (int status);
void abort  (void);

La funzione atexit() riceve come unico argomento il puntatore a una funzione, la quale non restituisce alcun valore (di tipo void) e non si attende alcun argomento (ancora il tipo void). La funzione atexit() restituisce un valore numerico da intendere come Vero o Falso, per comunicare il successo o l'insuccesso dell'operazione, dato che la quantità di puntatori a funzione che possono essere accumulati può avere un limite.

Per una descrizione completa dell'uso di queste funzioni si vedano le pagine di manuale abort(3), atexit(3), exit(3) e _Exit(3).

597.8   Funzioni di comunicazione con l'ambiente

Nel file stdlib.h sono dichiarate due funzioni per interagire con il sistema operativo, getenv() e system(), dove la prima consente di interrogare le variabili di ambiente (nel senso inteso nei sistemi Unix ed equivalenti) e la seconda consente di eseguire dei comandi attraverso la shell.

Il documento che descrive lo standard del linguaggio C, il concetto viene generalizzato, ma in pratica, il contesto da cui derivano queste funzioni è quello dei sistemi Unix.

char *getenv (const char *name);
int   system (const char *string);

La funzione getenv() si aspetta di ricevere come argomento il nome di una variabile di ambiente (o di qualcosa di comparabile, nel contesto di un altro tipo di sistema operativo), restituendo il puntatore al contenuto di tale variabile. La funzione system() può essere usata indicando un puntatore nullo e in tal caso restituisce un valore diverso da zero se il sistema operativo è in grado di recepire dei comandi testuali. Se invece viene passata una stringa, la funzione tenta di farla eseguire come comando del sistema operativo: in un sistema Unix o equivalente si tratta di un comando che deve essere eseguito da /bin/sh. L'esito della funzione system() dipende da quello del comando impartito e generalmente si ottiene lo stesso valore restituito dal comando eseguito.

Si vedano le pagine di manuale getenv(3) e system(3).

597.9   Funzioni di ricerca e riordino

Il file stdlib.h prevede la dichiarazione di due funzioni per il riordino degli array e per la ricerca all'interno di array ordinati. Si tratta precisamente delle funzioni qsort() e bsearch(), dove i nomi richiamano evidentemente gli algoritmi tradizionali noti come quick sort e binary search.

Le funzioni della libreria standard generalizzano il problema dell'ordinamento e della ricerca utilizzando puntatori di tipo void * e scandendo la memoria a blocchi di una dimensione determinata. Ma dal momento che l'area di memoria da scandire non ha la personalità di un array di un qualche tipo, occorre fornire a entrambe queste funzioni il puntatore a una funzione diversa, in grado di confrontare due valori nel contesto di proprio interesse.

void qsort (void *base,
            size_t nmemb,
            size_t size,
            int (*compar) (const void *, const void *));

void *bsearch (const void *key,
               const void *base,
               size_t nmemb,
               size_t size,
               int (*compar) (const void *, const void *));

Prima di descrivere il significato dei parametri delle due funzioni, conviene vedere un esempio in cui queste si utilizzano. Per la precisione viene scandito un piccolo array di elementi di tipo int: prima viene ordinato, poi si cerca un elemento al suo interno.

#include <stdio.h>
#include <stdlib.h>

int confronta (const void *a, const void *b)
{
    int x = *((int *) a);
    int y = *((int *) b);
    return x - y;
}

int main (void)
{
    int a[] = {3, 1, 5, 2};
    int cercato = 5;
    void *p;
    
    qsort (&a[0], sizeof (int), 4, confronta);
    printf ("%d %d %d %d\n", a[0], a[1], a[2], a[3]);

    p = bsearch (&cercato, &a[0], sizeof (int), 4, confronta);
    
    printf ("&a[0] = %u; \"%d\" si trova in %u.\n",
            (unsigned int) &a[0], cercato, (unsigned int) p);

    return 0;
}

Nell'esempio viene dichiarata la funzione confronta() che riceve due argomenti e restituisce un valore che può essere: minore, pari o maggiore di zero, se il primo argomento, rispetto al secondo, è minore, pari o uguale. Questo è il modo in cui deve comportarsi la funzione da passare come argomento a qsort() e a bsearch(), tenendo conto che è da tali funzioni che riceve gli argomenti.

La funzione qsort() vuole ricevere il puntatore alla prima posizione in memoria da riordinare (il parametro base), la quantità degli elementi da riordinare (nmemb, ovvero Number of memory blocks), la dimensione di tali elementi (size) e la funzione da usare per la loro comparazione.

La funzione bsearch() vuole ricevere il puntatore alla chiave di ricerca (il parametro key), il puntatore alla prima posizione in memoria da scandire (base), la quantità degli elementi da scandire (nmemb), la dimensione di tali elementi (size) e la funzione da usare per la loro comparazione, tenendo conto che questa riceve la chiave di ordinamento come primo argomento.

L'esempio mostrato esegue un ordinamento crescente e il testo visualizzato che si ottiene deve essere simile a quello seguente:

1 2 3 5
&a[0] = 3218927260; "5" si trova in 3218927272.

È sufficiente invertire il risultato della funzione di comparazione per ottenere un ordinamento decrescente e per scandire un array ordinato in modo decrescente:

int confronta (const void *a, const void *b)
{
    int x = *((int *) a);
    int y = *((int *) b);
    return y - x;
}

In tal caso, il testo che viene emesso deve essere simile a quello seguente:

5 3 2 1
&a[0] = 3218593340; "5" si trova in 3218593340.

597.10   Funzioni per l'aritmetica con i numeri interi

Un gruppo di funzioni il cui nome termina per ...abs() si occupa di calcolare il valore assoluto di un numero intero. Le funzioni sono precisamente: abs() per gli interi di tipo int, labs() per gli interi di tipo long int e llabs() per gli interi di tipo long long int.

int abs             (int j);
long int labs       (long int j);
long long int llabs (long long int j);

Evidentemente, la realizzazione di queste funzioni è estremamente banale. Viene presentato solo il caso di abs():

#include <stdlib.h>
int
abs (int j)
{
    if (j < 0)
      {
        return -j;
      }
    else
      {
        return j;
      }
}    

Un gruppo di funzioni il cui nome termina per ...div() si occupa di dividere due interi, calcolando il quoziente e il resto. Le funzioni sono precisamente: div() per gli interi di tipo int, ldiv() per gli interi di tipo long int e lldiv() per gli interi di tipo long long int. Il risultato viene restituito in una variabile strutturata che contiene sia il quoziente, sia il resto, a cui è stata attribuita un tipo particolare: rispettivamente si hanno i tipi div_t, ldiv_t e lldiv_t.

div_t   div   (int numer, int denom);
ldiv_t  ldiv  (long int numer, long int denom);
lldiv_t lldiv (long long int numer, long long int denom);

I tre tipi creati appositamente per contenere il risultato di queste funzioni contengono i membri quot e rem che rappresentano, rispettivamente, il quoziente e il resto. Anche la realizzazione di queste funzioni è molto semplice banale. Viene presentato solo il caso di div():

#include <stdlib.h>
div_t
div (int numer, int denom)
{
    div_t d;
    d.quot = numer / denom;
    d.rem = numer % denom;
    return d;
}    

597.11   Funzioni per la gestione di caratteri estesi e sequenze multibyte

Il linguaggio C distingue tra una gestione dei caratteri basata sul byte, tale da consentire la gestione di un insieme minimo, come quello della codifica ASCII, e una gestione a byte multipli, o multibyte. Per esempio, la codifica UTF-8 è ciò che si intende per «multibyte», ma esistono anche altre codifiche che sfruttano questo meccanismo.

Quando il contesto richiede l'interpretazione dei byte secondo una codifica multibyte, è necessario stabilire un punto di riferimento per iniziare l'interpretazione e occorre poterne conservare lo stato quando la lettura di un carattere viene interrotta e ripresa a metà. Nella documentazione dello standard, nell'ambito delle sequenze multibyte, lo stato viene definito shift state.

Per gestire internamente la codifica universale, il C utilizza un tipo specifico, wchar_t, corrispondente a un intero di rango sufficiente a rappresentare tutti i caratteri che si intendono gestire. Di conseguenza, le stringhe letterali, precedute dalla lettera L (per esempio L"àèìòùé"), sono array di elementi wchar_t.

Le funzioni che riguardano la gestione di caratteri estesi e sequenze multibyte del file stdlib.h, servono principalmente per convertire sequenze multibyte nel tipo wchar_t e viceversa. Tuttavia, occorre tenere presente che la configurazione locale deve essere tale da prevedere l'uso di caratteri da rappresentare attraverso sequenze multibyte, altrimenti le conversioni diventano prive di utilità.

Listato 597.27. Prototipi delle funzioni relative alla gestione multibyte.

int    mblen    (const char *s, size_t n);
int    mbtowc   (wchar_t *restrict pwc, const char *restrict s, size_t n);
int    wctomb   (char *s, wchar_t wc);
size_t mbstowcs (wchar_t *restrict pwcs, const char *restrict s, size_t n);
size_t wcstombs (char *restrict s, const wchar_t *restrict pwcs, size_t n);

597.12   Funzione «mblen()»

La funzione mblen() si usa normalmente per contare quanti byte sono presenti nella stringa s fornita come primo argomento, per comporre il primo carattere (multibyte) della stringa stessa, limitando la scansione a un massimo di n byte (il secondo argomento richiesto). Se al posto di indicare una stringa si fornisce il puntatore nullo, si ottiene un valore che può essere uno o zero, a seconda che sia prevista o meno una codifica multibyte con una gestione dello stato (shift state).

#include <locale.h>
#include <stdio.h>
#include <stdlib.h>

int main (void)
{
    int n;
    setlocale (LC_ALL, "en_US.UTF-8");
    n = mblen (NULL, 0);
    printf ("Gestione dello stato: %d\n", n);
    n = mblen ("€", 8);
    printf ("Il carattere %s richiede %d byte.\n", "€", n);
    return 0;
}

L'esempio mostrato dovrebbe chiarire alcune cose. La funzione richiede un argomento di tipo stringa di caratteri, perché un argomento di tipo char singolo, non consentirebbe di annotare una sequenza multibyte. Le sequenze multibyte sono stringhe normali, trattate come tali, salvo quando è necessario interpretare il loro contenuto; a questo proposito, si vede che la funzione printf() riceve una stringa multibyte e si limita a trattarla come una stringa normale.

Se la codifica in cui è scritto il sorgente è la stessa usata dal programma durante il suo funzionamento, si può ottenere il testo seguente:

Gestione dello stato: 0
Il carattere € richiede 3 byte.

Nell'uso normale della funzione mblen(), se la stringa che si fornisce contiene una sequenza multibyte errata o incompleta, il valore restituito è -1.

597.13   Funzioni «mbtowc()» e «wctomb()»

Le due funzioni mbtowc() e wctomb() si compensano a vicenda, fornendo il mezzo elementare di conversione dei caratteri da una sequenza multibyte a un numero intero di tipo wchar_t e viceversa. Quando a queste funzioni, al posto del puntatore alla stringa multibyte, si fornisce il puntatore nullo, si ottiene un funzionamento analogo a quello di mblen(), con un valore pari a uno se la configurazione locale prevede l'uso di sequenze multibyte con una gestione dello stato, oppure zero se questo problema non sussiste. Inoltre, per entrambe le funzioni, se la sequenza multibyte è errata o incompleta, si ottiene la restituzione del valore -1. Infine, se la conversione ha successo, si ottiene la quantità dei byte che compongono la sequenza multibyte (di origine o di destinazione, a seconda della funzione usata).

Viene mostrato un esempio molto semplice che dimostra l'uso delle due funzioni. In particolare viene convertita la sequenza multibyte che rappresenta la lettera «ä» in un numero wchar_t, quindi il numero viene incrementato e riconvertito in una nuova sequenza multibyte, per ottenere il carattere «å».

#include <locale.h>
#include <stdlib.h>
#include <stdio.h>

int main (void)
{
    int n;
    wchar_t wc;
    char mb[20] = {}; // Inizializza l'array a zero.
    
    setlocale (LC_ALL, "en_US.UTF-8");

    n = mbtowc (&wc, NULL, 8);
    printf ("Gestione dello stato: %d\n", n);
    n = wctomb (NULL, wc);
    printf ("Gestione dello stato: %d\n", n);

    n = mbtowc (&wc, "ä", 8);
    printf ("Il carattere \"ä\" si rappresenta con %d byte ", n);
    printf ("in una sequenza multibyte e con il numero %d ", (int) wc);
    printf ("in una variabile di tipo \"wchar_t\".\n");

    wc++;
    n = wctomb (mb, wc);
    printf ("Il carattere \"%s\" si rappresenta con %d byte ", mb, n);
    printf ("in una sequenza multibyte e con il numero %d ", (int) wc);
    printf ("in una variabile di tipo \"wchar_t\".\n");

    return 0;
}

Si dovrebbe ottenere un testo come quello seguente:

Gestione dello stato: 0
Gestione dello stato: 0
Il carattere "ä" si rappresenta con 2 byte in una sequenza \
  \multibyte e con il numero 228 in una variabile di tipo "wchar_t". Il carattere "å" si rappresenta con 2 byte in una sequenza \
  \multibyte e con il numero 229 in una variabile di tipo "wchar_t".

Per gli approfondimenti eventuali, si vedano le pagine di manuale mbtowc(3) e wctomb(3).

597.14   Funzioni «mbstowcs()» e «wcstombs()»

Le funzioni mbstowcs() e wcstombs servono rispettivamente per convertire una stringa multibyte in un stringa estesa (un array di elementi wchar_t) e per fare l'opposto. Entrambe le funzioni richiedono tre argomenti: l'array di destinazione, l'array di origine e la quantità di elementi da utilizzare nell'array di destinazione. Entrambe le funzioni restituiscono un numero che esprime la quantità di elementi di destinazione convertiti, escluso ciò che costituisce il carattere nullo di terminazione. Entrambe restituiscono un valore pari a (size_t) (-1) se la conversione produce un errore.(2)

Per convertire correttamente una stringa (multibyte o estesa), occorre che il numero di elementi di destinazione previsto includa anche il carattere nullo di terminazione. L'esempio seguente dovrebbe aiutare a comprendere il problema:

#include <locale.h>
#include <stdio.h>
#include <stdlib.h>

int main (void)
{
    size_t n;
    wchar_t wca[] = {1, 2, 3, 4, 5, 6, 7};
    wchar_t wcb[] = {1, 2, 3, 4, 5, 6, 7};
    char mba[] = "**************";
    char mbb[] = "**************";
    
    setlocale (LC_ALL, "en_US.UTF-8");

    n = mbstowcs (wca, "äåâ", 3);
    printf ("mbstowcs: %d: %d %d %d %d %d\n", n,
            wca[0], wca[1], wca[2], wca[3], wca[4]);

    n = mbstowcs (wcb, "äåâ", 5);
    printf ("mbstowcs: %d: %d %d %d %d %d\n", n,
            wcb[0], wcb[1], wcb[2], wcb[3], wcb[4]);

    n = wcstombs (mba, L"äåâ", 6);
    printf ("wcstombs: %d: \"%s\"\n", n, mba);

    n = wcstombs (mbb, L"äåâ", 9);
    printf ("wcstombs: %d: \"%s\"\n", n, mbb);

    return 0;
}

Nell'esempio, la funzione mbstowcs() viene usata due volte, per convertire una stringa multibyte, composta da tre caratteri, se non si conta quello di terminazione. Nel primo caso, viene specificato che si vogliono convertire esattamente tre caratteri, ma questo significa che nell'array di destinazione rimane il contenuto originale a partire dal quarto elemento. In modo analogo, la funzione wcstombs() viene usata due volte per convertire una stringa estesa in una stringa multibyte. La stringa estesa si compone di tre caratteri che nella conversione vanno a occupare esattamente sei byte, con l'aggiunta eventuale del carattere nullo di terminazione (che sarebbe il settimo). Si può vedere che quando si chiede una conversione di sei elementi, la stringa ricevente mantiene il contenuto precedente nella parte restante. Ecco cosa si dovrebbe vedere eseguendo il programma:

mbstowcs: 3: 228 229 226 4 5
mbstowcs: 3: 228 229 226 0 5
wcstombs: 6: "äåâ********"
wcstombs: 6: "äåâ"

Se al posto della destinazione (il primo argomento) viene posto il puntatore nullo, si ottiene la simulazione dell'operazione, senza memorizzare alcunché e senza tenere conto della quantità massima di elementi che si annota come ultimo argomento. Ciò ha lo scopo di contare quanti elementi servirebbero per produrre una conversione completa. L'esempio seguente modifica quello già visto, sfruttando questa funzionalità:

#include <locale.h>
#include <stdio.h>
#include <stdlib.h>

int main (void)
{
    size_t n;
    size_t max;
    wchar_t wca[] = {1, 2, 3, 4, 5, 6, 7};
    char mba[] = "**************";      // 15 byte total.
    
    setlocale (LC_ALL, "en_US.UTF-8");

    max = mbstowcs (NULL, "äåâ", 0);
    if (max <= 6)
      {
        n = mbstowcs (wca, "äåâ", max + 1);
        printf ("mbstowcs: %d: %d %d %d %d %d\n", n,
                wca[0], wca[1], wca[2], wca[3], wca[4]);
      }
    
    max = wcstombs (NULL, L"äåâ", 0);
    if (max <= 14)
      {
        n = wcstombs (mba, L"äåâ", max + 1);
        printf ("wcstombs: %d: \"%s\"\n", n, mba);
      }

    return 0;
}

Ecco cosa si dovrebbe vedere eseguendo il programma:

mbstowcs: 3: 228 229 226 0 5
wcstombs: 6: "äåâ"

Per gli approfondimenti eventuali, si vedano le pagine di manuale mbstowcs(3) e wcstombs(3).

597.15   Riferimenti


1) Si tratta di un puntatore di puntatore, solo perché si deve poter alterare ciò a cui punta, ma questo tipo di valore è, a sua volta, un puntatore.

2) Il tipo size_t, restituito dalle funzioni, è un intero senza segno; pertanto, in condizioni normali, ovvero con una rappresentazione dei valori negativi con il complemento a due, la conversione di -1 si traduce nel valore massimo rappresentabile.


Appunti di informatica libera 2008 --- Copyright © 2000-2008 Daniele Giacomini -- <appunti2 (ad) gmail·com>


Dovrebbe essere possibile fare riferimento a questa pagina anche con il nome c_171_stdlib_h_187.htm

[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico] [home]

Valid ISO-HTML!

CSS validator!

Gjlg Metamotore e Web Directory