【问题标题】:G++ generates code for unused template specializations?G++ 为未使用的模板特化生成代码?
【发布时间】:2013-01-14 13:33:50
【问题描述】:

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

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&lt;4&gt; 是该程序中唯一调用的一个,我希望它是编译器生成的唯一一个(在本例中为 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() 定义。所以看起来虽然他的回答解决了我的直接问题,但它并没有解释为什么根本没有使用的模板特化被编译。

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

【问题讨论】:

  • 启用优化。编译器不会包含未使用的代码。这称为“死代码消除”。
  • 只要编译器能确定它永远不会被调用,是的,优化会移除那些函数。
  • 完全特化不再是模板,其行为更像普通函数。例如,在标题中使用时,它们必须是 inline
  • 弗拉德,垫子:我应该指定的,我在 -O3 和 -Os 上都尝试过。没有任何区别。我将进行编辑以澄清。
  • @808140:你如何验证未使用的专业化没有被淘汰?

标签: c++ templates g++ specialization


【解决方案1】:

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

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

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

【讨论】:

  • Rob:我只是重复了你的测试,确实是这样!事实上,在我的系统上,即使在-O 级别优化,将函数放在匿名命名空间中也会忽略它们。然而,这似乎也是它与未使用的“正常”(即非模板专业化)函数一起使用的方式。
  • "编译器无法知道它们不会被另一个翻译单元调用。"是的,但链接器不应该能够弄清楚吗?
  • @NPE:我认为如果你在编译阶段使用 -fwhole-program,或者如果你使用 -ffunction-sections -fdata-sections 和 -Wl,--gc-sections 那么链接器将删除未使用的函数。
  • @ZanLynx:如果可行(我现在无法测试),可能值得将其写下来作为答案。
  • 虽然这实际上并没有回答我的问题,但它确实提供了一种解决方法,所以我接受了。看来我对 C++ 模板专业化的理解是不完整的(这并不奇怪)。
【解决方案2】:

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

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

How to remove unused C/C++ symbols with GCC and ld?

也可以看这里

Using GCC to find unreachable functions ("dead code")

这里

Dead code detection in legacy C/C++ project

为了试一试,我将代码修改如下:

#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++ 的链接器在您链接时默认剥离,但我没有测试。也许其他人可以插话。

【讨论】:

  • 我想我的具体问题是,为什么它会生成代码,而不是为什么它在生成后不剥离未使用的代码。我的理解一直是模板在使用时生成。这就是为什么您必须(例如)在任何可能实例化模板的编译单元中包含整个模板定义。但在这里,模板特化似乎表现得像通常声明的函数,可能带有外部链接,我可以想象用来自另一个编译单元的适当的 extern 声明来调用它。
  • 使用 CRTP(理解没有 virtual 关键字的 virtual) 我使用 static_assert 是为了在编译时知道我没有覆盖发出此断言的基本函数。如果它在 msvc 上运行良好,我不能在 g++ (4.9) 上使用它,所以我降级为断言,这很痛苦。
猜你喜欢
  • 1970-01-01
  • 2013-10-03
  • 2010-09-16
  • 1970-01-01
  • 2012-10-26
  • 1970-01-01
  • 2014-07-14
  • 2017-06-06
  • 2017-04-02
相关资源
最近更新 更多