2011-02-05 6 views
6

Ho questa gerarchia di classe banale:In C++, perché è necessario `new` per creare dinamicamente un oggetto piuttosto che un'allocazione?

class Base { 
public: 
    virtual int x() const = 0; 
}; 

class Derived : public Base { 
    int _x; 
public: 
    Derived(int x) : _x(x) { } 
    int x() const { return _x; } 
}; 

Se uso malloc di assegnare un'istanza di Derived, e quindi provare a accedere alla funzione polimorfica x, programma va in crash (ho un errore di segmentazione):

int main() { 
    Derived *d; 
    d = (Derived*) malloc(sizeof(Derived)); 
    *d = Derived(123); 

    std::cout << d->x() << std::endl; // crash 

    return 0; 
} 

Naturalmente la mia applicazione effettiva è molto più complessa (è una sorta di pool di memoria).


Sono abbastanza sicuro che sia a causa del modo in cui allocare d: Non ho usato new.

io sappia placement new dell'operatore, che deve essere quello che mi serve, ma non ho mai usato e hanno avuto alcune domande:

  • perché è la mia domanda crash, se io non uso new ?

    Cosa fa in realtà new?

    Perché non posso semplicemente utilizzare l'operatore di assegnazione per assegnare il valore di Derived(123); all'area di memoria indicata da d?

  • Devo usare new anche per tipi non polimorfi?

    E i POD?

  • Sul C++Faq I linked above si dice che l'area di memoria passata al posizionamento new deve essere allineata per l'oggetto che sto creando.

    So quale allineamento è, ma non so come controllare l'allineamento necessario per la mia classe.

    malloc manuale dice: funzioni

    La malloc() e calloc() restituiscono un puntatore alla memoria allocata che è opportunamente allineato per qualsiasi tipo di variabile.

    e spero che l'allineamento necessario per la mia classe è la dimensione della classe restituita dalla sizeof, in modo che qualsiasi indirizzo in forma address_returned_by_malloc + i * sizeof(my_class) è adatto per allocare i miei oggetti.

    Le mie speranze sono corrette?

+0

Perché non basta sostituire il nuovo operatore per questa classe? – Earlz

+0

"Perché non' malloc' funziona per le classi? " e "Perché abbiamo bisogno di' nuovo'? " sono domande molto diverse, una delle quali ha una risposta. –

+0

@Earlz, ho finito ** caricando ** nuovo operatore, ma volevo sapere perché dovevo usare 'nuovo'. @Chris Lutz: sì e no; questa domanda era: "Perché devo usare' new' invece di 'malloc' per istanziare oggetti di classe?" – peoro

risposta

3

Andiamo giù la linea

  1. perché la mia applicazione crash, se non usare di nuovo?

La tabella virtuale è danneggiata.

Il tavolo virtuale viene bloccato subito dopo la memoria allocata. quando si è new una classe, il codice generato configurerà correttamente il vtable. Tuttavia, malloc non inizializzare correttamente vtable

Per vedere la tabella virtuale, eseguire g ++ -fdump gerarchia di classi

Vtable for Derived 
Derived::_ZTV7Derived: 3u entries 
0  (int (*)(...))0 
8  (int (*)(...))(& _ZTI7Derived) 
16 Derived::x 

Class Derived 
    size=16 align=8 
    base size=12 base align=8 
Derived (0x10209fc40) 0 
    vptr=((& Derived::_ZTV7Derived) + 16u) <-- notice how this is part of the structure 
    Base (0x10209fcb0) 0 nearly-empty 
     primary-for Derived (0x10209fc40) 

Per un motivo simile, senza operatore sovraccaricare =, il codice assembly generato sarà solo copiare i dati e non il vtable [ancora una volta, il compilatore conosce solo per copiare i dati, non il vtable]

Se volete vedere una versione puntatore-based con una funzione vtable valida:

Derived e(123); 
d = &e; 
  1. Avrei bisogno di usare nuovo anche per i tipi non polimorfi?

Se si utilizza funzioni virtuali, allora sì, anche per i tipi non polimorfici

  1. Spero che l'allineamento necessario per la mia classe è la dimensione di classe come restituito da sizeof, in modo che qualsiasi indirizzo nella forma address_returned_by_malloc + i * sizeof (my_class) è adatto per allocare i miei oggetti.

L'allineamento non è un problema.

+2

Sizeof non salta la dimensione del vtable. La dimensione vtable è inclusa nella dimensione di, nessun problema. il problema è semplicemente che malloc non inizializza il vtable (o qualsiasi altra cosa). Se l'OP avesse utilizzato il posizionamento nuovo sui dati malloc'd, nessun problema – bdonlan

+0

"sizeof intenzionalmente ignora la dimensione vtable" non è corretto. –

+0

corretto questo ... –

3

Perché malloc non chiama il costruttore della classe, e non sa nulla di eventuali particolari esigenze di allineamento potrebbe avere. Se è necessario utilizzare malloc (non consigliato), dare un'occhiata a placement new (supponendo che non si desideri sovraccaricare il normale new per qualche motivo).

+0

Questo non risponde a nessuna delle mie tre domande. 'malloc' non chiama il costruttore, ma ho usato l'operatore di assegnazione, con un oggetto correttamente costruito su quella regione di memoria, quindi perché non è abbastanza? – peoro

+0

Non sono sicuro che l'assegnatore esegua tutti i controlli di integrità (ad esempio controlli di tipo?) Sull'assegnatario ... se lo fa, allora non funzionerà. Che tipo di violazione di accesso stai vedendo - leggi o scrivi? E dove esattamente accade nel codice? – Mehrdad

+0

@peoro: le precondizioni sull'operatore di assegnazione sono che ENTRAMBI i lati sono oggetti correttamente costruiti. –

1

Non credo che il costruttore dell'oggetto venga chiamato quando si usa malloc.

+0

Pensi che in qualche modo magicamente venga chiamato il ctor? – Tim

+0

Ho avuto l'impressione che il nuovo operatore chiami il costruttore. MSDN e Wikipedia sembrano essere d'accordo. Puoi elaborare? – alanp

+2

Non capisco il downvoting continuato qui. alanp ha corretto il doppio negativo nella sua risposta, ora è corretto. –

1

sezione [basic.life] dello standard dice

La durata di un oggetto è una proprietà runtime dell'oggetto. Si dice che un oggetto abbia un'inizializzazione non banale se è di classe o di tipo aggregato e questo o uno dei suoi membri è inizializzato da un costruttore diverso da un semplice costruttore di default. [Nota: l'inizializzazione da parte di un semplice costruttore di copia/spostamento è un'inizializzazione non banale. - fine nota] La durata di un oggetto di tipo T inizia quando: si ottiene

  • stoccaggio con il corretto allineamento e la dimensione per tipo T, e
  • se l'oggetto ha inizializzazione non banale, la sua inizializzazione è completare.

Dal momento che la classe ha membri virtuali, richiede l'inizializzazione non banale. Non è possibile assegnare un oggetto la cui durata non è iniziata, è necessario inizializzare con new.

2

Le classi con i membri virtual contengono un puntatore a un cosiddetto vtable, in pratica una tabella di puntatori di funzioni all'implementazione di questi membri virtuali.Quando si utilizza operator new, viene chiamato il costruttore, che, anche se è un costruttore implicito, imposterà correttamente questo puntatore al vtable.

Tuttavia, malloc non chiama il costruttore. Il puntatore vtable non è inizializzato, punta a una memoria casuale. Quando si tenta di chiamare una funzione virtuale, si desinera un puntatore non valido e si blocca (comportamento non definito).

La soluzione è quella di utilizzare nuova collocazione per inizializzare l'oggetto prima di utilizzarlo:

int main() { 
    Derived *d; 
    d = (Derived*) malloc(sizeof(Derived)); 
    new(d) Derived(123); // invoke constructor 
// You could also do: 
// new(d) Derived; 
// *d = Derived(123); 

    std::cout << d->x() << std::endl; // crash 

    // Although in your case it does not matter, it's good to clean up after yourself by 
    // calling the destructor 
    d->~Derived(); 
    return 0; 
} 

Alcune cose importanti da notare:

  • allineamento non è un problema. La memoria di malloc è correttamente allineata per qualsiasi tipo di C++.
  • L'assegnazione con = non è di aiuto. L'implementazione predefinita di = copia tutte le variabili membro, ma il puntatore vtable non è un membro e non viene copiato.
  • La costruzione non è richiesta per i tipi di POD. I tipi non POD possono o non possono averne bisogno (è un comportamento indefinito se non lo si fa). In particolare, il costruttore chiama anche costruttori di variabili membro; quindi se non costruisci l'oggetto esterno, anche gli oggetti interni potrebbero essere spezzati.
+3

Tutto questo materiale v-table è vero nella maggior parte delle implementazioni, ma non richiesto dallo standard. Tutti gli standard affermano che gli oggetti con membri virtuali non sono POD e pertanto devono essere inizializzati eseguendo il costruttore. –