66 votes

Le placement de tableau-new nécessite une surcharge non spécifiée dans la mémoire tampon?

5.3.4 [expr.new] du C++11 Fév projet donne l'exemple:

new(2,f) T[5] résultats dans un appel d' operator new[](sizeof(T)*5+y,2,f).

Ici, x et y sont non-négatif non spécifié les valeurs représentant le tableau d'allocation des frais généraux, le résultat de la nouvelle-expression sera compensé par ce montant de la valeur retournée par operator new[]. Ces frais peuvent être appliqués à l'ensemble de la matrice de la nouvelle-expressions, y compris ceux de référencement fonction de la bibliothèque operator new[](std::size_t, void*) de placement et d'autres fonctions d'allocation. Le montant des frais généraux peuvent varier d'une invocation de la nouvelle à l'autre. fin de l'exemple ]

Maintenant, prenez l'exemple de code suivant:

void* buffer = malloc(sizeof(std::string) * 10);
std::string* p = ::new (buffer) std::string[10];

Selon la citation ci-dessus, la deuxième ligne, new (buffer) std::string[10] en interne appel operator new[](sizeof(std::string) * 10 + y, buffer) (avant la construction de l'individu, std::string des objets). Le problème est que si y > 0, le pré-mémoire tampon allouée sera trop petit!

Alors, comment puis-je savoir combien de mémoire de pré-allouer lors de l'utilisation de la matrice de placement-nouvelle?

void* buffer = malloc(sizeof(std::string) * 10 + how_much_additional_space);
std::string* p = ::new (buffer) std::string[10];

Ou de la norme, quelque part, de garantir que y == 0 dans ce cas? Encore une fois, la citation dit:

Ces frais peuvent être appliqués à l'ensemble de la matrice de la nouvelle-expressions, y compris ceux de référencement fonction de la bibliothèque operator new[](std::size_t, void*) de placement et d'autres fonctions d'allocation.

48voto

Howard Hinnant Points 59526

Ne pas utiliser operator new[](std::size_t, void* p) , sauf si vous savez a priori la réponse à cette question. La réponse est un détail d'implémentation et peut changer avec le compilateur/plate-forme. Bien qu'il est généralement stable pour toute plate-forme donnée. E. g. c'est quelque chose que spécifié par l' Itanium ABI.

Si vous ne connaissez pas la réponse à cette question, écrire votre propre placement de la matrice de nouveau qui permet de vérifier au moment de l'exécution:

inline
void*
operator new[](std::size_t n, void* p, std::size_t limit)
{
    if (n <= limit)
        std::cout << "life is good\n";
    else
        throw std::bad_alloc();
    return p;
}

int main()
{
    alignas(std::string) char buffer[100];
    std::string* p = new(buffer, sizeof(buffer)) std::string[3];
}

En variant la taille de la matrice et de l'inspection n dans l'exemple ci-dessus, vous pouvez déduire y pour votre plate-forme. Pour ma plate-forme y est 1 mot. La sizeof(word) varie en fonction de si je suis la compilation pour un 32 bits ou 64 bits de l'architecture.

9voto

Kerrek SB Points 194696

Mise à jour: Après discussion, je comprends que ma réponse ne s'applique plus à la question. Je vais le laisser ici, mais une vraie réponse est certainement encore.

Je serai heureux de soutenir cette question avec une certaine générosité si une bonne réponse n'est pas trouvée rapidement.

Je vais reformuler la question ici, comme je le comprends, en espérant qu'une version plus courte peut aider les autres à comprendre ce qui est demandé. La question est:

Est la suite de la construction toujours correct? Est - arr == addr à la fin?

void * addr = std::malloc(N * sizeof(T));
T * arr = ::new (addr) T[N];                // #1

Nous savons à partir de la norme n ° 1 provoque l'appel ::operator new[](???, addr)??? est un nombre quelconque d'au moins N * sizeof(T), et nous savons aussi que cet appel ne renvoie addr et n'a pas d'autres effets. Nous savons aussi qu' arr est déduite à partir de addr respectivement. Ce que nous ne pas savoir, c'est si la mémoire pointée par addr est suffisamment grand, ou comment nous allions connaître la quantité de mémoire à allouer.


Vous semblez confondre un peu les choses:

  1. Votre exemple des appels operator new[](), pas operator new().

  2. Les fonctions d'allocation de ne pas construire quoi que ce soit. Ils allouer.

Ce qui se passe est que l' expression T * p = new T[10]; causes:

  1. un appel à l' operator new[]() avec la taille de l'argument 10 * sizeof(T) + x,

  2. dix appels au constructeur par défaut de T, efficacement ::new (p + i) T().

La seule particularité est que le tableau-nouvelle expression de la demande plus de mémoire que ce qui est utilisé par les données du tableau lui-même. Vous ne voyez pas tout cela et ne peut pas faire usage de cette information dans tout autre manière que par le silence de l'acceptation.


Si vous êtes curieux de savoir de combien de mémoire a été attribué, vous pouvez simplement remplacer la matrice des fonctions d'allocation operator new[] et operator delete[] et de le faire imprimer à la taille réelle.


Mise à jour: aléatoire morceau de l'information, il faut noter que le placement de nouvelles fonctions sont nécessaires pour être no-ops. C'est, lorsque vous construisez un objet ou un tableau en place comme suit:

T * p = ::new (buf1) T;
T * arr = ::new (buf10) T[10];

Ensuite, les appels correspondants à l' ::operator new(std::size_t, void*) et ::operator new[](std::size_t, void*) ne font rien, mais le retour de leur deuxième argument. Cependant, vous ne savez pas ce qu' buf10 est censé point: Il doit pointer vers 10 * sizeof(T) + y octets de mémoire, mais vous ne savez y.

6voto

Dietmar Kühl Points 70604

L'appel de n'importe quelle version de operator new[] () ne fonctionne pas trop bien avec une taille fixe pour la zone de mémoire. Essentiellement, il est supposé qu'il délègue à une véritable fonction d'allocation mémoire plutôt que de simplement retourner un pointeur vers la mémoire allouée. Si vous disposez déjà d'un mémoire de l'arène où vous voulez construire un tableau d'objets, vous souhaitez utiliser std::uninitialized_fill() ou std::uninitialized_copy() de construire les objets (ou une autre forme de individuellement la construction d'objets).

Vous pourriez argumenter que cela signifie que vous devez détruire les objets dans votre mémoire arène à la main. Cependant, appelant delete[] array sur le pointeur retourné par le placement new ne fonctionne pas: elle serait d'utiliser le non-placement version de operator delete[] ()! C'est, lors de l'utilisation de placement new vous devez manuellement destruction de l'objet(s) et de libérer de la mémoire.

1voto

j_kubik Points 4007

Après la lecture de la norme correspondante sections je suis satarting à penser que le placement de nouvelles pour les types tableau est tout simplement inutile idée, et la seule raison pour qu'il soit autorisé par la norme est de façon générique dans lequel la nouvelle-opérateur est décrit:

La nouvelle expression tente de créer un objet de la typeid (8.1) ou newtypeid à laquelle il est appliqué. Le type de cet objet est la alloué type. Ce type doit être un objet de type, mais pas un classe abstraite type de tableau ou de celle-ci (1.8, 3.9, 10.4). [Note: en raison les références ne sont pas des objets, les références ne peuvent pas être créés par newexpressions. ] [Note: le typeid peut être un cvqualified type, en auquel cas, l'objet créé par la newexpression a un cvqualified type. ]

new-expression: 
    ::(opt) new new-placement(opt) new-type-id new-initializer(opt)
    ::(opt) new new-placement(opt) ( type-id ) new-initializer(opt)

new-placement: ( expression-list )

newtypeid:
    type-specifier-seq new-declarator(opt)

new-declarator:
    ptr-operator new-declarator(opt)
    direct-new-declarator

direct-new-declarator:
    [ expression ]
    direct-new-declarator [ constant-expression ]

new-initializer: ( expression-list(opt) )

Il me semble que array placement new simplement les tiges de la compacité de la définition (toutes les utilisations possibles comme l'un schéma), et il semble qu'il n'y a aucune bonne raison pour qu'il soit interdit.

Ce qui nous laisse dans une situation où nous avons inutiles de l'opérateur, qui a besoin de la mémoire allouée avant de savoir combien il va être nécessaire. La seule solution que je vois serait de soit overallocate de la mémoire et de l'espoir que le compilateur ne veux plus que fourni, ou de ré-allouer de la mémoire dans surchargée array placement new fonction/méthode (plutôt défaites le but de l'utilisation d' array placement new en premier lieu).


Pour répondre à la question souligné par Kerrek SB: Votre exemple:

void * addr = std::malloc(N * sizeof(T));
T * arr = ::new (addr) T[N];                // #1

n'est pas toujours correcte. Dans la plupart des implémentations arr!=addr (et il y a de bonnes raisons pour cela), de sorte que votre code n'est pas valide, et votre tampon sera envahie.

Au sujet de ces "bonnes raisons" - notez que vous êtes libéré par la norme créateurs de la maison de maintien lors de l'utilisation d' array new de l'opérateur, et array placement new n'est pas différent à cet égard. Notez que vous n'avez pas besoin d'informer delete[] environ de la longueur de la matrice, de sorte que cette information doit être conservée dans le tableau lui-même. Où? Exactement dans cette mémoire supplémentaire. Sans elle, delete[]'ing nécessiterait gardant la longueur du tableau distinct (stl fait à l'aide de boucles et de non-placement new)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X