Ebbene si! Siamo arrivati al mostro del linguaggio!!!
Questo è forse l’argomento più caratterizzante del linguaggio, lo scoglio per tutti i principianti (ma non solo).
Cercheremo di procedere con calma e con molti esempi, per evidenziare tutte le proprietà di questi elementi.
Molti linguaggi di programmazione, come ad esempio Java o Python non li comprendono, hanno introdotto la così detta Garbage Collection (che in italiano potremmo chiamare spazzatura) in cui finiscono tutti i blocchi di memoria riservati dal programmatore che quando non servono più sono automaticamente liberati.
In C questo non c’è.
Tramite i puntatori il programmatore si riserva blocchi di memoria che poi deve liberare.
Ma basta chiacchiere ed andiamo a cominciare!
Sommario
- Soluzione esercizio proposto in Lezione 4
- Struttura della memoria di un calcolatore
- malloc
- Le stringhe
- Le funzioni
- Riassunto
- Esercizi
1 Soluzione esercizio proposto in Lezione 4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
/*********************** esercizio.c arkkimede aprile 2021 ***********************/ #include <stdio.h> #include <stdlib.h> #define NUMERO_DI_PARI 10 #define NUMERO_DI_DISPARI 8 int main() { int nPari = 0; int nDispari = 0; long long int sommaPari = 0; // Utilizzati long long int per evitare long long int sommaDispari = 0; // un possibile overflow int estratto; srand(100); while (1) { estratto = rand(); if(estratto%2==0) { // Questo controllo è necessario farlo subito // per evitare di superare il numero di pari desiderato // Se il numero di pari è stato raggiunto si salta al // prossimo numero casuale if ( nPari == NUMERO_DI_PARI ) { continue; } nPari++; sommaPari += estratto; } else { // Stesso discorso fatto per i pari if ( nDispari == NUMERO_DI_DISPARI ) { continue; } nDispari++; sommaDispari += estratto; } // Verifichiamo se entrambi le condizioni sono soddisfatte: if ( (nPari == NUMERO_DI_PARI) && (nDispari == NUMERO_DI_DISPARI) ) { break; } } printf("La somma dei primi %d numeri pari è %lld\n",NUMERO_DI_PARI,sommaPari); printf("La somma dei primi %d numeri dispari è %lld\n",NUMERO_DI_DISPARI,sommaDispari); return 0; } /********************* Output: La somma dei primi 10 numeri pari è 9794103776 La somma dei primi 8 numeri dispari è 7113881004 *********************/ |
2 Struttura della memoria di un calcolatore
Per addentrarci in questo argomento, per prima bisogna aver chiaro in mente come è strutturata la memoria del nostro calcolatore.
Fig1 – Memoria di un Calcolatore
Come illustrato nella Fig1, si tratta di una tabella con due colonne dove la prima è quella degli indirizzi (ADD) la seconda quella delle Celle Di Memoria (CDM).
L’indirizzo, con la sempre crescente necessità di memoria, è passato da 16 a 32 ed ultimamente a 64bit.
Il fattore determinante però è il sistema operativo. Ad esempio un Linux a 32 bit può girare tranquillamente su di una macchina a 64 bit, (l’opposto ovviamente no).
Notiamo poi, che la cella di memoria è di 8 bit, ed è la più piccola quantità di memoria con cui si possa lavorare (ricordate le variabili char ?).
Consideriamo adesso la seguente dichiarazione di un generico codice C:
1 2 3 4 5 6 |
... char x; double y; float z; char *a; ... |
nella fase iniziale dell’esecuzione, nella memoria verranno riservate locazioni atte a contenere ognuna delle variabili precedentemente introdotte.
A titolo di esempio, facendo riferimento alla variabile x, chiamando con Addr_x il suo indirizzo, in memoria si avrà:
Fig2 – Rappresentazione in memoria di x
In questo caso (x di tipo char lunga 1 byte), l’indirizzo Addr_x+1 sarà libero ed eventualmente utilizzabile per altre variabili; nel caso invece di y, se Addr_y è il suo indirizzo in memoria, sicuramente le locazioni comprese tra Addr_y+1 e Addr_y+7 non saranno disponibili in quanto riservate per contenere una variabile double appunto lunga 8 byte.
A livello di programmazione, l’indirizzo di x lo possiamo conoscere tramite l’operatore di referenziazione &
Fig3 – Operatore di referenziazione
In generale questo operatore può anche essere applicato ad una qualsiasi variabile.
Facendo sempre riferimento alla dichiarazione riportata precedentemente, a è invece un puntatore, ossia una grandezza che immagazzina un indirizzo (ADD).
Applicando a questo l’operatore di dereferenziazione *, si può conoscere il contenuto della cella di memoria.
Fig4 – Puntatore non inizializzato
Di fatti la Fig4 è una pseudo-rappresentazione della situazione immediatamente dopo la dichiarazione riportata.
Perché pseudo? Perché a, di fatti, dovrebbe contenere l’indirizzo di una cella di memoria, che in una macchina a 32bit, sarebbero in effetti 4 Celle di memoria.
Una rappresentazione più corretta, ma sicuramente più pesante, sarebbe quella di Fig5.
Fig5 – Memoria occupata da un puntatore
D’ora in avanti, per snellire la notazione e per evidenziare solo i concetti, utilizzeremo la notazione di Fig6, dove le celle rappresentate le si devono immaginare sufficientemente capienti per contenere i dati a loro destinati.
Fig6 – Rappresentazione alternativa di un puntatore
Quindi un puntatore inizializzato è:
Fig7 – Puntatore inizializzato
Proviamo adesso ad usare queste grandezze.
3 malloc
Uno degli esempi tipici dei puntatori è quello di definire gli array o vettori che possono avere più dimensioni.
Queste grandezze possono anche essere definite staticamente, ossia di dimensione fissa, vediamo un esempio. Si noti che in C ogni vettore, statico o definito tramite i puntatori, ha come primo elemento l’indice 0.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
/**************************** esempio5_1.c vettori statici arkkimede aprile 2021 ****************************/ #include <stdio.h> int main() { int vettore[4]; float *altezze; /************************* In vettore, per esempio possiamo immagazzinare i numeri da 0 a 3 *************************/ for(int i=0; i<4; i++) { vettore[i] = i; } /************************* Come curiosità, esaminiamo anche gli indirizzi degli elementi di vettore[i] *************************/ printf("Indirizzo espresso in decimale\n"); for(int i=0; i<4; i++) { printf("Vettore[%d]: indirizzo:%d contenuto:%d\n", i,&vettore[i], vettore } printf("********\n\n"); /************************* Indirizzi espressi in esadecimale usando il formato dei puntatori %p *************************/ printf("Indirizzo espresso in esadecimale\n"); for(int i=0; i<4; i++) { printf("Vettore[%d]: indirizzo:%p contenuto:%d\n", i,&vettore[i], vettore } return 0; } /********************** Output 1: Indirizzo espresso in decimale Vettore[0]: indirizzo:2125460660 contenuto:0 Vettore[1]: indirizzo:2125460664 contenuto:1 Vettore[2]: indirizzo:2125460668 contenuto:2 Vettore[3]: indirizzo:2125460672 contenuto:3 ******** Indirizzo espresso in esadecimale Vettore[0]: indirizzo:0x7eaff4b4 contenuto:0 Vettore[1]: indirizzo:0x7eaff4b8 contenuto:1 Vettore[2]: indirizzo:0x7eaff4bc contenuto:2 Vettore[3]: indirizzo:0x7eaff4c0 contenuto:3 Output 2: Indirizzo espresso in decimale Vettore[0]: indirizzo:2127197364 contenuto:0 Vettore[1]: indirizzo:2127197368 contenuto:1 Vettore[2]: indirizzo:2127197372 contenuto:2 Vettore[3]: indirizzo:2127197376 contenuto:3 ******** Indirizzo espresso in esadecimale Vettore[0]: indirizzo:0x7eca74b4 contenuto:0 Vettore[1]: indirizzo:0x7eca74b8 contenuto:1 Vettore[2]: indirizzo:0x7eca74bc contenuto:2 Vettore[3]: indirizzo:0x7eca74c0 contenuto:3 *************************/ |
Osserviamo alcune caratteristiche:
- Usando lo script di Lezione 0, verrà segnalato un Warning (formato intero per indirizzo di puntatore). E’ stata fatta una forzatura (vedi oltre). Usando invece il semplice comando gcc esempio5_1.c -o esempio5_1 non ci sarà nessun Warning
- Nella dichiarazione, la dimensione di un vettore statico va espresso tra parentesi quadre (int vettore[4];)
- Come anticipato, gli elementi saranno vettore[0],…vettore[3]
- Riportati indirizzi dei vettori, in forma decimale e esadecimale
- Dalla versione decimale (Warning) è facile osservare che la differenza tra un elemento e l’altro è 4, infatti un intero si rappresenta su 4 byte o 32 bit
- Dalla seconda rappresentazione invece, ricordando la nota relazione che fa corrispondere ad una cifra esadecimale 4 bit, verifichiamo che la nostra macchina è a 32 bit
- Infine, se si fa girare il codice più volte, si otterranno locazioni di memoria differenti, legate alla disponibilità di memoria del calcolatore in quel preciso istante.
Nell’esempio seguente vedremo un metodo più standard per verificare la dimensione degli indirizzi e poi l’allocazione dinamica di vettori.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
/**************************** esempio5_2.c vettori dinamici arkkimede aprile 2021 ****************************/ #include <stdio.h> #include <stdlib.h> #define GRUPPI_VEICOLI 3 #define ERROR_ID 1 #define ERROR_RUOTE 2 #define ERROR_ALTEZZA 3 #define ERROR_COSTO 4 int main() { int *ruoteVeicoli; float *altezzaVeicoli; char *idVeicoli; double *costoVeicoli; /************************* Verifica del numero di bit presenti nell'indirizzo sizeof(grandezza) restituisce il numero di byte occupati da grandezza *************************/ printf("int : %d byte \n",sizeof(ruoteVeicoli)); printf("float : %d byte \n",sizeof(altezzaVeicoli)); printf("char : %d byte \n",sizeof(idVeicoli)); printf("double : %d byte \n",sizeof(costoVeicoli)); idVeicoli = (char *)malloc(sizeof(char) * GRUPPI_VEICOLI); if( idVeicoli == NULL) { printf("Errore, non si è riusciti a riservare memoria per il vettore idVeicoli\n"); printf("Esecuzione interrotta\n"); return ERROR_ID; } idVeicoli[0] = 8; idVeicoli[1] = 13; idVeicoli[2] = 17; ruoteVeicoli = (int *)malloc(sizeof(int) * GRUPPI_VEICOLI); if( ruoteVeicoli == NULL) { printf("Errore, non si è riusciti a riservare memoria per il vettore ruoteVeicoli\n"); printf("Esecuzione interrotta\n"); return ERROR_RUOTE; } ruoteVeicoli[0] = 2; ruoteVeicoli[1] = 3; ruoteVeicoli[2] = 4; altezzaVeicoli = (float *)malloc(sizeof(float) * GRUPPI_VEICOLI); if( altezzaVeicoli == NULL) { printf("Errore, non si è riusciti a riservare memoria per il vettore altezzaVeicoli\n"); printf("Esecuzione interrotta\n"); return ERROR_ALTEZZA; } altezzaVeicoli[0] = 1.; altezzaVeicoli[1] = 1.3; altezzaVeicoli[2] = 1.8; costoVeicoli = (double *)malloc(sizeof(double) * GRUPPI_VEICOLI); if( costoVeicoli == NULL) { printf("Errore, non si è riusciti a riservare memoria per il vettore costoVeicoli\n"); printf("Esecuzione interrotta\n"); return ERROR_COSTO; } costoVeicoli[0] = 1500.; costoVeicoli[1] = 1900.; costoVeicoli[2] = 15000.; printf("\n\n***************************\n\n"); printf("I %d gruppi di veicoli hanno le seguenti caratteristiche:\n",GRUPPI_VEICOLI); for (int i=0; i<GRUPPI_VEICOLI; i++) { printf("id: %2d ",idVeicoli[i]); printf("numero di ruote %d ",ruoteVeicoli[i]); printf("altezza pari a %3.2f m ",altezzaVeicoli[i]); printf("costo pari a %5.0lf €\n",costoVeicoli[i]); } free(idVeicoli); free(ruoteVeicoli); free(altezzaVeicoli); free(costoVeicoli); return 0; } /********************** Output: int : 4 byte float : 4 byte char : 4 byte double : 4 byte *************************** I 3 gruppi di veicoli hanno le seguenti caratteristiche: id: 8 numero di ruote 2 altezza pari a 1.00 m costo pari a 1500 € id: 13 numero di ruote 3 altezza pari a 1.30 m costo pari a 1900 € id: 17 numero di ruote 4 altezza pari a 1.80 m costo pari a 15000 € *************************/ |
- l’istruzione sizeof riporta l’occupazione in byte della grandezza testata
- I 4 tipi di puntatori hanno tutti fornito come risultato 4 byte
- malloc è la funzione per allocare blocchi di memoria (stdlib.h)
- potendola applicare a qualsiasi tipo di dati (anche a quelli che impareremo a costruire), restituisce un puntatore tipo generico void a cui è necessario applicare un cast alla grandezza desiderata
- la quantità di memoria necessaria poi è definita sempre attraverso l’aiuto di sizeof
- buona norma sempre verificare che l’operazione sia andata a buon fine valutando il valore del puntatore
- in caso di errore segnaliamo il problema ed usciamo magari con un codice di errore codificato
- infine, come ultima operazione, se la memoria non serve più, liberiamola con free
I puntatori possono anche essere utilizzati per rappresentare strutture dati a due, tre o più dimensioni (esempio5_3.c)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
/**************************** esempio5_3.c matrice dinamica per tavola pitagorica arkkimede aprile 2021 ****************************/ #include <stdio.h> #include <stdlib.h> int main() { int **tavolaPitagorica; /************************* Riserviamo lo spazio per la matrice 10 x 10 *************************/ tavolaPitagorica = (int **)malloc(sizeof(int*) * 10); if( tavolaPitagorica == NULL) { printf("Errore di memoria per il puntatore doppio tavolaPitagorica \n"); printf("Esecuzione interrotta\n"); return 1; } for(int i=0; i<10; i++) { tavolaPitagorica[i] = (int *)malloc(sizeof(int) * 10); if( tavolaPitagorica[i] == NULL) { printf("Errore di memoria per il puntatore tavolaPitagorica[i] \n"); printf("Esecuzione interrotta\n"); return 2; } } /************************** Calcolo e memorizzazione della tavola pitagorica Ricordiamo che il primo indice è zero **************************/ for(int i=0; i<10; i++) { for(int j=0; j<10; j++) { tavolaPitagorica[i][j] = (i+1)*(j+1); } } /************************* Stampa del risultato *************************/ int etichettaRiga = 1; printf("La Tavola Pitagorica dei numeri da 1 a 10 è:\n"); for(int i=0; i<10; i++) { for(int j=0; j<10; j++) { if((i==0)&&(j==0)) // Test per poter mettere la riga dei numeri da 1 a 10 { printf(" | 1 2 3 4 5 6 7 8 9 10\n"); printf("----+---------------------------------------\n"); } if(j==0) // Test per poter mettere la colonna dei numeri da 1 a 10 { printf("%3d |",etichettaRiga); etichettaRiga++; } printf("%3d ",tavolaPitagorica[i][j]); // Stampa vera e propria } printf("\n"); } /************************* Liberare la memoria Bisogna procedere in verso inverso rispetto alla memorizzazione *************************/ for(int i=0; i<10; i++) { free(tavolaPitagorica[i]); } free(tavolaPitagorica); return 0; } /********************** Output: La Tavola Pitagorica dei numeri da 1 a 10 è: | 1 2 3 4 5 6 7 8 9 10 ----+--------------------------------------- 1 | 1 2 3 4 5 6 7 8 9 10 2 | 2 4 6 8 10 12 14 16 18 20 3 | 3 6 9 12 15 18 21 24 27 30 4 | 4 8 12 16 20 24 28 32 36 40 5 | 5 10 15 20 25 30 35 40 45 50 6 | 6 12 18 24 30 36 42 48 54 60 7 | 7 14 21 28 35 42 49 56 63 70 8 | 8 16 24 32 40 48 56 64 72 80 9 | 9 18 27 36 45 54 63 72 81 90 10 | 10 20 30 40 50 60 70 80 90 100 ***************************/ |
- si noti l’associazione vettore puntatore singolo, matrice doppio (ed analogamente con più dimensioni)
- le operazioni di casting sui tipi appropriati
- il controllo sul risultato della malloc
- la liberazione della memoria procedendo a ritroso
- i test condizionali per abbellire l’uscita
4 Le stringhe
Sono degli array di caratteri e non esistono come tipo nel C.
Appare quindi evidente perché siano state introdotte nella lezione dei puntatori.
Come i vettori possono essere statiche, ma le più usate sono quelle dinamiche.
Una stringa, analizzata carattere per carattere, alla fine ha il così detto terminatore di stringa ossia ‘\0’.
Una stringa si alloca come fatto precedentemente per gli altri tipi.
Il C fornisce tutta una serie di funzioni accessibili includendo string.h
Per le stringhe non vale l’operatore di assegnazione (=) ma si possono valorizzare almeno in quattro modi differenti (esempio5_5.c).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
/**************************** esempio5_5.c le stringhe arkkimede aprile 2021 ****************************/ #include <stdio.h> #include <stdlib.h> #include <string.h> // Header per le stringhe #define STR_LEN 100 int main() { /***************** Primo metodo per valorizzare una stringa: in sede di dichiarazione ****************/ char strValorizzataInDichiarazione[]="Stringa valorizzata"; char strStatica[5]; char *strDinam_1; char *strDinam_2; // Formato di uscita delle stringe %s printf("|%s| di lunghezza %d \n", strValorizzataInDichiarazione, strlen(strValorizzataInDichichiarazione)); /******************* Secondo metodo per valorizzare una sringa, carattere per carattere ******************/ strStatica[0] = 'C'; strStatica[1] = 'i'; strStatica[2] = 'a'; strStatica[3] = 'o'; strStatica[4] = '\0'; /******************* La stringa è lunga 4 caratteri ma bisogna aggiungerne uno per il terminatore di stringhe *******************/ printf("|%s| di lunghezza %d \n",strStatica, strlen(strStatica)); /******************* Terzo metodo per valorizzare una stringa, usare sprintf *******************/ strDinam_1 = (char *)malloc(sizeof(char)*STR_LEN); if (strDinam_1 == NULL) { printf("Errore di memoria allocando prima stringa dinamica"); printf("Esecuzione interrotta\n"); return 1; } sprintf(strDinam_1,"Stringa valorizzata con sprintf"); printf("|%s| di lunghezza %d\n",strDinam_1, strlen(strDinam_1)); /******************* Quarto metodo mediante strcpy *******************/ strDinam_2 = (char *)malloc(sizeof(char)*STR_LEN); if (strDinam_2 == NULL) { printf("Errore di memoria allocando seconda stringa dinamica"); printf("Esecuzione interrotta\n"); return 2; } strcpy(strDinam_2,"Utilizzo libreria C"); printf("|%s| di lunghezza %d\n",strDinam_2, strlen(strDinam_2)); free(strDinam_1); free(strDinam_2); return 0; } /********************** Output: |Stringa valorizzata| di lunghezza 19 |Ciao| di lunghezza 4 |Stringa valorizzata con sprintf| di lunghezza 31 |Utilizzo libreria C| di lunghezza 19 ***************************/ |
- il formato di uscita per stampare una stringa è %s
- l’header string.h presenta molte funzioni che si applicano alle stringhe dalla lunghezza, confronto, ricerca di sottostringhe o caratteri
- valutando la dimensione minima di una stringa è necessario tenere in conto il terminatore di stringa ‘\0’
- se allocate come tutti i blocchi di memoria, quando non servono più vanno liberate.
5 Le funzioni
Osservando i codici scritti, si noti quanto appesantisce la notazione, il controllo delle uscite della malloc: sono operazioni necessarie ma rendono il codice estremamente pesante da leggere.
Questo accade in generale quando ci sono blocchi di codice che sono ripetuti una o più volte.
Questa non è una buona programmazione.
Bisogna infatti riutilizzare quanto più possibile sorgenti già scritti, testati, alleggerendo il codice stesso ed evitando di cadere in errori che sono figli del copy&paste (copia ed incolla), come il propagare di errori e compicazione del sorgente.
A tale scopo esistono le funzioni. (esempio5_4.c)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
/**************************** esempio5_4.c introduzione delle funzioni arkkimede aprile 2021 ****************************/ #include <stdio.h> #include <stdlib.h> /******************** Lista dei prototipi ********************/ int somma(int a, int b); int main() { int x, y; int risultato; x = 10; y = 20; risultato = somma(x, y); printf("La somma tra %d e %d è %d\n",x,y,risultato); return 0; } /********************* * funzione somma ********************** * parametri in ingresso * int a Primo addendo * int b Secondo addendo ********************** * Parametro di uscita * int out La somma *********************/ int somma(int a, int b) { int out; out = a + b; return out; } /********************** Output: La somma tra 10 e 20 è 30 ***************************/ |
- in cima al codice è riportata la lista dei prototipi (ossia la dichiarazione delle funzioni)
- questi avvisano il compilatore sull’introduzione di altri elementi e consentono anche il controllo sintattico dell’uso (sia nei tipi sia nelle quantità)
- in coda al main è riportata la funzione che presenta 2 parametri di ingresso ed un’uscita.
- ce ne sono con e senza ingressi (rand()) con e senza uscite
- una descrizione è sempre utile (cosa sono ingressi, uscite e funzionamento)
- alla riga 20 è usata
L’esempio riportato è assolutamente banale ma scriviamo una funzione per alleggerire esempio5_2 con malloc.
Prima è necessari introdurre 2 macro ossia particolari costrutti che il linguaggio riconosce ed interpreta (macro.c)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
/********************** * macro.c * arkkimede aprile 2021 **********************/ #include <stdio.h> int main() { /************************ La prima macro ci indica la linea di codice in cui ci troviamo ************************/ printf("Ci troviamo alla linea di codice: %d\n",__LINE__); /************************ La seconda macro ci indica il nome del file in cui ci troviamo ************************/ printf("Ci troviamo nel sorgente di nome: %s\n",__FILE__); return 0; } /********************** Output: Ci troviamo alla linea di codice: 13 Ci troviamo nel sorgente di nome: macro.c ***********************/ |
Dal listato numerato è evidente il funzionamento delle due macro.
La funzione malloc_chk.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
void *malloc_chk(size_t size, char *funcName, int line) /******************************* ** funzione malloc_chk ** effettua un'allocazione di memoria ** verificando un eventuale errore e ** segnalandolo ** Parametri di ingresso: ** size_t size: la dimensione di memoria richiesta ** char *funcName: il nome del file sorgente in cui ** c'è stato il problema (macro __FUNC__) ** int line: la linea del sorgente in cui c'è ** stato il problema (macro __LINE__) ** Parametro di uscita ** void *ptr: puntatore al blocco di memoria richiesto ** da processare con un cast nella funzione chiamante ** arkkimede aprile 2021 ******************************** { void *ptr; ptr = malloc(size); if(ptr == NULL) { printf("Errore nell'allocazione di memoria.\n"); printf("Quantità di memoria richiesta: %zu\n",size); printf("Sorgente dove è accaduto l'errore %s\n",funcName); printf("Lina del codice sorgente ove è accaduto l'errore: %d\n",line); printf("Esecuzione interrotta\n"); exit(11); } else return ptr; } |
- size_t è una ridefinizione standard di unsigned int indipendente dalla piattaforma (quindi capace di contenere anche grosse moli di dati).
- in caso di errore, questa volta si è usata la funzione exit(codice) che ammette un valore di ritorno (utilizzabile come il valore di return).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
/**************************** esempio5_6.c Si tratta di esempio5_2.c in cui si usa la funzione malloc_chk arkkimede aprile 2021 ****************************/ #include <stdio.h> #include <stdlib.h> #define GRUPPI_VEICOLI 3 // Prototipo della funzione void *malloc_chk(size_t size, char *funcName, int line); int main() { int *ruoteVeicoli; float *altezzaVeicoli; char *idVeicoli; double *costoVeicoli; idVeicoli = (char *)malloc_chk(sizeof(char) * GRUPPI_VEICOLI, __FILE__, __LINE__); idVeicoli[0] = 8; idVeicoli[1] = 13; idVeicoli[2] = 17; ruoteVeicoli = (int *)malloc_chk(sizeof(int) * GRUPPI_VEICOLI, __FILE__, __LINE__); ruoteVeicoli[0] = 2; ruoteVeicoli[1] = 3; ruoteVeicoli[2] = 4; altezzaVeicoli = (float *)malloc_chk(sizeof(float) * GRUPPI_VEICOLI, __FILE__, __LINE__); altezzaVeicoli[0] = 1.; altezzaVeicoli[1] = 1.3; altezzaVeicoli[2] = 1.8; costoVeicoli = (double *)malloc_chk(sizeof(double) * GRUPPI_VEICOLI, __FILE__, __LINE__); costoVeicoli[0] = 1500.; costoVeicoli[1] = 1900.; costoVeicoli[2] = 15000.; printf("I %d gruppi di veicoli hanno le seguenti caratteristiche:\n",GRUPPI_VEICOLI); for (int i=0; i<GRUPPI_VEICOLI; i++) { printf("id: %2d ",idVeicoli[i]); printf("numero di ruote %d ",ruoteVeicoli[i]); printf("altezza pari a %3.2f m ",altezzaVeicoli[i]); printf("costo pari a %5.0lf €\n",costoVeicoli[i]); } free(idVeicoli); free(ruoteVeicoli); free(altezzaVeicoli); free(costoVeicoli); return 0; } void *malloc_chk(size_t size, char *funcName, int line) /******************************* ** funzione malloc_chk ** effettua un'allocazione di memoria ** verificando un eventuale errore e ** segnalandolo ** Parametri di ingresso: ** size_t size: la dimensione di memoria richiesta ** char *funcName: il nome del file sorgente in cui ** c'è stato il problema (macro __FUNC__) ** int line: la linea del sorgente in cui c'è ** stato il problema (macro __LINE__) ** Parametro di uscita ** void *ptr: puntatore al blocco di memoria richiesto ** da processare con un cast nella funzione chiamante ** arkkimede aprile 2021 ********************************/ { void *ptr; ptr = malloc(size); if(ptr == NULL) { printf("Errore nell'allocazione di memoria.\n"); printf("Quantità di memoria richiesta: %zu\n",size); printf("Sorgente dove è accaduto l'errore %s\n",funcName); printf("Lina del codice sorgente ove è accaduto l'errore: %d\n",line); printf("Esecuzione interrotta\n"); exit(11); } else return ptr; } /********************** I 3 gruppi di veicoli hanno le seguenti caratteristiche: id: 8 numero di ruote 2 altezza pari a 1.00 m costo pari a 1500 € id: 13 numero di ruote 3 altezza pari a 1.30 m costo pari a 1900 € id: 17 numero di ruote 4 altezza pari a 1.80 m costo pari a 15000 € *************************/ |
Si noti come il main sia più leggibile e chiaro, pur avendo comunque verificato un possibile errore della malloc
6 Riassunto
- Introduzione alla struttura della memoria di un calcolatore con indirizzi e celle di memoria
- I puntatori contengono l’indirizzo di una particolare cella di memoria
- L’operatore unario & consente di conoscere l’indirizzo di una cella di memoria e * la cella di memoria di un puntatore
- La funzione sizeof consente di conoscere l’occupazione, in termini di byte, di una qualsiasi grandezza C
- La funzione malloc consente di allocare blocchi di memoria (con l’aiuto di sizeof )che poi devono essere liberati
- Tale funzione restituisce un puntatore void che poi deve essere “castato” secondo le necessità
- E’ bene sempre controllare se l’allocazione di memoria sia riuscita o meno
- Le stringhe sono vettori di caratteri e possono essere statici e dinamici
- I secondi si creano sempre con la malloc
- Per queste strutture dati non vale l’operatore di assegnazione (=)
- Esistono comunque metodi per valorizzare le stringhe
- Le funzioni sono blocchi di codice che possono essere richiamati quando serve, semplificando il sorgente che le richiama
- Consentono di riutilizzare programmi e procedure già testate e verificate, senza dover ogni volta ricominciare tutto da capo.
7 Esercizi
Anche questa settimana vediamo un esercizio di cui la soluzione verrà pubblicata nella prossima lezione.
Fig8 – Cubo dati da riservare in memoria
In Fig8 è rappresentato un cubo con dei dati.
A partire dalla traccia riportata:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#include <stdio.h> #include <stdlib.h> /********************************* ** * ** i | ** * ** j \ ** *---* ** k **********************************/ int main() { int ***cubo; int contatore=0; int i,j,k; return 0; } |
Allocare nel puntatore triplo cubo una struttura 3x3x3.
Inserire i dati come riportati in figura.
Stampare poi le matrici 3×3 a partire dalla base ed andando su, poi dalla faccia frontale e procedendo in profondità ed infine dalla parete laterale a sinistra procedendo verso destra.
Dopo aver stampato i dati, liberare la memoria.
Con questo terminiamo la prima lezione sui puntatori in cui sono stati anche inseriti elementi che li utilizzano.
Continueremo seguendo la stessa filosofia anche nella prossima lezione: arrivederci!!
Qui il forum di supporto al corso.
Se vuoi restare aggiornato, seguici anche sui nostri social: Facebook, Twitter, Youtube
Se vuoi anche trovare prodotti e accessori Raspberry Pi in offerta, seguici anche su Telegram !!