strbuf.c
includescache.h
およびcache.h
includesstrbuf.h
であるため、質問 2 ( をstrbuf.c
含まないstrbuf.h
) の前提は間違っています。直接ではなく、含まれています。
extern
関数に適用
このextern
キーワードは、関数の宣言に必須ではありませんが、効果があります。関数を指定する識別子 (つまり、関数の名前) が以前に表示されていた宣言と同じリンケージを持っていることを宣言します。または、そのような宣言が表示されていない場合は、識別子には外部リンケージがあります。このやや紛らわしい言い回しは、実際には次のことを意味します。
static int foo(void); extern int foo(void);
の 2 番目の宣言foo
も itを宣言しstatic
、内部リンケージを与えます。あなたが書く場合:
static int foo(void); int foo(void); /* wrong in 1990s era C */
最初に内部リンケージを持つものとして宣言し、次に外部リンケージを持つものとして 2 番目に宣言し、1999 年より前のバージョンの C では未定義の動作を生成します。ある意味では、このextern
キーワードはstatic
、必要な場合に意味を持たせることができるため、(混乱を招くという代償を払って) ある程度の安全性を追加します。しかし、いつでも書き直すことができ、万能薬ではありませんstatic
。extern
extern int foo(void); static int foo(void); /* ERROR */
この 3 番目の形式はまだ誤りです。最初のextern
宣言には以前の可視宣言がないため、foo
外部リンケージがあり、2 番目のstatic
宣言はfoo
内部リンケージを与え、未定義の動作を引き起こします。
つまり、extern
関数宣言では必要ありません。スタイルの理由でそれを好む人もいます。
(注: 私はextern inline
C99 を省略していますが、これは奇妙なことであり、実装はさまざまです。詳細については、http://www.greenend.org.uk/rjk/2003/03/inline.htmlを参照してください。)
extern
変数宣言に適用
extern
変数宣言のキーワードには、複数の異なる効果があります。まず、関数宣言と同様に、識別子のリンケージに影響します。第 2 に、任意の関数の外側の識別子 (2 つの通常の意味の 1 つである「グローバル変数」) の場合、変数も初期化されていなければ、宣言は定義ではなく宣言になります。
次のような関数内の変数 (つまり、「ブロック スコープ」を使用) の場合somevar
:
void f(void) {
extern int somevar;
...
}
このextern
キーワードにより、識別子は「リンケージなし」(自動期間ローカル変数の場合) ではなく、何らかのリンケージ (内部または外部) を持つようになります。その過程で、変数自体が自動ではなく静的な期間を持つようにもなります。(自動期間変数にはリンケージがなく、常にファイル スコープではなくブロック スコープがあります。)
関数宣言の場合と同様に、リンケージextern
代入は、前に可視の内部リンケージ宣言がある場合は internal であり、それ以外の場合は external です。したがって、キーワードにもかかわらず、ここのx
内部f()
には内部リンケージがあります。extern
static int x;
void f(void) {
extern int x; /* note: don't do this */
...
}
この種のコードを書く唯一の理由は、他のプログラマーを混乱させることです。:-)
一般に、「グローバル」(つまり、ファイルスコープ、静的期間、外部リンケージ) 変数にextern
キーワードで注釈を付ける理由は、その特定の宣言が定義になるのを防ぐためです。いわゆる「def/ref」モデルを使用する C コンパイラは、同じ名前が複数回定義されている場合、リンク時に消化不良を起こします。したがって、と のどちらも定義であり、コードfile1.c
がコンパイルされない可能性があります (ほとんどの Unix ライクなシステムはデフォルトでいわゆる「共通モデル」を使用しているため、とにかくこれが機能します)。多くの異なるファイルからインクルードされる可能性が高いヘッダー ファイルでそのような変数を宣言している場合は、 を使用してその宣言を「単なる宣言」にします。int globalvar;
file2.c
int globalvar;
.c
extern
これらのファイルの 1 つだけ.c
が変数を再度宣言し、extern
キーワードを省略したり、初期化子を含めたりすることができます。または、ヘッダー ファイルが次のようなものを使用するスタイルを好む人もいます。
/* foo.h */
#ifndef EXTERN
# define EXTERN extern
#endif
EXTERN int globalvar;
この場合、これらの.c
ファイルの 1 つ (1 つだけ) にシーケンスを含めることができます。
#define EXTERN
#include "foo.h"
ここでは、EXTERN
が定義されているので#ifndef
、 は後続をオフにし、これが宣言ではなく定義になるよう#define
に行EXTERN int globalvar;
を展開します。int globalvar;
個人的には、このコーディング スタイルは嫌いですが、「同じことを繰り返すな」という原則は満たしています。ほとんどの場合、大文字はEXTERN
誤解を招きやすく、このパターンは初期化には役に立ちません。それを好む人は通常、2 つ目のマクロを追加して初期化子を非表示にします。
#ifndef EXTERN
# define EXTERN extern
# define INIT_VAL(x) /*nothing*/
#else
# define INIT_VAL(x) = x
#endif
EXTERN int globalvar INIT_VAL(42);
struct
しかし、初期化する項目が複合初期化子 (たとえば、 に初期化する必要がある a )を必要とする場合、これでさえバラバラになります{ 42, 23, 17, "hike!" }
。
(注: ここでは、「暫定的な定義」全体について意図的に説明を省略しました。初期化子のない定義は、翻訳単位の最後まで「暫定的に定義」されるだけです。これにより、他の方法では難しすぎる特定の種類の前方参照が可能になります。通常はあまり重要ではありません。)
f
関数を定義するコードに関数を宣言するヘッダーを含めるf
これは、1 つの単純な理由から常に良い考えです。コンパイラは、ヘッダー内のの宣言をコード内の の定義と比較します。2 つが一致しない場合 (何らかの理由で - 通常は最初のコーディングの誤り、またはメンテナンス中に 2 つのうちの 1 つを更新できなかったが、場合によっては単純にキーボード オン キーボード症候群などの理由で)、コンパイラは誤りを検出できます。コンパイル時に。f()
f()
1 1999 C 標準ではextern
、関数宣言でキーワードを省略することは、そこでキーワードを使用することと同じことを意味すると述べてextern
います。これは説明がはるかに簡単であり、未定義の動作ではなく、定義された(そして賢明な)動作が得られることを意味します(したがって、おそらく良いか悪い動作です)。