9

在我正在处理的项目的一些序列化代码中,我有一个类型,其大小取决于编译器。为了解决这个问题,我决定使用模板专业化,效果很好。一切都在编译时解决。代码看起来有点像这样(不是真正的代码,只是一个例子):

template <int size>
void
special_function()
{
     std::cout << "Called without specialization: " << size << std::endl;
}

template <>
void
special_function<4>()
{
     std::cout << "dword" << std::endl;
}

template <>
void
special_function<8>()
{
     std::cout << "qword" << std::endl;
}

int
main()
{
     special_function<sizeof(int)>();
     return 0;
}

在我的 32 位系统上dword,按预期执行上述程序输出。但是这样做而不只是这样做的全部意义if (sizeof(int) == 4) { ... } else if ...在于,我希望编译器只会为适当的函数生成代码。由于special_function<4>是该程序中唯一调用的一个,因此我希望它是编译器生成的唯一一个(在本例中为 gcc 4.1.2,在 x86 Linux 上)。

但这不是观察到的行为。

虽然它确实有效,但每个模板专业化的代码都是生成的,尽管从未使用过。但是,不会生成通用定义。

我应该提到,这是一步编译,而不是编译成中间目标文件,然后是链接。在这种情况下,将死代码删除推迟到链接阶段似乎很自然,而且我知道链接器并不总是非常擅长这一点。

有谁知道发生了什么?我在这里缺少模板专业化的微妙之处吗?上帝知道魔鬼在 C++ 的细节中。

编辑:既然已经提到过,-O3 和-Os 都会发生这种行为。

EDIT2:下面的 Rob 建议将函数放在匿名命名空间中。这样做并使用任何级别的优化进行编译确实会删除死代码,这很好。但我很好奇,所以我尝试对以下程序做同样的事情:

namespace {
void foo() { std::cout << "Foo!" << std::endl; }
void bar() { std::cout << "Bar!" << std::endl; }
}

int
main()
{
       foo();
       return 0;
}

这里的想法是查看 Rob 的解决方案是否实际上与模板专业化有关。事实证明,上述打开优化编译的代码忽略bar()了可执行文件中未使用的定义。所以看起来虽然他的回答解决了我的直接问题,但它并没有解释为什么根本不使用的模板特化被编译。

有谁知道标准中的相关片段可以解释这一点?我一直认为模板只是在使用时生成的,但对于完全专业化来说可能并非如此......

4

2 回答 2

10

您的示例中的模板特化是具有外部链接的函数。编译器无法知道它们不会被另一个翻译单元调用。

在我的 g++ 4.7.2 Ubuntu 系统上,将模板放入匿名命名空间并进行编译-O3可防止生成未使用的函数。

同样,声明函数模板static也有预期的效果。

于 2013-01-30T16:58:01.680 回答
3

这是一个特殊的问题。我调查了一下,这个问题与模板专业化无关。我猜 g++ 默认情况下不会删除未使用的符号。如果您以后想将输出链接到另一个程序,这很有意义。

但是,您可以使用命令行选项去除未使用的符号。有关详细信息,请参阅此帖子:

如何使用 GCC 和 ld 删除未使用的 C/C++ 符号?

但也看到这里

使用 GCC 查找无法访问的函数(“死代码”)

和这里

遗留 C/C++ 项目中的死代码检测

只是为了试试这个,我修改了代码如下:

#include <iostream>

void junk_function() {
    std::cout<<"test" << std::endl;    
}

template <int size>
void special_function()
{
     std::cout << "Called without specialization: " << size << std::endl;
}

template <>
void special_function<4>()
{
     std::cout << "dword" << std::endl;
}

template <>
void special_function<8>()
{
     std::cout << "qword" << std::endl;
}

int main()
{
     special_function<sizeof(int)>();
     return 0;
}

然后将此代码存储到 sp.cpp。第一的,

g++ -Os sp.cpp -o sp
nm sp

并得到了这个(注意,为了便于阅读,我删除了一堆符号):

0804879a T _Z13junk_functionv
080487b8 T _Z16special_functionILi4EEvv
080487f5 T _Z16special_functionILi8EEvv

似乎有两个未使用的符号。我也试过-O1、-O2、-O3,结果都一样。

下一个:

g++ -Os -fdata-sections -ffunction-sections sp.cpp -o sp -Wl,--gc-sections
nm sp

得到了这个:

0804875a T _Z16special_functionILi4EEvv

就是这样。所以看起来你只需要传递正确的参数来告诉 g++ 去除未使用的符号。在 mac 上,我猜他们有 -dead_strip 选项,但我不知道为什么它在 g++ 中不起作用(即使它在手册页中提到。诚然,我没有深入研究这个,所以可能有是我错过的精美印刷品)。

我认为在链接时默认情况下 Visual C++ 的链接器会剥离,但我没有测试。也许其他人可以插话。

于 2013-01-30T17:55:39.303 回答