Почему gcc не может девиртуализировать вызов этой функции?
#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();
}
При компиляции с g++ - 7 -O3 -flto -fdevirtualize-at-ltrans -fipa-pta -fuse-linker-plugin
вышеупомянутый ptr->f()
не может быть девиртуализирован.
Кажется, что никакая внешняя библиотека не может изменять ptr
. Является ли это недостатком оптимизатора GCC или потому, что некоторые другие источники делают недоступным в этом случае девиртуализацию?
Ссылка Godbolt
UPDATE: Кажется, что clang-7 с -flto -O3 -fwhole-program-vtables -fvisibility=hidden
является единственным флагом компилятора + (как в 2018/03), который может девиртуализировать эту программу.
Ответы
Ответ 1
Если вы переместите ptr в основную функцию, результат будет очень информативным и даст сильный намек на то, почему gcc не хочет де виртуализировать указатель.
Разборки для этого показывают, что если флаг "имеет статический флаг инициализирован" равен false, он инициализирует статический, а затем переходит обратно к вызову виртуальной функции, хотя между ними ничего не могло произойти.
Это говорит мне, что gcc жестко связан с тем, что любой глобальный постоянный указатель всегда должен рассматриваться как указатель на неизвестный тип.
На самом деле это еще хуже. Если вы добавите локальную переменную, важно, возникает ли вызов функции f
на статическом указателе между созданием локальной переменной и вызовом f
или нет. Узел, показывающий f
вставленного случая здесь: Еще одна связи godbolt; и просто перестроить его самостоятельно на сайте, чтобы увидеть, как сборка превращается в строку f
когда другой вызов не вставлен.
Таким образом, gcc должен предположить, что фактический тип, на который ссылается указатель, может меняться всякий раз, когда поток управления покидает функцию по любой причине. И независимо от того, объявлено ли оно const
имеет значения. Также не имеет значения, если он когда-либо принимается, или любое другое.
clang делает то же самое. Это кажется слишком осторожным для меня, но я не писатель-компилятор.
Ответ 2
Я только что сделал другой эксперимент, как и во всём ответе. Но на этот раз я делаю указатель на статический объект:
Impl1 x;
static Interface* const ptr = &x ;
GCC выполняет девиртуализацию вызова функции, -O2
является достаточным. Я не вижу никакого правила в стандарте C++, который бы делал указатель на статическое хранилище обработанным иначе, чем указатель на динамическое хранилище.
Разрешено изменять объект по адресу, на который указывает ptr
. Поэтому компилятор должен отслеживать, что происходит на этом адресе, чтобы узнать, каков фактический динамический тип объекта. Поэтому я считаю, что разработчик оптимизатора, возможно, счел, что отслеживание того, что происходит в куче, будет слишком сложно в реальной программе, поэтому компилятор просто этого не сделает.