为什么 MSVC 编译器将模板实例化二进制文件放入程序集中?
Why does MSVC compiler put template instantiation binaries in assembly?
我在 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
/FA
开关为每个翻译单元生成列表文件。由于这是 before 链接阶段,MSVC 无法确定程序中其他任何地方是否需要这两个函数,因此仍包含在生成的 .asm 文件中(注意:这可能对 MS 来说是为了简单起见,因为它可以将模板视为与生成的 .obj 文件中的常规函数相同,但实际上没有实际需要将它们存储在 .obj 文件中,正如 user17732522 在评论中指出的那样)。
在链接过程中,MSVC 确定这些函数实际上并没有在其他任何地方实际使用/需要,因此可以删除(即使它们在其他地方使用过,因为结果可以在编译时确定,它们会仍然被删除)从编译的可执行文件中。
为了查看最终编译的可执行文件中的内容,您可以通过反汇编程序查看可执行文件。使用 MSVC 执行此操作的示例是在主函数中放置一个断点,运行 它,然后当断点被击中时,右键单击并“查看反汇编”。在这里,你会看到这两个函数已经不存在了。
您也可以使用 /MAP
选项生成 Mapfile,这也表明它不存在。
If 我正确地阅读了 documentation,似乎那些 MS 选择包含模板 类 和函数的显式实例化,因为它“是在创建库时很有用”。不过,未实例化的模板不会放入 obj 文件中。
只需将 /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 标准一致性的一部分,允许此优化。
在 command-line 版本中是 off-by-default。在 MSBuild 中,<RemoveUnreferencedCodeData>
默认为 true。
OTHERWISE 它将在链接器阶段用 /OPT:REF
清除。
我在我的 vs2022 上以发布模式编译了你的代码。我得到
return value<5>() + value<10>();
00007FF65CD21000 mov eax,0Fh
}
00007FF65CD21005 ret
我在 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
/FA
开关为每个翻译单元生成列表文件。由于这是 before 链接阶段,MSVC 无法确定程序中其他任何地方是否需要这两个函数,因此仍包含在生成的 .asm 文件中(注意:这可能对 MS 来说是为了简单起见,因为它可以将模板视为与生成的 .obj 文件中的常规函数相同,但实际上没有实际需要将它们存储在 .obj 文件中,正如 user17732522 在评论中指出的那样)。
在链接过程中,MSVC 确定这些函数实际上并没有在其他任何地方实际使用/需要,因此可以删除(即使它们在其他地方使用过,因为结果可以在编译时确定,它们会仍然被删除)从编译的可执行文件中。
为了查看最终编译的可执行文件中的内容,您可以通过反汇编程序查看可执行文件。使用 MSVC 执行此操作的示例是在主函数中放置一个断点,运行 它,然后当断点被击中时,右键单击并“查看反汇编”。在这里,你会看到这两个函数已经不存在了。
您也可以使用 /MAP
选项生成 Mapfile,这也表明它不存在。
If 我正确地阅读了 documentation,似乎那些 MS 选择包含模板 类 和函数的显式实例化,因为它“是在创建库时很有用”。不过,未实例化的模板不会放入 obj 文件中。
只需将 /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 标准一致性的一部分,允许此优化。
在 command-line 版本中是 off-by-default。在 MSBuild 中,<RemoveUnreferencedCodeData>
默认为 true。
OTHERWISE 它将在链接器阶段用 /OPT:REF
清除。
我在我的 vs2022 上以发布模式编译了你的代码。我得到
return value<5>() + value<10>();
00007FF65CD21000 mov eax,0Fh
}
00007FF65CD21005 ret