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) によって生成されるのはこれだけだと予想しました。

しかし、それは観測された動作ではありません。

実際に機能しますが、使用されていないにもかかわらず、各テンプレートの特殊化のコードが生成されます。ただし、一般的な定義は生成されません。

これは 1 ステップのコンパイルであり、リンクが続く中間オブジェクト ファイルへのコンパイルではありません。その場合、デッド コードの削除をリンク段階まで延期するのが自然に思えますが、リンカが常にこれを得意としているわけではないことを私は知っています。

誰が何が起こっているのか知っていますか?ここで見逃している微妙なテンプレートの専門化はありますか? 主は、悪魔が C++ の細部に潜んでいることを知っています。

編集: 言及されているため、この動作は -O3 と -Os の両方で発生します。

EDIT2:以下のロブは、関数を匿名の名前空間に配置することを提案しました。そうすることで、任意のレベルの最適化でコンパイルすると、実際にデッド コードが削除されます。これは良いことです。でも気になったので、次のプログラムで同じことをやってみました。

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

未使用のシンボルが 2 つあるようです。-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++ で機能しないのかわかりません (man ページに記載されていますが、確かに、私はこれを掘り下げていないので、私が見逃した細字であること)。

Visual C++ のリンカは、リンクするとデフォルトで削除されると思いますが、テストはしていません。たぶん、他の誰かが声をかけることができます。

于 2013-01-30T17:55:39.303 に答える