web-dev-qa-db-fra.com

Pourquoi gcc ne peut-il pas dévirtualiser cet appel de fonction?

#include <cstdio>
#include <cstdlib>
struct Interface {
    virtual void f() = 0;
};

struct Impl1: Interface {
    void f() override {
        std::puts("foo");
    }
};

// or __attribute__ ((visibility ("hidden")))/anonymous namespace
static Interface* const ptr = new Impl1 ;

int main() {
    ptr->f();
}

Lorsqu'il est compilé avec g ++ - 7 -O3 -flto -fdevirtualize-at-ltrans -fipa-pta -Fuse-linker-plugin, L'appel ptr->f() ci-dessus ne peut pas être dévirtualisé.

Il semble qu'aucune bibliothèque externe ne puisse modifier ptr. Est-ce une déficience de l'optimiseur GCC, ou parce que d'autres sources rendent la dévirtualisation indisponible dans ce cas?

Lien Godbolt

MISE À JOUR: Il semble que clang-7 avec -flto -O3 -fwhole-program-vtables -fvisibility=hidden Est le seul compilateur + drapeaux (comme en 2018/03) qui puisse dévirtualiser ce programme .

30
lz96

Si vous déplacez le ptr dans la fonction principale, le résultat est très révélateur et offre une forte indication de la raison pour laquelle gcc ne veut pas virtualiser le pointeur.

Le désassemblage pour cela montre que si le `` drapeau statique a été initialisé '' est faux, il initialise le statique puis revient directement à l'appel de fonction virtuelle, même si rien ne lui est peut-être arrivé. entre.

Cela me dit que gcc est câblé pour croire que tout type de pointeur globalement persistant doit toujours être traité comme un pointeur vers un type inconnu.

En fait, c'est encore pire que ça. Si vous ajoutez une variable locale, il importe que l'appel à f sur le pointeur statique se produise entre la création de la variable locale et l'appel à f ou non. L'assemblée montrant le cas interposé f est ici: n autre lien de godbolt ; et il est simple de le réorganiser vous-même sur le site pour voir comment l'assembly se transforme en ligne de f une fois que l'autre appel n'est pas interposé.

Ainsi, gcc doit supposer que le type réel auquel un pointeur fait référence peut changer chaque fois que le flux de contrôle quitte la fonction pour une raison quelconque. Et qu'il soit déclaré ou non const est sans importance. Cela n'est pas non plus pertinent si son adresse est prise, ou tout autre chose.

clang fait la même chose. Cela me semble trop prudent, mais je ne suis pas un auteur de compilateur.

9
Omnifarious

Je viens de faire une autre expérience, comme dans la réponse Omnifarious. Mais cette fois, je fais pointer le pointeur vers un objet statique:

Impl1 x;
static Interface* const ptr = &x ;

GCC ne dévirtualise pas l'appel de fonction , -O2 est suffisant. Je ne vois aucune règle dans la norme C++ qui ferait que le pointeur vers le stockage statique soit traité différemment du pointeur vers le stockage dynamique.

Il est autorisé de modifier l'objet à l'adresse indiquée par ptr. Le compilateur doit donc suivre ce qui se passe à cette adresse pour savoir quel est le type dynamique réel de l'objet. Donc, mon opinion est que l'implémenteur d'optimiseur peut avoir considéré que le suivi de ce qui se passe sur le tas serait trop difficile dans un programme réel, donc le compilateur ne le fait tout simplement pas.

4
Oliv