Domanda

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

Sembra che il puntatore che ottieni da new[] non sia sempre lo stesso dell'indirizzo che passi (5.3.4, nota 12 nello standard sembra confermare che questo è corretto), ma non vedo come tu può allocare un buffer in cui inserire l'array se questo è il caso.

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

#include <new>
#include <stdio.h>

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, sembra che il compilatore utilizzi i primi quattro byte del buffer per archiviare il conteggio del numero di elementi in esso contenuti.Ciò significa che poiché il buffer è solo sizeof(A)*NUMELEMENTS big, l'ultimo elemento dell'array viene scritto nell'heap non allocato.

Quindi la domanda è: puoi scoprire quanto sovraccarico aggiuntivo richiede la tua implementazione per utilizzare il posizionamento new[] in modo sicuro?Idealmente, ho bisogno di una tecnica che sia portabile tra diversi compilatori.Si noti che, almeno nel caso di VC, l'overhead sembra differire per le diverse classi.Ad esempio, se rimuovo il distruttore virtuale nell'esempio, l'indirizzo restituito da new[] è lo stesso dell'indirizzo che passo.

È stato utile?

Soluzione

Personalmente sceglierei l'opzione di non utilizzare il posizionamento new sull'array e utilizzare invece il posizionamento new su ciascun elemento dell'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 utilizzi, assicurati di distruggere manualmente ciascuno di questi elementi nell'array prima di eliminare pBuffer, poiché potresti ritrovarti con perdite ;)

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


Modificare:

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

Altri suggerimenti

@Derek

5.3.4, sezione 12 parla del sovraccarico di allocazione dell'array e, a meno che non lo interpreti male, mi sembra suggerire che sia valido per il compilatore aggiungerlo anche al posizionamento nuovo:

Questo sovraccarico può essere applicato a tutte le nuove espressioni dell'array, comprese quelle che fanno riferimento all'operatore della funzione di libreria new[](std::size_t, void*) e ad altre funzioni di allocazione del posizionamento.La quantità di sovraccarico può variare da un'invocazione di new a un'altra.

Detto questo, penso che VC sia stato l'unico compilatore che mi ha dato problemi con questo, tra cui GCC, Codewarrior e ProDG.Dovrei controllare di nuovo per esserne sicuro, però.

Grazie per le risposteUsare il posizionamento new per ogni elemento nell'array è stata la soluzione che ho finito per usare quando mi sono imbattuto in questo (mi dispiace, avrei dovuto menzionarlo nella domanda).Sentivo solo 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 ulteriore sovraccarico non specificato all'array.Non vedo come potresti mai usarlo in modo sicuro e portabile.

Non sono nemmeno molto chiaro il motivo per cui abbia bisogno di dati aggiuntivi, dato che comunque non chiameresti delete[] sull'array, quindi non capisco del tutto perché abbia bisogno di sapere quanti elementi ci sono al suo interno.

@James

Non sono nemmeno molto chiaro il motivo per cui abbia bisogno di dati aggiuntivi, dato che comunque non chiameresti delete[] sull'array, quindi non capisco del tutto perché abbia bisogno di sapere quanti elementi ci sono al suo interno.

Dopo averci pensato un po', sono d'accordo con te.Non vi è alcun motivo per cui il posizionamento nuovo debba memorizzare il numero di elementi, poiché non è prevista l'eliminazione del posizionamento.Poiché non è prevista l'eliminazione del posizionamento, non c'è motivo di inserire un nuovo posizionamento per memorizzare il numero di elementi.

L'ho testato anche con gcc sul mio Mac, utilizzando una classe con un distruttore.Sul mio sistema, il posizionamento nuovo era non cambiando il puntatore.Questo mi fa chiedere se si tratti di un problema VC++ e se ciò potrebbe violare lo standard (lo standard non affronta specificamente questo problema, per quanto ho potuto trovare).

Il posizionamento new è di per sé portabile, ma le ipotesi fatte su cosa fa con un blocco di memoria specificato non sono portabili.Come detto prima, se fossi un compilatore e ti venisse data una porzione di memoria, come sapresti come allocare un array e distruggere correttamente ogni elemento se tutto ciò che avessi fosse un puntatore?(Vedi l'interfaccia dell'operatore delete[].)

Modificare:

E in realtà esiste un'eliminazione del posizionamento, solo che viene chiamata solo quando un costruttore lancia un'eccezione durante l'allocazione di un array con il posizionamento new[].

Se new[] debba effettivamente tenere traccia del numero di elementi in qualche modo è qualcosa che viene lasciato allo standard, che lascia la scelta 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 = new (buffer) Arr;

Questo dovrebbe utilizzare il posizionamento scalare new.

In modo simile a come utilizzeresti un singolo elemento per calcolare la dimensione per un posizionamento nuovo, utilizza un array di tali elementi per calcolare la dimensione richiesta per un array.

Se hai bisogno della dimensione per altri calcoli in cui il numero di elementi potrebbe non essere noto, puoi utilizzare sizeof(A[1]) e moltiplicare per il conteggio degli elementi richiesti.

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();
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top