Pourquoi cette variable inutilisée n’est-elle pas optimisée?

J’ai joué avec ComstackrExplorer de Godbolt. Je voulais voir à quel point certaines optimisations sont bonnes. Mon exemple de travail minimum est:

#include  int foo() { std::vector v {1, 2, 3, 4, 5}; return v[4]; } 

L’assembleur généré (par clang 5.0.0, -O2 -std = c ++ 14):

 foo(): # @foo() push rax mov edi, 20 call operator new(unsigned long) mov rdi, rax call operator delete(void*) mov eax, 5 pop rcx ret 

Comme on peut le voir, Clang connaît la réponse, mais fait pas mal de choses avant de revenir. Il me semble que même le vecteur est créé, à cause de “operator new / delete”.

Quelqu’un peut-il m’expliquer ce qui se passe ici et pourquoi il ne fait pas que revenir?

Le code généré par GCC (non copié ici) semble construire explicitement le vecteur. Est-ce que quelqu’un sait que GCC n’est pas capable de déduire le résultat?

std::vector est une classe assez compliquée qui implique une allocation dynamic. Alors que clang++ est parfois capable d’éliminer les allocations de tas , c’est une optimisation assez délicate et vous ne devriez pas vous y fier. Exemple:

 int foo() { int* p = new int{5}; return *p; } 
 foo(): # @foo() mov eax, 5 ret 

À titre d’exemple, l’utilisation de std::array (qui ne diffuse pas dynamicment) produit un code entièrement intégré :

 #include  int foo() { std::array v{1, 2, 3, 4, 5}; return v[4]; } 
 foo(): # @foo() mov eax, 5 ret 

Comme le note Marc Glisse dans les commentaires des autres réponses, voici ce que dit la norme dans [expr.new] # 10 :

Une implémentation est autorisée à omettre un appel à une fonction d’allocation globale remplaçable ([new.delete.single], [new.delete.array]). Dans ce cas, le stockage est plutôt fourni par l’implémentation ou fourni par l’extension de l’allocation d’une autre nouvelle expression. L’implémentation peut étendre l’allocation d’une nouvelle expression e1 pour fournir un stockage pour une nouvelle expression e2 si ce qui suit serait vrai si l’allocation n’était pas étendue: […]

Comme le remarquent les commentaires, l’ operator new peut être remplacé. Cela peut arriver dans n’importe quelle unité de traduction. L’optimisation d’un programme pour le cas où il n’est pas remplacé nécessite donc une parsing par programme complet. Et si elle est remplacée, vous devez l’appeler bien sûr.

L’ operator new par défaut operator new est un appel d’ E / S de bibliothèque non spécifié. Cela est important, car les appels d’E / S de bibliothèque sont observables et ne peuvent donc pas être optimisés.

La modification de N3664 en [expr.new], citée dans une réponse et un commentaire, permet à new-expression s de ne pas appeler une fonction d’allocation globale remplaçable. Mais vector alloue de la mémoire en utilisant std::allocator::allocate , qui appelle directement ::operator new , pas via une nouvelle expression . Cette autorisation spéciale ne s’applique donc pas, et les compilateurs ne peuvent généralement pas échapper à de tels appels directs à ::operator new .

Tout espoir n’est pas perdu, cependant, pour la spécification std::allocator::allocate a ceci à dire:

Remarques: le stockage est obtenu en appelant ​::​operator new , mais il n’est pas spécifié à quel moment ou à quelle fréquence cette fonction est appelée.

En exploitant cette autorisation, std::allocator allocator de libc ++ utilise des fonctions intégrées de clang pour indiquer au compilateur que l’élision est autorisée. Avec -stdlib=libc++ , clang comstack votre code jusqu’à

 foo(): # @foo() mov eax, 5 ret