web-dev-qa-db-ja.com

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

質問は、コメントで明らかにされているように、 c ++staticメソッドではなく、単純な c 関数に関するものでした。

わかりました、static変数とは何かを理解していますが、static関数とは何ですか?

関数を宣言する場合、void print_matrix、たとえばa.ca.hなし)を含めて"a.c"を含めると、"print_matrix@@....) already defined in a.obj"を取得します。 、しかし、static void print_matrixとして宣言すると、コンパイルされますか?

UPDATEただ明確にするために-.cを含めることは多くの人が指摘しているように悪いことを知っています。これらのすべての機能を適切なmain.cおよび.hファイルにグループ化する方法についてより良いアイデアが得られるまで、.cのスペースを一時的にクリアするためにそれを行います。一時的で迅速な解決策です。

454
Slava V

static関数は、同じファイル(より正確には同じ 翻訳単位)内の他の関数からしか見えない関数です。

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

609
Johannes Weiss

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

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

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

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

184
Dima

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"ポインタはありません。

73
Brian Neal

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

ここではstaticが複数のファイルにわたる関数定義の範囲にどのように影響するかを説明します。

交流

#include <stdio.h>

/* Undefined behavior: already defined in main.
 * Binutils 2.24 gives an error and refuses to link.
 * https://stackoverflow.com/questions/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

解釈

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

いつものように、範囲が小さければ小さいほどよいので、可能であれば常にstatic関数を宣言してください。

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

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

それについての規格は何を言っている

C99 N1256 draft 6.7.1 "記憶クラス指定子"はstaticが "記憶クラス指定子"であることを示しています。

6.2.2/3 "識別子の結合"はstaticinternal linkageを意味すると言っています:

オブジェクトまたは関数のファイル有効範囲識別子の宣言に記憶域クラス指定子staticが含まれている場合、その識別子は内部リンケージを持ちます。

そして6.2.2/2はinternal linkageはこの例のように振る舞うと言っています:

プログラム全体を構成する翻訳単位およびライブラリーのセットでは、外部リンケージを使用した特定のIDの各宣言は、同じオブジェクトまたは機能を表します。 1つの翻訳単位内で、内部リンケージを持つIDの各宣言は、同じオブジェクトまたは機能を表します。

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

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_LOCALは、ELF仕様の http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html :に記載されています。

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

これはstaticを表すのに最適な選択です。

静的でない関数はSTB_GLOBALであり、その仕様は次のように述べています。

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

これは、複数の非静的定義でのリンクエラーとコヒーレントです。

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

も参照のこと。

C++の匿名ネームスペース

C++では、staticの代わりに無名の名前空間を使用することをお勧めします。これにより、同様の効果が得られますが、型定義はさらに隠されます。 無名/無名の名前空間と静的関数

以下は単純なC関数についてです - C++クラスでは修飾子 'static'は別の意味を持ちます。

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

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

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

これが、内部でのみ使用されるこの関数を静的関数にする理由です。この場合、コンパイラはリンカのための典型的な "あなたはこれをリンクすることができます"フラグを作成しないので、リンカはこの関数を見ず、エラーを生成しません。

19
dersimn

最初:.cppファイルを別のファイルにインクルードするのは一般的に悪い考えです - それはこのような問題を引き起こします:-)通常の方法は別々のコンパイルユニットを作成してインクルードファイルのヘッダファイルを追加することです。

第二に:

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

a)static functions - Cから継承したもの、そしてあなたがここで話していること。クラス外静的な 関数 は、それが現在のコンパイル単位の外には表示されないことを意味します - したがって、あなたのケースではa.objはコピーを持ち、他のコードは独立したコピーを持ちます。 (コードの複数のコピーで最終的な実行ファイルを肥大化させる)。

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

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

16
Douglas Leeder

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

14
raimue

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

例えば、非静的は以下のようになります。

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

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

Person* tom = Person::createNewPerson();
8
Parrots

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

1)CのようにOOPSのない言語では、その関数が定義されているファイル内でのみアクセス可能であることを意味します。

2)C++のようにOOPSを持つ言語では、インスタンスを作成せずに関数をクラスから直接呼び出せることを意味します。

7
user2410022

マイナーニット:静的関数は翻訳単位から見えます。ほとんどの実用的なケースでは、それはその関数が定義されているファイルです。あなたが得るエラーは一般にOne Definition Ruleの違反と呼ばれています。

規格はおそらく次のように言っています。

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

これがCの静的関数の見方です。ただし、これはC++では推奨されていません。

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

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

6
dirkgently