私にとっては、ソースファイル内の静的関数を定義および宣言するルールです。つまり、.cファイルです。
しかし、ごくまれに、ヘッダーファイルで宣言している人がいます。静的関数には内部リンケージがあるため、関数を宣言するヘッダーファイルを含めるすべてのファイルで静的関数を定義する必要があります。これはかなり奇妙に見え、静的なものとして宣言するときに通常必要なものとはかけ離れています。
一方、素朴な誰かがそれを定義せずにその関数を使おうとすると、コンパイラは不満を言うでしょう。だから、ある意味でこれを行うことは奇妙に聞こえても本当に危険ではありません。
私の質問は:
最初に、あなたが説明する状況についての理解を明確にしたいと思います:ヘッダーには静的関数宣言が含まれていますが、Cファイルには定義、つまり関数のソースコードが含まれています。例えば
some.h:
static void f();
// potentially more declarations
some.c:
#include "some.h"
static void f() { printf("Hello world\n"); }
// more code, some of it potentially using f()
これがあなたが説明する状況である場合、私はあなたの発言で問題を取ります
静的関数には内部リンケージがあるため、関数が宣言されているヘッダーファイルを含めるすべてのファイルで静的関数を定義する必要があります。
関数を宣言しても、特定の翻訳単位で使用しない場合は、定義する必要はないと思います。 gccはそれを受け入れ、警告を出します。私が何かを見逃さない限り、標準はそれを禁止していないようです。これは、関数を使用しないが宣言にヘッダーを含む翻訳単位が未使用の定義を提供する必要がないため、シナリオで重要になる場合があります。
これは上記の質問に対する答えではありませんが、ヘッダーファイルにstatic
(または_static inline
_)関数を実装する可能性のあるwhyを示しています。
個人的には、ヘッダーファイルでいくつかの関数static
を宣言する2つの正当な理由しか考えられません。
ヘッダーファイルが、現在のコンパイル単位でのみ表示されるインターフェイスを完全に実装している場合
これは非常にまれですが、たとえばいくつかのサンプルライブラリの開発中のある時点での教育的コンテキスト。または、最小限のコードで別のプログラミング言語とインターフェイスする場合。
開発者は、ライブラリまたはインターフェイスの実装が簡単でほぼそうであり、コードサイズよりも(ヘッダーファイルを使用する開発者にとって)使いやすさが重要な場合に、そうすることを選択できます。これらの場合、ヘッダーファイルの宣言ではプリプロセッサマクロを使用することが多く、同じヘッダーファイルを複数回含めることができ、Cで何らかの粗雑なポリモーフィズムを提供します。
実用的な例は次のとおりです。線形合同擬似乱数ジェネレーター用の足で自分で遊べます。実装はコンパイルユニットに対してローカルであるため、各コンパイルユニットはPRNGの独自のコピーを取得します。この例では、Cで粗雑な多型を実装する方法も示しています。
prng32.h:
_#if defined(PRNG_NAME) && defined(PRNG_MULTIPLIER) && defined(PRNG_CONSTANT) && defined(PRNG_MODULUS)
#define MERGE3_(a,b,c) a ## b ## c
#define MERGE3(a,b,c) MERGE3_(a,b,c)
#define NAME(name) MERGE3(PRNG_NAME, _, name)
static uint32_t NAME(state) = 0U;
static uint32_t NAME(next)(void)
{
NAME(state) = ((uint64_t)PRNG_MULTIPLIER * (uint64_t)NAME(state) + (uint64_t)PRNG_CONSTANT) % (uint64_t)PRNG_MODULUS;
return NAME(state);
}
#undef NAME
#undef MERGE3
#endif
#undef PRNG_NAME
#undef PRNG_MULTIPLIER
#undef PRNG_CONSTANT
#undef PRNG_MODULUS
_
上記を使用した例、example-prng32.h:
_#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#define PRNG_NAME glibc
#define PRNG_MULTIPLIER 1103515245UL
#define PRNG_CONSTANT 12345UL
#define PRNG_MODULUS 2147483647UL
#include "prng32.h"
/* provides glibc_state and glibc_next() */
#define PRNG_NAME borland
#define PRNG_MULTIPLIER 22695477UL
#define PRNG_CONSTANT 1UL
#define PRNG_MODULUS 2147483647UL
#include "prng32.h"
/* provides borland_state and borland_next() */
int main(void)
{
int i;
glibc_state = 1U;
printf("glibc lcg: Seed %u\n", (unsigned int)glibc_state);
for (i = 0; i < 10; i++)
printf("%u, ", (unsigned int)glibc_next());
printf("%u\n", (unsigned int)glibc_next());
borland_state = 1U;
printf("Borland lcg: Seed %u\n", (unsigned int)borland_state);
for (i = 0; i < 10; i++)
printf("%u, ", (unsigned int)borland_next());
printf("%u\n", (unsigned int)borland_next());
return EXIT_SUCCESS;
}
_
__state
_変数と_next()
関数static
の両方をマークする理由は、このようにヘッダーファイルを含む各コンパイルユニットが変数と関数の独自のコピーを持っているためです。 -ここでは、PRNGの独自のコピー。もちろん、それぞれ個別にシードする必要があります。また、同じ値にシードされた場合、同じシーケンスが生成されます。
複雑なプリプロセッサマクロシェナンガンになり、実装を必要以上に理解、保守、および修正することがはるかに難しくなるため、一般にCでのこのような多態性の試みは避けてください。
ただし、exploringいくつかのアルゴリズムのパラメータ空間-ここのように 2ビット線形合同ジェネレータ の場合、これにより単一の検証する各ジェネレーターの実装。それらの間に実装の違いがないことを確認します。この場合でさえ、開発ツールに似ており、他の人が使用するために提供されている実装で見るべきものではないことに注意してください。
ヘッダーが単純な_static inline
_アクセサー関数を実装する場合
プリプロセッサマクロは一般に、複雑な構造タイプにアクセスするコードを簡素化するために使用されます。 _static inline
_関数は似ていますが、コンパイル時に型チェックも提供し、パラメーターを複数回参照できることを除いて(マクロで問題があります)。
実用的な使用例の1つは、低レベルのPOSIX.1 I/Oを使用してファイルを読み取るための単純なインターフェイスです(_<unistd.h>
_の代わりに_<fcntl.h>
_および_<stdio.h>
_を使用します)。 GNU Cの標準I/Oとして、実数(カスタムのfloat/doubleパーサーを使用)を含む非常に大きな(数十メガバイトからギガバイトの範囲)のテキストファイルを読み取るときに自分でこれを行いました。特に高速ではありません。
たとえば、inbuffer.h:
_#ifndef INBUFFER_H
#define INBUFFER_H
typedef struct {
unsigned char *head; /* Next buffered byte */
unsigned char *tail; /* Next byte to be buffered */
unsigned char *ends; /* data + size */
unsigned char *data;
size_t size;
int descriptor;
unsigned int status; /* Bit mask */
} inbuffer;
#define INBUFFER_INIT { NULL, NULL, NULL, NULL, 0, -1, 0 }
int inbuffer_open(inbuffer *, const char *);
int inbuffer_close(inbuffer *);
int inbuffer_skip_slow(inbuffer *, const size_t);
int inbuffer_getc_slow(inbuffer *);
static inline int inbuffer_skip(inbuffer *ib, const size_t n)
{
if (ib->head + n <= ib->tail) {
ib->head += n;
return 0;
} else
return inbuffer_skip_slow(ib, n);
}
static inline int inbuffer_getc(inbuffer *ib)
{
if (ib->head < ib->tail)
return *(ib->head++);
else
return inbuffer_getc_slow(ib);
}
#endif /* INBUFFER_H */
_
上記のinbuffer_skip()
およびinbuffer_getc()
は、ib
がNULLでないかどうかをチェックしないことに注意してください。これはそのような機能の典型です。これらのアクセサー関数は、「高速パスで」、つまり非常に頻繁に呼び出されると想定されます。このような場合、関数呼び出しのオーバーヘッドでさえ問題になります(呼び出しサイトのコードで複製されるため、_static inline
_関数では回避されます)。
上記のinbuffer_skip()
やinbuffer_getc()
のような単純なアクセサー関数は、関数がパラメーターを特定のレジスターまたは上に配置することを期待するため、コンパイラーが関数呼び出しに関連するレジスター移動を回避することもできますインライン関数は、インライン関数を囲むコードに適合させることができます(レジスタの使用)。
個人的には、まずインライン化されていない関数を使用していくつかのテストプログラムを作成し、パフォーマンスと結果をインライン化されたバージョンと比較することをお勧めします。結果を比較することで、インラインバージョンにバグがないことを確認し(ここでは1つのタイプがオフになっていることが一般的です!)、パフォーマンスと生成されたバイナリ(少なくともサイズ)を比較することで、インライン化が一般に価値があるかどうかがわかります。
なぜグローバル関数と静的関数の両方が必要なのですか? cでは、関数はデフォルトでグローバルです。静的関数は、宣言されているファイルへの関数へのアクセスを制限する場合にのみ使用します。したがって、静的と宣言することにより、積極的にアクセスを制限します...
ヘッダーファイルでの実装の唯一の要件は、c ++テンプレート関数とテンプレートクラスメンバー関数です。