web-dev-qa-db-ja.com

Cのヘッダーファイルに静的関数定義を配置するのはいつですか?

ヘッダーファイルに大きな静的関数が含まれているコードに出くわしましたが、これを行うのが適切であるかどうかが気になるところです。たとえば、多数の.cファイルにはヘッダーが含まれていますが、関数を非静的に定義してリンクするだけではどうですか。

静的関数の定義をCのヘッダーファイルに配置するタイミングとタイミングについてのアドバイスや経験則は、高く評価されます。

ありがとう

26
horseyguy

いくつかのアイデア:

  • 私が考えることができる1つの正当な使用法は、外部リンケージでシンボルを作成したり、外部名前空間を汚染したりせずに関数を使用可能にしたい場合です。 (しかし、ヘッダーファイルでmylib123__foobar#define foobar mylib123__foobarなどのあいまいな接頭辞付きの名前を使用するだけでよいので、これは少しわかりにくいかもしれません。)
  • ユーザーがライブラリ/オブジェクトファイルをリンクする必要なしに、特定の機能を純粋にヘッダーファイルから利用できるようにします。データ構造とそれを操作するいくつかの些細なコードの断片に過ぎない「ライブラリ」を提供するときに、これが本当の動機であることがわかりました。実際、データ構造が不透明ではなく、アプリケーションから直接アクセスすることを意図している場合は、それらを使用する関数を同じヘッダーファイルに(ライブラリではなく)配置することで、データを変更した場合やデータを変更したときにデータが破壊されるリスクを大幅に削減できます構造。
  • おそらく、関数は外部関数の単なるラッパーであり、ラッパーの動作方法は、呼び出し側のコンパイル単位のコンパイル時オプションに依存する場合があります。例えば:

    static int foobar(int x)
    {
        return real_foobar(COMPILETIME_PARAMETER, x);
    }
    

    単にマクロを使用すると言うかもしれませんが、意図した使用法のためにfoobarを関数ポインター経由で呼び出す必要がある場合はどうでしょうか?

それが言われたことで...

実際には、人々がヘッダーファイルにstatic関数を配置する主な理由は、通常、コンパイラーが関数などをインライン化できるようにすることで、パフォーマンスが向上するという10年の古い概念に基づいています。これを行うほとんどの人は測定を行っていません。現代のコンパイラーは、プログラム全体を1つの単位としてコンパイルすることができ、理論的には最適化の可能性がはるかに高くなるので、そもそも最適化に問題があるため、パフォーマンスの目的でヘッダーに関数を配置することには本当に懐疑的です。

この批判は、特にヘッダーファイル内の「大きな」静的関数のOPの例に当てはまります。定数の引数値によってコンパイラーがコードの90%などを削除できない限り、大きな関数がインライン化から利益を得る方法はほとんどありません。 (この極端なケースの実際の例については、libavcodecで使用されているクレイジーなインライン関数/マクロ定義の一部を参照してください。:-)

26
R..

経験則として、ヘッダーファイルに静的関数を配置しないでください。 1回限りのプログラムでは、各モジュールに冗長なコピーがあるため、コードのサイズを拡大することを除けば、おそらく何も害はありません。共有ライブラリでは、ライブラリの一部がライブラリの呼び出し元に埋め込まれているため、バグが発生しやすくなり、バージョンの不一致が発生しやすくなります。

関数呼び出しに費やす時間が重要な、ひどくタイムクリティカルな関数がある場合は、それをヘッダーに入れることを検討できますが、その場合は(a)インラインで宣言することもできます。 (b)見つけることができる他のすべての最適化をすでに実行している。

要するに、ヘッダーファイルに静的関数が必要であるという疑いの陰を超えない限り...ヘッダーファイルに静的関数が必要ではありません。ヘッダーが.hの.cファイルに非静的関数が必要な場合。

9
Jander

私の経験では、一般に.hファイルの関数defineを使用することはお勧めできません。

ヘッダーを含む各ファイルに独自のseparate関数の実装を許可することはできると思いますが、関数に静的変数がある場合、これは望ましい動作になる可能性があります。ファイルごとにいくつかの情報を個別に追跡する必要がある場合。

9
tobyodavies

最近のCでは、このようなタスクにC++のinlineキーワードを採用しています。しかし、コンパイラに(まだ?)ない場合は、ヘッダーファイルにstaticを追加することでそれをエミュレートできます。 inlineは、関数が必ずしも呼び出し元にインライン化されることを意味するのではなく、通常、最終的な実行可能ファイルに最大で1つのコピーが存在することを意味します。 (技術的には、対応するリンカーシンボルは「弱い」シンボルです。)対照的に、staticを宣言しただけの場合、すべてのコンパイルユニットがコピーを保持します。

ヘッダーに関数を定義するこのようなアプローチは、コンパイラーがコードを呼び出し側関数に最適化した場合にコードを大幅に改善できる小さなタスクを実行するsmall関数に制限する必要があります。

その際、これらの関数の実装にも注意してください。これにより、C++に宣言を含める可能性がなくなる可能性があります。一般に、2つの言語はインターフェースについて(ほとんど)同意するだけであり、必ずしも実装については同意しません。微妙な違いがあります。

3
Jens Gustedt

各翻訳単位に対してローカルである静的作業バッファーを使用して関数を定義する場合にも役立ちます。特定の例はstrtok()です。 strtok()は、呼び出しごとに1つのトークンをバッファを介して行進します。 strtok()呼び出しが2つの異なる場所(つまり、2つの異なる変換単位)からインターリーブされている場合、結果は予期したものとは異なります。各翻訳単位に独自のstrtok()のコピーがあり、したがって各翻訳単位に独自のstrtok()静的変数がある場合、このような内部状態の踏みつけはなくなります。状態の踏みつけが発生している場合、両方の(セットの)呼び出しは同じ変換単位内にあり、デバッグは局所性に似ています。

(「正しい」解決策は、strtok()をステートレス関数に置き換えて、呼び出し側にコンテキストと状態情報を保持させることであることに注意してください。fopen()やフレンドが呼び出し側に各コンテキストのFILEを保持させるのと同じ方法です。)

2
Eric Towers