Perché "free" in C non richiede il numero di byte da liberare?


84

Giusto per essere chiari: lo so malloce freesono implementati nella libreria C, che di solito alloca blocchi di memoria dal sistema operativo e fa la propria gestione per distribuire lotti più piccoli di memoria all'applicazione e tiene traccia del numero di byte allocati . Questa domanda non è come fa il libero a sapere quanto liberare .

Piuttosto, voglio sapere perché è freestato creato in questo modo in primo luogo. Essendo un linguaggio di basso livello, penso che sarebbe perfettamente ragionevole chiedere a un programmatore C di tenere traccia non solo di quale memoria è stata allocata ma di quanta (infatti, trovo comunemente che finisco per tenere traccia del numero di byte malloced comunque). Mi viene anche in mente che fornire esplicitamente il numero di byte a freepotrebbe consentire alcune ottimizzazioni delle prestazioni, ad esempio un allocatore che ha pool separati per diverse dimensioni di allocazione sarebbe in grado di determinare da quale pool liberarsi semplicemente guardando gli argomenti di input, e ci sarebbe meno spazio in testa nel complesso.

Quindi, in breve, perché sono stati creati malloce freecreati in modo tale da essere tenuti a tenere traccia internamente del numero di byte allocati? È solo un incidente storico?

Una piccola modifica: alcune persone hanno fornito punti come "e se liberassi un importo diverso da quello che hai assegnato". La mia immaginata API potrebbe semplicemente richiederne una per liberare esattamente il numero di byte allocati; liberare più o meno potrebbe essere semplicemente UB o implementazione definita. Tuttavia, non voglio scoraggiare la discussione su altre possibilità.


18
Perché è già un problema tenere traccia delle allocazioni stesse, e complicherebbe ulteriormente il codice se dovessi anche tenere traccia delle dimensioni.
Jens Gustedt

11
Posso pensare a diversi motivi: perché costringere l'utente a farlo se non è necessario? E se l'utente rovina tutto? È comunque una domanda ridondante. Se avessero fatto l'altra scelta, staresti comunque chiedendo perché.
BoBTFish

15
@BoBTFish: Stiamo parlando di C , non di Python o addirittura di C ++. L'utente deve già fare un $ h! 1 tonnellata che non deve. Non è un motivo.
user541686

4
K&R non ha niente da dire neanche su questo. Possiamo speculare su tutto ciò che ci piace, ma penso che la ragione originale possa essere persa nella storia .
BoBTFish

36
Non è possibile richiedere al programmatore di passare correttamente la dimensione del blocco, perché il chiamante di mallocnon conosce la dimensione del blocco restituito . mallocspesso restituisce un blocco più grande di quanto richiesto. Nella migliore delle ipotesi, il programmatore potrebbe trasmettere la dimensione richiesta nella malloc()chiamata, il che non aiuterebbe affatto l'implementatore free().
Ben Voigt

Risposte:


97

Un argomento free(void *)(introdotto in Unix V7) ha un altro grande vantaggio rispetto ai due argomenti precedenti mfree(void *, size_t)che non ho visto menzionato qui: un argomento freesemplifica notevolmente ogni altra API che funziona con la memoria heap. Ad esempio, se fosse freenecessaria la dimensione del blocco di memoria, in strdupqualche modo dovrebbe restituire due valori (puntatore + dimensione) invece di uno (puntatore) e C rende i rendimenti a più valori molto più ingombranti dei rendimenti a valore singolo. Invece di char *strdup(char *)dovremmo scrivere char *strdup(char *, size_t *)o altro struct CharPWithSize { char *val; size_t size}; CharPWithSize strdup(char *). (Al giorno d'oggi questa seconda opzione sembra piuttosto allettante, perché sappiamo che le stringhe con terminazione NUL sono "il bug di progettazione più catastrofico nella storia dell'informatica", ma questo è il senno di poi. Negli anni '70, la capacità del C di gestire le stringhe come un semplice char *era in realtà considerata un vantaggio decisivo rispetto a concorrenti come Pascal e Algol .) Inoltre, non è solo strdupche soffre di questo problema: colpisce ogni sistema o definito dall'utente funzione che alloca memoria heap.

I primi progettisti di Unix erano persone molto intelligenti e ci sono molte ragioni per cui freeè meglio di mfreecosì fondamentalmente penso che la risposta alla domanda sia che hanno notato questo e hanno progettato il loro sistema di conseguenza. Dubito che troverai una registrazione diretta di ciò che stava accadendo nelle loro teste nel momento in cui hanno preso quella decisione. Ma possiamo immaginare.

Immagina di scrivere applicazioni in C da eseguire su Unix V6, con i suoi due argomenti mfree. Finora sei riuscito bene, ma tenere traccia di queste dimensioni dei puntatori sta diventando sempre più una seccatura poiché i tuoi programmi diventano più ambiziosi e richiedono un uso sempre maggiore di variabili allocate nell'heap. Ma poi hai un'idea brillante: invece di copiarli size_ttutto il tempo, puoi semplicemente scrivere alcune funzioni di utilità, che nascondono la dimensione direttamente all'interno della memoria allocata:

E più codice scrivi usando queste nuove funzioni, più sembrano fantastiche. Non solo rendono il tuo codice più facile da scrivere, ma rendono anche il tuo codice più veloce - due cose che spesso non vanno insieme! Prima di passare questi messaggi size_tdappertutto, il che aggiungeva il sovraccarico della CPU per la copia e significava che dovevi spargere i registri più spesso (specialmente per gli argomenti della funzione extra) e sprecare memoria (poiché le chiamate di funzione nidificate spesso risulteranno in più copie della size_tmemorizzazione in diversi stack frame). Nel nuovo sistema, devi ancora spendere la memoria per archiviare il filesize_t, ma solo una volta e non viene mai copiato da nessuna parte. Questi possono sembrare piccoli vantaggi, ma tieni presente che stiamo parlando di macchine di fascia alta con 256 KiB di RAM.

Questo ti rende felice! Quindi condividi il tuo fantastico trucco con gli uomini barbuti che stanno lavorando alla prossima versione di Unix, ma non li rende felici, li rende tristi. Vedi, stavano solo aggiungendo un mucchio di nuove funzioni di utilità come strdup, e si rendono conto che le persone che usano il tuo fantastico trucco non saranno in grado di usare le loro nuove funzioni, perché le loro nuove funzioni usano tutte l'ingombrante puntatore + dimensione API. E poi questo ti rattrista anche tu, perché ti rendi conto che dovrai riscrivere la buona strdup(char *)funzione da solo in ogni programma che scrivi, invece di poter usare la versione di sistema.

Ma aspetta! Siamo nel 1977 e la retrocompatibilità non verrà inventata per altri 5 anni! E inoltre, nessuno serio usa questa oscura cosa "Unix" con il suo nome off-color. La prima edizione di K&R è in arrivo per l'editore ora, ma non c'è problema - dice proprio sulla prima pagina che "C non fornisce operazioni per trattare direttamente oggetti compositi come stringhe di caratteri ... non c'è mucchio ... ". A questo punto della storia, string.he mallocsono estensioni del fornitore (!). Quindi, suggerisce Bearded Man # 1, possiamo cambiarli come preferiamo; perché non dichiariamo semplicemente che il tuo difficile allocatore è l' allocatore ufficiale ?

Pochi giorni dopo, Bearded Man # 2 vede la nuova API e dice ehi, aspetta, questo è meglio di prima, ma sta ancora spendendo un'intera parola per allocazione per memorizzare le dimensioni. La considera la cosa successiva alla blasfemia. Tutti gli altri lo guardano come se fosse pazzo, perché cos'altro puoi fare? Quella notte rimane in ritardo e inventa un nuovo allocatore che non memorizza affatto la dimensione, ma invece la deduce al volo eseguendo bitshift di magia nera sul valore del puntatore e lo scambia mantenendo la nuova API in posizione. La nuova API significa che nessuno si accorge del passaggio, ma notano che la mattina dopo il compilatore utilizza il 10% in meno di RAM.

E ora tutti sono felici: ottieni il tuo codice più facile da scrivere e più veloce, Bearded Man # 1 può scrivere un bel semplice strdupche le persone useranno davvero e Bearded Man # 2 - fiducioso di essersi guadagnato il suo mantenimento per un po '- - torna a scherzare con i quines . Spediscilo!

O almeno, è così che sarebbe potuto accadere.


14
Ehm, nel caso non fosse chiaro, questo è un volo di fantasia, con dettagli corroboranti inseriti per fornire una versatilità artistica. Qualsiasi somiglianza con persone vive o morte è semplicemente perché tutte le persone coinvolte sembravano uguali . Per favore, non confondere con la storia reale.
Nathaniel J. Smith

5
Hai vinto. Questa mi sembra la spiegazione più plausibile (e il miglior pezzo di scrittura). Anche se tutto qui si è dimostrato errato o non valido, questa è la migliore risposta per le eccellenti raffigurazioni di uomini barbuti.
jaymmer - Ripristina Monica il

Wow, una risposta su questa pagina che sembra effettivamente plausibile. +1 da me.
user541686

Ancora meglio: una risposta su questa pagina che è davvero divertente! +1 pure.
David C. Rankin

Mi chiedo se qualche sistema Pascal degno di nota abbia utilizzato un pool di stringhe raccolto dai rifiuti in modo simile agli interpreti BASIC del microcomputer? La semantica di C non funzionerebbe con una cosa del genere, ma in Pascal una cosa del genere potrebbe essere gestita abbastanza bene se il codice mantenesse stack frame tracciabili (cosa che molti compilatori facevano comunque).
supercat

31

"Perché freein C non prende il numero di byte da liberare?"

Perché non ce n'è bisogno, e comunque non avrebbe molto senso .

Quando allochi qualcosa, vuoi dire al sistema quanti byte allocare (per ovvie ragioni).

Tuttavia, quando hai già allocato il tuo oggetto, la dimensione della regione di memoria che ottieni ora è determinata. È implicito. È un blocco di memoria contiguo. Non puoi deallocarne una parte (dimentichiamolo realloc(), non è quello che sta facendo comunque), puoi solo deallocare l' intera cosa. Non puoi nemmeno "deallocare X byte": o liberi il blocco di memoria da cui hai ottenuto malloc()o non lo fai.

E ora, se vuoi liberarlo, puoi semplicemente dire al sistema di gestione della memoria: "ecco questo puntatore, free()il blocco a cui punta". - e il gestore della memoria saprà come farlo, o perché conosce implicitamente la dimensione, o perché potrebbe non averne nemmeno bisogno.

Ad esempio, le implementazioni più tipiche malloc()mantengono un elenco collegato di puntatori a blocchi di memoria liberi e allocati. Se passi un puntatore a free(), cercherà semplicemente quel puntatore nell'elenco "allocato", scollegherà il nodo corrispondente e lo collegherà all'elenco "libero". Non aveva nemmeno bisogno delle dimensioni della regione. Avrà solo bisogno di queste informazioni quando potenzialmente tenterà di riutilizzare il blocco in questione.


Se prendo in prestito $ 100 da te e poi prendo in prestito $ 100 da te di nuovo e poi lo faccio altre cinque volte, ti importa davvero che ho preso in prestito denaro sette volte da te (a meno che tu non stia effettivamente addebitando interessi!) ?? O ti interessa solo che ti ho preso in prestito 700 dollari? Stessa cosa qui: il sistema si preoccupa solo della memoria non allocata, non (e non ha bisogno e non dovrebbe) preoccuparsi di come viene suddivisa la memoria allocata.
user541686

1
@ Mehrdad: No, e non lo fa. C, però, sì. Il suo scopo è rendere le cose (un po ') più sicure. Non so davvero cosa stai cercando qui.
Gare di leggerezza in orbita

12
@ user3477950: Non è necessario passare il numero di byte : Sì, perché è stato progettato in questo modo. L'OP ha chiesto perché è stato progettato in questo modo?
Deduplicator

5
"Perché non ha bisogno di" - potrebbe essere stato progettato altrettanto bene in modo che sia necessario.
user253751

5
@Mehrdad che un'analogia del tutto poco chiara e difettosa. Se assegni 4 byte cento volte, sicuramente importa esattamente quale libera. liberare il primo non è la stessa cosa che liberare il secondo. con i soldi d'altra parte non importa se ripaghi prima il primo o il secondo prestito, è solo una grande pila
user2520938

14

Il C potrebbe non essere "astratto" come il C ++, ma è comunque inteso come un'astrazione sull'assembly. A tal fine, i dettagli di livello più basso vengono eliminati dall'equazione. Questo ti impedisce di doverti allontanare con l'allineamento e il riempimento, per la maggior parte, il che renderebbe tutti i tuoi programmi in C non portabili.

In breve, questo è l'intero punto di scrivere un'astrazione .


9
Non sono sicuro di cosa abbia a che fare l'allineamento o il riempimento con questo. La risposta in realtà non risponde a nulla.
user541686

1
@Mehrdad C non è un linguaggio x86, cerca (più o meno) di essere portabile e quindi libera il programmatore da quel carico significativo. Puoi comunque raggiungere quel livello in vari altri modi (es. Assemblaggio in linea) ma l'astrazione è la chiave. Sono d'accordo con questa risposta.
Marco A.

4
@Mehrdad: Se hai chiesto mallocN byte e invece ha restituito un puntatore all'inizio di un'intera pagina (a causa dell'allineamento, del riempimento o di altri vincoli , non ci sarebbe modo per l'utente di tenerne traccia, costringendolo a non sarebbe controproducente.
Michael Foukarakis

3
@MichaelFoukarakis: mallocpuò semplicemente restituire sempre un puntatore allineato senza mai memorizzare la dimensione dell'allocazione. freepotrebbe quindi arrotondare all'allineamento appropriato per garantire che tutto sia liberato correttamente. Non vedo dove sia il problema.
user541686

6
@ Mehrdad: non c'è alcun vantaggio per tutto quel lavoro extra che hai appena menzionato. Inoltre, il passaggio di un sizeparametro a freeapre un'altra fonte di bug.
Michael Foukarakis

14

In realtà, nell'antico allocatore di memoria del kernel Unix, ha mfree()preso un sizeargomento. malloc()e mfree()tenevano due array (uno per la memoria principale, un altro per lo scambio) che contenevano informazioni su indirizzi e dimensioni dei blocchi liberi.

Non c'era alcun allocatore dello spazio utente fino a Unix V6 (i programmi usavano solo sbrk()). In Unix V6, iolib includeva un allocatore con alloc(size)e una free()chiamata che non accettava un argomento size. Ogni blocco di memoria è stato preceduto dalla sua dimensione e da un puntatore al blocco successivo. Il puntatore è stato utilizzato solo sui blocchi liberi, quando si cammina nell'elenco libero, ed è stato riutilizzato come memoria dei blocchi sui blocchi in uso.

In Unix 32V e in Unix V7, questo è stato sostituito da un nuovo malloc()e free()implementazione, dove free()non ha preso un sizeargomento. L'implementazione era un elenco circolare, ogni blocco era preceduto da una parola che conteneva un puntatore al blocco successivo e da un bit "occupato" (allocato). Quindi, malloc()/free()non ha nemmeno tenuto traccia di una dimensione esplicita.


10

Perché freein C non richiede il numero di byte da liberare?

Perché non è necessario. Le informazioni sono già disponibili nella gestione interna eseguita da malloc / free.

Ecco due considerazioni (che possono o meno aver contribuito a questa decisione):

  • Perché ti aspetti che una funzione riceva un parametro di cui non ha bisogno?

    (questo complicherebbe praticamente tutto il codice client che si basa sulla memoria dinamica e aggiungerebbe ridondanza completamente inutile all'applicazione). Tenere traccia dell'allocazione del puntatore è già un problema difficile. Tenere traccia delle allocazioni di memoria e delle dimensioni associate aumenterebbe inutilmente la complessità del codice client.

  • Cosa farebbe la freefunzione alterata , in questi casi?

    Non sarebbe gratuito (causerebbe una perdita di memoria?)? Ignorare il secondo parametro? Fermare l'applicazione chiamando exit? L'implementazione di questo aggiungerebbe punti di errore extra nella tua applicazione, per una caratteristica di cui probabilmente non hai bisogno (e se ne hai bisogno, vedi il mio ultimo punto, sotto - "implementazione della soluzione a livello di applicazione").

Piuttosto, voglio sapere perché è stato creato gratuitamente in questo modo in primo luogo.

Perché questo è il modo "corretto" per farlo. Un'API dovrebbe richiedere gli argomenti di cui ha bisogno per eseguire la sua operazione, e non di più .

Mi viene anche in mente che dare esplicitamente il numero di byte da liberare potrebbe consentire alcune ottimizzazioni delle prestazioni, ad esempio un allocatore che ha pool separati per diverse dimensioni di allocazione sarebbe in grado di determinare da quale pool liberare semplicemente guardando gli argomenti di input, e in generale ci sarebbe meno spazio in testa.

I modi corretti per implementarlo sono:

  • (a livello di sistema) all'interno dell'implementazione di malloc - non c'è nulla che impedisca all'implementatore della libreria di scrivere malloc per utilizzare internamente varie strategie, in base alla dimensione ricevuta.

  • (a livello di applicazione) avvolgendo malloc e gratuitamente all'interno delle tue API e utilizzando quelle invece (ovunque nell'applicazione di cui potresti aver bisogno).


6
@ user3477950: Non è necessario passare il numero di byte : Sì, perché è stato progettato in questo modo. L'OP ha chiesto perché è stato progettato in questo modo?
Deduplicator

4
"Perché non ha bisogno di" - avrebbe potuto essere progettato altrettanto bene in modo che sia necessario, non salvando quelle informazioni.
user253751

1
Per quanto riguarda il punto 2, mi chiedo se free (NULL) sia un comportamento definito. Aha, "Tutti gli standard versioni compatibili della libreria C trattamento gratuito (NULL) come un no-op" - fonte stackoverflow.com/questions/1938735/...
Mawg dice ripristinare Monica

10

Cinque ragioni mi vengono in mente:

  1. È conveniente. Rimuove un intero carico di overhead dal programmatore ed evita una classe di errori estremamente difficili da tracciare.

  2. Apre la possibilità di rilasciare parte di un blocco. Ma poiché i gestori di memoria di solito vogliono avere informazioni di tracciamento, non è chiaro cosa significherebbe?

  3. Lightness Races In Orbit è perfetto per quanto riguarda il riempimento e l'allineamento. La natura della gestione della memoria significa che il file dimensione effettiva allocata è molto probabilmente diversa dalla dimensione richiesta. Ciò significa che se fosse freenecessaria una dimensione e una posizione mallocdovrebbe essere modificata per restituire anche la dimensione effettiva assegnata.

  4. Non è chiaro comunque che ci sia un reale vantaggio nel trasferire la taglia. Un tipico gestore di memoria ha 4-16 byte di intestazione per ogni blocco di memoria, inclusa la dimensione. Questa intestazione di blocco può essere comune per la memoria allocata e non allocata e quando i blocchi adiacenti si liberano possono essere compressi insieme. Se stai facendo in modo che il chiamante memorizzi la memoria libera, puoi liberare probabilmente 4 byte per blocco non avendo un campo di dimensione separato nella memoria allocata, ma quel campo di dimensione probabilmente non viene guadagnato comunque poiché il chiamante deve memorizzarlo da qualche parte. Ma ora quell'informazione è dispersa nella memoria piuttosto che essere prevedibilmente collocata nel blocco di intestazione, il che probabilmente sarà comunque meno efficiente dal punto di vista operativo.

  5. Anche se fosse più efficiente, è radicalmente improbabile che il tuo programma stia impiegando una grande quantità di tempo a liberare memoria comunque, quindi il vantaggio sarebbe minimo.

Per inciso, la tua idea di allocatori separati per elementi di dimensioni diverse è facilmente implementabile senza queste informazioni (puoi utilizzare l'indirizzo per determinare dove si è verificata l'allocazione). Questo viene fatto abitualmente in C ++.

Aggiunto in seguito

Un'altra risposta, piuttosto ridicolmente, ha portato std :: allocator come prova che freepotrebbe funzionare in questo modo ma, in effetti, serve come un buon esempio del perché freenon funziona in questo modo. Ci sono due differenze fondamentali tra cosa malloc/ freefare e cosa fa std :: allocator. In primo luogo, mallocefree sono utente di fronte - sono progettati per i programmatori generali per lavorare con - mentre std::allocatorè progettato per fornire l'allocazione della memoria specialista per la libreria standard. Questo fornisce un bell'esempio di quando il primo dei miei punti non ha importanza, o non lo farebbe. Trattandosi di una libreria, le difficoltà di gestire le complessità delle dimensioni di tracciamento sono comunque nascoste all'utente.

In secondo luogo, std :: allocator funziona sempre con l'elemento della stessa dimensione, ciò significa che è possibile utilizzare il numero di elementi originariamente passato per determinare la quantità di spazio libero. Il motivo per cui questo differisce da freese stesso è illustrativo. Gli std::allocatorarticoli da allocare sono sempre della stessa taglia, conosciuti e sempre dello stesso tipo, quindi hanno sempre lo stesso tipo di requisiti di allineamento. Ciò significa che l'allocatore potrebbe essere specializzato per allocare semplicemente un array di questi elementi all'inizio e distribuirli secondo necessità. Non puoi farlo confree perché non v'è alcun modo per garantire che la dimensione migliore per il ritorno è la dimensione chiesto, invece, è molto più efficiente di tornare a volte blocchi di dimensioni superiori al chiamante chiede * e quindi sial'utente o il gestore deve tenere traccia delle dimensioni esatte effettivamente concesse. Passare questi tipi di dettagli di implementazione all'utente è un inutile mal di testa che non offre alcun vantaggio al chiamante.

- * Se qualcuno ha ancora difficoltà a capire questo punto, considera questo: un tipico allocatore di memoria aggiunge una piccola quantità di informazioni di tracciamento all'inizio di un blocco di memoria e quindi restituisce un offset del puntatore da questo. Le informazioni memorizzate qui in genere includono puntatori al blocco successivo libero, ad esempio. Supponiamo che l'intestazione sia lunga solo 4 byte (che in realtà è più piccola della maggior parte delle librerie reali) e non includa la dimensione, quindi immaginiamo di avere un blocco libero di 20 byte quando l'utente chiede un blocco di 16 byte, un ingenuo il sistema restituirebbe il blocco di 16 byte ma poi lascerebbe un frammento di 4 byte che non potrebbe mai e poi mai essere usato perdendo tempo ogni voltamallocviene chiamato. Se invece il gestore restituisce semplicemente il blocco di 20 byte, salva questi frammenti disordinati dall'accumulo ed è in grado di allocare in modo più pulito la memoria disponibile. Ma se il sistema deve farlo correttamente senza tenere traccia della dimensione stessa, allora chiediamo all'utente di tenere traccia - per ogni singola allocazione - della quantità di memoria effettivamente allocata se deve restituirla gratuitamente. Lo stesso argomento si applica al riempimento per tipi / allocazioni che non corrispondono ai limiti desiderati. Quindi, al massimo, richiedere freedi prendere una dimensione è (a) completamente inutile poiché l'allocatore di memoria non può fare affidamento sulla dimensione passata per corrispondere alla dimensione effettivamente allocata o (b) richiede inutilmente all'utente di eseguire il lavoro di tracciamento del reale dimensioni che sarebbero facilmente gestibili da qualsiasi gestore di memoria ragionevole.


# 1 è vero. # 2: non sono sicuro di cosa intendi. Non così sicuro del punto 3, l'allineamento non richiede la memorizzazione di informazioni aggiuntive. # 4 è il ragionamento circolare; i gestori di memoria richiedono solo quell'overhead per blocco perché memorizzano la dimensione, quindi non è possibile utilizzarlo come argomento per il motivo per cui memorizzano la dimensione. E il n. 5 è molto discutibile.
user541686

Ho accettato e poi non l'ho accettato: questa sembra una buona risposta ma la domanda sembra avere molta attività, penso che potrei essere stato un po 'prematuro. Questo mi dà sicuramente qualcosa su cui riflettere.
jaymmer - Ripristina Monica il

2
@jaymmer: Sì, è prematuro. Suggerisco di aspettare più di un giorno o due prima di accettare, e suggerisco di pensarci da solo. È una domanda davvero interessante e la maggior parte / tutte le risposte che otterrai all'inizio per qualsiasi domanda del "perché" su StackOverflow saranno solo tentativi semi-automatici di giustificare il sistema corrente piuttosto che di affrontare effettivamente la domanda sottostante.
user541686

@ Mehrdad: Hai frainteso quello che sto dicendo al punto 4. Non sto dicendo che richiederà una dimensione extra, sto dicendo (a) sposterà chi deve memorizzare la dimensione in modo da non risparmiare effettivamente spazio e (b) è probabile che la modifica risultante lo faccia meno efficiente non di più. Quanto al n. 5, non sono affatto convinto che sia affatto discutibile: stiamo - al massimo - parlando di salvare un paio di istruzioni dalla chiamata gratuita. Rispetto ai costi della chiamata gratuita che sarà minuscola.
Jack Aidley

1
@ Mehrdad: Oh, e il n. 3, no, non richiede informazioni aggiuntive, richiede memoria aggiuntiva. Un tipico gestore di memoria progettato per funzionare con l'allineamento a 16 byte restituirà un puntatore a un blocco di 128 byte quando viene richiesto un blocco di 115 byte. Se la freechiamata deve passare correttamente la dimensione da liberare, deve saperlo.
Jack Aidley

5

Lo sto solo postando come una risposta non perché è quella che speri, ma perché credo che sia l'unica plausibilmente corretta:

Probabilmente era ritenuto conveniente in origine e non poteva essere migliorato in seguito.
Probabilmente non c'è una ragione convincente per questo. (Ma lo cancellerò felicemente se mostrato che non è corretto.)

sarebbe vantaggi se fosse possibile: potresti allocare un unico grande pezzo di memoria di cui conoscevi la dimensione in anticipo, quindi liberarlo un po 'alla volta, invece di allocare e liberare ripetutamente piccoli pezzi di memoria. Attualmente compiti come questo non sono possibili.


A molti (molti 1 !) Di voi che pensano che passare la taglia sia così ridicolo:

Posso fare riferimento alla decisione di progettazione di C ++ per il std::allocator<T>::deallocatemetodo?

Tutti gli oggetti nell'area indicata da devono essere distrutti prima di questa chiamata. deve corrispondere al valore passato an Tp
nallocate per ottenere questa memoria.

Penso che avrai un periodo piuttosto "interessante" per analizzare questa decisione di progettazione.


Per quanto riguarda operator delete, risulta che la proposta N3778 del 2013 ("C ++ Sized Deallocation") ha lo scopo di risolvere anche questo.


1 Basta guardare i commenti sotto la domanda originale per vedere quante persone hanno fatto affermazioni frettolose come "la dimensione richiesta è completamente inutile per la freechiamata" per giustificare la mancanza del sizeparametro.


5
Per l' malloc()implementazione eliminerebbe anche la necessità di ricordare qualsiasi cosa su una regione assegnata, riducendo l'overhead di allocazione al overhead di allineamento. malloc()sarebbe in grado di fare tutta la contabilità all'interno dei blocchi liberati stessi. Ci sono casi d'uso in cui questo sarebbe un grande miglioramento. La deallocazione di una grande porzione di memoria in diversi piccoli blocchi dovrebbe essere scoraggiata, tuttavia, ciò aumenterebbe drasticamente la frammentazione.
cmaster - ripristina monica il

1
@cmaster: quei casi d'uso erano esattamente del tipo a cui mi riferivo, hai colpito nel segno, grazie. Per quanto riguarda la frammentazione: non sono sicuro di come ciò aumenti la frammentazione rispetto all'alternativa, che è sia allocare che liberare memoria in piccoli blocchi.
user541686

std::allocatoralloca solo elementi di una dimensione specifica e nota. Non è un allocatore generico, il confronto è tra le mele e le arance.
Jack Aidley

Mi sembra che sia stata presa una decisione in parte filosofica, in parte progettuale per rendere la libreria standard in C un insieme minimo di primitive da cui praticamente qualsiasi cosa può essere costruita - originariamente intesa come linguaggio a livello di sistema e portabile a molti sistemi che questo approccio primitivo rende senso. Con C ++, è stata presa una decisione diversa per rendere la libreria standard molto ampia (e ingrandirsi con C ++ 11). Hardware di sviluppo più veloce, memorie più grandi, architetture più complesse e la necessità di affrontare lo sviluppo di applicazioni di livello superiore contribuiscono forse a questo cambiamento di enfasi.
Clifford

1
@Clifford: Esatto, ecco perché ho detto che non c'è una ragione convincente per questo. È solo una decisione che è stata presa e non c'è motivo di credere che sia strettamente migliore delle alternative.
user541686

2

malloc e free vanno mano nella mano, con ogni "malloc" abbinato a uno "free". Quindi ha assolutamente senso che la corrispondenza "libera" di un precedente "malloc" dovrebbe semplicemente liberare la quantità di memoria allocata da quel malloc - questo è il caso d'uso maggioritario che avrebbe senso nel 99% dei casi. Immagina tutti gli errori di memoria se tutti gli usi di malloc / free da parte di tutti i programmatori in tutto il mondo avessero bisogno che il programmatore tenga traccia della quantità allocata in malloc, e poi si ricordi di liberare lo stesso. Lo scenario di cui parli dovrebbe davvero utilizzare più mallocs / free in una sorta di implementazione della gestione della memoria.


5
Penso che "Immagina tutti gli [...] errori" sia discutibile quando pensi ad altre fabbriche di bug come gets, printfloop manuali (off-by-one), comportamenti indefiniti, stringhe di formato, conversioni implicite, trucchi, ecc. cetera.
Sebastian Mach

1

Suggerirei che sia perché è molto comodo non dover tenere traccia manualmente delle informazioni sulla dimensione in questo modo (in alcuni casi) e anche meno incline a errori del programmatore.

Inoltre, realloc avrebbe bisogno di queste informazioni di contabilità, che mi aspetto contengano più della semplice dimensione dell'allocazione. cioè permette di definire il meccanismo con cui funziona.

Potresti scrivere il tuo allocatore che ha funzionato in qualche modo nel modo che suggerisci ed è spesso fatto in c ++ per gli allocatori di pool in una sorta di modo simile per casi specifici (con guadagni di prestazioni potenzialmente enormi) sebbene questo sia generalmente implementato in termini di operatore nuovo per l'allocazione di blocchi di pool.


1

Non vedo come funzionerebbe un allocatore che non tiene traccia della dimensione delle sue allocazioni. Se non lo facesse, come potrebbe sapere quale memoria è disponibile per soddisfare una mallocrichiesta futura ? Deve almeno memorizzare una sorta di struttura dati contenente indirizzi e lunghezze, per indicare dove si trovano i blocchi di memoria disponibili. (E, naturalmente, memorizzare un elenco di spazi liberi equivale a memorizzare un elenco di spazi allocati).


Non ha nemmeno bisogno di un campo di dimensione esplicito. Può avere solo un puntatore al blocco successivo e un bit allocato.
ninjalj

0

Bene, l'unica cosa di cui hai bisogno è un puntatore che userai per liberare la memoria che hai precedentemente allocato. La quantità di byte è qualcosa gestito dal sistema operativo, quindi non devi preoccuparti. Non sarebbe necessario ottenere il numero di byte allocati restituiti da free (). Ti suggerisco un modo manuale per contare il numero di byte / posizioni allocati da un programma in esecuzione:

Se lavori in Linux e vuoi conoscere la quantità di byte / posizioni che malloc ha allocato, puoi creare un semplice programma che usi malloc una an volte e stampa i puntatori che ottieni. Inoltre, è necessario sospendere il programma per alcuni secondi (sufficienti per eseguire le seguenti operazioni). Dopodiché, esegui quel programma, cerca il suo PID, scrivi cd / proc / process_PID e digita semplicemente "cat maps". L'output ti mostrerà, in una riga specifica, sia l'indirizzo di memoria iniziale che quello finale della regione di memoria dell'heap (quella in cui stai allocando la memoria dinamicamente) .Se stampi i puntatori a queste regioni di memoria allocate, tu può indovinare quanta memoria hai allocato.

Spero che sia d'aiuto!


0

Perché dovrebbe? malloc () e free () sono primitive di gestione della memoria intenzionalmente molto semplici e la gestione della memoria di livello superiore in C dipende in gran parte dallo sviluppatore. T

Inoltre realloc () lo fa già - se riduci l'allocazione in realloc () non sposterà i dati e il puntatore restituito sarà lo stesso dell'originale.

È generalmente vero per l'intera libreria standard che è composta da semplici primitive da cui è possibile costruire funzioni più complesse per soddisfare le esigenze dell'applicazione. Quindi la risposta a qualsiasi domanda del modulo "perché la libreria standard non fa X" è perché non può fare tutto ciò a cui un programmatore potrebbe pensare (questo è ciò a cui sono destinati i programmatori), quindi sceglie di fare pochissimo - crearne una propria o utilizzare librerie di terze parti. Se desideri una libreria standard più ampia, inclusa una gestione della memoria più flessibile, C ++ potrebbe essere la risposta.

Hai etichettato la domanda C ++ e C, e se C ++ è quello che stai usando, allora non dovresti quasi usare malloc / free in ogni caso - a parte new / delete, le classi container STL gestiscono la memoria automaticamente e in un modo probabile essere specificamente adeguati alla natura dei vari contenitori.


Inoltre realloc () lo fa già - se riduci l'allocazione in realloc () non sposterà i dati e il puntatore restituito sarà lo stesso dell'originale. Questo comportamento è garantito? Sembra essere comune in diversi allocatori incorporati, ma non ero sicuro che questo comportamento fosse specificato come parte dello standard-c.
rsaxvc

@rsaxvc: Bella domanda - documenti cplusplus.com "Se la nuova dimensione è maggiore, il valore della porzione appena assegnata è indeterminato." , il che implica che se più piccolo è determinato . [opengroup.org () dice "Se la nuova dimensione dell'oggetto in memoria richiede il movimento dell'oggetto, lo spazio per la precedente istanziazione dell'oggetto viene liberato." - se fosse più piccolo, i dati non dovrebbero essere spostati. Ancora una volta, l'implicazione è che il più piccolo non verrà riallocato. Non sono sicuro di cosa dica lo standard ISO.
Clifford

1
reallocè assolutamente autorizzato a spostare i dati. Secondo lo standard C è totalmente legale da implementare realloccome malloc+ memcpy+ free. E ci sono buone ragioni per cui un'implementazione potrebbe voler spostare un'allocazione di dimensioni ridotte, ad esempio per evitare di frammentare la memoria.
Nathaniel J. Smith
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.