En ce qui concerne les optimisations du compilateur, est-il légal et/ou possible de changer une allocation de tas en une allocation de pile? Ou est-ce que cela briserait la comme si la règle ?
Par exemple, disons qu'il s'agit de la version originale du code
{
Foo* f = new Foo();
f->do_something();
delete f;
}
Un compilateur serait-il en mesure de changer cela comme suit
{
Foo f{};
f.do_something();
}
Je ne le pense pas, car cela aurait des implications si la version originale s'appuyait sur des choses comme les allocateurs personnalisés. La norme dit-elle quelque chose de spécifique à ce sujet?
Oui, c'est légal. expr.new/10
de C++ 14:
Une implémentation est autorisée à omettre un appel à une fonction d'allocation globale remplaçable (18.6.1.1, 18.6.1.2). Dans ce cas, le le stockage est plutôt fourni par l'implémentation ou fourni en étendant l'allocation d'une autre nouvelle expression.
expr.delete/7
:
Si la valeur de l'opérande de l'expression de suppression n'est pas une valeur de pointeur nulle, alors:
- Si l'appel d'allocation de la nouvelle expression pour l'objet à supprimer n'a pas été omis et que l'allocation n'a pas été étendue (5.3.4), l'expression de suppression doit appeler une fonction de désallocation (3.7.4.2). La valeur renvoyée par l'appel d'allocation de la nouvelle expression doit être transmise comme premier argument à la fonction de désallocation.
- Sinon, si l'allocation a été étendue ou a été fournie en étendant l'allocation d'une autre nouvelle expression, et que l'expression de suppression pour chaque autre valeur de pointeur produite par une nouvelle expression dont le stockage était fourni par la nouvelle expression étendue a été évaluée , l'expression de suppression doit appeler une fonction de désallocation. La valeur renvoyée par l'appel d'allocation de la nouvelle expression étendue doit être transmise comme premier argument à la fonction de désallocation.
- Sinon, l'expression de suppression n'appellera pas une fonction de désallocation (3.7.4.2).
Donc, en résumé, il est légal de remplacer new
et delete
par quelque chose d'implémentation définie, comme utiliser la pile au lieu du tas.
Remarque: Comme Massimiliano Janes le fait remarquer, le compilateur ne peut pas respecter exactement cette transformation pour votre exemple, si do_something
throws: le compilateur doit omettre l'appel du destructeur de f
dans ce cas (alors que votre échantillon transformé appelle le destructeur dans ce cas). Mais à part ça, il est libre de mettre f
dans la pile.
Ce ne sont pas équivalents. f.do_something()
peut lancer, auquel cas le premier objet reste en mémoire, le second est détruit.
Je voudrais souligner quelque chose que l'OMI n'a pas suffisamment souligné dans les autres réponses:
struct Foo {
static void * operator new(std::size_t count) {
std::cout << "Hey ho!" << std::endl;
return ::operator new(count);
}
};
Une allocation new Foo()
ne peut généralement pas être remplacée, car:
Une implémentation est autorisée à omettre un appel à une fonction d'allocation remplaçable globale (18.6.1.1, 18.6.1.2). Dans ce cas, le stockage est plutôt fourni par l'implémentation ou fourni en étendant l'allocation d'une autre nouvelle expression.
Ainsi, comme dans l'exemple Foo
ci-dessus, le Foo::operator new
Doit être appelé. L'omission de cet appel modifierait le comportement observable du programme.
Exemple réel: Foo
s peut avoir besoin de résider dans une région de mémoire spéciale (comme les entrées/sorties mappées en mémoire) pour fonctionner correctement.