11

優れたプログラミング手法などを理解するために、実際のアプリケーションのソース コードを確認したかったので、Git を選択し、バージョン 1.8.4 のソースをダウンロードしました。

さまざまなファイルをランダムに参照した後、次の 2 つのファイルで何かが気になりました: strbuf.h strbuf.c

これら 2 つのファイルは、このドキュメントで API を定義しているようです。

2 つの質問があります。

  1. 「strbuf.h」の 16、17、18、19 行目の関数宣言と 6 行目のグローバル変数が extern を宣言したのはなぜですか?

  2. 「strbuf.h」が strbuf .c に #include されないのはなぜですか?

私は初心者のプログラマーとして、関数定義を .c ファイルに記述し、関数宣言、マクロ、インラインなどは .h ファイルに記述し、これらを使用するすべての .c ファイルに #include することを常に学びました。機能など

誰でもこれを説明できますか?

4

1 に答える 1

32

strbuf.cincludescache.hおよびcache.hincludesstrbuf.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、必要な場合に意味を持たせることができるため、(混乱を招くという代償を払って) ある程度の安全性を追加します。しかし、いつでも書き直すことができ、万能薬ではありませんstaticextern

extern int foo(void); static int foo(void); /* ERROR */

この 3 番目の形式はまだ誤りです。最初のextern宣言には以前の可視宣言がないため、foo外部リンケージがあり、2 番目のstatic宣言はfoo内部リンケージを与え、未定義の動作を引き起こします。

つまり、extern関数宣言では必要ありません。スタイルの理由でそれを好む人もいます。

(注: 私はextern inlineC99 を省略していますが、これは奇妙なことであり、実装はさまざまです。詳細については、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.cint globalvar;.cextern

これらのファイルの 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います。これは説明がはるかに簡単であり、未定義の動作ではなく、定義された(そして賢明な)動作が得られることを意味します(したがって、おそらく良いか悪い動作です)。

于 2013-08-11T15:32:36.283 に答える