Виртуальные функции С++: может ли компоновщик удалять записи в таблице виртуальных функций, которые не вызываются?

Этот вопрос является своего рода продолжением устранения неиспользуемых виртуальных функций, что недостаточно для моего интереса.

Проблема: при определении классов, имеющих виртуальные функции, компилятор выделяет хранилище для таблицы виртуальных функций и сохраняет указатели на функции в таблице. Это приводит к тому, что компоновщик сохраняет код этих функций независимо от того, вызваны ли они когда-либо. Это потенциально может привести к тому, что в исполняемом файле будет сохранено много мертвого кода, даже если параметры оптимизации компилятора требуют устранения мертвого кода.

Теперь, если нигде в исполняемом файле нет вызова определенной виртуальной функции (или, другими словами, доступа к соответствующему слоту таблицы виртуальных функций), соответствующий функциональный указатель может быть опущен из виртуальной функции таблица, и компоновщик удалит код функции, с возможными дальнейшими упущениями другого кода, который становится неучтенным.

Очевидно, это не может быть сделано компилятором, так как в момент ссылки становится ясно, будет ли вызвана конкретная виртуальная функция (при условии статической привязки - ясно, что это невозможно с динамической компоновкой). Я недостаточно хорошо знаком с компоновщиками, чтобы определить, может ли компилятор генерировать таблицы виртуальных функций таким образом, чтобы компоновщик мог выборочно исключать отдельные неиспользуемые записи в таблице.

В принципе, мой ход мысли таков: указатель функции в таблице виртуальных функций является ссылкой на функцию, которую использует компоновщик, чтобы определить, что код функции должен быть сохранен в исполняемом файле. Подобным же образом вызов виртуальной функции является ссылкой на определенный слот во всех таблицах виртуальных функций, которые производятся от класса, вызывающая виртуальную функцию. Может ли такой вид ссылки быть передан компоновщику таким образом, чтобы он мог лишиться слота таблицы виртуальных функций, когда на нем есть нулевые ссылки?

Обратите внимание, что это не то же самое, что замена вызова виртуальной функции прямым вызовом, когда компилятор может определить цель вызова во время компиляции. Я знаю, что некоторые компиляторы могут это сделать, но это другой случай, потому что функция фактически вызвана, и это накладные расходы на удаление виртуальной функции, которая удалена. В моем случае я хочу, чтобы весь код удалялся для функций, которые не вызываются.

Если бы я имел контроль над всеми определениями классов, я мог бы вручную устранить все виртуальные функции, которые не вызываются. Но это нереально при использовании библиотек.

Это что-то, что можно сделать с "оптимизацией времени ссылки" или "оптимизацией всей программы"? Есть ли компиляторы, которые успешно это делают?

Ответ 1

Проблема с мертвым кодом заключается в том, что компилятор не может быть уверен, что код мертв с точки зрения динамических библиотек. Исполняемый файл может динамически включать библиотеку, которая использует мертвый код (происходит от классов, владеющих мертвым кодом).

В дополнение к этому, изменение структуры v-таблицы в течение времени ссылки может работать отлично, если исполняемый файл является единственным вызовом функций. Однако, если динамическая библиотека выполняет любые вызовы, у нее будет другое понимание v-таблицы, и она вызовет неправильную функцию.

Из-за этих фактов, и по номинальной стоимости не так много (если таковые имеются) производительность достигается, оптимизация компоновщиков вряд ли будет иметь эту функцию.

Де-виртуализация виртуальных функций на самом деле связана с этим, и безопасные оптимизирующие линкеры могут только де-виртуализировать очень небольшое количество вызовов функций. Например, он может только де-виртуализировать функцию, если она может гарантировать, что никакая динамическая библиотека не может сыграть какую-либо часть в стоп-кадре.

edit @curiousguy раскрыл случай, когда компилятор может быть немного более либеральным с оптимизацией, и именно тогда компоновщик может знать, что никакой внешний код не знает об этом классе. Примером этого является класс с областью файлов.