Tout d'abord, excellente question.
EDIT :
Je n'ai pas vraiment lu votre code correctement la première fois. Il y a un appel externe important dans votre code . C'est dans cette instruction e8 00 00 00 00 callq 2c <_Z3foov+0x2c>1
. Il n'appelle pas l'adresse avant lui-même - au lieu de cela, sa cible sera remplacée au moment de la liaison. C'est ainsi que la liaison fonctionne - le fichier elf dira "telle ou telle instruction sera résolue vers cette cible au moment de la liaison". L'assembleur n'a pas été répertorié complètement et nous ne connaissons donc pas l'adresse de cet appel. On peut supposer que c'est delete _value
dans le code, cependant libstdc++ (avec delet, etc) est lié dynamiquement par défaut. Vous pouvez changer cela en utilisant mes drapeaux de compilateur, ou faire votre liste dans gdb (c'est-à-dire après l'édition de liens).
Revenons à la réponse :
C'est un cas particulier où les programmeurs de gcc ont fait le choix de ne pas optimiser. La raison est que les opérateurs new et delete sont marqués comme des 'symboles faibles' et l'éditeur de liens cherchera des alternatives, choisissant un utilisateur fourni ou revenant en arrière si aucun n'est trouvé.
Ici est une discussion sur le raisonnement qui sous-tend cette démarche. Ces opérateurs sont conçus pour être globalement remplaçables, et la liaison faible est une solution.
L'établissement de liens statiques ne change pas cela, car free et malloc dans la glibc peuvent toujours être modifiés au moment de la liaison.
Liaison statique, ou utilisation de l'optimisation du temps de liaison devrait utiliser la suppression intégrée, cependant, dans ce cas Si, au lieu de cela, vous établissez un lien statique, la chance est toujours manquée. Dans votre exemple original, cependant, cette chance n'existe pas.
Je compile ça :
#include <array>
#include <cassert>
#include <cstdlib>
void * operator new(std::size_t n) throw(std::bad_alloc)
{
return malloc(n);
}
void operator delete(void * p) throw()
{
if(p != nullptr)
free(p);
}
class P {
public:
P() : _value(nullptr) {}
~P() { delete _value; }
private:
char *_value;
};
void foo() {
if(std::array<P, 4>().size() != 4)
assert(false);
}
int main(){
foo();
}
avec ceci ; g++ -std=c++11 -O3 -static -Wa,-alh test.cpp -o test
liens vers libstdc++/glibc statiquement Il devrait donc savoir ce que sont free et malloc, mais il ne réalise pas tout à fait que foo est trivial.
Cependant, nm -gC test | grep free
produit le symbole faible __free_hook
pour laquelle j'ai trouvé une explication aquí . Ainsi, le comportement de free et malloc (et donc, de l'opérateur new et delete) est en fait toujours modifiable à l'adresse temps de liaison lors de la compilation avec gcc. C'est ce qui empêche l'optimisation - fâcheusement, l'option -fno-weak
laisse ces symboles là.
Le paragraphe ci-dessus est vrai, mais il est le résultat de l'optimisation manquée, et non une raison d'éviter l'optimisation.
Avec l'optimisation du temps de liaison, il est possible d'amener gcc à faire cette optimisation, mais vous devez d'abord construire tout le reste avec -flto, y compris libstdc++.
Voici ce que gcc fait de mieux pour moi lors de la construction statique de l'exemple :
Dump of assembler code for function _Z3foov:
0x08048ef0 <+0>: push %esi
0x08048ef1 <+1>: push %ebx
0x08048ef2 <+2>: sub $0x24,%esp
0x08048ef5 <+5>: movl $0x0,0x10(%esp)
0x08048efd <+13>: lea 0x20(%esp),%ebx
0x08048f01 <+17>: movl $0x0,0x14(%esp)
0x08048f09 <+25>: lea 0x10(%esp),%esi
0x08048f0d <+29>: movl $0x0,0x18(%esp)
0x08048f15 <+37>: movl $0x0,0x1c(%esp)
0x08048f1d <+45>: lea 0x0(%esi),%esi
0x08048f20 <+48>: sub $0x4,%ebx
0x08048f23 <+51>: mov (%ebx),%eax
0x08048f25 <+53>: test %eax,%eax
0x08048f27 <+55>: je 0x8048f31 <_Z3foov+65>
0x08048f29 <+57>: mov %eax,(%esp)
0x08048f2c <+60>: call 0x804dea0 <free>
0x08048f31 <+65>: cmp %esi,%ebx
0x08048f33 <+67>: jne 0x8048f20 <_Z3foov+48>
0x08048f35 <+69>: add $0x24,%esp
0x08048f38 <+72>: pop %ebx
0x08048f39 <+73>: pop %esi
0x08048f3a <+74>: ret
L'appel à la gratuité ne va nulle part.
Si cela pose un problème, optimisez-le vous-même. Les modèles rendent cela facile (en supposant que std::array est passé en tant qu'argument de modèle, sinon pourquoi vérifier sa taille() ?).
#include <array>
#include <cassert>
class P {
public:
P() : _value(nullptr) {}
~P() { delete _value; }
private:
char *_value;
};
void foo() {
if(std::tuple_size<std::array<P, 4> >::value != 4)
assert(false);
}
int main(){
foo();
}
On peut faire en sorte que le code échoue silencieusement si std::array<P, 4>
est un vecteur ou quelque chose comme ça, et se rabattre sur votre méthode de construction par défaut.
nm -C test
sorties W operator new(unsigned int, void*)
quand j'ai ajouté #include <new>
Les autres sont spéciaux, et leurs cibles éventuelles résident dans libstdc++ (encore une fois, ils sont liés dynamiquement par défaut).