Cの「静的」関数とは何ですか?


506

問題は単純なものでした 関数ではなく static コメントで明記されているメソッド。

static変数とは何か理解しましたが、static関数とは何ですか?

そして、なぜ私が関数を宣言した場合、たとえばvoid print_matrixa.c(WITHOUT a.h)に含めてインクルードするのでしょうか"a.c"-取得しますが"print_matrix@@....) already defined in a.obj"、そのように宣言したstatic void print_matrix場合、コンパイルされますか?

更新明確にするために- .c多くの人が指摘したように、インクルードは悪いことだと私は知っています。main.cこれらのすべての関数を適切なファイルにグループ化する方法がよくわかるまで、一時的にスペースを空けるためにそれを.h行い.cます。一時的で迅速な解決策です。

回答:


685

static関数は、同じファイル(正確には同じ翻訳単位)内の他の関数にのみ表示される関数です。

編集:質問の作成者は「クラスメソッド」を意味すると思った人のために:質問がタグ付けされているので、C彼は普通の古いC関数を意味します。(C ++ / Java / ...)クラスメソッドの場合static、このメソッドはクラス自体で呼び出すことができ、そのクラスのインスタンスは必要ありません。


2
実際にはc ++のタグを付けていませんが、管理者の一部はおそらくタグを付けましたが、それはC ++に関するものだったので、C ++の違いは何ですか?
スラバV

16
C ++メソッドは「メンバー関数」と呼ばれることが多いので、C ++によって少しあいまいさが生じることに同意します。それはあなたのせいではありません—言語は単に2つの異なることにキーワードを使用します。
チャック

2
いいえ、彼はまだC ++関数を意味します。C ++メンバー関数ではなくC ++フリー関数。
Orbitの軽量レース、

3
@チャック:C ++の用語では、「メソッド」という単語は決して使用されません。それがJavaの用語です。C++標準ドキュメントでは、常に「メンバー関数」と呼ばれます(この回答またはC ++とJavaの用語のこの用語集を参照してください(たとえば、C ++は「データメンバー」を使用し、Javaは「フィールド」を使用します))。
ShreevatsaR 2015年

6
この回答を少し明確にするために、関数の名前は、その名前の最初の宣言の下にある同じ翻訳単位の他の部分にのみ表示されます。関数は、関数ポインタなどの他の手段を介して他のユニット(および同じユニットの以前の部分)から呼び出すことができます。
MM

199

Cの静的関数とC ++の静的メンバー関数には大きな違いがあります。Cでは、静的関数は、その翻訳単位(コンパイルされるオブジェクトファイル)の外からは見えません。つまり、関数を静的にすると、そのスコープが制限されます。静的関数は* .cファイルに対して「プライベート」であると考えることができます(厳密には正しくありません)。

C ++では、「静的」はクラスのメンバー関数とデータメンバーにも適用できます。静的データメンバーは「クラス変数」とも呼ばれ、非静的データメンバーは「インスタンス変数」です。これはSmalltalkの用語です。つまり、クラスのすべてのオブジェクトで共有される静的データメンバーのコピーは1つだけですが、各オブジェクトには非静的データメンバーの独自のコピーがあります。したがって、静的データメンバーは基本的にグローバル変数、つまりクラスのメンバーです。

非静的メンバー関数は、クラスのすべてのデータメンバー(静的および非静的)にアクセスできます。静的メンバー関数は、静的データメンバーのみを操作できます。

これについて考える1つの方法は、C ++では静的データメンバーと静的メンバー関数はオブジェクトではなくクラス全体に属しているということです。


42
C ++にも静的ファイルがあります。これにCを組み込む必要はありません。
オービットの

17
C ++では、静的関数は静的関数です。静的メンバー関数は、メソッドとも呼ばれる静的メンバー関数です。Cにメンバーがないということは、関数が「C」であることを意味しません。
Gerasimos R

3
グローバル変数とクラス静的変数(名前空間を除く)の間に違いはありますか?
Alexander Malakhov 2013年

3
名前空間が主な違いです。その他の違いは、静的データメンバーをプライベートにできるため、クラスのメンバー関数内からのみアクセスできることです。つまり、グローバル変数と比較して、静的データメンバーをより詳細に制御できます。
Dima

2
静的関数を.cファイルに対してプライベートであると厳密に正しく考えない理由を誰かが説明できますか?言うことは何ですか?
YoTengoUnLCD 2015年

78

C ++の関数に関しては、キーワードstaticには2つの用途があります。

1つ目は、関数に内部リンケージがあるとマークして、他の翻訳単位で参照できないようにすることです。この使用法はC ++では推奨されていません。名前のない名前空間は、この用途に適しています。

// inside some .cpp file:

static void foo();    // old "C" way of having internal linkage

// C++ way:
namespace
{
   void this_function_has_internal_linkage()
   {
      // ...
   }
}

2番目の使用法は、クラスのコンテキストでの使用です。クラスに静的メンバー関数がある場合、それは関数がクラスのメンバーであること(および他のメンバーへの通常のアクセス権があること)を意味しますが、特定のオブジェクトを介して呼び出す必要はありません。つまり、その関数内には「this」ポインタはありません。


1
問題はcの静的についてです。
2013

8
@Deqing質問は元々C ++のタグが付けられており、著者は「.cpp」ファイルの使用について語っています。
Brian Neal 2013

57

最小限の実行可能なマルチファイルスコープの例

ここでstaticは、複数のファイルにわたって関数定義のスコープにどのように影響するかを示します。

交流

#include <stdio.h>

/* Undefined behavior: already defined in main.
 * Binutils 2.24 gives an error and refuses to link.
 * /programming/27667277/why-does-borland-compile-with-multiple-definitions-of-same-object-in-different-c
 */
/*void f() { puts("a f"); }*/

/* OK: only declared, not defined. Will use the one in main. */
void f(void);

/* OK: only visible to this file. */
static void sf() { puts("a sf"); }

void a() {
    f();
    sf();
}

main.c

#include <stdio.h>

void a(void);        

void f() { puts("main f"); }

static void sf() { puts("main sf"); }

void m() {
    f();
    sf();
}

int main() {
    m();
    a();
    return 0;
}

GitHubアップストリーム

コンパイルして実行:

gcc -c a.c -o a.o
gcc -c main.c -o main.o
gcc -o main main.o a.o
./main

出力:

main f
main sf
main f
a sf

解釈

  • sfファイルごとに1つ、2つの別々の関数があります。
  • 単一の共有機能があります f

いつものように、スコープが小さいほど良いので、static可能であれば常に関数を宣言してください。

Cプログラミングでは、ファイルは「クラス」を表すためによく使用され、static関数はクラスの「プライベート」メソッドを表します。

一般的なCパターンはthis、最初の「メソッド」引数として構造体を渡すことです。これは、基本的にC ++が内部で行うことです。

それについて規格が言うこと

C99 N1256ドラフト 6.7.1「ストレージクラス指定子」staticは、「ストレージクラス指定子」であると述べています。

6.2.2 / 3「識別子のリンケージ」は次のstaticことを意味すると言いますinternal linkage

オブジェクトまたは関数のファイルスコープ識別子の宣言にストレージクラス指定子staticが含まれている場合、識別子には内部リンクがあります。

6.2.2 / 2はinternal linkage、この例のように動作することを示しています。

プログラム全体を構成する翻訳単位とライブラリのセットでは、外部リンケージを使用した特定の識別子の各宣言は、同じオブジェクトまたは関数を示します。1つの変換単位内で、内部リンケージを持つ識別子の各宣言は、同じオブジェクトまたは関数を示します。

ここで、「翻訳単位」は前処理後のソースファイルです。

GCCがELF(Linux)用にどのように実装するのですか?

STB_LOCAL結合。

コンパイルすると:

int f() { return 0; }
static int sf() { return 0; }

シンボルテーブルを次のように分解します。

readelf -s main.o

出力には以下が含まれます。

Num:    Value          Size Type    Bind   Vis      Ndx Name
  5: 000000000000000b    11 FUNC    LOCAL  DEFAULT    1 sf
  9: 0000000000000000    11 FUNC    GLOBAL DEFAULT    1 f

したがって、バインディングはそれらの間の唯一の重要な違いです。セクションValueへのオフセットにすぎない.bssため、異なることが予想されます。

STB_LOCALELF仕様のhttp://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.htmlに記載されています

STB_LOCALローカルシンボルは、それらの定義を含むオブジェクトファイルの外には表示されません。同じ名前のローカルシンボルが互いに干渉することなく複数のファイルに存在する可能性があります

表現するのに最適な選択staticです。

staticのない関数はSTB_GLOBALで、仕様には次のように記載されています。

リンクエディターが複数の再配置可能なオブジェクトファイルを組み合わせる場合、同じ名前のSTB_GLOBALシンボルを複数定義することはできません。

これは、複数の非静的定義のリンクエラーと一貫しています。

を使用して最適化を開始する-O3と、sfシンボルはシンボルテーブルから完全に削除されます。外部からは使用できません。TODO最適化がない場合に、シンボルテーブルに静的関数を保持する理由 それらは何にでも使用できますか?

こちらもご覧ください

C ++の匿名の名前空間

C ++では、静的ではなく匿名の名前空間を使用することをお勧めします。これにより、同様の効果が得られますが、型定義がさらに非表示になります。名前のない名前空間と静的関数の比較


3
注:(void f() { puts("sf"); }つまりの2つの定義f())は、診断が不要な未定義の動作を引き起こします。実際にエラーメッセージが表示されるのは、リンカーの品質の問題です。
MM

2
これが一番正確な説明です!あなたより!
Aqua

20

以下は単純なC関数に関するものです。C++クラスでは、修飾子 'static'には別の意味があります。

ファイルが1つしかない場合、この修飾子はまったく違いがありません。違いは、複数のファイルを持つ大きなプロジェクトにあります。

Cでは、すべての「モジュール」(sample.cとsample.hの組み合わせ)が独立してコンパイルされ、その後、それらのコンパイル済みオブジェクトファイル(sample.o)のすべてがリンカーによって実行可能ファイルにリンクされます。

あなたがメインファイルにインクルードするいくつかのファイルがあり、それらの2つが呼び出される便宜のために内部でのみ使用される関数を持っているとしましょadd(int a, b)う-コンパイラーはこれら2つのモジュールのオブジェクトファイルを簡単に作成しますが、リンカーはエラーをスローします。同じ名前の2つの関数が見つかり、どの関数を使用するかがわかりません(リンクするものがない場合でも、他の場所では使用されないため、独自のファイルで使用されます)。

これが、内部でのみ使用されるこの関数を静的関数にする理由です。この場合、コンパイラーはリンカーに対して通常の「これをリンクできます」フラグを作成しないため、リンカーはこの関数を認識せず、エラーを生成しません。


16

まず、.cppファイルを別のファイルにインクルードすることは一般に悪い考えです。次のような問題が発生します:-)通常の方法は、個別のコンパイルユニットを作成し、インクルードするファイルのヘッダーファイルを追加することです。

第二に:

C ++にはここでいくつかの混乱する用語があります-コメントで指摘されるまで、私はそれについて知りませんでした。

a)static functions-Cから継承され、ここであなたが話していること クラス外。静的関数は、現在のコンパイルユニットの外からは見えないことを意味します。つまり、あなたの場合、a.objにはコピーがあり、他のコードには独立したコピーがあります。(最終的な実行可能ファイルをコードの複数のコピーで膨らませます)。

b)static member function-どのオブジェクト指向が静的メソッドと呼ぶか。クラスの中に住んでいます。これは、オブジェクトインスタンスではなくクラスで呼び出します。

これら2つの異なる静的関数の定義は完全に異なります。注意してください-ここではドラゴンになります。


まあ、私は、適切な.hppと共にファイルをライブラリに編成する方法を決定するまで、main.cppの一時的にスペースを空けるためだけにそれを行います。これを行う方法のより良いアイデアはありますか?
スラバV

1
C ++での正しい用語は、メソッドではなくメンバー関数です。C ++リーガル語には「メソッド」はありません。メソッドは一般的なオブジェクト指向の用語です。C ++は、メンバー関数を介してそれらを実装します。
ブライアンニール

14

静的関数定義は、このシンボルを内部としてマークします。そのため、外部からのリンクでは表示されず、同じコンパイル単位(通常は同じファイル)内の関数にのみ表示されます。


7

静的関数は、クラスのインスタンスではなく、クラス自体で呼び出すことができる関数です。

たとえば、非静的は次のようになります。

Person* tom = new Person();
tom->setName("Tom");

このメソッドは、クラス自体ではなく、クラスのインスタンスで機能します。ただし、インスタンスがなくても機能する静的メソッドを使用できます。これはFactoryパターンで時々使用されます:

Person* tom = Person::createNewPerson();

2
あなたは「関数」ではなく静的な「メソッド」について話しているように思えますか?
スラバV

クラス内の静的関数を参照していると思いました。
オウム

C ++で「メソッド」が「メソッド関数」と呼ばれていることがわかっている場合は、その点についてより明確になります。ええと、今私はそうします:)とにかくありがとう
Slava V

5
C ++には「メソッド」はなく、関数だけです。C ++標準では、「メソッド」については言及されておらず、「関数」についてのみ言及しています。
ブライアンニール

1
@水たまり私はあなたが言っていることを知っていますが、C ++標準では「メソッド」の定義はありません。C ++には、さまざまな種類の関数しかありません。「メソッド」は一般的なオブジェクト指向の用語であり、他の言語や非公式にC ++で使用されます。メソッドは、C ++では「メンバー関数」として正式に知られています。
ブライアンニール

7

マイナーニット:静的関数は翻訳単位から認識されます。これは、ほとんどの場合、関数が定義されているファイルです。発生しているエラーは、一般に、1つの定義ルールの違反と呼ばれます。

標準はおそらく次のように述べています:

「すべてのプログラムには、そのプログラムで使用されるすべての非インライン関数またはオブジェクトの定義が1つだけ含まれている必要があります。診断は必要ありません。」

これが静的関数をCで見る方法です。ただし、これはC ++では非推奨です。

さらに、C ++では、メンバー関数を静的に宣言できます。これらは主にメタ関数です。つまり、特定のオブジェクトの動作/状態を記述/変更するのではなく、クラス全体に作用します。また、これは、静的メンバー関数を呼び出すためにオブジェクトを作成する必要がないことを意味します。さらに、これは、そのような関数内からのみ静的メンバー変数にアクセスできることも意味します。

Parrotの例に、この種の静的メンバー関数に基づくシングルトンパターンを追加して、プログラムの存続期間を通じて単一のオブジェクトを取得/使用します。


7

静的関数に対する答えは言語によって異なります。

1)CのようなOOPSのない言語では、定義されたファイル内でのみ関数にアクセスできます。

2)C ++などのOOPSを使用する言語では、インスタンスを作成せずに関数をクラスで直接呼び出すことができます。


本当じゃない。2番目の段落の説明は、「静的関数」ではなく、クラスの「静的メンバー関数」を参照しています。C ++では、で修飾機能が、それはCであるように、あまりにも、ファイル・スコープを持っているstatic
ロバーツはモニカチェッリオサポートして

0

静的関数はこのファイルでのみ表示されるため。実際、ある関数に「静的」を宣言すると、コンパイラーが最適化を行うことができます。

これは簡単な例です。

main.c

#include <stdio.h>

static void test() 
{
    ghost(); // This is an unexist function.
}

int main()
{
    int ret = 0;

#ifdef TEST
#else
    test();
#endif
    return (ret);
} 

そしてコンパイル

gcc -o main main.c

失敗したことがわかります。ghost()関数も実装していないからです。

しかし、次のコマンドを使用するとどうなるでしょうか。

gcc -DTEST -O2 -o main main.c

それは成功し、このプログラムが正常に実行することができます。

どうして?3つのポイントがあります。

  1. -O2:コンパイラの最適化レベル2以上。
  2. -DTEST:TESTを定義するため、test()は呼び出されません。
  3. test()に「静的」を定義しました。

これら3つの条件がすべて当てはまる場合にのみ、コンパイルを渡すことができます。この「静的」宣言により、コンパイラはtest()が他のファイルで呼び出されないことを確認できます。コンパイラはコンパイル時にtest()を削除できます。test()は必要ないので、ghost()が定義されているか実装されているかは関係ありません。


0

Cのstatic」関数とは何ですか?

最初から始めましょう。

それはすべて「リンケージ」と呼ばれるものに基づいています:

異なるスコープまたは同じスコープで複数回宣言された識別子は、リンケージと呼ばれるプロセスによって同じオブジェクトまたは関数を参照できます。29)リンケージには、外部、内部、なしの3種類があります。

出典:C18、6.2.2 / 1


「プログラム全体を構成する翻訳単位とライブラリのセットでは、外部リンケージを持つ特定の識別子の各宣言は同じオブジェクトまたは関数を示します。1つの翻訳単位内では、内部リンケージを持つ識別子の各宣言は同じオブジェクトまたは関数を示しますリンケージのない識別子の各宣言は、一意のエンティティを示します。」

出典:C18、6.2.2 / 2


ストレージクラス指定子なしで関数が定義されている場合、関数にはexternデフォルトですべてのリンケージがあります。

「関数の識別子の宣言にストレージクラス指定子がない場合、そのリンケージは、ストレージクラス指定子externで宣言されたかのように正確に決定されます。」

出典:C18、6.2.2 / 5

つまり、プログラムに複数の翻訳単位/ソースファイル(.cまたは.cpp)が含まれている場合、関数は、プログラムに含まれるすべての翻訳単位/ソースファイルに表示されます。

これが問題になる場合があります。2つの異なる関数(定義)を使用したいが、2つの異なるコンテキスト(実際にはファイルコンテキスト)で同じ関数名を使用する場合はどうでしょうか。

C および C ++では、staticファイルスコープで関数に適用されるストレージクラス修飾子(C ++のクラスの静的メンバー関数や別のブロック内の関数ではない)が役立ち、それぞれの関数が内部でのみ表示されることを示します他のTLU /ファイルではなく、それが定義された翻訳単位/ソースファイル。

「オブジェクトまたは関数のファイルスコープ識別子の宣言にストレージクラス指定子staticが含まれている場合、識別子には内部リンケージがあります。30)」


30)関数宣言には、ファイルスコープにある場合に限り、ストレージクラス指定子staticonlyを含めることができます。6.7.1を参照してください。

出典:C18、6.2.2 / 3


したがって、次のstatic場合にのみ、関数は意味をなします。

  1. プログラムには、複数の翻訳単位/ソースファイル(.cまたは.cpp)が含まれています。

    そして

  2. 関数のスコープを、特定の関数が定義されているファイルに制限したいとします。

ない場合は、両方のこれらの要件が一致し、あなたはとしての機能を適格についての周りにあなたの頭をラップする必要はありませんstatic


サイドノート:

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.