Исследователь изучил, как современные компиляторы справляются с оптимизацией devirtualization — заменой виртуального вызова на прямой код. В основном они справляются с вызовами финальных методов надёжно, но есть множество странных исключений, где разные компиляторы ведут себя по-разному. Сначала стоит отметить, что линковка временного выполнения (LTO) могла бы работать лучше всего благодаря анализу всей программы, но экспериментировать с ней сложно, поэтому речь идёт только о возможностях самого компилятора. Существуют две основные ситуации, когда компилятор способен точно определить, что виртуализация не нужна. Первая — если известно динамический тип экземпляра. Классический пример, когда переменная имеет тип Apple, а вызывается метод f() — оптимизация сработает даже если метод виртуальный, так как диспетчеризация просто вызывает функцию на фактическом типе. Компилаторы вроде MSVC и ICC справляются даже со случаями, когда объект хранится в указателе базового класса. Более сложный вариант, где выбор объекта зависит от условия cond, ухмыляется Clang, но GCC тянет этот трюк — пока не обернуть приведение к Base* внутрь самого условного оператора, что ломает даже анализ GCC. Вторая ситуация возникает, когда есть гарантия, что динамический тип не изменится. Это можно доказать тем, что класс помечен как final, запрещая наличие детей, либо если метод сам объявлен как final. GCC, Clang и MSVC проходят тесты с финальными классами и методами, хотя ICC иногда путается. Есть ещё странные уловки, например, класс с финальным деструктором не может иметь наследников, но кроме Clang никто эту глупость не учитывает. Ещё один приём использует внутреннюю ссылку (internal linkage) для скрытого от внешних модулей класса. Если класс лежит в анонимном пространстве имён или является специализацией шаблона с внутренним именем параметра, компилятор понимает, что детей вне этого файла быть не может, и деревиртуализирует вызовы. GCC умеет ловить даже ситуации, когда тип внешнего класса не может быть завершён в других модулях, тогда как другие компиляторы упускают это. Тесты на платформе Godbolt показывают чёткие различия: GCC часто правильно деревиртуализирует методы, определённые напрямую в классе, но не всегда справляется с наследуемыми функциями, где Clang и MSVC показывают себя устойчивее. Один из участников даже сообщил автору, что виртуальные базовые классы с частными конструкторами гарантируют отсутствие детей, но ни один компилятор не пытается распутать эту логику.
Компиляторы Clang GCC и MSVC различаются в оптимизации виртуальных вызовов
17.05.2026 08:58 · hackernews