Question

Est-il possible d'utiliser réellement le placement new dans le code portable lors de son utilisation pour des tableaux ?

Il semble que le pointeur que vous récupérez de new[] n'est pas toujours le même que l'adresse que vous transmettez (5.3.4, la note 12 de la norme semble confirmer que c'est correct), mais je ne vois pas comment vous peut allouer un tampon dans lequel le tableau doit entrer si tel est le cas.

L'exemple suivant montre le problème.Compilé avec Visual Studio, cet exemple entraîne une corruption de la mémoire :

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

En regardant la mémoire, le compilateur semble utiliser les quatre premiers octets du tampon pour stocker le nombre d'éléments qu'il contient.Cela signifie que parce que le tampon est uniquement sizeof(A)*NUMELEMENTS big, le dernier élément du tableau est écrit dans le tas non alloué.

La question est donc la suivante : pouvez-vous savoir quelle surcharge supplémentaire votre implémentation souhaite afin d'utiliser le placement new[] en toute sécurité ?Idéalement, j'ai besoin d'une technique portable entre différents compilateurs.Notez que, au moins dans le cas de VC, la surcharge semble différer selon les classes.Par exemple, si je supprime le destructeur virtuel dans l'exemple, l'adresse renvoyée par new[] est la même que l'adresse que je transmets.

Était-ce utile?

La solution

Personnellement, j'opterais pour la possibilité de ne pas utiliser le placement new sur le tableau et d'utiliser plutôt le placement new sur chaque élément du tableau individuellement.Par exemple:

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

Quelle que soit la méthode que vous utilisez, assurez-vous de détruire manuellement chacun de ces éléments du tableau avant de supprimer pBuffer, car vous pourriez vous retrouver avec des fuites ;)

Note:Je n'ai pas compilé cela, mais je pense que cela devrait fonctionner (je suis sur une machine sur laquelle aucun compilateur C++ n'est installé).Cela indique toujours le point :) J'espère que cela aide d'une manière ou d'une autre !


Modifier:

La raison pour laquelle il doit garder une trace du nombre d'éléments est de pouvoir les parcourir lorsque vous appelez delete sur le tableau et de s'assurer que les destructeurs sont appelés sur chacun des objets.S’il ne sait pas combien il y en a, il ne pourra pas le faire.

Autres conseils

@Derek

5.3.4, la section 12 parle de la surcharge d'allocation de tableau et, à moins que je ne la lise mal, cela me semble suggérer qu'il est valable pour le compilateur de l'ajouter également lors du placement nouveau :

Cette surcharge peut être appliquée à toutes les nouvelles expressions de tableau, y compris celles faisant référence à l'opérateur de fonction de bibliothèque new[](std::size_t, void*) et à d'autres fonctions d'allocation de placement.Le montant de la surcharge peut varier d’une invocation de new à une autre.

Cela dit, je pense que VC était le seul compilateur qui m'a posé des problèmes avec cela, parmi ceux-ci, GCC, Codewarrior et ProDG.Il faudrait cependant que je vérifie à nouveau pour en être sûr.

Merci pour les réponses.Utiliser le placement new pour chaque élément du tableau était la solution que j'ai fini par utiliser lorsque j'ai rencontré cela (désolé, j'aurais dû le mentionner dans la question).J'ai juste senti qu'il devait y avoir quelque chose qui me manquait dans le fait de le faire avec le placement new[].Dans l'état actuel des choses, il semble que le placement new[] soit essentiellement inutilisable grâce à la norme permettant au compilateur d'ajouter une surcharge supplémentaire non spécifiée au tableau.Je ne vois pas comment vous pourriez l'utiliser en toute sécurité et de manière portable.

Je ne comprends même pas vraiment pourquoi il a besoin de données supplémentaires, car de toute façon, vous n'appelleriez pas delete[] sur le tableau, donc je ne vois pas vraiment pourquoi il a besoin de savoir combien d'éléments il contient.

@James

Je ne comprends même pas vraiment pourquoi il a besoin de données supplémentaires, car de toute façon, vous n'appelleriez pas delete[] sur le tableau, donc je ne vois pas vraiment pourquoi il a besoin de savoir combien d'éléments il contient.

Après réflexion, je suis d'accord avec vous.Il n'y a aucune raison pour que le placement nouveau doive stocker le nombre d'éléments, car il n'y a pas de suppression de placement.Puisqu'il n'y a pas de suppression de placement, il n'y a aucune raison pour que le placement nouveau stocke le nombre d'éléments.

J'ai également testé cela avec gcc sur mon Mac, en utilisant une classe avec un destructeur.Sur mon système, le nouveau placement était pas changer le pointeur.Cela me fait me demander s'il s'agit d'un problème VC++ et si cela pourrait violer la norme (la norme ne traite pas spécifiquement de ce problème, pour autant que je sache).

Le placement new lui-même est portable, mais les hypothèses que vous faites sur ce qu'il fait avec un bloc de mémoire spécifié ne sont pas portables.Comme ce qui a été dit précédemment, si vous étiez un compilateur et disposiez d'une partie de la mémoire, comment sauriez-vous comment allouer un tableau et détruire correctement chaque élément si tout ce que vous aviez était un pointeur ?(Voir l'interface de l'opérateur delete[].)

Modifier:

Et il existe en fait une suppression de placement, mais elle n'est appelée que lorsqu'un constructeur lève une exception lors de l'allocation d'un tableau avec le placement new[].

La question de savoir si new[] doit réellement garder une trace du nombre d'éléments est quelque chose qui dépend de la norme, ce qui laisse le choix au compilateur.Malheureusement, dans ce cas.

Je pense que gcc fait la même chose que MSVC, mais bien sûr cela ne le rend pas "portable".

Je pense que vous pouvez contourner le problème lorsque NUMELEMENTS est effectivement une constante de compilation, comme ceci :

typedef A Arr[NUMELEMENTS];

A* p = new (buffer) Arr;

Cela devrait utiliser le nouveau placement scalaire.

De la même manière que vous utiliseriez un seul élément pour calculer la taille d'un nouveau placement, utilisez un tableau de ces éléments pour calculer la taille requise pour un tableau.

Si vous avez besoin de la taille pour d'autres calculs où le nombre d'éléments peut ne pas être connu, vous pouvez utiliser sizeof(A[1]) et multiplier par le nombre d'éléments requis.

par exemple

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

for(int i = 0; i < NUMELEMENTS; ++i)
{
    pA[i] = new (pA + i) A();
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top