Cの静的関数


回答:


212

関数staticを作成すると、他の翻訳単位から関数が非表示になるため、カプセル化を提供できます

helper_file.c

int f1(int);        /* prototype */
static int f2(int); /* prototype */

int f1(int foo) {
    return f2(foo); /* ok, f2 is in the same translation unit */
                    /* (basically same .c file) as f1         */
}

int f2(int foo) {
    return 42 + foo;
}

main.c

int f1(int); /* prototype */
int f2(int); /* prototype */

int main(void) {
    f1(10); /* ok, f1 is visible to the linker */
    f2(12); /* nope, f2 is not visible to the linker */
    return 0;
}

8
翻訳単位は、ここで使用する正しい用語ですか?オブジェクトファイルはもっと正確ではないでしょうか?私の理解では、静的関数はリンカから隠されており、リンカは翻訳単位を操作しません。
Steven Eckhoff、2014

2
私はそれをリンカから隠されていると考えるのも好きだと言っておくべきでした。そうすればより明確になるようです。
Steven Eckhoff、2014

1
それで、内部関数(cファイルの外で呼び出さないようにします)、それを静的関数として配置する必要がありますよね?したがって、他の場所で呼び出すことはできません。ありがとう:)
hqt 14

1
これをどのようにコンパイルしますか?使用し#include <helper_file.c>ますか?その場合、単一の翻訳単位になると思います...
Atcold

2
@Atcold:このコードの記述方法では、次のように、コマンドラインに2つのソースファイルを含めるだけgcc -std=c99 -pedantic -Wall -Wextra main.c helper_file.cです。関数のプロトタイプは両方のソースファイルに存在します(ヘッダーファイルは必要ありません)。リンカーは関数を解決します。
pmg

80

pmgはカプセル化にスポットを当てています。関数を他の変換単位から隠す(またはそのため)だけでなく、関数staticを作成すると、コンパイラーの最適化が存在する場合にパフォーマンス上の利点が得られます。

static関数は現在の変換単位の外部からは呼び出すことができないため(コードがそのアドレスへのポインターを取得しない限り)、コンパイラーは関数へのすべての呼び出しポイントを制御します。

これは、非標準のABIを使用したり、完全にインライン化したり、外部リンケージを使用する関数では不可能なその他の最適化をいくつでも自由に実行できることを意味します。


9
...関数のアドレスが取得されない限り。
カフェ

1
@caf関数のアドレスが取得されるとはどういう意味ですか?私にとって、コンパイル時にアドレスを持つ、またはアドレスが割り当てられる関数/変数の概念は少し混乱しています。詳しく説明していただけますか?
SayeedHussain 2013

2
@crypticcoder:プログラムはメモリに読み込まれるため、関数にもメモリの場所があり、アドレスを取得できます。関数ポインタを使用すると、それらのいずれかを呼び出すことができます。これを行うと、コードが同じ場所にそのまま残る必要があるため、コンパイラーが実行できる最適化のリストが減ります。

5
@crypticcoder:式は関数へのポインターを評価し、関数をすぐに呼び出す以外の方法で何かを実行することを意味します。static関数へのポインターが現在の変換単位をエスケープする場合、その関数は他の変換単位から直接呼び出すことができます。
caf 2013

@caf関数のアドレスが取得された場合、コンパイラーはそれを検出し、この回答で言及されている静的関数の最適化をオフにしますか(例:非標準のABIを使用)。必要があると思います。
sevko

28

staticC のキーワードは、コンパイルされたファイル(.hではなく.c)で使用されるため、関数はそのファイルにのみ存在します。

通常、関数を作成すると、コンパイラはリンカが関数呼び出しをその関数にリンクするために使用できるクリフトを生成します。staticキーワードを使用すると、同じファイル内の他の関数がこの関数を呼び出すことができます(リンカーを使用せずに実行できるため)。一方、リンカーには他のファイルが関数にアクセスするための情報がありません。


1
3Doub:「残骸」という言葉の使用は、あなたが信じているよりも正確です。質問の文脈では、「cruft」がここで使用する正しい言葉です。
Erik Aronesty 2014年

@ 3Doubloons簡略化されていることには同意しますが、初心者には理解しやすいと思います。
IngoBürk2015年

11

上記の投稿を見て、私は一つの詳細を指摘したいと思います。

メインファイル( "main.c")が次のようになっているとします。

#include "header.h"

int main(void) {
    FunctionInHeader();
}

ここで、3つのケースを考えます。

  • ケース1:ヘッダーファイル( "header.h")は次のようになります。

    #include <stdio.h>
    
    static void FunctionInHeader();
    
    void FunctionInHeader() {
        printf("Calling function inside header\n");
    }

    次に、Linuxで次のコマンドを実行します。

    gcc main.c header.h -o main

    成功します!実行した場合、それに続いて

    ./main

    出力は

    ヘッダー内の関数の呼び出し

    これは、静的関数が出力するものです。

  • ケース2:ヘッダーファイル( "header.h")は次のようになります。

    static void FunctionInHeader();     

    また、次のような「header.c」ファイルも1つあります。

    #include <stdio.h>
    
    #include "header.h"
    
    void FunctionInHeader() {
        printf("Calling function inside header\n");
    }

    次に、次のコマンド

    gcc main.c header.h header.c -o main

    エラーになります。

  • ケース3:

    ケース2と同様ですが、ヘッダーファイル( "header.h")が次のようになっています。

    void FunctionInHeader(); // keyword static removed

    次に、ケース2と同じコマンドが成功し、さらに./mainを実行すると、予期した結果が得られます。

これらのテスト(Acer x86マシン、Ubuntu OSで実行)から、私は

staticキーワードは、関数が定義されている場所とは別の* .cファイルで呼び出されるのを防ぎます。

私が間違っていたら訂正してください。


5

Cプログラマーは、JavaおよびC ++でパブリックおよびプライベート宣言を使用するのと同じように、静的属性を使用してモジュール内の変数および関数宣言を非表示にします。Cソースファイルはモジュールの役割を果たします。static属性で宣言されたグローバル変数または関数は、そのモジュール専用です。同様に、静的属性なしで宣言されたグローバル変数または関数はすべてパブリックであり、他のモジュールからアクセスできます。可能な限りstatic属性を使用して変数と関数を保護することは、プログラミングの習慣として適切です。


4

pmgの答えは非常に説得力があります。静的宣言がオブジェクトレベルでどのように機能するかを知りたい場合は、以下の情報が興味深いかもしれません。私はpmgで作成した同じプログラムを再利用し、それを.so(共有オブジェクト)ファイルにコンパイルしました

以下の内容は、.soファイルを人間が読める形式にダンプした後のものです

0000000000000675 f1f1関数のアドレス

000000000000068c f2f2(staticc)関数のアドレス

関数アドレスの違いに注意してください、それは何かを意味します。異なるアドレスで宣言された関数の場合、f2がオブジェクトファイルの別のセグメントまたは非常に離れた場所にあることを示すことができます。

リンカーは、PLT(プロシージャリンクテーブル)およびGOT(グローバルオフセットテーブル)と呼ばれるものを使用して、へのリンクにアクセスできるシンボルを理解します。

とりあえず、GOTとPLTがすべてのアドレスを魔法のようにバインドし、動的セクションがリンカから見えるこれらすべての関数の情報を保持していると考えます。

.soファイルの動的セクションをダンプした後、一連のエントリを取得しますが、関心があるのはf1およびf2関数のみです。

動的セクションは、アドレス0000000000000675のf1関数のエントリのみを保持し、f2のエントリは保持しません。

数値:値サイズタイプバインドVis Ndx名

 9: 0000000000000675    23 FUNC    GLOBAL DEFAULT   11 f1

以上です !。これから、リンカは.soファイルの動的セクションにないため、f2関数の検索に失敗することは明らかです。


0

一部の関数へのアクセスを制限する必要がある場合は、関数を定義および宣言するときにstaticキーワードを使用します。

            /* file ab.c */ 
static void function1(void) 
{ 
  puts("function1 called"); 
} 
And store the following code in another file ab1.c

/* file ab1.c  */ 
int main(void) 
{ 
 function1();  
  getchar(); 
  return 0;   
} 
/* in this code, we'll get a "Undefined reference to function1".Because function 1 is declared static in file ab.c and can't be used in ab1.c */

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