ABI ARM C ++: valeurs de retour constructeur / destructeur

J’ai lu le code source de Clang et découvert quelque chose d’intéressant à propos de ABI ARM C ++ dont je ne semble pas comprendre la justification. À partir de la version en ligne de la documentation ARM ABI :

Cette ABI nécessite que les constructeurs C1 et C2 renvoient cette information (au lieu d’être des fonctions vides) afin qu’un constructeur C3 puisse appeler le constructeur C1 et que le constructeur C1 puisse appeler C2.

(et de même pour les destructeurs non virtuels)

Je ne suis pas sûr de ce que référence C1 , C2 et C3 ici … cette section est censée être une modification du § 3.1.5 du ABI générique (c’est-à-dire Itanium), mais cette section (du moins dans cette version en ligne ) déclare simplement:

Les constructeurs renvoient des résultats nuls .

Quoi qu’il en soit, je ne peux vraiment pas comprendre le but de cette opération: comment un constructeur renvoie-t-il cette optimisation, et dans quelles circonstances?

Autant que je sache, le seul cas où un constructeur pourrait appeler un autre avec le même résultat serait le cas d’une classe dérivée avec une seule classe de base, d’un corps de constructeur sortingvial, sans membres avec des constructeurs non-sortingviaux et sans pointeur de table virtuelle. En fait, il semble qu’il serait plus facile, et non plus difficile, d’optimiser avec un appel final avec un retour void , car la ressortingction d’une classe de base unique pourrait être éliminée (dans le cas de classes de base multiples, le pointeur this renvoyé à partir du dernier constructeur appelé ne sera pas le pointeur this de l’object dérivé).

Qu’est-ce que j’oublie ici? Y at-il quelque chose dans la convention d’appel ARM qui rend le retour nécessaire?

Ok, le lien utile de @Michael a clairement indiqué tout cela … C1 , C2 et C3 font référence au nom-mangling du “constructeur d’object complet”, du “constructeur d’object de base” et du “constructeur d’affectation d’object complet”, respectivement, de l’Itanium ABI:

   ::= C1 # complete object constructor ::= C2 # base object constructor ::= C3 # complete object allocating constructor ::= D0 # deleting destructor ::= D1 # complete object destructor ::= D2 # base object destructor 

Le “constructeur d’allocation d’object complet” est une version du constructeur qui, plutôt que de fonctionner sur la mémoire déjà allouée qui lui est transmise via le paramètre this , alloue de la mémoire en interne (via l’ operator new ) puis appelle le constructeur d’object complet C1 / ” “, qui est le constructeur normal utilisé pour le cas d’object complet. Étant donné que le constructeur C3 doit renvoyer le pointeur this au nouvel object alloué et construit, le constructeur C1 doit également renvoyer le pointeur this pour pouvoir utiliser un appel final.

Le C2 / “constructeur d’object de base” est le constructeur appelé par les classes dérivées lors de la construction d’un sous-object de classe de base; la sémantique des constructeurs C1 et C2 diffère en cas d’inheritance virtual et peut également être implémentée différemment à des fins d’optimisation. Dans le cas de virtual inheritance virtual , un constructeur C1 pourrait être implémenté avec des appels à virtual constructeurs de classe de base virtual suivis d’un appel final à un constructeur C2 . Ce dernier devrait donc également le renvoyer si le premier le fait.

Le cas du destructeur est légèrement différent mais lié. Selon le ABI ARM:

De même, nous demandons à D2 et à D1 de renvoyer cette information afin que D0 n’ait pas besoin de la sauvegarder ni de la restaurer et que D1 puisse suivre l’appel D2 (s’il n’y a pas de bases virtuelles). D0 est toujours une fonction vide.

Le destructeur de suppression / D0 est utilisé lors de la suppression d’un object, il appelle le destructeur d’object complet / D1 et appelle ensuite l’ operator delete avec le pointeur this pour libérer la mémoire. Le renvoi du destructeur D1 permet au destructeur D0 d’utiliser sa valeur de retour pour appeler l’ operator delete , plutôt que de le sauvegarder dans un autre registre ou de le mettre en mémoire; de même, le destructeur d’object de base / D2 / devrait aussi le renvoyer.

ARM ABI ajoute également:

Nous n’avons pas besoin des thunks aux destructeurs virtuels pour renvoyer ceci . Un tel bruit devrait ajuster le résultat du destructeur, l’empêchant d’appeler le destructeur et annulant toute sauvegarde possible.

Par conséquent, seuls les appels non virtuels des destructeurs D1 et D2 peuvent être utilisés pour le renvoyer.

Si je comprends bien, cela signifie que cette optimisation save-restore-elision ne peut être utilisée que lorsque D0 appelle D1 manière statique (c’est-à-dire dans le cas d’un destructeur non virtual ).