C関数に対するexternキーワードの影響


171

Cでは、extern関数宣言の前に使用したキーワードの影響に気付きませんでした。最初は、私が定義するときと考えextern int f();、単一のファイルにあなたが外で、ファイルのスコープのそれを実装します。しかし、私は両方がわかった:

extern int f();
int f() {return 0;}

そして

extern int f() {return 0;}

gccからの警告なしで、問題なくコンパイルします。私が使用しましたgcc -Wall -ansi//コメントすら受け付けません。

extern 関数定義の前に使用する効果はありますか?それとも、関数の副作用のない単なるオプションのキーワードですか?

後者の場合、標準の設計者が文法を余分なキーワードで散らかすことを選択した理由がわかりません。

編集:明確にするために、extern変数での使用法は知っていますが、私は関数についてのみ質問しexternています


クレイジーなテンプレート目的でこれを使用しようとしたときに行ったいくつかの調査によると、externはほとんどのコンパイラで意図されている形式ではサポートされていないため、実際には何も実行されません。
エドジェームス

4
それは常に不必要であるとは限りません。私の答えを見てください。モジュール間でパブリックヘッダーに含めたくないものを共有する必要がある場合はいつでも、非常に便利です。ただし、パブリックヘッダー内のすべての単一関数を(外部コンパイラーを使用して)「外部化」することは、自分でそれを理解できるため、ほとんどメリットがありません。
Tim Post

@Ed .. volatile int fooがfoo.cのグローバルであり、bar.cがそれを必要とする場合、bar.cはそれをexternとして宣言する必要があります。それには利点があります。さらに、パブリックヘッダーで公開したくない関数を共有する必要がある場合があります。
Tim Post


2
@バリー仮にあったとしても、他の質問はこれと同じです。2009 vs 2012
Elazar Leibovich

回答:


138

foo.cとbar.cの2つのファイルがあります。

ここにfoo.cがあります

#include <stdio.h>

volatile unsigned int stop_now = 0;
extern void bar_function(void);

int main(void)
{
  while (1) {
     bar_function();
     stop_now = 1;
  }
  return 0;
}

さて、これがbar.cです

#include <stdio.h>

extern volatile unsigned int stop_now;

void bar_function(void)
{
   while (! stop_now) {
      printf("Hello, world!\n");
      sleep(30);
   }
}

ご覧のとおり、foo.cとbar.cの間に共有ヘッダーはありませんが、bar.cはリンクされるときにfoo.cで宣言されたものを必要とし、foo.cはリンクされるときにbar.cからの関数を必要とします。

'extern'を使用することにより、リンク時に後続のものが見つかる(静的ではない)ことをコンパイラーに伝えます。後で発生するため、現在のパスでは何も予約しないでください。これに関して、関数と変数は同等に扱われます。

モジュール間でいくつかのグローバルを共有する必要があり、それをヘッダーに配置/初期化したくない場合に非常に便利です。

技術的には、ライブラリのパブリックヘッダー内のすべての関数は 'extern'ですが、コンパイラーによっては、これらの関数にラベルを付けることのメリットはほとんどありません。ほとんどのコンパイラは、自分でそれを理解することができます。ご覧のとおり、これらの関数は実際には別の場所で定義されています。

上記の例では、main()はhello worldを1回だけ出力しますが、bar_function()を引き続き入力します。また、bar_function()はこの例では返されないことに注意してください(単純な例にすぎないため)。これが十分に実用的ではないように思われる場合、シグナルが処理される(したがって、揮発性)ときにstop_nowが変更されることを想像してみてください。

エクスターンは、シグナルハンドラー、ヘッダーや構造体に入れたくないミューテックスなどの場合に非常に役立ちます。ほとんどのコンパイラーは、外部オブジェクト用にメモリを予約しないように最適化します。 'オブジェクトが定義されているモジュールでそれを予約します。ただし、繰り返しになりますが、パブリック関数のプロトタイプを作成するときに、最新のコンパイラーでそれを指定しても意味がありません。

それが役に立てば幸い:)


56
コードは、bar_functionの前にexternがなくても問題なくコンパイルされます。
Elazar Leibovich、

2
@ティム:それで、あなたは私が扱うコードを扱うという怪しい特権を持っていませんでした。有りうる。ヘッダーには、静的関数定義も含まれていることがあります。それは醜く、不必要な時間の99.99%です(私は1桁か2桁、または必要な頻度を過大評価してオフにすることができました)。これは通常、他のソースファイルが情報を使用する場合にのみヘッダーが必要であると人々が誤解しているときに発生します。ヘッダーは1つのソースファイルの宣言情報を格納するために(ab)使用され、他のファイルには含まれません。時々、それはより歪んだ理由で発生します。
ジョナサンレフラー

2
@ジョナサンレフラー-確かに怪しい!以前はかなり大雑把なコードを継承しましたが、正直に言って、誰かが静的宣言をヘッダーに入れるのを見たことがありません。けれども、あなたはかなり楽しくて興味深い仕事をしているようです:)
Tim Post

1
「ヘッダーにない関数プロトタイプ」の欠点は、の関数定義bar.cとの宣言の整合性を自動的に独立してチェックできないことですfoo.c。関数がで宣言されfoo.h 両方のファイルにが含まfoo.hれている場合、ヘッダーは2つのソースファイル間の一貫性を強制します。これがないと、bar_functionin の定義はbar.c変更されますが、inの宣言foo.cが変更されないと、実行時に問題が発生します。コンパイラは問題を特定できません。ヘッダーを適切に使用すると、コンパイラーが問題を見つけます。
ジョナサンレフラー

1
関数宣言のexternは、「unsigned int」の「int」のように不要です。プロトタイプが前方宣言ではない場合は、 'extern'を使用することをお勧めします...しかし、タスクがエッジケースでない限り、実際には 'extern'のないヘッダーに存在する必要があります。 stackoverflow.com/questions/10137037/...

82

私が標準を覚えている限り、すべての関数宣言はデフォルトで「外部」と見なされるため、明示的に指定する必要はありません。

変数でも使用できるため、このキーワードが役に立たないわけではありません(その場合、リンケージの問題を解決するための唯一のソリューションです)。しかし、機能があります-はい、それはオプションです。


21
次に、標準的な設計者として、文法にノイズを追加するだけなので、関数でのexternの使用を禁止します。
Elazar Leibovich、

3
下位互換性は苦痛です。
MathuSum Mut

1
@ElazarLeibovich実際、この特定のケースでは、それを許可しないことが文法にノイズを追加することになります。
にオービット

1
キーワード制限することでどのようにノイズが加わるかは私を超えていますが、好みの問題だと思います。
Elazar Leibovich

ただし、関数が現在のファイルではなく別のファイルで定義されており、インクルードされているヘッダーの1つでも宣言されていないことを他のプログラマーに示すため、関数に「extern」の使用を許可すると便利です。
DimP

23

関数定義とシンボル宣言という2つの異なる概念を区別する必要があります。「extern」はリンケージ修飾子であり、後で参照されるシンボルが定義されている場所に関するコンパイラへのヒントです(ヒントは「ここではありません」)。

私が書いたら

extern int i;

Cファイルのファイルスコープ(関数ブロックの外)では、「変数は他の場所で定義されている可能性があります」と言っています。

extern int f() {return 0;}

関数fの宣言と関数fの定義の両方です。この場合の定義は、externをオーバーライドします。

extern int f();
int f() {return 0;}

は最初の宣言で、その後に定義が続きます。

externファイルスコープ変数を宣言し、同時に定義する場合は、使用方法が間違っています。例えば、

extern int i = 4;

コンパイラに応じて、エラーまたは警告が表示されます。

の使用はextern、変数の定義を明示的に避けたい場合に役立ちます。

説明させてください:

acファイルに次の内容が含まれているとします。

#include "a.h"

int i = 2;

int f() { i++; return i;}

ファイルああが含まれています:

extern int i;
int f(void);

そして、ファイルbcは以下を含みます:

#include <stdio.h>
#include "a.h"

int main(void){
    printf("%d\n", f());
    return 0;
}

ヘッダーのexternは、リンク段階でコンパイラに「これは宣言であり、定義ではない」ことを伝えるので、役に立ちます。acでiを定義している行を削除し、そのためのスペースを割り当てて、それに値を割り当てた場合、プログラムは未定義の参照でコンパイルに失敗するはずです。これは、変数を参照したが、まだ定義していないことを開発者に知らせます。一方、「extern」キーワードを省略してint i = 2行を削除しても、プログラムは引き続きコンパイルされます-iはデフォルト値0で定義されます。

関数の先頭で宣言するブロックスコープ変数とは異なり、明示的に値を割り当てない場合、ファイルスコープ変数は暗黙的にデフォルト値0またはNULLで定義されます。externキーワードは、この暗黙の定義を回避するため、間違いを回避するのに役立ちます。

関数の場合、関数宣言では、キーワードは実際に冗長です。関数宣言には暗黙の定義はありません。


int i = 2-3番目の段落の行を削除するつもりですか?そしてint i;、コンパイラがその変数にメモリを割り当てるのを見て、コンパイラがメモリをextern int i;割り当てずに、他の場所で変数を検索することを確認するのは正しいですか?
Frozen Flame 14

実際、「extern」キーワードを省略した場合、プログラムは、acとbcでのiの再定義のためにコンパイルされません(ahのため)。
Nixt

15

externキーワードは環境によって異なる形態をとります。宣言が使用可能な場合、externキーワードは、前に翻訳単位で指定されたリンケージを取ります。そのような宣言がない場合は、extern外部リンケージを指定します。

static int g();
extern int g(); /* g has internal linkage */

extern int j(); /* j has tentative external linkage */

extern int h();
static int h(); /* error */

C99ドラフト(n1256)の関連する段落を以下に示します。

6.2.2識別子のリンク

[...]

4ストレージクラス指定子externを使用して宣言された識別子で、その識別子の以前の宣言が表示されているスコープで23)、以前の宣言で内部または外部リンケージが指定されている場合、後の宣言での識別子のリンケージは同じです。前の宣言で指定されたリンケージとして。以前の宣言が表示されない場合、または以前の宣言でリンケージが指定されていない場合、識別子には外部リンケージがあります。

5関数の識別子の宣言にストレージクラス指定子がない場合、そのリンケージは、ストレージクラス指定子externで宣言されたかのように正確に決定されます。オブジェクトの識別子の宣言にファイルスコープがあり、ストレージクラス指定子がない場合、そのリンケージは外部です。


それは標準ですか、それとも典型的なコンパイラの動作を教えているだけですか?標準の場合、標準へのリンクを喜んで提供します。しかし、ありがとう!
Elazar Leibovich、

これは標準的な動作です。C99ドラフトはこちらから入手できます:< open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf >。ただし、実際の標準は無料ではありません(ドラフトはほとんどの目的には十分です)。
2009年

1
私はそれをgccでテストしましたが、「extern int h(); static int h(){return 0;}」と「int h(); static int h(){return 0;}」の両方が同じで受け入れられます警告。ANSIではなくC99だけですか?ドラフトの正確なセクションを参照してください。これはgccには当てはまらないようです。
Elazar Leibovich、

再び確かめる。gcc 4.0.1でも同じことを試しましたが、本来あるべき場所でエラーが発生しました。他のコンパイラにアクセスできない場合は、comeauのオンラインコンパイラまたはcodepad.orgも試してください。標準をお読みください。
2009年

2
@dirkgently、私の本当の質問は、関数宣言でexetrnを使用することに何らかの影響があるか、そして何もない場合に関数宣言にexternを追加できる理由は何ですか?答えは「いいえ」で、効果はありません。かつて、それほど標準的でないコンパイラでは効果がありました。
Elazar Leibovich

11

インライン関数には、その意味について特別なルールがありますextern。(インライン関数はC99またはGNUの拡張であることに注意してください。元のCにはありませんでした。

非インライン関数の場合、externデフォルトでオンになっているため、必要ありません。

C ++の規則は異なることに注意してください。たとえばextern "C"、C ++から呼び出すC関数のC ++宣言ではが必要であり、にはさまざまなルールがありinlineます。


これがここでの唯一の答えであり、どちらも正しく、実際に質問に答えます。
robinjam

4

IOW、externは冗長で、何もしません。

それが10年後の理由です。

Denton Liu()によるcommit ad6dad0commit b199d71commit 5545442(2019年4月29日)を参照してください。(合併によりJunio C浜野- -4aeeef3コミット 2019年5月13日)Denton-L
gitster

*.[ch]:をextern使用して関数宣言から削除spatch

extern関数宣言から削除するよう要求されました。

externCoccinelleがキャッチする関数宣言の" "のインスタンスをいくつか削除します。
Coccinelleは、__attribute__またはvarargsを使用して関数を処理するのが難しいため、一部のextern宣言は、将来のパッチで処理するために残されています。

これは使用されたCoccinelleパッチでした:

  @@
    type T;
    identifier f;
    @@
    - extern
    T f(...);

そしてそれはで実行されました:

  $ git ls-files \*.{c,h} |
    grep -v ^compat/ |
    xargs spatch --sp-file contrib/coccinelle/noextern.cocci --in-place

ただし、これは必ずしも簡単ではありません。

Denton Liu()によるcommit 7027f50(2019年9月4日)を参照してください。(合併によりデントン劉- -7027f50コミットし、2019年9月5日)をDenton-L
Denton-L

compat/*.[ch]externspatchを使用して関数宣言から削除する

5545442(*.[ch]:spatch externを使用して関数宣言から削除、2019-04-29、Git v2.22.0-rc0)、を使用して関数宣言からexternを削除しましたspatchcompat/、一部は上流から直接コピーされるため、意図的にファイルを除外しました。将来の更新を手動でマージすることがより簡単になるようにそれらをチャーンします。

最後のコミットでは、上流から取得したファイルを特定し、それらを除外しspatchて残りの部分で実行できるようにしました。

これは使用されたCoccinelleパッチでした:

@@
type T;
identifier f;
@@
- extern
  T f(...);

そしてそれはで実行されました:

$ git ls-files compat/\*\*.{c,h} |
    xargs spatch --sp-file contrib/coccinelle/noextern.cocci --in-place
$ git checkout -- \
    compat/regex/ \
    compat/inet_ntop.c \
    compat/inet_pton.c \
    compat/nedmalloc/ \
    compat/obstack.{c,h} \
    compat/poll/

Coccinelle __attribute__とvarargsの処理に問題があるため、次のコマンドを実行して、変更が残っていないことを確認します。

$ git ls-files compat/\*\*.{c,h} |
    xargs sed -i'' -e 's/^\(\s*\)extern \([^(]*([^*]\)/\1\2/'
$ git checkout -- \
    compat/regex/ \
    compat/inet_ntop.c \
    compat/inet_pton.c \
    compat/nedmalloc/ \
    compat/obstack.{c,h} \
    compat/poll/

Git 2.24(2019年第4四半期)では、スプリアスexternが削除されることに注意してください。

Emily Shaffer()によるcommit 65904b8(2019年9月30日)を参照してください。 協力者:ジェフキング(Denton Liu()によるcommit 8464f94(2019年9月21日)を 参照してください。 協力者:ジェフキング((合併によりJunio C浜野- -59b19bcコミット 2019年10月7日)nasamuffin
peff
Denton-L
peff
gitster

promisor-remote.hextern関数宣言から削除

このファイルの作成中、新しい関数宣言が導入されるたびに、が含まれていましたextern
ただし、5545442*.[ch]:、2019-04-29、Git v2.22.0-rc0 externを使用して関数宣言から削除するspatch)以降、externは不要であるため、関数宣言で使用されないように積極的に取り組んできました。

これらの偽externのsを削除します。


3

externキーワード知らせる機能や変数が外部結合を有することをコンパイラ-言い換えれば、それはそれが定義されている以外のファイルから見えること。この意味で、staticキーワードとは逆の意味を持っています。extern他のファイルが定義の可視性を持たないため(または複数の定義になる可能性があるため)、定義時に置くのは少し奇妙です。通常extern、外部可視性(ヘッダーファイルなど)を使用して宣言を配置し、その定義を別の場所に配置します。


2

関数externを宣言すると、その定義はコンパイル時ではなくリンク時に解決されます。

externとして宣言されていない通常の関数とは異なり、それは任意のソースファイルで定義できます(ただし、複数のソースファイルでは定義できません)。そのため、リンカは同じファイル内の関数定義を解決します。

これを行うことはあまり役に立ちませんが、そのような種類の実験を行うと、言語のコンパイラとリンカがどのように機能するかについてより良い洞察が得られます。


2
IOW、externは冗長で、何もしません。このようにすれば、はるかに明確になります。
Elazar Leibovich、2011

@ElazarLeibovich私はコードベースで同様のケースに遭遇し、同じ結論に達しました。ここで回答を介してそれらすべてを1つのライナーでまとめることができます。実用的な効果はありませんが、読みやすさの点で優れている場合があります。ミートアップだけでなく、オンラインでお会いできてうれしいです:)
Aviv

1

これが効果がない理由は、リンク時にリンカーが外部定義(あなたの場合extern int f())を解決しようとするためです。同じファイルで見つかった場合でも、別のファイルで見つかった場合でも、問題ありません。

これがあなたの質問に答えることを願っています。


1
では、なぜextern関数に追加を許可するのでしょうか?
Elazar Leibovich

2
無関係なスパムを投稿に含めないでください。ありがとう!
Mac
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.