Pergunta

É possível realmente usar o posicionamento new em código portátil ao usá-lo para matrizes?

Parece que o ponteiro que você recebe de new[] nem sempre é o mesmo que o endereço que você passa (5.3.4, a nota 12 no padrão parece confirmar que isso está correto), mas não vejo como você pode alocar um buffer para o array entrar, se for esse o caso.

O exemplo a seguir mostra o problema.Compilado com o Visual Studio, este exemplo resulta em corrupção de memória:

#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;
}

Olhando para a memória, o compilador parece estar usando os primeiros quatro bytes do buffer para armazenar uma contagem do número de itens nele contidos.Isso significa que, como o buffer é apenas sizeof(A)*NUMELEMENTS grande, o último elemento da matriz é gravado em um heap não alocado.

Portanto, a questão é: você pode descobrir quanta sobrecarga adicional sua implementação deseja para usar o posicionamento new[] com segurança?Idealmente, preciso de uma técnica que seja portátil entre diferentes compiladores.Observe que, pelo menos no caso do VC, a sobrecarga parece diferir para diferentes classes.Por exemplo, se eu remover o destruidor virtual no exemplo, o endereço retornado de new[] será o mesmo que o endereço que passei.

Foi útil?

Solução

Pessoalmente, eu optaria por não usar o posicionamento new na matriz e, em vez disso, usar o posicionamento new em cada item da matriz individualmente.Por exemplo:

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;
}

Independentemente do método usado, certifique-se de destruir manualmente cada um desses itens do array antes de excluir o pBuffer, pois você pode acabar com vazamentos;)

Observação:Não compilei isso, mas acho que deve funcionar (estou em uma máquina que não possui um compilador C++ instalado).Ainda indica o ponto :) Espero que ajude de alguma forma!


Editar:

A razão pela qual ele precisa controlar o número de elementos é para que ele possa iterar através deles quando você chamar delete no array e garantir que os destruidores sejam chamados em cada um dos objetos.Se não souber quantos existem, não seria capaz de fazer isso.

Outras dicas

@Derek

5.3.4, seção 12 fala sobre a sobrecarga de alocação de array e, a menos que eu esteja interpretando mal, parece sugerir que é válido para o compilador adicioná-lo no posicionamento novo também:

Essa sobrecarga pode ser aplicada em todas as novas expressões de matriz, incluindo aquelas que fazem referência ao operador de função de biblioteca new[](std::size_t, void*) e outras funções de alocação de posicionamento.A quantidade de sobrecarga pode variar de uma invocação nova para outra.

Dito isto, acho que o VC foi o único compilador que me deu problemas com isso, dentre eles, GCC, Codewarrior e ProDG.Eu teria que verificar novamente para ter certeza.

Obrigado pelas respostas.Usar o posicionamento new para cada item da matriz foi a solução que acabei usando quando me deparei com isso (desculpe, deveria ter mencionado isso na pergunta).Eu simplesmente senti que devia haver algo que estava faltando em fazer isso com o posicionamento new[].Do jeito que está, parece que o posicionamento new[] é essencialmente inutilizável graças ao padrão que permite ao compilador adicionar uma sobrecarga adicional não especificada ao array.Não vejo como você poderia usá-lo com segurança e portabilidade.

Eu nem estou muito claro por que ele precisa de dados adicionais, já que você não chamaria delete[] no array de qualquer maneira, então não vejo exatamente por que ele precisa saber quantos itens estão nele.

@James

Eu nem estou muito claro por que ele precisa de dados adicionais, já que você não chamaria delete[] no array de qualquer maneira, então não vejo exatamente por que ele precisa saber quantos itens estão nele.

Depois de pensar um pouco sobre isso, concordo com você.Não há razão para que o posicionamento new precise armazenar o número de elementos, porque não há exclusão de posicionamento.Como não há exclusão de posicionamento, não há razão para que o posicionamento new armazene o número de elementos.

Também testei isso com o gcc no meu Mac, usando uma classe com um destruidor.No meu sistema, o posicionamento novo era não mudando o ponteiro.Isso me faz pensar se este é um problema do VC++ e se isso pode violar o padrão (o padrão não aborda isso especificamente, até onde posso descobrir).

O posicionamento new em si é portátil, mas as suposições que você faz sobre o que ele faz com um bloco de memória especificado não são portáteis.Como foi dito antes, se você fosse um compilador e recebesse um pedaço de memória, como saberia como alocar um array e destruir adequadamente cada elemento se tudo que você tivesse fosse um ponteiro?(Veja a interface do operador delete[].)

Editar:

E na verdade existe uma exclusão de posicionamento, só que ela só é chamada quando um construtor lança uma exceção ao alocar um array com posicionamento new[].

Se new[] realmente precisa controlar o número de elementos de alguma forma é algo que fica a critério do padrão, o que deixa isso para o compilador.Infelizmente, neste caso.

Acho que o gcc faz a mesma coisa que o MSVC, mas é claro que isso não o torna "portátil".

Acho que você pode contornar o problema quando NUMELEMENTS é de fato uma constante de tempo de compilação, assim:

typedef A Arr[NUMELEMENTS];

A* p = new (buffer) Arr;

Isso deve usar o novo posicionamento escalar.

Semelhante a como você usaria um único elemento para calcular o tamanho de um novo posicionamento, use uma matriz desses elementos para calcular o tamanho necessário para uma matriz.

Se você precisar do tamanho para outros cálculos onde o número de elementos pode não ser conhecido, você pode usar sizeof(A[1]) e multiplicar pela contagem de elementos necessária.

por exemplo

char *pBuffer = new char[ sizeof(A[NUMELEMENTS]) ];
A *pA = (A*)pBuffer;

for(int i = 0; i < NUMELEMENTS; ++i)
{
    pA[i] = new (pA + i) A();
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top