Il posizionamento nuovo per gli array può essere utilizzato in modo portatile?

È ansible utilizzare effettivamente il posizionamento nuovo nel codice portatile quando lo si utilizza per gli array?

Sembra che il puntatore che torni da new [] non sia sempre uguale all’indirizzo che hai inserito (5.3.4, la nota 12 nello standard sembra confermare che questo è corretto), ma non vedo come tu può allocare un buffer per l’array per entrare se questo è il caso.

L’esempio seguente mostra il problema. Compilato con Visual Studio, questo esempio provoca il danneggiamento della memoria:

#include  #include  class A { public: A() : data(0) {} virtual ~A() {} int data; }; int main() { const int NUMELEMENTS=20; char *pBuffer = new char[NUMELEMENTS*sizeof(A)]; A *pA = new(pBuffer) A[NUMELEMENTS]; // With VC++, pA will be four bytes higher than pBuffer printf("Buffer address: %x, Array address: %x\n", pBuffer, pA); // Debug runtime will assert here due to heap corruption delete[] pBuffer; return 0; } 

Osservando la memoria, il compilatore sembra utilizzare i primi quattro byte del buffer per memorizzare un conteggio del numero di elementi in esso contenuti. Ciò significa che poiché il buffer è solo sizeof(A)*NUMELEMENTS grande, l’ultimo elemento dell’array viene scritto nell’heap non allocato.

Quindi la domanda è: puoi scoprire quanto sovraccarico aggiuntivo la tua implementazione vuole per usare il posizionamento nuovo [] in sicurezza? Idealmente, ho bisogno di una tecnica che sia portatile tra diversi compilatori. Si noti che, almeno nel caso di VC, l’overhead sembra differire per classi diverse. Ad esempio, se rimuovo il distruttore virtuale nell’esempio, l’indirizzo restituito da new [] è uguale all’indirizzo che ho inserito.

Personalmente sceglierei di non utilizzare il posizionamento nuovo nell’array e utilizzare invece il posizionamento nuovo su ogni elemento nell’array individualmente. Per esempio:

 int main(int argc, char* argv[]) { const int NUMELEMENTS=20; char *pBuffer = new char[NUMELEMENTS*sizeof(A)]; A *pA = (A*)pBuffer; for(int i = 0; i < NUMELEMENTS; ++i) { pA[i] = new (pA + i) A(); } printf("Buffer address: %x, Array address: %x\n", pBuffer, pA); // dont forget to destroy! for(int i = 0; i < NUMELEMENTS; ++i) { pA[i].~A(); } delete[] pBuffer; return 0; } 

Indipendentemente dal metodo che usi, assicurati di distruggere manualmente ognuno di questi elementi nell'array prima di eliminare pBuffer, poiché potresti finire con perdite;)

Nota : non l'ho compilato, ma penso che dovrebbe funzionare (sono su una macchina che non ha un compilatore C ++ installato). Indica ancora il punto 🙂 Spero che aiuti in qualche modo!


Modificare:

Il motivo per cui è necessario tenere traccia del numero di elementi è che può iterare attraverso di essi quando si chiama delete sull'array e assicurarsi che i distruttori vengano chiamati su ciascuno degli oggetti. Se non sa quanti ce ne sono, non sarebbe in grado di farlo.

@Derek

5.3.4, la sezione 12 parla del sovraccarico dell’allocazione dell’array e, a meno che non lo stia interpretando erroneamente, sembra suggerirmi che sia valido per il compilatore aggiungerlo anche al nuovo posizionamento:

Questo sovraccarico può essere applicato a tutte le nuove espressioni della matrice, comprese quelle che fanno riferimento all’operatore della funzione di libreria new [] (std :: size_t, void *) e altre funzioni di assegnazione dei posizionamenti. La quantità di spese generali può variare da una chiamata di nuova a un’altra.

Detto questo, penso che VC sia stato l’unico compilatore a darmi problemi con questo, fuori da esso, GCC, Codewarrior e ProDG. Dovrei controllare di nuovo per essere sicuro, però.

Grazie per le risposte. L’utilizzo del posizionamento nuovo per ciascun elemento nell’array è stata la soluzione che ho utilizzato quando ho eseguito l’operazione (scusate, avrei dovuto menzionarlo nella domanda). Ho solo sentito che doveva esserci qualcosa che mi mancava nel farlo con il posizionamento nuovo []. Così com’è, sembra che il posizionamento new [] sia essenzialmente inutilizzabile grazie allo standard che consente al compilatore di aggiungere un overhead aggiuntivo non specificato all’array. Non vedo come potresti mai usarlo in modo sicuro e portabile.

Non sono nemmeno del tutto chiaro perché abbia bisogno dei dati aggiuntivi, poiché non si chiamerebbe delete [] sull’array in ogni caso, quindi non vedo del tutto perché abbia bisogno di sapere quanti oggetti ci sono dentro.

@James

Non sono nemmeno del tutto chiaro perché abbia bisogno dei dati aggiuntivi, poiché non si chiamerebbe delete [] sull’array in ogni caso, quindi non vedo del tutto perché abbia bisogno di sapere quanti oggetti ci sono dentro.

Dopo aver riflettuto, sono d’accordo con te. Non vi è alcun motivo per cui il posizionamento nuovo dovrebbe avere bisogno di memorizzare il numero di elementi, perché non vi è alcuna eliminazione del posizionamento. Poiché non è stato eliminato il posizionamento, non c’è motivo per cui il posizionamento è nuovo per memorizzare il numero di elementi.

Ho anche provato questo con gcc sul mio Mac, usando una class con un distruttore. Sul mio sistema, il posizionamento nuovo non stava cambiando il puntatore. Questo mi fa chiedere se questo è un problema VC ++, e se questo potrebbe violare lo standard (lo standard non affronta in modo specifico questo, per quanto posso trovare).

Il nuovo posizionamento è di per sé portatile, ma le ipotesi che si fanno su ciò che fa con un blocco di memoria specificato non sono portabili. Come quello che è stato detto prima, se tu fossi un compilatore e ti fosse dato un pezzo di memoria, come sapresti come allocare un array e distruggere correttamente ogni elemento se tutto ciò che avevi era un puntatore? (Vedi l’interfaccia dell’operatore delete [].)

Modificare:

E in effetti esiste un delete di posizionamento, solo viene chiamato solo quando un costruttore lancia un’eccezione mentre assegna un array con il posizionamento new [].

Se new [] in realtà ha bisogno di tenere traccia del numero di elementi in qualche modo è qualcosa che è lasciato allo standard, che lo lascia al compilatore. Sfortunatamente, in questo caso.

Penso che gcc faccia la stessa cosa di MSVC, ma ovviamente questo non lo rende “portatile”.

Penso che tu possa aggirare il problema quando NUMELEMENTS è effettivamente una costante di tempo di compilazione, in questo modo:

typedef A Arr[NUMELEMENTS];

A * p = nuovo (buffer) Arr;

Questo dovrebbe usare il posizionamento scalare nuovo.

In modo simile al modo in cui si utilizzerà un singolo elemento per calcolare le dimensioni di un posizionamento, nuovo, utilizzare un array di tali elementi per calcolare le dimensioni richieste per un array.

Se si richiede la dimensione per altri calcoli in cui il numero di elementi potrebbe non essere noto, è ansible utilizzare sizeof (A [1]) e moltiplicare per il numero di elementi richiesto.

per esempio

 char *pBuffer = new char[ sizeof(A[NUMELEMENTS]) ]; A *pA = (A*)pBuffer; for(int i = 0; i < NUMELEMENTS; ++i) { pA[i] = new (pA + i) A(); }