C++虚函数:链接器是否可以删除未被调用的虚函数表中的条目?

11
这个问题是消除未使用的虚函数的后续,但对我来说不够深入。
问题:当定义具有虚函数的类时,编译器为虚函数表分配存储空间,并在表中存储函数指针。这会导致链接器保留这些函数的代码,无论它们是否被调用。即使编译器优化设置要求消除死代码,这可能导致大量死代码保留在可执行文件中。
现在,如果在可执行文件中没有地方调用特定的虚函数(或者换句话说,访问虚函数表的相应槽),则可以从虚函数表中省略相应的函数指针,并且链接器将删除该函数的代码,并有可能进一步省略其他不再被引用的代码。
显然,编译器无法做到这一点,因为只有在链接时才能确定是否调用了特定的虚函数(假设静态链接 - 明显不能在动态链接中完成)。我对链接器不太熟悉,无法确定编译器是否可以以这种方式发出虚函数表,以便链接器可以选择性地省略表中单个未使用的条目。
基本上,我的思路是这样的:虚函数表中的函数指针是对函数的引用,链接器使用它来确定需要在可执行文件中保留函数的代码。类似地,虚函数调用是对从调用其虚函数的类派生出的所有虚函数表中特定插槽的引用。是否可以以这种方式将此类引用通知链接器,以便在没有对其进行任何引用时省略虚函数表插槽?请注意,这与编译器能够在编译时确定调用目标并替换虚函数调用为直接调用不同。我知道有些编译器可以这样做,但这是另一种情况,因为实际上调用了该函数,并且移除的是虚函数分派的开销。在我的情况下,我希望未调用的函数的整个代码都被删除。如果我控制所有类定义,我可以手动消除所有未调用的虚函数。但是在使用库时,这是不现实的。这是否可以通过“链接时优化”或“整个程序优化”来实现?是否有成功实现这种功能的编译器?

常见的链接器不提供and运算符,但这很容易实现。 - curiousguy
1个回答

2
死代码的问题在于编译器无法确定从动态库的角度来看,代码是否已经死亡。可执行文件可能会动态包含使用死代码(继承自拥有死代码的类)的库。
此外,在链接时更改v表的结构可能完美地工作,如果可执行文件是唯一进行函数调用的程序。然而,如果动态库进行任何调用,则它将对v表有不同的理解,并且会调用错误的函数。
由于这些事实,而且在表面上没有多少(如果有的话)性能提升,因此优化链接器很少具备此功能。
虚函数的去虚拟化实际上与此相关,安全的优化链接器只能去虚拟化非常少量的函数调用。例如,仅当它可以保证没有动态库可以在调用堆栈中起任何作用时,才能去虚拟化该函数。
编辑@curiousguy提出了一个情况,在这种情况下编译器可以更加自由地进行优化,即链接器可以知道没有外部代码知道该类。这种情况的一个例子是具有文件范围的类。

谁说要改变vtable布局了? - curiousguy
你如何在不改变布局的情况下删除条目? - Gregory Currie
我理解你的观点。你可以保留相同的布局......但是为了什么目的呢?调用已删除的函数仍然无法正常工作。虽然调用其他函数仍然是安全的,但优化必须100%安全。 - Gregory Currie
1
一个vtable偏移量就像在struct中变量的偏移量一样,它是一个编译时常量。你不能在之后更改它。改变vtable布局的唯一方法是在代码生成之前执行优化。如果你知道一个成员函数不可能被虚拟调用,你可以在vtable中省略它,在vtable中使用空指针(或指向abort的指针),而不改变vtable布局并破坏对其他函数的虚拟调用。如果该函数既不是虚拟调用也不是非虚拟调用,则可以省略其代码。 - curiousguy
非成员函数可以通过后缀表达式或者取其地址的方式进行调用。编译器会知道这一点,并且可以从可到达的代码中删除没有以这两种方式被调用的函数。请参见 https://goo.gl/IGm8JZ。 - curiousguy
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接