Почему компилятор MSVC помещает двоичные файлы экземпляра шаблона в сборку?

avatar
Nima Ghorab
2 апреля 2022 в 18:35
626
3
6

Я обнаружил что-то странное в компиляторе MSVC.

определение шаблона функции помещается в сборку, а оптимизация устраняет необходимость в них. Кажется, что Clang и GCC успешно удаляют определение функции, а MSVC — нет.

Можно ли это исправить?

main.cpp:

#include <iostream>

template <int n> int value() noexcept
{
    return n;
}

int main()
{
    return value<5>() + value<10>();
}

сборка:

int value<5>(void) PROC                                ; value<5>  COMDAT
        mov     eax, 5
        ret     0
int value<5>(void) ENDP                                ; value<5>

int value<10>(void) PROC                                ; value<10>  COMDAT
        mov     eax, 10
        ret     0
int value<10>(void) ENDP                                ; value<10>

main    PROC                                            ; COMDAT
        mov     eax, 15
        ret     0
main    ENDP

Пример кода на Godbolt

Источник
ChrisMM
2 апреля 2022 в 18:44
1

Это просто из-за того, как godbolt компилирует код (я думаю, он не полностью компилируется для MSVC). Если вы компилируете с помощью MSVC на своем компьютере в режиме «Выпуск», этих функций также не существует.

Iłya Bursov
2 апреля 2022 в 18:45
0

с переключателем /GL компилируется в mov eax,0xf ret

Nima Ghorab
2 апреля 2022 в 18:51
0

@ChrisMM Я попробовал на своей локальной машине, и результат тот же!

ChrisMM
2 апреля 2022 в 18:54
0

Какие настройки вы используете? Режим «Выпуск» по умолчанию в VS 2022 не имеет для меня этих функций....

Nima Ghorab
2 апреля 2022 в 18:55
0

@IłyaBursov Я тоже пробовал, но обе функции доступны в сборке!

Nima Ghorab
2 апреля 2022 в 18:57
0

@ChrisMM Я скомпилировал код с использованием VS 2022 со следующими флагами: -arch:AVX -O2 -GR- -FA

ChrisMM
2 апреля 2022 в 19:00
0

Как вы смотрите на сборку?

Nima Ghorab
2 апреля 2022 в 19:02
0

@ChrisMM, используя флаг -FA, VS создает main.asm, и вы можете его прочитать.

ChrisMM
2 апреля 2022 в 19:03
0

Это не оптимизированный/полностью скомпилированный файл сборки...

Nima Ghorab
2 апреля 2022 в 19:04
0

@ChrisMM Итак, как вы просматриваете ассемблерный код?

user17732522
2 апреля 2022 в 19:16
0

Дубликат избыточных экземпляров шаблона, оставленных MSVC. (на который нет ответа)

Ответы (3)

avatar
pm100
12 апреля 2022 в 23:48
0

Я скомпилировал ваш код, как указано на моем vs2022 в режиме выпуска. Я получаю

    return value<5>() + value<10>();
00007FF65CD21000  mov         eax,0Fh  
}
00007FF65CD21005  ret  
avatar
Chuck Walbourn
2 апреля 2022 в 19:18
4

Просто добавьте /Zc:inline в оператор компиляции, и он сделает то же самое, что и clang/GCC, если вы также поместите шаблон в анонимное пространство имен, чтобы убедиться, что он не виден извне.

#include <iostream>

namespace
{
    template <int n> int value() noexcept
    {
        return n;
    }
}

или если вы отметите функцию шаблона inline

template <int n> inline int value() noexcept
{
    return n;
}

Оба результата:

main    PROC
        mov     eax, 15
        ret     0
main    ENDP

Переключатель /Zc:inline (удалить несвязанный COMDAT) был добавлен в VS 2015 Update 2 как часть соответствия стандарту C++11, которое позволяет эту оптимизацию.

По умолчанию отключено в сборках командной строки. В MSBuild <RemoveUnreferencedCodeData> по умолчанию имеет значение true.

См. Microsoft Docs

ИНАЧЕ Он будет очищен на этапе компоновки с помощью /OPT:REF.

Nima Ghorab
2 апреля 2022 в 19:23
0

Я протестировал -Zc:inline, но определения все еще существуют в main.asm!

user17732522
2 апреля 2022 в 19:25
0

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

Chuck Walbourn
2 апреля 2022 в 19:25
0

Извините, я забыл, что он должен иметь «локальную» область действия.

user17732522
2 апреля 2022 в 19:27
0

@ChuckWalbourn В вашем примере шаблон имеет статическую связь. Теперь, очевидно, нет смысла его испускать, так как другие единицы перевода вообще не могут использовать шаблон. Вопрос касается шаблонов внешней компоновки.

user17732522
2 апреля 2022 в 19:29
1

Это работает правильно, если вы сделаете шаблон inline: godbolt.org/z/na4dPWWf9 Так что действительно кажется, что они применяют это поведение только к встроенным функциям, а не к неявным экземплярам шаблона, что странно, поскольку inline по стандартной спецификации практически не влияет на шаблон функции.

Chuck Walbourn
2 апреля 2022 в 19:36
0

Я думаю, что ответ на первоначальный вопрос: «Нет, это нельзя «исправить», потому что это предусмотрено дизайном» согласно Microsoft Docs.

avatar
ChrisMM
2 апреля 2022 в 19:08
7

Переключатель /FA создает файл листинга для каждой единицы перевода. Поскольку это до этапа компоновки, MSVC не определяет, требуются ли эти две функции где-либо еще в программе, и, таким образом, все еще включаются в сгенерированный файл .asm (Примечание: это может быть сделано для простоты на Часть MS, поскольку она может обрабатывать шаблоны так же, как обычные функции в сгенерированном файле .obj, хотя на самом деле нет необходимости хранить их в файле .obj, как указывает пользователь17732522 в комментариях).

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

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

Вы также можете сгенерировать Mapfile, используя параметр /MAP, который также показывает, что он не существует.


Если я правильно читаю документацию, кажется, что эти MS решили включить явные экземпляры классов и функций шаблонов, потому что это "полезно" при создании библиотек. Однако шаблоны без экземпляров не помещаются в файлы obj.

Chuck Walbourn
2 апреля 2022 в 19:11
0

Верный. Это делается в компоновщике с помощью переключателя /OPT:REF.

user17732522
2 апреля 2022 в 19:13
0

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

ChrisMM
2 апреля 2022 в 19:41
0

@ user17732522, вы правы, я думал об обычных функциях, а не о шаблонах с комментарием о невозможности удалить их перед связыванием. попробую перефразировать.