Come funziona l'allocazione dinamica C ++?

1

Ho un grosso dubbio sull'allocazione dinamica. Supponi di avere una classe come questa:

class MyClass {
    public:
        MyClass() {}
        ~MyClass() {}
    private:
        std::vector<MyOtherClass> others;
};

Se creo un'istanza di MyClass utilizzando new MyClass , la% internavector verrà allocata nell'heap o nello stack?

    
posta Marco Stramezzi 25.09.2017 - 08:56
fonte

4 risposte

1

Nota: le implementazioni non devono avere "stack" e "heap", ma sono artefatti di implementazione piuttosto comuni.

Se crei un'istanza "locale" di MyClass (cioè una variabile automatica), la sua memoria sarà in pila. La variabile membro "altri" di solito occupa un paio di "parole" in memoria (ad esempio una 'parola' potrebbe essere di 32 bit); uno per la variabile di dimensione dei vettori e un puntatore alla memoria dinamica per i membri del vettore. La memoria dinamica verrà allocata nell'heap in base a qualcosa come malloc ().

    
risposta data 25.09.2017 - 10:46
fonte
3

If I create an instance of MyClass using new MyClass, the inner vector will be allocated in heap or on stack?

Non ti interessa (poiché è un dettaglio di implementazione). Quello di cui devi preoccuparti è che qualsiasi risorsa creata (per il vettore interno) al momento della costruzione verrà rilasciata correttamente al momento della distruzione; e qualsiasi classe ben educata dovrebbe obbedire a questa regola (a meno che non sia documentare in caso contrario). Leggi RAII e la wiki su Critica di C ++ . Tieni presente la regola di cinque .

In pratica, le variabili automatiche di% tipostd::vector probabilmente manterranno i loro dati interni in alcuni heap ha allocato memoria (che viene cancellata dal vettore distruttore ), ma la lunghezza del vettore e il puntatore a quel dato interno è probabilmente sullo stack delle chiamate. Per std::string -s è probabile (ma non richiesto) essere diverso: leggi su ottimizzazioni delle stringhe piccole (in alcuni casi, i dati della stringa rimangono nello stack di chiamate).

Si noti che uno stack di chiamate non è sempre richiesto (ma praticamente presente). Le specifiche C ++ 11 o C11 non richiedono alcun stack di chiamate. Un (ipotetico) ottimizzatore del compilatore potrebbe essere abbastanza intelligente da evitare di richiedere uno stack di chiamate per il tuo programma.

Se ti interessa davvero, studia il codice macchina prodotto dal tuo particolare compilatore. Con GCC (almeno su Linux), compila yourprog.cc file sorgente usando g++ -Wall -S -O yourprog.cc quindi guarda nel file assembler yourprog.s generato .

Ma non dovresti preoccuparti di dove si trova un dato (in heap o in pila). Devi essere sicuro che sarebbe rilasciato in modo appropriato (dai distruttori).

Ti interesserebbe (per quanto riguarda le dimensioni del frame di chiamata) per il benchmarking e le ragioni di rendimento; quindi usa il compilatore, ad esempio usa g++ con -Wstack-usage=800

In il tuo codice , l'unico modo per ottenere i dati allocati nell'heap è usare ::operator new o chiamare alcune funzioni che lo usano. Ovviamente delete (o qualche funzione che lo usa) dovrebbe in seguito essere usato per evitare perdite di memoria .

Nel tuo codice, i distruttori dei campi membri (ad esempio others nel tuo caso) verrebbero chiamati dopo il corpo (attualmente vuoto) del tuo distruttore ~MyClass() .

    
risposta data 25.09.2017 - 11:04
fonte
1

Una rapida panoramica della bozza di lavoro C ++ del 14 novembre 2014, otteniamo il punto 18.6.1.1:

void* operator new(std::size_t size);

1 Effects: The allocation function (3.7.4.1) called by a new-expression (5.3.4) to allocate size bytes of storage suitably aligned to represent any object of that size.

Che può essere letto come, quando new un oggetto, viene creato un singolo pezzo di memoria per memorizzare tutti i suoi membri. Tuttavia, solo il vettore stesso si trova in quella prima allocazione. I membri del vettore sono una matrice allocata dinamicamente e come tale si troveranno da qualche altra parte nella memoria.

Ciò significa che se crei un'istanza di MyClass come locale, anche il vettore interno others sarà locale. Se crei un'istanza di MyClass nella memoria heap, others sarà nell'heap. Per essere precisi: chiamare new significa che il tuo vettore interno sarà sull'heap, incluso nell'istanza di MyClass , supponendo che new allochi effettivamente all'heap (che è standard nella maggior parte dei compilatori).

    
risposta data 25.09.2017 - 10:07
fonte
0

Quello che segue rappresenta ciò che di solito accade in C ++. Alla fine, elencherò alcuni possibili motivi per cui potrebbe non essere il caso.

Di solito, quando scrivi MyClass *clz = new MyClass(); , l'operatore new alloca sizeof MyClass byte sull'heap, chiama il costruttore del suo membro e restituisce un puntatore alla memoria allocata.

Poiché stai includendo vector come membro, tutti i membri del vettore verranno inclusi nell'allocazione dell'heap sizeof MyClass . Questo dipende dall'implementazione, ma di solito è una dimensione, una capacità e un puntatore a un'altra allocazione dell'heap che viene utilizzata per una matrice di dimensioni variabili di MyOtherClass es. In altre parole, potresti ottenere 2 allocazioni di heap con new originale.

Se, tuttavia, hai appena incluso MyClass clz; come variabile locale, il sizeof MyClass sarà allocato nello stack. Tuttavia, l'array valido all'interno di vector può ancora essere allocato nello heap.

Ma , essendo questo C ++, quanto sopra potrebbe non essere vero. Innanzitutto, è possibile sovrascrivere l'operatore new per fare qualcos'altro. In teoria, potresti farlo allocare in pila, usando alloca per dire, anche se questa sarebbe una pessima idea.

In secondo luogo, l'implementazione di std::vector può variare considerevolmente. Un'ottimizzazione, nota come ottimizzazione dei piccoli vettori, se possibile, memorizza alcuni oggetti nella sua struct piuttosto che nell'heap. In determinate circostanze, ciò può migliorare le prestazioni anche se è probabilmente improbabile nel tuo caso (dipende dall'implementazione di vector e MyOtherClass ). Inoltre, e più probabilmente nel tuo caso, l'implementazione potrebbe non eseguire un'allocazione interna dell'heap fino a quando non inserisci qualcosa. Quindi new MyClass non esegue 2 allocazioni dell'heap ma potresti ottenerne una o più mentre riempi il vettore nel programma in esecuzione.

    
risposta data 25.09.2017 - 12:14
fonte

Leggi altre domande sui tag