Pregunta ¿Por qué no puede gcc desvirtualizar esta llamada de función?


#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();
}

Cuando se compila con g ++ - 7 -O3 -flto -fdevirtualize-at-ltrans -fipa-pta -fuse-linker-plugin, lo anterior ptr->f() la llamada no se puede desvirtualizar

Parece que ninguna biblioteca externa puede modificar ptr. ¿Es esto una deficiencia del optimizador de GCC, o porque algunas otras fuentes hacen que la desvirtualización no esté disponible en este caso?

Enlace Godbolt

ACTUALIZAR: Parece que clang-7 con -flto -O3 -fwhole-program-vtables -fvisibility=hidden es el único compilador + flags (como en 2018/03) que puede desvirtualizar este programa.


28
2018-02-21 12:47


origen


Respuestas:


Si mueve el ptr a la función principal, el resultado es muy revelador y ofrece una pista clara de por qué gcc no quiere desvirtuar el puntero.

los desmontaje para esto muestra que si 'tiene el indicador de estado estático inicializado' es falso, inicializa la estática y luego salta de regreso a la llamada de función virtual, aunque nada podría haberle sucedido entre ellos.

Esto me dice que gcc está cableado para creer que cualquier tipo de puntero globalmente persistente siempre debe ser tratado como un puntero a un tipo desconocido.

De hecho, es incluso peor que esto. Si agrega una variable local, importa si la llamada al f en el puntero estático se produce entre la creación de la variable local y la llamada a f o no. El conjunto que muestra el f el caso interpuesto está aquí: Otro enlace godbolt; y es sencillo reorganizarlo usted mismo en el sitio para ver cómo el conjunto se convierte en una línea de f una vez que la otra llamada no se interpone.

Entonces, gcc debe suponer que el tipo real al que se refiere un puntero puede cambiar cada vez que el flujo de control abandone la función por cualquier razón. Y si está declarado o no const es irrelevante. Tampoco es relevante si alguna vez se toma una dirección, o cualquier cantidad de otras cosas.

clang hace lo mismo. Esto parece demasiado prudente para mí, pero no soy un escritor de compilación.


8
2018-02-28 06:15



Acabo de hacer otro experimento, como en Omnifarious answer. Pero esta vez hago que el puntero apunte a un objeto estático:

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

GCC desvirtualiza la llamada de función, -O2 es suficiente. No veo ninguna regla en el estándar de C ++ que haga que el puntero al almacenamiento estático se trate de manera diferente que el puntero al almacenamiento dinámico.

Se permite cambiar el objeto en la dirección señalada por ptr. Entonces el compilador debe rastrear qué está sucediendo en esa dirección para saber cuál es el tipo dinámico real del objeto. Así que mi opinión es que el implementador del optimizador puede haber considerado que rastrear lo que está sucediendo en el montón sería demasiado difícil en un programa real, por lo que el compilador simplemente no lo hace.


4
2018-02-28 20:09