1 Compilazione, operatori, funzioni e tipi
Appunti del 29/09/2026.
1.1 Compilazione ed esecuzione
Il compilatore si invoca con il comando:
gcc nomefile.c -o nomeprogramma.exe
-ospecifica il nome del file eseguibile di output..exeè l’estensione utilizzata per gli eseguibili Windows.
In una shell che utilizza questa sintassi, il programma si esegue con:
./nomeprogramma.exe
1.2 Operatore AND e valutazione a cortocircuito
In C un valore scalare nullo è interpretato come falso e un valore scalare non nullo come vero. I connettivi logici corrispondono agli operatori Questi operatori restituiscono un intero uguale a o . Anche || usa il cortocircuito: se il primo operando è vero, il secondo non viene valutato. Gli operatori & e | operano invece sui bit e non vanno confusi con && e ||. Nei confronti, == verifica l’uguaglianza e != la disuguaglianza; = esegue un’assegnazione.
L’operatore logico AND, scritto &&, valuta il primo operando e valuta il secondo soltanto se il primo risulta vero. La valutazione procede quindi da sinistra verso destra.
Se la prima espressione è falsa, l’intera espressione è necessariamente falsa e la seconda non viene valutata. Questa modalità si chiama short-circuit evaluation, o valutazione a cortocircuito.
1.2.1 Tavola di verità dell’AND
| A | B | A && B |
false |
false |
false |
false |
true |
false |
true |
false |
false |
true |
true |
true |
L’espressione:
false && qualcosa
è sempre falsa; pertanto qualcosa non viene valutato.
Esempio 1. Si consideri la condizione:
x != 0 && 10 / x > 2
Se viene utilizzata in un’istruzione if e x == 0, la prima condizione è falsa. Di conseguenza, l’espressione 10 / x > 2 non viene valutata e si evita la divisione per zero.
1.3 Overloading
In C non è possibile definire più funzioni con lo stesso nome e parametri diversi. Il linguaggio non supporta questo tipo di overloading delle funzioni.
Le seguenti dichiarazioni, riferite a funzioni con lo stesso nome nello stesso ambito, non sono valide insieme:
int somma(int a, int b);
double somma(double a, double b);
Comportamenti simili possono essere ottenuti mediante funzioni con nomi diversi, macro o _Generic.
1.4 Il tipo void
void indica l’assenza di un valore; nel contesto dei parametri di una funzione può indicare l’assenza di parametri.
1.4.1 Funzione che non restituisce un valore
void saluta(void) {
printf("Ciao!");
}
Il primo void indica che la funzione non restituisce alcun valore. Il secondo indica che la funzione non accetta parametri.
1.4.2 Parentesi vuote nella dichiarazione
Nelle versioni del C precedenti a C23, la dichiarazione:
void funzione();
non specifica il numero e i tipi dei parametri. Non equivale quindi a dichiarare esplicitamente una funzione senza parametri.
Per dichiarare esplicitamente che la funzione non accetta parametri si usa:
void funzione(void);
1.5 L’istruzione return
L’istruzione return termina l’esecuzione di una funzione e, quando previsto, restituisce un valore.
Esempio 2.
int somma(int a, int b) {
return a + b;
}
La funzione restituisce la somma dei due argomenti.
La funzione main restituisce uno stato di terminazione all’ambiente di esecuzione, normalmente il sistema operativo:
int main(void) {
return 0;
}
Convenzionalmente:
0indica una terminazione corretta;un valore diverso da
0segnala un errore o una condizione particolare.
Osservazione 1. Il significato preciso dei valori diversi da 0 dipende dal programma e dal sistema.
1.6 Tipi di dati
Tra i tipi fondamentali del C compaiono:
int
char
float
double
void
Sono inoltre disponibili specificatori come:
short
long
signed
unsigned
Esempio 3.
int eta = 18;
double altezza = 1.80;
float temperatura = 25.5f;
char iniziale = 'S';
1.6.1 Il tipo booleano
Nelle versioni da C99 a C17, il nome bool può essere utilizzato includendo:
#include <stdbool.h>
Si possono quindi scrivere dichiarazioni come:
bool acceso = true;
bool spento = false;
In queste versioni, bool è una macro che rappresenta il tipo _Bool.
1.6.2 Stringhe
In C non esiste un tipo primitivo string. Le stringhe sono rappresentate mediante sequenze di caratteri in array di char, terminate dal carattere nullo ’\0’.
Esempio 4.
char nome[] = "Simone";
In memoria, l’array contiene:
'S' 'i' 'm' 'o' 'n' 'e' '\0'
2 Output formattato e incremento
Appunti del 30/09/2026.
2.1 La funzione printf
printf() stampa informazioni sullo standard output. Per inserire il valore di una variabile o di un’espressione nella stringa si usano gli specificatori di formato, detti anche format specifier.
Esempio 5. Lo specificatore %d permette di stampare un valore di tipo int:
printf("Ciao, ho %d anni", 15);
L’output è:
Ciao, ho 15 anni
Alcuni specificatori comuni sono:
%d: un valore di tipoint.%f: un valore di tipodouble; un argomentofloatviene promosso adoublenella chiamata.%lf: inprintf, equivale a%f.%c: un carattere, passato comeintdopo le promozioni degli argomenti.%s: una stringa, fornita tramite un puntatore a caratteri, anche ottenuto da un array dichar.
2.2 Sicurezza di printf
Ogni specificatore che richiede un valore deve avere un argomento corrispondente, del tipo appropriato.
La chiamata:
printf("%d %d", 10, 20);
è corretta: ci sono due specificatori %d e due argomenti interi.
Nella chiamata:
printf("%d %d", 10);
il secondo specificatore non ha un argomento corrispondente. Il comportamento è indefinito, o undefined behavior.
Gli argomenti delle funzioni vengono trasmessi attraverso registri e/o memoria, secondo le convenzioni di chiamata della piattaforma. Nel tentativo di recuperare l’argomento mancante, printf() può leggere dati che non le sono stati forniti.
Per esempio, la chiamata:
printf("%d %d %d", 10);
potrebbe produrre un output simile al seguente:
10 18374652 -9283746
Questo risultato non è garantito. Il comportamento è imprevedibile e può dipendere dal compilatore, dall’architettura, dalle convenzioni di chiamata e dallo stato della memoria.
2.2.1 Possibile problema di sicurezza
La situazione può diventare un problema di sicurezza se un attaccante controlla la stringa di formato. Specificatori come:
%x
%p
%s
possono essere sfruttati per tentare di leggere informazioni nella memoria accessibile al processo.
La chiamata:
printf(input);
è pericolosa quando input è controllato dall’utente. Un input come:
%x %x %x %x
fa interpretare gli specificatori come richieste di argomenti che non sono stati passati.
Questa vulnerabilità è detta format string vulnerability e può causare information disclosure, cioè la divulgazione di informazioni presenti nella memoria del processo.
2.2.2 Forma sicura
Per stampare il contenuto di una stringa come testo si scrive:
printf("%s", input);
In questo modo input è trattato come una stringa. Gli eventuali %x, %d e %p contenuti al suo interno non sono interpretati come specificatori di formato.
2.2.3 Riepilogo
printf("%d %d", 10);omette un argomento richiesto e ha comportamento indefinito.printf(input);espone a una possibile vulnerabilità se l’utente controlla la stringa di formato.printf("%s", input);trattainputcome testo e impedisce che il suo contenuto venga interpretato come formato.
Osservazione 2. Non è garantito che venga mostrato un indirizzo o un particolare dato di memoria: leggere argomenti inesistenti ha comportamento indefinito. In determinate condizioni questo comportamento può comunque essere sfruttato per ottenere informazioni dalla memoria del processo.
2.3 Pre-incremento
L’espressione ++var incrementa var di uno e produce il valore aggiornato.
Esempio 6.
int var = 5;
int x = ++var;
Al termine delle istruzioni:
var = 6
x = 6
L’inizializzazione di x ha quindi lo stesso effetto di incrementare prima var e poi assegnarne il valore a x:
var = var + 1;
x = var;
2.3.1 Differenza rispetto al post-incremento
++varincrementa la variabile e produce il nuovo valore.var++produce il valore precedente all’incremento e incrementa la variabile come effetto dell’operazione.
int a = 5;
int x = ++a; // x = 6, a = 6
int y = a++; // y = 6, a = 7
2.4 Tipi di segnaposto per printf
I segnaposto indicano come interpretare e stampare un valore.
%d: unint.printf("%d", numero);%s: una stringa fornita tramite un puntatore a caratteri.printf("%s", nome);%lld: unlong long.printf("%lld", numeroGrande);%c: un carattere, passato comeintdopo le promozioni.printf("%c", carattere);%p: un puntatore di tipovoid *. La rappresentazione è definita dall’implementazione ed è comunemente esadecimale.printf("%p", (void *)p);%zu: un valore di tiposize_t, un tipo intero senza segno usato spesso per dimensioni e indici.printf("%zu", dimensione);
Osservazione 3. %zu non significa “puntatore in decimale”: serve a stampare valori di tipo size_t. Per stampare un puntatore a un oggetto si usa %p, passando il puntatore convertito a void *.
Esempio 7.
int numero = 42;
printf("%d\n", numero);
3 Costanti
Un oggetto definito con il qualificatore const non può essere modificato dopo l’inizializzazione:
const int numero = 0;
Il tentativo di assegnazione seguente non è valido:
const int numero = 0;
numero = 3; // Errore: numero e' qualificato const.
Il compilatore deve segnalare il problema: numero può essere letto, ma non modificato.
4 Conversioni esplicite di tipo
Il cast converte esplicitamente il valore di un’espressione da un tipo a un altro; non modifica il tipo dichiarato di un’eventuale variabile coinvolta.
4.1 Il problema della divisione intera
Si consideri:
float divisione = 11 / 4;
Il risultato assegnato non è 2.75. Entrambi gli operandi sono di tipo int, quindi viene eseguita una divisione intera:
11 / 4 = 2
Il risultato intero 2 viene poi convertito in float durante l’inizializzazione. La sua stampa con %f produce 2.000000:
float divisione = 11 / 4;
printf("%f\n", divisione);
// 2.000000
4.2 Soluzione con il cast
Si può convertire uno dei due operandi in float:
float divisioneGiusta = (float)11 / 4;
printf("%f\n", divisioneGiusta);
// 2.750000
Il cast:
(float)11
converte il valore 11 in un valore di tipo float. Anche l’altro operando viene convertito per eseguire la divisione in virgola mobile, che produce 2.75:
11.0 / 4 = 2.75
4.2.1 Regola fondamentale
Se almeno uno dei due operandi è di tipo float o double, la divisione non è una divisione intera. Le espressioni:
11.0 / 4
11 / 4.0
(float)11 / 4
11 / (float)4
producono tutte il valore 2.75.
5 Puntatori
Definizione 1. Una variabile puntatore può contenere l’indirizzo di un oggetto, per esempio di un’altra variabile.
Due operatori fondamentali sono:
&, che permette di ottenere l’indirizzo di una variabile;*, che dereferenzia un puntatore e consente di accedere all’oggetto all’indirizzo puntato.
5.1 Ottenere l’indirizzo di una variabile
Data la dichiarazione:
int a = 344;
l’espressione:
&a
fornisce l’indirizzo di a. Per stamparlo si scrive:
printf("%p\n", (void *)&a);
Osservazione 4. Per stampare un indirizzo si usa %p, non %d.
5.2 L’asterisco nelle dichiarazioni e nelle espressioni
L’asterisco ha due ruoli distinti quando si lavora con i puntatori.
5.2.1 Dichiarare un puntatore
La dichiarazione:
int *p;
indica che p è un puntatore a int. Si può inizializzarlo con l’indirizzo di una variabile intera:
int a = 344;
int *p = &a;
La situazione si rappresenta così:
Il valore contenuto in p è l’indirizzo di a.
5.2.2 Dereferenziare un puntatore
In un’espressione, l’operatore unario * applicato a un puntatore permette di accedere all’oggetto all’indirizzo memorizzato.
Esempio 8.
int a = 344;
int *p = &a;
printf("%d\n", *p);
L’output è:
344
p contiene l’indirizzo di a; *p permette di leggerne il valore, cioè 344. Questa operazione si chiama dereferenziazione.
5.3 Indirizzo e dereferenziazione a confronto
Nel caso considerato, ottenere un indirizzo e dereferenziarlo sono operazioni inverse:
int a = 344;
int *p = &a;
&aè l’indirizzo dia.pcontiene lo stesso indirizzo.*ppermette di accedere al valore dia.
Pertanto:
printf("%p\n", (void *)&a); // Indirizzo di a.
printf("%p\n", (void *)p); // Stesso indirizzo.
printf("%d\n", *p); // Valore di a: 344.
5.4 Modificare una variabile attraverso un puntatore
La dereferenziazione permette sia di leggere sia di modificare l’oggetto puntato, quando questo è modificabile.
Esempio 9.
int a = 344;
int *p = &a;
*p = 500;
printf("%d\n", a);
L’output è:
500
L’assegnazione:
*p = 500;
scrive 500 nell’oggetto all’indirizzo contenuto in p. Poiché p punta ad a, viene modificata direttamente a.
5.5 Riepilogo sui puntatori
Date le dichiarazioni:
int a = 344;
int *p = &a;
si ha:
| Espressione | Significato |
a |
Valore della variabile: 344. |
&a |
Indirizzo di a. |
p |
Indirizzo memorizzato nel puntatore. |
*p |
Valore dell’oggetto puntato: 344. |
L’operatore & permette di ottenere un indirizzo; l’operatore unario * permette di accedere all’oggetto a quell’indirizzo.
Esempio 10 (Programma completo).
#include <stdio.h>
int main(void) {
int a = 344;
int *p = &a;
printf("Valore di a: %d\n", a);
printf("Indirizzo di a: %p\n", (void *)&a);
printf("Valore di p: %p\n", (void *)p);
printf("Valore puntato da p: %d\n", *p);
*p = 500;
printf("Nuovo valore di a: %d\n", a);
return 0;
}
Un output indicativo è:
Valore di a: 344
Indirizzo di a: 0x7ffe12345678
Valore di p: 0x7ffe12345678
Valore puntato da p: 344
Nuovo valore di a: 500
6 Array, stringhe e operatore ternario
6.1 Array e puntatori
Esempio 11 (Puntatore al primo elemento di un array).
int a[4] = {10, 20, 30, 40};
int *b = a;
a[3] = 45;
printf("%d\n", b[3]);
L’output è:
45
int a[4]dichiara un array di quattro interi.int *b = adichiara un puntatore a interi e lo inizializza con l’indirizzo del primo elemento dia.a[3] = 45modifica il quarto elemento dell’array.b[3]accede al quarto elemento attraverso il puntatore.
b non è una copia dell’array: permette di accedere agli stessi elementi. Una modifica effettuata tramite a è quindi visibile anche accedendo allo stesso elemento tramite b.
Osservazione 5. L’indirizzo effettivo può cambiare tra un’esecuzione e l’altra, in base alla gestione della memoria da parte del sistema.
6.2 Le stringhe in C
Definizione 2. Una stringa in C è una sequenza di caratteri terminata dal carattere nullo, detto terminatore nullo e rappresentato da ’\0’.
Il terminatore indica la fine della stringa.
6.2.1 Dichiarazione di una stringa
char str[] = "Ciao";
L’istruzione dichiara un array di caratteri che contiene:
| Elemento | Valore |
str[0] |
’C’ |
str[1] |
’i’ |
str[2] |
’a’ |
str[3] |
’o’ |
str[4] |
’\0’ |
L’array contiene cinque caratteri: anche il terminatore occupa una posizione.
6.2.2 Mutabilità delle stringhe
I caratteri di un array modificabile possono essere cambiati, rispettando i limiti dell’array e mantenendo il terminatore per usarlo come stringa.
Esempio 12.
char str[] = "Ciao";
str[0] = 'B';
printf("%s\n", str);
L’output è:
Biao
La modifica è valida perché str è un array di caratteri modificabile.
6.3 Stringhe e puntatori: differenze
La dichiarazione:
char *saluto = "Ciao!";
ha un significato diverso dalla dichiarazione:
char saluto[] = "Ciao!";
6.3.1 Array di caratteri
char saluto[] = "Ciao!";
Viene creato un array che contiene una copia dei caratteri della stringa, compreso il terminatore.
Se dichiarato all’interno di una funzione senza specificatori che ne cambino la durata, l’array ha durata automatica ed è generalmente collocato nello stack.
I caratteri dell’array sono modificabili.
L’istruzione seguente è quindi valida:
saluto[0] = 'B';
6.3.2 Puntatore a una stringa letterale
char *saluto = "Ciao!";
salutoè un puntatore a carattere.La stringa letterale
"Ciao!"ha durata statica ed è normalmente conservata in un’area dedicata ai dati statici.I caratteri della stringa letterale non devono essere modificati.
L’assegnazione:
saluto = "Buongiorno";
è valida: il puntatore viene fatto puntare a un’altra stringa letterale.
Il tentativo:
saluto[0] = 'B';
ha invece comportamento indefinito, perché tenta di modificare una stringa letterale.
6.3.3 Distinguere il puntatore dalla stringa
Nella dichiarazione:
char *saluto = "Ciao!";
sono presenti due elementi distinti:
saluto, la variabile puntatore che contiene un indirizzo;"Ciao!", la stringa letterale a cui il puntatore fa riferimento.
La variabile puntatore, se dichiarata localmente senza specificatori che ne cambino la durata, ha durata automatica; la stringa letterale ha durata statica.
Per modificare i caratteri si usa un array modificabile, come:
char saluto[] = "Ciao!";
6.4 L’operatore ternario
L’operatore condizionale ?:, detto ternario, sceglie un valore tra due alternative in base a una condizione.
6.4.1 Sintassi
condizione ? valore_se_vero : valore_se_falso;
Se la condizione è vera, viene valutata e scelta l’espressione dopo
?.Se la condizione è falsa, viene valutata e scelta l’espressione dopo
:.
Esempio 13.
int reddito = 60000;
int aliquota = reddito > 50000 ? 43 : 30;
La valutazione procede così:
reddito > 50000verifica se il reddito supera50000.La condizione è vera, perché
60000 > 50000.Alla variabile
aliquotaviene assegnato43.
Pertanto:
printf("%d\n", aliquota);
produce:
43
In questo caso, il codice equivale a:
int reddito = 60000;
int aliquota;
if (reddito > 50000) {
aliquota = 43;
} else {
aliquota = 30;
}
L’operatore ternario permette di esprimere scelte semplici in modo più compatto rispetto a un blocco if-else.
7 Memoria in C: stack, heap e allocazione dinamica
7.1 Significato di allocare memoria
Dichiarare una variabile richiede spazio per conservarne il valore:
int x = 10;
int numeri[10] = {0};
xoccupa lo spazio necessario per unint.numericontiene dieci interi, tutti inizializzati a zero.
È utile distinguere tra memoria automatica, spesso associata allo stack, e memoria allocata dinamicamente, spesso associata all’heap.
Osservazione 6. Stack e heap costituiscono un modello pratico del funzionamento dei programmi. Lo standard C non impone che ogni variabile automatica si trovi fisicamente nello stack.
7.2 Memoria automatica: un array locale
int x[10] = {0};
x[2] = 35;
L’array contiene dieci elementi, con indici a partire da zero. x[2] è quindi il terzo elemento. Dopo l’assegnazione, i valori sono:
[0, 0, 35, 0, 0, 0, 0, 0, 0, 0]
Un array locale dichiarato in questo modo ha durata automatica: la sua durata di vita termina quando si esce dal blocco a cui appartiene, per esempio al ritorno dalla funzione se è dichiarato nel suo blocco principale. Non si deve chiamare free() su questo array.
Questa soluzione è adatta quando la dimensione è nota o gestibile e i dati servono soltanto durante l’esecuzione del blocco o della funzione.
7.3 Allocazione dinamica con malloc
malloc() richiede una quantità di memoria determinata durante l’esecuzione:
#include <stdlib.h>
int *y = malloc(10 * sizeof(int));
sizeof(int)restituisce la dimensione in byte di unint.10 * sizeof(int)calcola lo spazio necessario per dieci interi.mallocrestituisce un puntatore all’inizio della memoria allocata, oppureNULLse l’allocazione fallisce.int *ydichiara un puntatore aint.
Se sulla piattaforma un int occupa quattro byte, sono necessari byte. Non è garantito che sizeof(int) valga sempre quattro.
7.3.1 La memoria non viene inizializzata
Dopo l’istruzione:
int *y = malloc(10 * sizeof(int));
gli elementi non sono automaticamente inizializzati a zero. Prima di leggerli bisogna assegnare loro un valore.
È necessario controllare il risultato dell’allocazione:
int *y = malloc(10 * sizeof(int));
if (y == NULL) {
return 1; // Allocazione fallita.
}
7.3.2 Accesso agli elementi
Dopo un’allocazione riuscita si può scrivere:
y[2] = 35;
La notazione y[2] equivale a *(y + 2): accede al terzo intero a partire dall’indirizzo contenuto in y.
Gli indici si possono usare sia con array sia con puntatori a memoria allocata dinamicamente, purché l’accesso resti nei limiti della memoria valida.
7.4 Allocazione inizializzata con calloc
calloc() alloca memoria per un certo numero di elementi e inizializza a zero tutti i byte della porzione allocata:
int *array = calloc(10, sizeof(int));
In questo caso si richiede memoria per dieci interi inizializzati a zero. Anche calloc() può restituire NULL, quindi occorre controllarne il risultato.
7.5 Rilascio della memoria con free
La memoria ottenuta con malloc() o calloc() rimane allocata anche quando termina la durata di vita del puntatore locale che la indicava. Deve essere rilasciata quando non serve più:
int *y = malloc(10 * sizeof(int));
if (y == NULL) {
return 1;
}
y[2] = 35;
free(y);
y = NULL; // Facoltativo: evita riutilizzi accidentali di y.
free(y)rilascia la memoria allocata.Dopo il rilascio non si deve leggere o scrivere nella memoria attraverso
y.Assegnare
NULLal puntatore dopofree()è facoltativo e può aiutare a evitare riutilizzi accidentali.Non si deve chiamare
free()su un array automatico, su un puntatore non valido per il rilascio mediante una funzione di allocazione compatibile, oppure due volte sulla stessa allocazione.
Dimenticare di rilasciare memoria che non serve più può causare una memory leak, cioè una perdita di memoria.
7.6 Dati che devono sopravvivere alla funzione
La funzione seguente è errata:
int *creaArray(void) {
int array[3] = {10, 20, 30};
return array; // L'array locale cessa di esistere.
}
Quando creaArray() termina, termina anche la durata di vita dell’array locale. Il puntatore restituito non può essere usato validamente per accedere ai suoi elementi.
7.6.1 Versione con allocazione dinamica
#include <stdlib.h>
int *creaArray(void) {
int *array = malloc(3 * sizeof(int));
if (array == NULL) {
return NULL;
}
array[0] = 10;
array[1] = 20;
array[2] = 30;
return array;
}
La memoria allocata dinamicamente continua a esistere dopo il ritorno dalla funzione. Il chiamante deve liberarla quando non serve più:
#include <stdio.h>
#include <stdlib.h>
int *creaArray(void);
int main(void) {
int *numeri = creaArray();
if (numeri == NULL) {
return 1;
}
printf("%d\n", numeri[0]);
printf("%d\n", numeri[1]);
printf("%d\n", numeri[2]);
free(numeri);
return 0;
}
La variabile puntatore locale array cessa di esistere al termine della funzione, mentre la memoria allocata dinamicamente rimane disponibile. La funzione restituisce l’indirizzo affinché il chiamante possa accedervi.
L’allocazione dinamica non è l’unica soluzione per conservare dati oltre una chiamata: è anche possibile passare alla funzione un array creato dal chiamante oppure usare memoria statica.
7.7 Quando utilizzare l’allocazione dinamica
7.7.1 Dimensione decisa durante l’esecuzione
Dopo aver letto e validato un intero n, l’allocazione può essere espressa così:
int *valori = malloc((size_t)n * sizeof *valori);
La quantità di memoria può dipendere da un valore fornito dall’utente o dal numero di dati da elaborare. Occorre validare n, controllare che il calcolo della dimensione sia rappresentabile e verificare il risultato di malloc(). Per utilizzare esplicitamente size_t bisogna includere <stddef.h> o un’intestazione appropriata, come <stdlib.h>.
In alcuni contesti si possono usare array a lunghezza variabile, detti VLA. Il supporto dipende dalla versione del linguaggio e dall’implementazione: gli array automatici a lunghezza variabile non sono obbligatori a partire da C11.
7.7.2 Quantità di dati che cambia
realloc() permette di tentare il ridimensionamento di una porzione di memoria allocata dinamicamente. Può essere utile quando si legge un file senza conoscerne in anticipo la dimensione.
Per una nuova_dimensione strettamente positiva:
char *temp = realloc(testo, nuova_dimensione);
if (temp != NULL) {
testo = temp;
}
/* In caso di fallimento, testo conserva il puntatore originale. */
Se l’operazione riesce, l’indirizzo può cambiare. Se fallisce con una dimensione non nulla, la vecchia allocazione rimane valida. Per questo si salva il risultato in un puntatore temporaneo.
7.8 Confronto tra array automatici e memoria dinamica
Dichiarazione e allocazione. Un array automatico si può dichiarare con:
int x[10] = {0};La memoria dinamica si può richiedere con:
int *y = malloc(10 * sizeof *y);Dimensione. Per un array automatico è determinata nella dichiarazione; sono possibili VLA dove supportati. Per la memoria dinamica è decisa durante l’esecuzione.
Inizializzazione. Nell’array,
{0}inizializza tutti gli elementi a zero.malloc()non inizializza la memoria;calloc()ne azzera i byte.Durata. La memoria dell’array automatico è legata alla durata di vita dell’oggetto. La memoria dinamica rimane allocata fino al rilascio.
Liberazione manuale. Non è richiesta per l’array automatico; per la memoria dinamica si usa
free().Rischi principali. Per l’array automatico occorre rispettare limiti e durata di vita. Per la memoria dinamica si aggiungono il fallimento dell’allocazione, le perdite di memoria e l’uso dopo il rilascio, oltre agli accessi fuori limite.
7.9 Applicazione al codice d’esempio
int x[10] = {0};
int *y = malloc(10 * sizeof(int));
if (y == NULL) {
return 1;
}
x[2] = 35;
y[2] = 35;
free(y);
x e y consentono entrambi di accedere al terzo elemento delle rispettive aree di memoria. Se servono dieci interi usati soltanto nella funzione, malloc() non offre un vantaggio evidente: l’array automatico è più semplice.
L’allocazione dinamica è utile quando è necessaria la sua flessibilità; non è sempre preferibile a un array automatico.
7.10 Criteri pratici di scelta
Prima di scegliere come gestire la memoria occorre chiedersi:
Quanta memoria serve? La dimensione è nota oppure dipende dai dati disponibili durante l’esecuzione?
Per quanto tempo servono i dati? Devono esistere anche dopo il ritorno dalla funzione che li crea?
La dimensione può cambiare? Potrebbe essere necessario aumentare o ridurre la memoria?
Se è sufficiente una variabile o un array locale, si preferisce la soluzione automatica. Se occorre una dimensione determinata durante l’esecuzione o una durata di vita indipendente dalla funzione che richiede la memoria, si valuta l’allocazione dinamica.
La memoria automatica è comoda per dati temporanei; l’allocazione dinamica permette di richiedere, conservare, ridimensionare e rilasciare memoria esplicitamente.