Newsgroup: it.comp.lang.c++
Titolo: stack dinamico

Stavo pensando.. potrebbe essere utile avere una classe per la gestione 
dinamica della memoria e istanziarne (si dice?) un oggetto come 
variabile automatica, facendo in modo che tutta la memoria sia liberata 
automaticamente all'uscita della funzione?
class mem{
private:
   char mem_[100];
   ...
public:
   mem();
   ~mem();
   void* alloca(size_t);
   void libera(void*);
};
void funzione()
{
   mem heap2;
   int *pt;
   pt=heap2.alloca(sizeof(int)); //sizeof da un size_t?
   //Magari si potrebbe anche definire un operator new..
}
Scusate se e' un post inutile per voi, ma mi piacerebbe sapere se questa 
idea che mi e' venuta potrebbe essere in qualche modo utile.
-- 
Devaraja (Xdevaraja87^gmail^c0mX)
Linux Registerd User #338167
http://counter.li.org

 }
magari ho frainteso quello che vuoi fare...
Tommaso
-- 
Posted via Mailgate.ORG Server - 
http://www.Mailgate.ORG

 
No, hai capito. A me sembrava piu' comodo avere un blocco di memoria 
dinamica locale per avere i vantaggi dell'heap ma non lo svantaggio di 
doversi ricordare i vari delete. Ovviamente usando un semplice int* non 
si ottengono vantaggi. Comunque lo faccio per fare pratica. Chiedevo se 
poteva tornare utile solo per evitare di cancellarlo e poi trovarmi nel 
mezzo di un programma piu' complicato con la necessita' di riscrivere 
mem da zero.


DevarajA ha scritto:

solitamente con int si intende la "parola" di default della 
macchina. Coi long DOVRESTI essere sul sicuro .... nel dubbio 
crea una struttura (ai soli fini di corretto allineamento) di 
lunghezza fissa e sedici byte di dimensione, ad es. un array di 
size fissa di sedici unsigned char.
Se usi quel tipo di oggetto per allocare pezzi con new 
(naturalmente il numero di oggetti sarà circa :
N_oggetti = (Byte_richiesti + sizeof(struttura) - 1) / 
sizeof(struttura)
dove sizeof(struttura) = 16
otterrai un puntatore il cui cast non crea mai problemi di 
allineamento corretto. (fermo restante il divieto a strutture 
sparse !). Se puoi permetterlo o meno dipende da quante volte 
chiami la new, ma direi tutto OK perché tu preallochi tutto 
quanto una volta sola all'inizio e poi vuoi distribuire la 
risorsa sucessivamente dentro al programma.
Tieni anche conto di restituire oltre al puntatore iniziale 
indici multipli esatti di 16 (ciò è automatico se incrementi un 
puntatore tipizzato, ma in tal caso devi ricalcolarti il numero 
di indici assegnati ad un oggetto, sempre arrotondando per 
eccesso). Se invece usi un puntatore void, incrementalo tu 
secondo multipli di sedici. 16 non è un numero del tutto 
casuale, era ciò che veniva definito nei tempi antichi paragrafo 
di memoria, e ogni oggetto ai tempi poteva essere allineato 
correttamente a quegli indirizzi. Immagino che col tempo si sia 
andati verso un rilassamento dei requisiti. Cmq fidarsi è bene ....
In definitiva ti vuoi scrivere a mano una sorta di memory 
manager, ma non vedo nessun vantaggio in prestazioni o sicurezza 
rispetto a New / Delete, forse soltanto una garanzia di 
riservare tutta la memoria da subito e sapere che non verrà a 
mancare dopo.
nota al riguardo la possibilità di utilizzare il cosiddetto new 
con "placement" ossia indicando dove andare ad allocare pezzi. 
E' una caratteristica del C++ che sfortutatamente ho solo letto 
ma non ho capito bene come si usi, anche se mi aveva incuriosito 
per le medesime ragioni che penso animino te.
Se qualcuno riesce a spiegare in modo chiaro il new con 
placement, gliene sono grato anche io
ciao
Soviet_Mario


 gliene sono grato anche io
Provo a dirti quello che ho capito io, ma solo per preparare il terreno 
agli esperti :-)
Quando definisci un nuovo operator new, e' di default che il primo 
argomento sia un size_t che indica la dimensione dello spazio da 
allocare. Tu pero' puoi aggiungere un parametro extra (anche piu' di uno 
credo), e quel parametro puo' essere del tipo che vuoi. Se tu come tipo 
usi una classe dotata di funzioni per la gestione della memoria puoi 
definire un operator new cosi':
void* operator new(size_t sz, MyHeap *hp)
{
   return hp->alloca(sz);
}
Ovviamente MyHeap e' una classe che gestisce un pezzo di memoria e ha 
come funzione pubblica alloca(size_t), che riserva un pezzo della sua 
memoria interna e ne restituisce un puntatore.
class MyHeap{
   char mem[1000];
   .. //tabella
   ..
public:
   void *alloca(size_t); //riserva un po' di mem
   void free(void*);     //libera
};
A questo punto puoi dichiarare un istanza di MyHeap e usare le sue 
funzioni alloca e free per gestire la sua memoria interna. Invece di 
limitarti a questo definisci quell'operator new che ti dicevo prima.
Quando lo chiami la dimensione (il primo parametro) e' passato gia' di 
default, tu devi solo aggiungere un puntatore a un MyHeap. Le regole 
dell'overloading fanno in modo che venga chiamato quel new.
MyHeap heeaap;
int *pt;
pt=new(&heeaap) int;
diventa
pt=(int*)operator new(sizeof(int),&heeaap); //il cast..
E quindi pt punta alla memoria privata di heeaap. Se riesci a capire 
qualcose in questo casino non fidarti troppo e aspetta che qualcuno piu' 
affidabile di un povero niubbo ti spieghi meglio tutto.


 }
questo non e' il classimo placement new. Nel classico placement new
passi un puntatore alla regione di memoria su cui costruire l'oggetto.
Tu invece stai passando un puntatore ad un oggetto gia' costruito su cui
chiamare una funzione membro per allocare memoria su cui poi in seguito
costruire un oggetto.
Insomma la cosa e' abbastanza differente.
HTH,


 
Interessante, grazie per la spiegazione.


 
Ho visto che dentro all'header new c'e' questa riga:
void* operator new(std::size_t, void* __p) throw() {return __p;}
e sopra c'e' scritto che serve per il placement-new. Ma se il 
placement-new crea un oggetto dentro a un'area di memoria gia' allocata, 
cosa se ne fa di chiamare un operator new? E poi cosa serve un operator 
new che si limita a restituire uno dei suoi parametri?
Poi un'altra domanda: se a me non interessa che sia chiamato il 
costruttore di un oggetto posso anche fare cosi' (sotto), giusto?
ClProva *cp;
cp=operator new(sizeof(ClProva)); //non conosco ancora i cast in c++
cp->a=5;
cp->b=10;
cp->lol();


 cp->lol();
il comportamento e' undefined. In pratica c'e' caso che funzioni, ma non
puoi farci alcun affidamento! Per cio' che ne sa lo standard questo puo'
provocare qualunque reazione.


Tommaso Galleri ha scritto:
CUT
c'è una cosa che non ho capito bene sulla filosofia del new 
placement.
Sinora ho visto esempi di costruzione di una singola istanza di 
oggetti.
Ma supponiamo che uno preallochi un grosso array statico e 
voglia usarlo come heap (adatto quindi a molteplici oggetti, 
eventualmente altri sotto array, e di tipo eterogeneo tra loro), 
la domanda è :
la forma new-placement, traccia internamente ed automaticamente 
l'occupazione dell'array in qualche maniera e consente chiamate 
multiple (col che delete dovrebbe fare l'inverso), oppure 
occorre fare funambolismi manuali tipo aggiornare di volta in 
volta il puntatore a seconda dello spazio già usato ? E' ovvio 
che in questo caso l'array fungerebbe facilmente come stack, 
last allocated first freed, e molto difficilmente come vero heap 
(bisognerebbe tracciare a mano e mantenere altrove lo stato e i 
confini dei blocchi).
Da dove mi viene il dubbio ? Semplice, il vecchio windows 
(scritto in C e con molte API C-Like) consentiva di creare heap 
locali dimensionati a piacere : gli passavi l'indirizzo iniziale 
e le dimensioni in byte di un blocco memoria, ed il gestore 
istanziato si occupava di masticartelo e fornirtelo in modo 
asincrono in piccoli pezzi gestiti internamente (senza lifo 
quindi). Pensavo ci fosse dietro qualcosa di simile al new 
placement.
Come funge nel caso di oggetti multipli ? Si può raggiungere lo 
scopo che faceva il local heap manager inizializzabile dalla api 
di windows (che concettualmente più che un allocatore era un 
distributore di uan risorsa acquisita once-only)
ciao
Soviet_Mario


 
Tommaso sei un mito!

 di windows 
la risposta semplice e' no. In pratica pero' puoi emularne tu il
comportamento.
al placement new dici solo dove creare un oggetto. lui non fa alcun
memory management.
tu puoi invece fare tutto il memory management che vuoi a mano, o se
preferisci appoggiarti a quello del sistema operativo:
ad esempio in win32 se crei una heap con HeapCreate e poi con HeapAlloc
allochi un'area di memoria di sizeof(T), puoi poi usare tale indirizzo
con il placement new per costruirvici, proprio li, un oggetto sopra.
Quindi il placement new e' molto semplice, non fa nulla di complesso.


 comportamento.
si, ma diventa difficoltoso il garbage collection e la ricerca 
degli eventuali buchi liberi se allochi e liberi in modo non lifo.
ciao
Soviet_Mario


 
Quindi non funziona nemmeno con le struct dato che sono classi, vero? Ma 
allora cosa fanno di magico i miei costruttori e quelli di default? Se 
la memoria e' gia' allocata e io conosco gli offset di tutti i membri 
non dovrebbe essere necessario altro per utilizzare un oggetto.


 non dovrebbe essere necessario altro per utilizzare un oggetto.
Il fatto che sia probabile che sul tuo sistema il quel codice funzioni
significa che il tuo ragionamento e' sensato...
basta pero' ad esempio che tu renda la funzione membro ClProva::lol
virtuale (se gia' non lo e') per fare in modo che il tuo codice (se
prima funzionava) non funga piu'.
Lasciando stare le funzioni virtuali, un altro problema e' che se la
classe contiene tipi non primitivi (e non e' detto che a e b lo siano
per cio' che hai postato), i loro costruttori non vengono invocati.
Supponi che uno di questi oggetti sia un oggetto il cui costruttore deve
allocare memoria dinamicamente. Bene, il costruttore non viene invocato
e tu se usi tale oggetto membro della classe ClProva fai, con grossa
probabilita', un de-reference di un puntatore non valido (e magari non
puoi settare a mano tale punatore in quanto e' private).
Se ClProva non e' una classe base, il costruttore della classe base non
viene invocato.
Poi, non hai modo (decente) di settare il valore di costanti (tipo const
int z;)
etc...
D'altro canto, con una struttura con soli tipi primitivi (POD-struct):
struct C{
   int a;
   double z;
};
il discorso cambia e codice come quello sopra diviene sicuro, e questo
chiaramente e' confermato dal fatto che con un compilatore C++ puoi
compilare codice C89 (che usa malloc che, come operator new, non
costruisce l'oggetto) e che questo non deve assumere improvvisamente un
undefined behaviour;-)
Il comportamento e' invece gia' undefined con:
struct U{
   int a;
   string b;
};


 
Ho capito, grazie ancora.
