Allocazione dinamica e statica.

di il
12 risposte

Allocazione dinamica e statica.

Buongiorno a tutti,
per motivi didattici sto facendo dei progetti in C++ e passando dal java/python non riesco a capire se c'è una differenza nell'allocazione di un oggetto tra i due modi
1) Class *a = new Class(par1,par2);
2) Class a(par1,par2);

Si tratta di un allocazione statica e l'altra dinamica? Se si qual è il vantaggio nell'usarla rispetto agli oggetti? Una delle due è più efficiente dell'altra?

Un altra curiosità è data dall'operatore () per l'assegnazione. Da quel che ho capito, avendo int a = 5, posso dire int b(a) quindi anche b vale cinque, ma non dovrebbe essere un alias ma semplicemente inizializzo il valore in questo caso. Ma c'è un vantaggio in termini di efficienza o è semplicemente un modo alternativo per fare la stessa cosa (nel caso lo fosse a mio parere non migliora la visibilità) ?

12 Risposte

  • Re: Allocazione dinamica e statica.

    Class *a = new Class(par1,par2);
    allocazione dinamica : l'accesso ai membri lo fai con ->, l'istanza è nella memoria heap che è grande rispetto allo stack, devi libererare tu la memoria puntata con delete quando non ne hai più bisogno
    Class a(par1,par2);
    allocazione statica: l'accesso ai membri lo fai con ., l'istanza è nella memoria stack che è piccola rispetto allo heap, la memoria usata torna automaticamente disponibile quando a va fuori scope
    Un altra curiosità è data dall'operatore () per l'assegnazione. Da quel che ho capito, avendo int a = 5, posso dire int b(a) quindi anche b vale cinque, ma non dovrebbe essere un alias ma semplicemente inizializzo il valore in questo caso. Ma c'è un vantaggio in termini di efficienza o è semplicemente un modo alternativo per fare la stessa cosa (nel caso lo fosse a mio parere non migliora la visibilità) ?
    Sono dettagli secondari (strano che tu senta l'esigenza di approfondire questi dettagli provenendo da Python), comunque stando allo standard
    14
    The form of initialization (using parentheses or =) is generally insignificant, but does matter when the initializer or the entity being initialized has a class type; see below. If the entity being initialized does not have class type, the expression-list in a parenthesized initializer shall be a single expression.

    15
    The initialization that occurs in the form

    T x = a;
    as well as in argument passing, function return, throwing an exception ([except.throw]), handling an exception ([except.handle]), and aggregate member initialization ([dcl.init.aggr]) is called copy-initialization. [ Note: Copy-initialization may invoke a move ([class.copy]). — end note ]

    16
    The initialization that occurs in the forms

    T x(a);
    T x{a};
    as well as in new expressions ([expr.new]), static_cast expressions ([expr.static.cast]), functional notation type conversions ([expr.type.conv]), and base and member initializers ([class.base.init]) is called direct-initialization.
    Quindi nel tuo caso non c'è differenza. Se invece c'è di mezzo una classe ci possono essere differenze
    
    #include <iostream>
    using namespace std;
    
    class cl { 
    public: 
      explicit cl(int) { 
        cout << "inizializzazione diretta" << endl;
      };
      cl(double) {
        cout << "inizializzazione per copia" << endl;
      };
    };
    
    int main() { 
      cl a(0);
      cl b = 0;
      return 0;
    }
    
  • Re: Allocazione dinamica e statica.

    Grazie mille per la risposta
    allocazione dinamica : l'accesso ai membri lo fai con ->, l'istanza è nella memoria heap che è grande rispetto allo stack, devi liberare tu la memoria puntata con delete quando non ne hai più bisogno



    allocazione statica: l'accesso ai membri lo fai con ., l'istanza è nella memoria stack che è piccola rispetto allo heap, la memoria usata torna automaticamente disponibile quando a va fuori scope
    Per quanto riguarda l'allocazione non riesco a capire per quale motivo (mi riferisco agli oggetti) potrebbe nascere la necessità di istanziare nell'heap piuttosto che nello stack.
    Sono dettagli secondari (strano che tu senta l'esigenza di approfondire questi dettagli provenendo da Python), comunque stando allo standard
    E' più una curiosità, in realtà vengo più da Java e li il GC si occupava di questo.

    14
    The form of initialization (using parentheses or =) is generally insignificant, but does matter when the initializer or the entity being initialized has a class type; see below. If the entity being initialized does not have class type, the expression-list in a parenthesized initializer shall be a single expression.

    15
    The initialization that occurs in the form

    T x = a;
    as well as in argument passing, function return, throwing an exception ([except.throw]), handling an exception ([except.handle]), and aggregate member initialization ([dcl.init.aggr]) is called copy-initialization. [ Note: Copy-initialization may invoke a move ([class.copy]). — end note ]

    16
    The initialization that occurs in the forms

    T x(a);
    T x{a};
    as well as in new expressions ([expr.new]), static_cast expressions ([expr.static.cast]), functional notation type conversions ([expr.type.conv]), and base and member initializers ([class.base.init]) is called direct-initialization.
    
    #include <iostream>
    using namespace std;
    
    class cl { 
    public: 
      explicit cl(int) { 
        cout << "inizializzazione diretta" << endl;
      };
      cl(double) {
        cout << "inizializzazione per copia" << endl;
      };
    };
    
    int main() { 
      cl a(0);
      cl b = 0;
      return 0;
    }
    
    Grazie mille per gli esempi.
  • Re: Allocazione dinamica e statica.

    DrBeat1926 ha scritto:


    Per quanto riguarda l'allocazione non riesco a capire per quale motivo (mi riferisco agli oggetti) potrebbe nascere la necessità di istanziare nell'heap piuttosto che nello stack.
    A pare il fatto che è più grande e che, quindi, è meglio allocare lì gli oggetti grandi (per evitare il rischio di overflow dello stack), è abbastanza comune nelle applicazioni reali: ad esempio, se devi gestire i frame che arrivano da un network adapter, solitamente li metti nello heap e gestisci i puntatori, anche perché i pacchetti devono essere usati fuori dalla funzione dove crei l'allocazione. Poi, in generale, comunque è meglio evitare l'allocazione dinamica quando si riesce.
  • Re: Allocazione dinamica e statica.

    Weierstrass ha scritto:


    DrBeat1926 ha scritto:


    Per quanto riguarda l'allocazione non riesco a capire per quale motivo (mi riferisco agli oggetti) potrebbe nascere la necessità di istanziare nell'heap piuttosto che nello stack.
    A pare il fatto che è più grande e che, quindi, è meglio allocare lì gli oggetti grandi (per evitare il rischio di overflow dello stack), è abbastanza comune nelle applicazioni reali: ad esempio, se devi gestire i frame che arrivano da un network adapter, solitamente li metti nello heap e gestisci i puntatori, anche perché i pacchetti devono essere usati fuori dalla funzione dove crei l'allocazione. Poi, in generale, comunque è meglio evitare l'allocazione dinamica quando si riesce.
    Ok quindi è semplicemente una questione di prevenzione.
    Diciamo che concettualmente non riuscivo a capire come un oggetto possa creare overflow.
    Ricordo che per l'allocazione dinamica facevano l'esempio tipico dell'array e li è facile concettualmente capire come potesse nascere overflow, ma ripeto per gli oggetti proprio non capisco.
  • Re: Allocazione dinamica e statica.

    In alcuni casi non puoi fare a meno di usare l'heap. Ad esempio se hai una lista collegata, dove gli elementi vengono aggiunti ed eliminati a runtime. Inoltre sono sparsi in memoria, mentre lo stack è una memoria ordinata. Nel caso degli array possono anche stare nello stack, essendo contigui, ma la loro dimensione va specificata nel codice, quindi non può dipendere ad esempio da un'altra variabile.
    Gli oggetti di classi predefinite come list o vector li puoi allocare nello stack, ma i dati li gestiscono comunque nell'heap tramite puntatore.
  • Re: Allocazione dinamica e statica.

    Non e' in "alcuni casi", ma nel 99.999999%dei casi.

    Bisogna ragionare in questo modo:

    l'applicazione e' un programma di 'fotoritocco' quindi deve 'manipolare' delle immagini.
    per manipolare si usano delle funzioni
    le immagini vengono caricate in memoria e salvate su file.


    OK, ma dove vengono messe queste immagini?
    non possono essere lette e scritte su disco ogni millisecond, perche sarebbe troppo lento.

    Quindi devono essere tenute in memoria. Ma dove?
    Non possono essere messe nello stack perche' alla fine della funzione verrebbero gettate via.
    non possono essere 'statiche' perche' cambiano di volta in volta, altrimenti in questo caso il programma dovrebbe essere fermato e fatto riavviare per ogni nuova immagine.

    Resta lo heap, memoria 'labera' assegnata al processo, che puo' essere usata a piacimento. E viene rilasciata solo al termine.

    i 'vettori, o le strutture dati/classi, sono i componenti base per creare gli oggetti veramente complessi che l' applicazione deve manipolare.

    Tu non manipoli 'la lista'.
    Tu manipoli la 'lista di immegini'
  • Re: Allocazione dinamica e statica.

    I motivi sono storici
    una volta, tanto tempo fa, la memoria era reale, e addirittura segmentata (lo è anche oggi ma con segmentoni a 32 e flat a 64)
    Lo stack era quindi piccolo, e soggetto rapidamente ad esaurimento.
    veniva usato il meno possibile per quanto non fosse in sostanza parametri funzioni.

    Oggi non è più così, anzi lo stack potrebbe benissimo non esistere più. Viene mantenuto per motivi storici (cuttone)

    La differenza vera riguarda la località e i tlb ed anche la tipologia di cache suddivisa per codice e dati o no.
    Questione molto lunga e dipendente dalla singola CPU.
    La bibbia è agner
    Versione breve : molto oltre il livello della domanda
  • Re: Allocazione dinamica e statica.

    Ma uno stack piccolo e ordinato è comunque di più semplice gestione da parte del programma (basta sommare e sottrarre un registro), in più con le cache enormi di oggi, ci può stare dentro tutto, quindi ha senso mentenere una struttura simile per i dati locali e automatici.
    Comunque, tornando a quanto detto da migliorabile, il vantaggio dei dati dinamici in breve sono la visibilità indipendente dallo scopo e la possibilità di aggiungere e rimuovere dati a piacimento.
  • Re: Allocazione dinamica e statica.

    Banalita': ma dove pensate sia allocato lo stack dei thread? Nello heap!
    I vecchissimi processori, monothread, usavano la memoria da un lato per lo stack e quello che rimaneva per lo heap.
    Oggi si creano thread al volo che necessitano di uno stack, e questo si alloca nello heap.

    La memoria segmentata la si puo' vedere semplicemente come tanti vettori di dimensione massima. Fino a che le cose stanno dentro un singolo segmento, tutto bene.
    Se non ci stanno, bisogna fare i salti mortali carpiati con avvitamento triplo per suddividere la memoria richiesta in blocchi che possono essere contenuti nei segmenti.

    Comunque oggi la segmentazione e' gestita a livello di SO e di processo come suddivisione tra codice (segmento e non scrivibile) probabilmente dati statici, heap, stack, e servizi speciali come Memory mapped files o buffer per il trasferimento rapido da e per il disco, da e per la scheda grafica, e altra robbbbba.

    Il programmatore vede un bel blocco di memoria lineare. Ma puo' richiedere altri blocchi allocati in segmenti diversi, per cose super particolari.
  • Re: Allocazione dinamica e statica.

    migliorabile ha scritto:


    I vecchissimi processori, monothread, usavano la memoria da un lato per lo stack e quello che rimaneva per lo heap.
    E' ancora così in molti processori, ad esempio negli ARM single core e dual core: c'è la RAM unica, la porzioni nel linker e solitamente metti la stack in testa e lo heap in fondo. Ma qui stiamo divagando
  • Re: Allocazione dinamica e statica.

    Non ho detto che è segmentata la memoria. Anche se sta nell'heap, non vuol dire che non venga gestito come uno stack old style (cioè una memoria contigua "a fisarmonica").
  • Re: Allocazione dinamica e statica.

    Credo che ci sia un po' di confusione su questo argomento anche per via dei nomi.

    L'allocazione statica e l'allocazione locale sono 2 cose diverse in c++, l'allocazione dinamica della memoria è un'altra cosa ancora.

    Inoltre una cosa è ciò che è definito sullo standard C++, un'altra è l'implementazione vera e propria che è lasciata al compilatore e dipende da architettura e S.O.

    Sia l'allocazione locale ( Chiamata anche "su stack" perché in pratica sempre implementata con uno stack ) che l'allocazione dinamica ( chiamata "su heap" perché usa solitamente implementazioni specifiche del S.O. che usano un heap ), sono di solito entrambe definite dinamiche perché avvengono a runtime.

    Quello che viene definito nello standard a seconda dei 3 casi è il ciclo di vita delle variabili, cioè fino a quando sono accessibili e quando viene chiamato il distruttore.

    Le variabili statiche ( e.g. dichiarate fuori dal main in un file sorgente o dichiarate come "static" ) sono disponibili per tutto il ciclo di vita del programma, quando finisce il main viene chiamato il distruttore in ordine non ben definito.

    Le variabili locali ( e.g. dichiarate dentro il main ) Sono valide dal momento della dichiarazione fino alla fine del blocco dove sono dichiarate. Alla fine del blocco viene chiamato il costruttore nell'ordine inverso di dichiarazione.

    La memoria allocata dinamicamente ( con "new" ) non ha invece una variabile associata, il ciclo di vita è lasciato al programmatore che decide autonomamente quando chiamare la delete sul puntatore alla locazione di memoria. Anche se il programma finisce le delete e quindi i distruttori non verranno chiamati.
     int * i = new int; 
    NB: Quando si fa questo, non abbiamo una variabile int allocata dinamicamente, abbiamo una variabile locale di tipo (int *) che punta a della memoria allocata dinamicamente, contigua, di dimensione del tipo int che sarà disponibile finchè non verrà chiamata la delete sul puntatore.

    All'atto pratico, nelle implementazioni effettive del linguaggio, il vantaggio dell'utilizzare uno stack per le variabili locali è che l'allocazione non costa nulla e che non devi preoccuparti di distruggere gli oggetti. In pratica quando viene chiamata una funzione lo stack pointer della cpu aumenta del valore necessario a coprire la dimensione delle variabili locali, quando esce fa il contrario (più o meno, dipende dall'architettura ma è comunque molto veloce).
    L'allocazione dinamica invece ha bisogno di routine più complesse di solito implementate dal sistema operativo attraverso un heap. Quando fai una new viene fatta una chiamata al s.o. che cerca un pezzo di memoria contiguo e libero dove ci stia tutta la memoria che hai chiesto. Questo può essere ordini di grandezza più lento che non allocare sullo stack.
    Oltre a questo man mano che si alloca e si disalloca memoria sull'heap questo si frammenta rendendo più difficile trovare il posto per pezzi di memoria contigui. I S.O. moderni usano varie tecniche per mantenere deframmentata la memoria ma questo dipende appunto dall'implementazione.
    Il problema non è dato dall'utilizzare ( leggere e scrivere sulla ) memoria dinamica ma l'atto di allocare e disallocare continuamente memoria. Anche perché, in pratica, si parla esattamente della stessa memoria eccetto che in architetture particolari.
Devi accedere o registrarti per scrivere nel forum
12 risposte