Ciao e Benvenuto. Ora che comprendiamo l'allocazione dinamica
della memoria, dobbiamo implementarla, dobbiamo sapere effettivamente come farlo nel codice c.
Quindi parleremo di tre funzioni davvero importanti,
malloc, calloc, realloc.
Si vede che hanno nomi in cui l'allocazione della
memoria calloc e la riallocazione.
Il modo più semplice e comune per allocare la memoria
è tramite la funzione di libreria standard denominata malloc.
Questa è l'allocazione della memoria dinamica in fase di esecuzione.
Per chiamare questa funzione, è necessario includere il file di intestazione lib standard.
Si specifica il numero di byte di memoria che si desidera allocare come argomento.
Ciò che viene restituito da questa funzione è l'indirizzo del primo byte di memoria allocato.
Poiché ottieni un indirizzo restituito, un puntatore è l'unico posto in cui posizionarlo.
Quindi questo è il motivo per cui l'allocazione dinamica della memoria è
possibile solo tramite questo concetto di puntatore perché restituisce indirizzi.
Puoi solo inserire un indirizzo in un puntatore, questo è ciò che un puntatore memorizza, un puntatore memorizza un indirizzo.
Quindi ecco come lo fai con una sintassi molto semplice.
Di solito, quando crei il tuo puntatore, non devi.
Puoi creare il tuo puntatore e impostarlo su null.
E poi nella riga successiva, potresti malloc.
Ma ecco la sintassi standard che stai creando il tuo puntatore int, puntatore
a un tipo int.
E poi noterai che chiami malloc.
E qui, stiamo dicendo malloc, creeremo 100 byte per malloc e
poi lo invieremo a un puntatore int.
Poiché malloc restituisce solo un indirizzo, è necessario eseguirne il
cast a un puntatore finale perché quell'indirizzo sta
memorizzando un puntatore int.
Questo è il modo standard per farlo.
Ora stai richiedendo 100 byte di
memoria e assegnandoli all'indirizzo del blocco di memoria al numero p.
pNumber punterà alla prima posizione int all'inizio dei 100 byte allocati.
Quindi ciò significa che poiché gli interi sono 4 byte per pezzo,
può contenere 25 valori int.
E ancora, questo presuppone che tu abbia 4 byte.
Ora, a seconda del sistema in esecuzione, potrebbero essere più grandi, forse più piccoli.
Quindi dovresti vedere il problema con questo. Questo codice non funzionerà
su sistemi diversi perché stai allocando in base a un presupposto,
basato sul presupposto della dimensione int.
Quindi non è molto utile usare malloc in
questo modo con un numero costante 100.
Quello che vuoi fare è usare l'operatore sizeof.
Quindi sarebbe meglio rimuovere l'ipotesi che gli int siano 4 byte.
Se invece vuoi allocare spazio per 25 interi all'interno dell'argomento malloc, specifica il
numero di interi
che vuoi allocare e poi moltiplicalo per la dimensione degli interi.
E questo renderà il codice molto più portabile.
Questo codice ora funzionerà su diversi sistemi, diversi sistemi operativi, diverse architetture.
Si esegue comunque il cast del valore di ritorno dell'indirizzo perché deve puntare a un
tipo int, quindi si dice int star. L'unica differenza qui è che usi l'operatore sizeof.
L'operatore Sizeof è molto importante quando si alloca la memoria utilizzando malloc o
utilizzando calloc o realloc.
Usi sempre e vuoi usare l'operatore sizeof per
il tipo che stai allocando e poi moltiplicalo per il numero
di numeri particolari che vuoi, quindi se vuoi memorizzare
25 int, allora fai 25.
Quindi l'argomento di malloc sopra indica chiaramente che dovrebbero essere resi disponibili byte
sufficienti per accogliere 25 valori di tipo int.
Inoltre, come ho già detto, nota il cast. Questo converte l'indirizzo, restituito
dalla funzione al puntatore di tipo in puntatore int e int.
Malloc restituisce un puntatore di tipo void, ecco cosa lo rende così utile.
Restituisce quell'indirizzo ma è un puntatore vuoto.
Ricorda, abbiamo parlato dei puntatori void e del perché i puntatori void
sono utili perché ora puoi avere una funzione che può accettare parametri diversi e può
restituire un puntatore void. Ora stai vedendo il
potere di restituire un puntatore
void, di nuovo, ma per usarlo, per assegnargli effettivamente dei dati, per de-referenziarlo,
devi lanciarlo.
Se non volessimo lanciarlo, potremmo dire void star p number.
Non lo lanciamo, ma poi dovremmo lanciarlo una volta
che avessimo voluto de-referenziarlo.
Ma è per questo che dobbiamo lanciare da malloc. Restituisce un indirizzo e vuoi che punti a
un tipo int e restituisce un tipo void.
Puoi richiedere qualsiasi numero di byte.
Tuttavia, dipende dal sistema e dalla quantità di memoria disponibile sul sistema.
Quindi, se provi a creare o provi a richiedere una tonnellata di
byte o c'è qualche limitazione, potrebbe fallire.
E quando fallisce, restituisce un valore di null, malloc restituirà null.
E questo è positivo perché i puntatori quando puntano a zero, significa che
non puntano a nessuna memoria, quindi
non puoi corrompere o modificare alcuna memoria inavvertitamente.
Quindi è sempre una buona idea, dopo aver
eseguito un malloc, verificare immediatamente la presenza di null
prima di utilizzarlo, proprio come quando
si assegnano i dati o come quando si de-referenzia un puntatore.
Vuoi assicurarti di indicare effettivamente qualcosa.
Quindi qui, stiamo allocando un po 'di memoria per p number il nostro puntatore e
poi stiamo dicendo se non p number.
Ricorda, il null è uno zero.
Quindi puoi semplicemente usare un non su di esso. Non è necessario confrontarlo effettivamente con null.
Puoi, puoi confrontarlo con null lo rende più leggibile, forse lo rende più esplicito.
Ma puoi semplicemente dire non numero p, e questo in realtà significa non
uno e null è zero. Quindi questo verificherà la presenza di null.
E se è nullo, significa fondamentalmente che non è
stato possibile allocare la memoria. Se non sei in grado di allocare memoria nel tuo programma, sei nei guai.
E quindi quello che dovresti fare di solito in questo codice per affrontare
un errore di allocazione della memoria è che dovresti interrompere,
dovresti chiamare exit, dovresti uscire dal tuo programma. Perché se il tuo programma non è in grado di allocare memoria, il tuo programma è bloccato.
Quindi di solito questo è che chiami interruzione accidentale se non puoi allocare memoria.
Almeno quando si esegue questa operazione, è possibile visualizzare un messaggio per
terminare il programma. È molto meglio che
consentire al programma di continuare quando non lo eri non siamo in grado di allocare memoria.
Poiché in tal caso potresti continuare, potresti provare a utilizzare
quella memoria, sarà nulla, quindi il programma si bloccherà comunque
ma non saprai necessariamente perché.
Devi guardare lo stack di chiamate. Ci vorrà molto più tempo per eseguire il debug di tale errore.
Liberare la memoria. Ora ho detto che
poiché sei il programmatore è responsabile dell'allocazione della memoria, devi sbarazzarti
della memoria quando non la usi più perché la memoria è una
risorsa vitale, specialmente sui sistemi embedded.
Non dovresti usare la memoria se non ne hai bisogno. Quindi devi liberare la memoria.
Vuoi sempre liberare la memoria quando non è più necessaria.
La memoria allocata nell'heap verrà rilasciata automaticamente quando il programma esiste, il che è positivo
perché esegue alcune operazioni di pulizia.
Ma è molto meglio rilasciare esplicitamente la memoria quando hai finito,
anche se è appena prima di uscire.
È più
pulito, fallo sempre gratuitamente o libera sempre la memoria dopo
averla allocata, vuoi seguire questa regola. Questa è una regola rigorosa e rigorosa.
Rilascia sempre la memoria se la assegni.
Si verifica una perdita di memoria quando si alloca una parte della memoria in
modo dinamico e non si conserva il riferimento, quindi non è possibile rilasciarlo.
Forse sei fuori portata per quel riferimento che non puoi. Non lo rilasci.
Si chiama perdita di memoria. La memoria è allocata ma non viene più utilizzata.
Ciò si verifica spesso quando ci si trova in un ciclo e si alloca la memoria in un ciclo.
Sei nel giro. Assegnate questa memoria. Lo usi all'interno del
loop, quindi salti fuori dal loop e non lo rilasci più.
Ogni iterazione del ciclo lo sta allocando.
Quindi, per liberare la memoria che hai allocato dinamicamente,
devi comunque avere accesso a quell'indirizzo.
Quindi di solito vuoi una memoria libera quando hai finito di usarla e
di solito nel metodo che l'hai allocata.
Tuttavia, se si passa quella memoria a un'altra funzione e questa è
responsabilità di quella funzione, ma di solito si desidera rilasciare la memoria e
la funzione che si alloca.
Per liberare la memoria per un blocco di memoria allocata dinamicamente
il cui indirizzo è stato memorizzato in un puntatore, tutto ciò che devi
fare è chiamare la funzione free.
Questo è anche nella libreria standard. Devi solo passare il puntatore alla funzione libera.
E di solito, dopo aver passato quel puntatore, vuoi impostarlo su null
perché significa che non hai memoria allocata per esso.
La funzione free ha un parametro formale di tipo
void star, quindi puoi passare qualsiasi tipo
di puntatore e funzionerà.
Non devi davvero lanciarlo o qualcosa del genere.
Finché il numero p contiene l'indirizzo che è stato restituito quando la memoria viene allocata, l'intero blocco
di memoria sarà libero per un ulteriore utilizzo. Altri programmi possono utilizzare quella memoria, il tuo
programma ora può utilizzare quella memoria,
in modo significativo quando sei su un sistema integrato.
E poi, come ho detto, vuoi sempre impostare il puntatore
su null per indicare che quel puntatore non punta più a nulla.
Quella memoria è stata liberata.
C'è un'altra bella funzione chiamata calloc.
Questo ha un paio di vantaggi rispetto a malloc. È molto simile a malloc,
ma ciò che fa è allocare la memoria come un numero di elementi in una
data dimensione e inizializza anche la memoria.
Questa è la differenza, l'inizializzazione. Ho parlato dell'importanza dell'inizializzazione.
Ecco perché
vuoi sempre inizializzare i dati in modo da capire che ci
sono dati che ti aspetti.
Quando annulli l'inizializzazione dei dati, potrebbero esserci dati di cui non hai la minima idea.
Quindi inizializza sempre i tuoi dati con qualcosa.
E calloc è un po 'meglio perché lo fa per te.
Richiede due argomenti, ha due parametri.
Il numero di elementi di dati per i quali è richiesto spazio, la dimensione di ciascun elemento di dati.
È dichiarato nell'intestazione della libreria standard.
Lo chiami proprio come chiami malloc. Sembra più o meno allo stesso modo.
In questo esempio, stiamo creando un
puntatore intero e stiamo allocando 75 interi di
sizeof int. Quindi ci vogliono due parametri.
È un po 'più pulito perché non devi effettivamente fare una moltiplicazione.
In malloc, dovevi fare 75 eventi di dimensione temporale.
Qui dici solo 75, stai
creando 75 int e la dimensione per ciascuno è di 4 byte
o qualunque cosa il sistema restituisca dalla dimensione dell'operatore.
Ma ancora una volta il significato di questo è che inizializzerà anche tutti quei dati, il valore restituito
potrebbe essere nullo se non è in grado di allocare quella memoria che a sua volta non dovresti interrompere.
Ma è molto simile all'uso di malloc tranne che il grande vantaggio è questa inizializzazione.
Tutti gli int sono inizializzati a zero, e questo è davvero un grosso problema,
un grande vantaggio.
È anche un po 'più facile da usare con i due parametri.
Parliamo di un'altra funzione chiamata realloc.
La funzione realloc consente di riutilizzare o estendere la memoria allocata
in precedenza utilizzando malloc o calloc.
Quindi questo è un modo per modificare dinamicamente la quantità di memoria
allocata durante l'esecuzione.
Quindi inizialmente potresti creare così tanta memoria e poi ti rendi conto
che non è abbastanza memoria. Bene, quello che puoi fare lì è che puoi
semplicemente chiamare realloc, e questo riallocherà la memoria.
Si aspetta due valori di argomento, un puntatore contenente un indirizzo che
è stato precedentemente trasformato da una chiamata a malloc.
Quindi hai un puntatore che ha della memoria allocata o calloc e quindi la
dimensione e i byte della nuova memoria che vuoi allocare.
Quindi questo allocherà la quantità di memoria specificata dal secondo argomento,
che è il secondo argomento. Trasferisce il contenuto della memoria allocata in
precedenza, a cui fa riferimento il puntatore, in modo che sappia automaticamente quanta
memoria avevi allocato in precedenza perché passi il puntatore.
E poi aggiungerà effettivamente a quello, la memoria appena allocata.
E restituisce un puntatore vuoto alla nuova memoria.
Quindi devi rifarlo quando chiami rialloc proprio come gli altri.
Restituisce null se non sei stato in grado di riallocare,
il che è probabilmente un errore fatale e dovresti interrompere.
La caratteristica più importante di questa operazione è
che realloc preserva il contenuto dell'area di memoria originale, stai semplicemente estendendo quella memoria.
Potrebbe essere frammentato perché potresti non essere stato in grado di farlo in sequenza,
ma hai ancora tutta quella memoria che puoi assegnare ai dati ora.
Diamo un'occhiata a un esempio.
In questo esempio, useremo malloc e poi riallocheremo la memoria e
poi la libereremo.
Quindi qui stiamo creando un puntatore di carattere.
Non lo stiamo inizializzando su null, probabilmente dovremmo esserlo.
Ma non ha memoria allocata per ciò a cui punta.
Ha memoria allocata per memorizzare il
puntatore, che è solo un indirizzo. Ma se provi ad andare a quell'indirizzo, il tuo programma sarà host.
Quindi quello che fai è chiamare malloc.
Qui stiamo dicendo malloc 15.
Questo creerà 15 byte per un puntatore a caratteri.
Lo lanciamo, perché restituisce un puntatore void. Quindi str è un puntatore
ora che ha memoria allocata per ciò a cui punta.
Possiamo quindi usare str copy per passare alcuni dati a ciò a
cui punta, una stringa
e stampiamo la stringa nell'indirizzo.
Bene, allora ci rendiamo conto che quello che abbiamo allocato 15 byte
non è abbastanza grande.
Quindi chiamiamo realloc e passiamo il
puntatore che è str e vogliamo riallocare altri 25 byte e poi questo restituisce
un punto void poiché dobbiamo lanciarlo di nuovo.
Ora avremo ancora in memoria Jason.
Quindi ora possiamo chiamare stringa concatenata e concatenare alla stringa com,
quindi ora sarà Jason. com e poi lo stampiamo.
E poi, ultimo, ma non meno importante, probabilmente la
cosa più importante di questa diapositiva, quindi non abbiamo una perdita di memoria è che chiamiamo libero.
Chiama sempre gratis quando chiami malloc.
Quindi diamo un'occhiata a questo esempio all'interno dei blocchi di codice.
Ecco il nostro esempio di codice all'interno del
nostro editor di blocchi di codice.
E la prima cosa che dovresti notare è che includiamo
il file di intestazione lib standard perché è lì che si trovano le nostre
funzioni malloc e le nostre funzioni realloc così come la nostra funzione libera.
Anche perché stiamo usando alcune funzioni str, concatenazione
e copia, includiamo string. h.
Ora sulla riga otto, creeremo il nostro puntatore di carattere.
Non ha memoria allocata per esso.
Dovremmo inizializzarlo su null perché non
punta ancora a nulla. Sulla linea 11, chiamiamo malloc.
E dovremmo probabilmente dire 15 volte la dimensione
di char nel caso in cui la dimensione
dei byte di char sia diversa e quindi lanciarla in
un puntatore a
carattere perché malloc restituisce un void. Quindi lo copiamo.
Se provassimo a copiarlo e non facessimo nulla sulla riga 11, il programma
andrebbe in crash perché non abbiamo memoria allocata per ciò a cui punta la stringa.
Quindi stampiamo solo la stringa e l'indirizzo.
Ora chiamiamo realloc.
Dobbiamo memorizzare più dati di 15 byte,
quindi riallocheremo con 25 byte
e probabilmente vorremo rifare sizeof char per essere
coerenti.
Lo lanciamo e poi ci concateniamo sulla nostra posizione di
memoria esistente che contiene Jason. com, e stamperemo jason. com.
E poi, ultimo ma non meno importante, alla riga 20, liberiamo quella memoria che
è davvero importante.
Quindi andiamo avanti ed eseguiamo questo. E quello che vedrai è
che quando lo stampiamo la seconda volta, abbiamo la concatenazione.
Quindi la
nostra prima stringa, abbiamo memoria allocata ed è un indirizzo 000C2918.
Quindi abbiamo creato un nuovo indirizzo riallocando e prendendo la dimensione esistente del puntatore che
gli abbiamo passato come argomento.
Quindi 000C0CD8 ha in realtà più dati
allocati, i dati esistenti
in Jason e altri 25 byte.
E quindi quello che noti è che la concatenazione funziona.
Perché quando ci siamo
riallocati, abbiamo inserito il contenuto di Jason in quella memoria e l'abbiamo semplicemente esteso.
Quindi molti vantaggi nell'usare queste chiamate malloc, calloc e realloc gratuite.
Vuoi usarli, vuoi capirli. Funzioni piuttosto semplici da usare.
Quindi nella nostra ultima diapositiva, esaminiamo solo alcune linee guida.
Vuoi evitare di allocare molte piccole quantità di memoria, non è efficiente.
Quando si alloca la memoria sull'heap,
ha un po 'di overhead con esso.
E se allochi piccoli blocchi di memoria, avrà molto più overhead dell'allocazione di un minor
numero di blocchi più grandi.
Quindi alloca la memoria di cui
hai bisogno, ma non farlo ogni due istruzioni con la riallocazione, fai
grandi blocchi.
Aggrappati alla memoria solo finché ne hai bisogno. La memoria è una risorsa scarsa.
Quando è sul mucchio, è ancora più raro.
Quindi, quando hai finito con un blocco di memoria, chiama gratis.
Assicurati sempre di provvedere al rilascio della memoria che hai allocato, decidi dove nel
tuo codice rilascerai la memoria quando scrivi il codice che lo alloca.
Assicurati di avere accesso a quel puntatore.
Se hai bisogno di rendere globale il puntatore, allora devi farlo.
Ma di solito, ovunque tu assegni quella funzione, dovresti rilasciarlo in quella funzione.
Se hai altre funzioni che rilasciano memoria
per te, stai assumendo che non va bene. Ci sono troppi accoppiamenti e dipendenze lì.
La persona che lo crea dovrebbe essere la persona che lo elimina.
Non affidare la responsabilità ad altre funzioni e simili.
Assicurati di non sovrascrivere inavvertitamente l'indirizzo di memoria che hai allocato sull'heap
prima di averlo rilasciato, questo causerà una perdita di memoria.
Quindi, se provi a scrivere troppi byte o se hai overflow, potresti
avere problemi.
Quindi stai particolarmente attento quando stai allocando la memoria all'interno di
un ciclo perché questo può causare problemi di buffer overflow.
Spero che questo sia stato utile. L'allocazione dinamica della memoria è un grosso problema.
Ti darò una sfida che ti aiuterà a familiarizzare con
questo, ma assicurati di capire questo concetto. Grazie.