web-dev-qa-db-ja.com

C関数に対するexternキーワードの効果

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

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

そして

extern int f() {return 0;}

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

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

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

編集:明確にするために、変数でexternが使用されていることを知っていますが、functionsexternについてのみ質問しています。

159

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'を使用することにより、リンク時に後続のものが(非静的に)検出されることをコンパイラーに伝えています。現在では何も予約しないでくださいこの点では、関数と変数は等しく扱われます。

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

技術的には、ライブラリパブリックヘッダーのすべての関数は「外部」ですが、コンパイラに応じて、それらにラベルを付けても、ほとんどまたはまったく利点がありません。ほとんどのコンパイラは、独自にそれを把握できます。ご覧のとおり、これらの関数は実際には別の場所で定義されています。

上記の例では、main()はhello worldを一度だけ出力しますが、bar_function()を入力し続けます。また、この例ではbar_function()が返されないことに注意してください(単純な例であるため)。信号が処理されたときにstop_nowが変更されることを想像してください(したがって、揮発性)。

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

それが役立つことを願っています:)

126
Tim Post

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

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

76
Rageous

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

書いたら

extern int i;

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

extern int f() {return 0;}

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

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

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

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

extern int i = 4;

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

externの使用法は、変数の定義を明示的に避けたい場合に便利です。

説明させてください:

ファイルa.cに次のものが含まれているとします。

#include "a.h"

int i = 2;

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

ファイルa.hには以下が含まれます。

extern int i;
int f(void);

また、ファイルb.cには次が含まれます。

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

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

ヘッダーのexternは、リンクフェーズ中にコンパイラに「これは定義ではなく宣言です」と伝えるため、便利です。 iを定義するa.cの行を削除し、それにスペースを割り当て、値を割り当てると、プログラムは未定義の参照でコンパイルに失敗するはずです。これにより、開発者は変数を参照したが、まだ定義していないことがわかります。一方、「extern」キーワードを省略し、int i = 2行を削除しても、プログラムはコンパイルされます。デフォルト値0で定義されます。

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

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

20
Dave Neary

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で宣言された場合とまったく同じように決定されます。オブジェクトの識別子の宣言にファイルスコープがあり、ストレージクラス指定子がない場合、そのリンケージは外部です。

14
dirkgently

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

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

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

7
user9876

externキーワードは、関数または変数に外部リンケージがあること、つまり、定義されているファイル以外のファイルから見えることをコンパイラーに通知します。この意味で、staticキーワードとは逆の意味を持ちます。定義時にexternを指定するのは少し変です。これは、他のファイルには定義が表示されないためです(または、複数の定義が作成されるため)。通常、externは、ヘッダーファイルなどの外部可視性のある時点で宣言に配置し、定義を他の場所に配置します。

3

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

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

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

2

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

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

1
Umer

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

それが、10年後の理由です:

  • Coccinelle のようなコード変換ツールは 関数宣言のexternname__フラグ を削除する傾向があります。
  • git/git のようなコードベースがその結論に従い、そのコードからexternname__を削除します(Git 2.22、Q2 2019)。

commit ad6dadcommit b199d71commit 5545442 (2019年4月29日)by Denton Liu(Denton-L を参照してください。
浜野潤夫-gitstername__- in commit 4aeeef 、2019年5月13日)

*.[ch]externNAME _ を使用して、関数宣言からspatchname__を削除します

関数宣言からexternname__を削除するプッシュがありました。

Coccinelleがキャッチする関数宣言の「externname__」のインスタンスをいくつか削除します。
Coccinelleは__attribute__またはvarargsを使用して関数を処理するのに多少の困難があるため、いくつかのexternname__宣言は今後のパッチで対処するために残されていることに注意してください。

これは使用された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
0
VonC