Newsgroup: it.comp.lang.c++
Titolo: operator new[] e operator delete[]

Perche' sono necessari gli *operatori* (non le espressioni) new[] e 
delete[]?
Mi spiego meglio con un esempio:
class X { ... };
void foo()
{
        X* p1 = new X;
        X* p2 = new X[10];
        ...
        delete p1
        delete[] p2;
}
Se non erro la new-expression riguardante p1 esegue queste operazioni:
1) chiama ::operator new per allocare sizeof(X) byte di memoria
2) chiama il costruttore di X
Per contro la relativa delete-expression esegue questo:
1) chiama il dtor di X
2) chiama ::operator delete per liberare la memoria allocata
Invece, la new[]-expression riguardante p2 esegue questo:
1) chiama ::operator new[] per allocare 10 blocchi contigui di 
sizeof(X) byte.
2) chiama 10 volte il costruttore di X
stesso discorso per il delete[].
Ecco il mio dubbio: la differenza fondamentale tra le new-expression e 
le new[]-expression e' che la prima richiama un costruttore mentre la 
seconda ne richiama tot. Ma perche' differenziare il modo in cui tale 
memoria e' allocata? La new[]-expression non potrebbe richiamare 
::operator new per allocare la memoria?
ciao Anto
-- 
Antonio Cuni, 19 anni, studente
Brainbench C++ Master: 4.6, higher than 98% of all previous test takers.
C Language: 3.32, higher than 79% of all previous test takers.
Guida C/C++ di Programmazione.it: 
http://cpp.programmazione.it

ciao Anto
forse xché richiamando soltanto una volta ::operator new restituisce
un solo riferimento all'heap in cui é allocato l'oggetto.. mentre lui
x semplicità ottiene n riferimenti all'heap.. e richiama n volte il
costruttore passando di volta in volta l'indirizzo x[0...n-1] al
costruttore per inizializzare le variabili ......
io ho detto la mia ... e l'ho pensata  ora ...
buon natale
Antonio Mazzeo

::operator new per allocare la memoria?
E' un grado di liberta' in piu', che personalmente non ho mai
trovato utile. Ogni volta che ho ridefinito gli allocatori
globali (per implementare delle "versioni di debug") ho
sempre fatto in modo che ::operator new[]() richiemasse la
versione non-array ::operator new().
Una cosa che invece mi e' piaciuta poco del C++ e' la sintassi
usata per l'allocazione di memoria... In particolare risulta
difficile riuscire ad instrumentare le allocazioni per tenere
traccia di chi ha richiesto la memoria. Per ottenere questo
(utile) risultato sarebbe bastato pretendere un paio di
parentesi in piu' che non credo avrebbero danneggiato nessuno.
Il codice "x = new(Pippo)" non e' certamente meno leggibile
di "x = new Pippo" ma permette con qualche macro di tenere
traccia del sorgente/linea che alloca la memoria.
Andrea

 ::operator new per allocare la memoria?
Vedi sopra per la mia congettura in merito.
--
Andrea Ferro
---------
Brainbench C++ Master. Scored higher than 97% of previous takers
Scores: Overall 4.46, Conceptual 5.0, Problem-Solving 5.0
More info 
http://www.brainbench.com/transcript.jsp?pid=2522556

 traccia del sorgente/linea che alloca la memoria.
La solita caccia al baco nella gestione della memoria.
Indovina cosa ti risponderebbe un purista dell'OO (hint: ha le stesse
iniziali del cinematografaro fiorentino squattrinato).


 neppure asserzioni contro) non è garantita.
hai ragione, a questo non avevo minimamente pensato. In pratica la 
differenza consiste nel fatto che operator new() puo` anche restituire 
un puntatore all'ultima cella indirizzabile di memoria, al contrario di 
operator new[](). Ho capito bene?
Questo significa anche che e` in teoria sbagliato implementare operator 
new[]() in funzione di operator new(), dato che quest'ultimo puo` 
restituire un valore non valido per il primo, giusto?


 restituire un valore non valido per il primo, giusto?
Yep. Se è vero (e credo che lo sia) che operator new() non è obbligato a
garantire la validità computazionale del puntatore all'elemento
successivo, sarebbe più corretto (o meglio piu' "safe") implementare
operator new() chiamando operator new[] che non viceversa. Per quanto
antiintuitivo possa sembrare.


 con byte di 8 bit e sizeof(void*) = 4. 
[cut]
ok, grazie. Anche se spero proprio che tu non ti metta mai a progettare 
un calcolatore ;-)))


 un calcolatore ;-)))
Rotfl.
Però tempo fa (leggendo per la prima volta i reference tecnici del 386 e
tutto il casino architatturale che quasto ha aggiunto rispetto all'8086
(avevo saltato il 286) mi era venuta una mezza idea. Dato che in quei
giorni stavo giochicchiando con Smalltalk ho pensato che sarebbe stato
bello un processore con una architettura completamente diversa. Con
istruzioni di tipo OO. Ci ho pensato per qualche giorno poi ... ho letto
da qualche parte che la Intel lo stava facendo!!
Chissà perchè hanno abbandonato del tutto quella linea di ricerca
(suppongo abbiano scoperto che è meglio andare nella direzione delle
istruzioni semplici invece che in quella delle istruzioni complesse,
come dimostrano i core RISC degli intel che sono usciti dopo).


iniziali del cinematografaro fiorentino squattrinato).
Il gestore di memoria in versione "debug" non va solo
a caccia dei memory leak... ma aiuta un po' a trovare:
  - Letture effettuate da aree deallocate ovvero
    accesso ad oggetti gia' distrutti
  - Scritture su aree deallocate
  - Sconfinamenti
Io poi ho una naturale repulsione verso i GC... forse
cambiero' idea quando ne vedro' qualcuno funzionare
bene (no... per piacere non citatemi Java).
Per il momento mi sembra come il buttare le
schifezze dal finestrino della macchina perche'
tanto c'e' chi e' pagato per pulire e prima o poi
passera' di li'.
Nota che ad esempio a me piacciono molto le "gestioni
automatiche"... ma preferisco il reference counting
(con finalization deterministica)... e al limite una
"passata finale" solo per i loop.
Andrea

 poi ... ho letto da qualche parte che la Intel lo stava facendo!!
qualcosa tipo la JVM? So ad esempio che essa dispone di una istruzione 
nativa per chiamare una funzione virtuale.


 "passata finale" solo per i loop.
Calma. La finalizzazione deterministica è una cosa, la gestione della
memoria dinamica un'altra. Una gestione senza GC *richiede* la
finalizzazione deterministica sempre e comunque. Molti GC e molti
linguaggi GC based la proibiscono. Ma esistono ambienti in cui la
gestione della memoria dinamica è basata su GC e la finalizzazione
deterministica è possibile ma non obbligatoria.
Tra l'altro ci sono applicazioni in cui la finalizzazione deterministica
è un difetto e non un pregio. Il caso più banale sono applicazioni in
cui è essenziale e fondamentale il tempo di esecuzione, che eseguono
parecchie allocazioni e disallocazioni di piccoli oggetti tutti aventi
distruttori di default e che "girano" per periodi di tempo molto brevi.
Il meglio che puoi fare in C/C++ per velocizzare queste applicazioni è
... non usare free e gli operator delete. Il risultato è una
applicazione che usa più memoria ma gira più veloce. Tutta la memoria
allocata è leaking. Ma se la applicazione gira per breve tempo e alloca
oggetti piccoli questo potrebbe non essere un problema (anzi: non lo è
dato che prima di fare una cosa del genere hai verificato proprio che i
requirement prestazionali siano più stringenti di quelli sulla quantità
di memoria disponibile e quindi sai a priori che c'è abbastanza
memoria).
Quello che non puoi fare ... è far fare lo stesso alle librerie senza
doverle modificare (e per modificarle ti serve il sorgente che potrebbe
non esserci). Teoricamente puoi anche riscrivere malloc e le varie new
in modo che non stiano a perdere tempo in gestione di strutture
dell'heap che tanto non verranno usate: allochi per semplice incremento
di un puntatore (cosa che tra l'altro è probabile che ti permetta di
risparmiare memoria visto che allocherai tanti oggetti piccoli).
Naturalmente il mio esempio è un estremo. Ma in generale (come sempre)
ci sono casi in cui è meglio un heap e casi in cui è meglio un GC.
Dipende. Nulla è assoluto.


 nativa per chiamare una funzione virtuale.
Si. Ma in HW. E con la possibilità di gestire in HW uno spazio di
indirizzamento virtuale distinto per ogni oggetto, di gestire in HW e
automaticamente la sincronizzazione (hai presente quella specie di mutex
che tutti gli oggetti Java hanno? Ecco, una cosa del genere gestita però
direttamente in HW in modo che una singola "call" in assembler
verificasse il mutex oltre a gestire la virtualizzazione della
chiamata).
Ovviamente la mia era solo fantasia. Un esercizio mentale. Di fatto la
mia preparazione informatica era più o meno (anzi, direi certamente un
tocco meno) di quella che hai tu ora (per quel che rieco a valutare
dalle risposte che dai e dalle domande che fai).
Sono convinto che capita anche a te di metterti a "pontificare"
mentalmente su "idee" in qualche modo "hypertecnologiche" per il solo
gusto di sviscerarle mentalmente fin dove riesci ad arrivare. Salvo poi
che alcuni anni dopo ti accorgi/accorgerai di quanto tu abbia magari
azzeccato due o tre punti fondamentali e mancato il bersaglio di un
kilometro sugli altri 185 punti altrettanto fondamentali.
Però mica sempre. Sai cosa mi era venuto in mente quando MS cominciò a
rilasciare della documentazione su come sarebbe stato OLE2/COM ?
Di mettermi a definire un linguaggio nuovo che fosse OO, permettesse di
definire un membro dati virtuale con accesso distinto per lettura e
scrittura ma con sintassi di accesso dall'esterno non a parentesi, che
permettesse direttamente di colloquiare con COM e le sue strutture (e
anche con Corba, la mia idea era che una classe definita in quel
linguaggio fosse automaticamente interoperabile via COM e/o Corba), che
permettesse di lavorare compilando in pseudo codice e rimandando la
compilazione nativa a runtime, che avesse meccanismi di chiamata
ridefinibili come con CLOS e alcuni Smalltalk, che quindi fosse
possibile "iniettare" codice nuovo in una applicazione ... mentre questa
funziona (cosa che si puù fare per esempio in smalltalk) e un paio di
altre cosette.
Il problema è che nessuno ci crederà, ma io mi divertivo a sviscerare il
problema di realizzare una cosa di quel genere.
Se gli togli Corba, i meccanismi di binding ridefinibile e (in piccola
parte) l'inject di codice (che sono idee che derivano da CLOS e
Smalltalk) e aggiungi invece l'interoperabilità fra linguaggi anche
senza COM/Corba e l'uso di SOAP/XML invece di COM/Corba per le chiamate
intersistema ... stiamo molti vicini a C# (infatti anche se sembra
incredibile pure a me molte delle cose che stanno in C# le avevo pensate
pure io, come per esempio il discorso delle properties. E le avevo
pensate con una sintassi molto simile a quella di C#).
Visto oggi il problema più grosso è che è difficile mettere tutte le
caratteristiche di C# e altre ancora in un linguaggio ... senza dover
ridefinire oltre al linguaggio anche l'environment. In altre parole è
difficile fare tutto quello che farà (in teoria) C# senza però avere
.NET. E io ai tempi mi ero concentrato su come definire il linguaggio
... facendolo girare su Windows 3.1. Non avevo pensato di ridefinire
quello che ci sta sotto. Ma anche così mi sono grippato parecchi
neuroni. Mi sono divertito. Il progetto era nato come AFC (le mie
iniziali e la C del C/C++) poi ricordo che pensavo di chiamarlo C% ma
non ricordo perchè.


 distruttori di default e che "girano" per periodi di tempo molto brevi.
Gia', e magari il GC parte proprio nel momento meno opportuno. Non mi
pare un esempio calzante.
Ciao,
Nicola

 wrote in message
news:3C396901...@divalsim.it...

Un GC decente lavora in un thread distinto. Inoltre un GC con un minimo
di "configurabilità" può essere fermato/riavviato a piacere. E può
essere configurato per non intervenire fino a che la memoria allocata
non raggiunge un "tot".


 non raggiunge un "tot".
Dopo di che bisogna dimostrare che configurare il GC e' piu' semplice
che non gestire le cose direttamente.
Ciao,
Nicola

 kilometro sugli altri 185 punti altrettanto fondamentali.
hai ragione; non piu` di un anno fa mi era venuta in mente una 
rivoluzionaria idea per raggiungere una elevata capacita` di calcolo 
collegando due o piu` computer... peccato che poi ho scoperto che 
l'avessero gia` inventata e si chiamasse clustering...
ciao Anto
-- 
Antonio Cuni, 19 anni, studente
Brainbench C++ Master: 4.73, higher than 99% of all previous test takers.
C Language: 3.79, higher than 92% of all previous test takers.


 wrote in message
 > news:udh50a...@localhost.localdomain...
 >
 >>Perche' sono necessari gli *operatori* (non le espressioni) new[] e
 >>delete[]?
 >>
 >
 > Ci ho pensato su un pezzo e mi è venuta in mente una sola possibile
 > ragione (spero di non sbagliarmi): a causa di una sottilissima
 > differenza fra l'allocare un elemento e l'allocare un array contenente
 > un solo elemento.
 >
 > In pratica nel secondo caso deve essere garantita la validità
 > (computazionale, non referenziale) del puntatore all'elemento
 > successivo, cosa che nel primo caso (è questo il punto sul quale ho un
 > "mezzo" dubbio dato che pur non trovando asserzioni pro non trovo
 > neppure asserzioni contro) non è garantita.
Supponiamo che operator new[] sia cosi` implementato:
void* operator new[](std::size_t s) throw(std::bad_alloc)
{
          void* p = std::malloc(s);
          if (!p) throw std::bad_alloc();
          return p;
}
In questo caso,come fa ad essere garantita la validita` del puntatore
all'elemento successivo? Ci pensa malloc (che non conosce la dimensione
del singolo oggetto)?
Oppure il compilatore aumenta "di nascosto" la dimensione del blocco
di memoria per soddisfare il requisito? (al momento e` l'idea piu`
logica che mi venga in mente)
A proposito: ho sbirciato nei sorgenti del GCC 3.0.3, e ::operator
new[] non fa altro che chiamare ::operator new.
Rimango perplesso...
-- 
Danilo Piazzalunga
Linux Registered User #245762 at 
http://counter.li.org
mailto:
leo-...@ibero.it

Rimango perplesso...
Guarda che il problema è più filosofico che reale. Se il processore è un
306 che lavora in modalità protetta ad indirizzamento flat, esiste uno
ed un solo indirizzo in tutto il sistema che non sarebbe valido. Ed è
certamente usato dal sistema operativo. Quindi il problema di fatto non
si pone. Diverso quando stai su un sistema embedded dove non esiste
sistema operativo, TU devi implementare gli operatori new & new[] e gli
spazi di indirizzamento sono multipli e brevi. Ma come diceva qualcuno,
anche in questo caso ci sono due possibilità: o il tuto sistema non usa
tutta la memoria disponibile e il problema sparisce, o il tuo sistema
certamente non usa un heap punto e basta.
Quindi si resta ancora in ambito filosofico.
--
Andrea Ferro
IHHC - Member of HT-Group  DVD Authoring operator  Fan of I.Asimov
C++ Master: 
http://www.brainbench.com/transcript.jsp?pid=2522556
This post expresses personal opinions which may or may not be the
opinions of the company(s) I work for. I speak for myself only.

 è certamente usato dal sistema operativo. 
immagino fosse un 386 :-)
cmq il fatto che l'ultimo indirizzo valido sia usato dall'O.S. non e` 
cosi` certo. Chi di voi ha un computer con 2^(sizeof(void*) * CHAR_BITS) 
byte di ram?
ciao Anto
-- 
Antonio Cuni, 19 anni, studente
Brainbench C++ Master: 4.73, higher than 99% of all previous test takers.
C Language: 3.79, higher than 92% of all previous test takers.


Andrea Ferro wrote:
 > Non è necessario. Basta che l'heap su cui lavora malloc non
 > comprenda eventuali byte situati al limite degli spazi di
 > indirizzamento. Ovvero in un sistema avente uno spazio di
 > indirizzamento unico e piatto (ovvero quello solito) basta che
 > malloc non gestisca l'ultimissimo byte dello spazio di
 > indirizzamento. E in generale questo è assicurato dal sistema
 > operativo stesso che "piazza" in genere lo stack (crescente verso
 > indirizzi bassi) dopo gli heap (crescenti verso indirizzi alti).
 > Oppure piazza ad indirizzi molto alti del codice (per esempio
 > Windows ci mette il sistema operativo stesso).
Capito. Ad esempio, sul mio sistema gli indirizzi nello heap 
cominciano con 0x0800.... mentre le variabili allocate sullo stack 
hanno indirizzi del tipo 0xbfff....
 > Guarda che il problema è più filosofico che reale. Se il processore
 > è un 306 che lavora in modalità protetta ad indirizzamento flat,
 > esiste uno ed un solo indirizzo in tutto il sistema che non sarebbe
 > valido.
Credevo che dovessi avere l'accesso garantito non solo al *char* 
successivo, ma all'*elemento* successivo. Cioe` se ho uno `struct X' 
di dimensione 100, una chiamata a `new X[5]' deve assicurarmi 
l'esistenza di quei 100 byte oltre la fine dell'array.
Evidentemente ho capito male.
 > Quindi si resta ancora in ambito filosofico.
Infatti. Non ho il terrore che questo accada sul mio pc ;) . Si tratta 
solo di pura e semplice speculazione filosofica.
Ciao e grazie per la tua risposta esauriente :)


 Evidentemente ho capito male.
Non è garantito neppure l'accesso al char "successivo". Solo l'esistenza
dell'indirizzo in se è garantita, non il fatto che l'indirizzo si
riferisca a memoria esistente. Ovviamente l'indirizzo dell'elemento
successivo, tradotto in termini di raw memory, corrisponderebbe
all'indirizzo del char successivo (sempre tradotto in termini di raw
memory), se il modello fisico della memoria è quello lineare classico
(cosa che lo standard non richiede).
--
Danilo Piazzalunga
Linux Registered User #245762 at 
http://counter.li.org
mailto:
leo-...@ibero.it


 byte di ram?
Attento! Quello che conta è l'indirizzo logico non quello fisico. Dato
che parlavamo di un 386 in modalità flat questa distinzione è più che
fondamentale.
Sull'implementazione più logica di un compilaotore per un sistema
operativo che lavora con un 386 in modalità flat (sizeof(void*) *
CHAR_BITS) equivale a 4*8 ovvero 32. Quindi lo spazio di indirizzamento
del processo è di 4 gigabyte e gli indirizzi vanno da 0x00000000 a
0xFFFFFFFF. Ma non è detto che tu li possa usare tranquillamente.
Nel caso di Windows, per esempio, la cosa funziona in questo modo:
da 0x00000000 a 0x003FFFFF (4 Mb) Indirizzi condivisi fra tutti i
processi riservati per il supporto al DOS e all'architettura "virtual
real mode".
da 0x00400000 a 0x7FFFFFFF (2 Gb - 4 Mb) Indirizzi privati di ciascun
processo. Contengono il codice del processo, i dati statici, lo stack e
i dati dinamici.
da 0x80000000 a 0xBFFFFFFF (1 Gb) Indirizzi condivisi fra tutti i
processi usati per l'heap globale dei processi Win16, per mappare i
memory mapped files (e la memoria condivisa fra processi) e per caricare
codice e dati condivisi (ovvero le DLL).
da 0xC0000000 a 0xFFFFFFFF (1 Gb) Indirizzi condivisi fra tutti i
processi usati per VxD, VMM etc. Contengono i device driver e il sistema
operativo.
Quindi come vedi, anche ammettendo che l'indirizzo 0xFFFFFFFF non sia di
fatto usato, praticamente tutti i processi hanno un qualche puntatore (a
dati o a codice, accedibile in user mode o solo in kernel mode) il cui
valore è superore a 0xC0000000.
Ma questo non significa che tutti i PC devono avere almeno 3Gb di
memoria. L'indirizzo, così come visto dal processo, è virtuale. Ciascun
byte nello spazio di indirizzamento virtuale (di 4Gb) può poi essere
"inesistente" o meno. E se è "esistente" può avere un indirizzo fisico
(locazione in RAM) che, peraltro, non è costante ma cambia nel tempo,
oppure può non averlo (per esempio se è in un blocco "swappato" su disco
dal sistema perchè il processo non ci ha acceduto per un certo tempo e
la memoria fisica è servita per qualche altro scopo).


 "inesistente" o meno. 
hai ragione, mi ero dimenticato in toto della memoria virtuale... :-(
