組み込みCの観点からの静的キーワードの概念


9
static volatile unsigned char   PORTB   @ 0x06;

これは、PICマイクロコントローラーヘッダーファイルのコード行です。@オペレータは、アドレスの内部PORTB値を格納するために使用される0x06PORTBを表すPICコントローラ内部のレジスタです。この時点までに、私は明確な考えを持っています。

この行は、ヘッダーファイル(.h)内でグローバル変数として宣言されています。したがって、私がC言語について知ったことから、「静的グローバル変数」は他のファイルからは見えません-または、単純に、静的グローバル変数/関数は現在のファイルの外では使用できません。

次に、このキーワードをPORTBメインのソースファイルや手動で作成した他の多くのヘッダーファイルに表示するにはどうすればよいですか?

メインのソースファイルでは、ヘッダーファイルのみを追加しました#include pic.h。これは私の質問と関係がありますか?


2
質問は問題ありませんが、SEのセクションは間違っています。
恐れ入ります

staticは通常、関数内で使用され、変数が一度作成され、関数の実行から次の実行までその値を保持することを指定します。グローバル変数は、関数の外部で作成されるため、どこからでも見えるようになります。静的グローバルは実際には意味がありません。
Finbarr

8
@Finbarr間違っています。staticグローバルは単一のコンパイル単位全体の内部で表示され、それを超えてエクスポートされることはありません。これらはprivate、OOPのクラスのメンバーによく似ています。つまり、コンパイルユニット内のさまざまな関数間で共有する必要があるが、そのcu 実際にあるはずの外からは見えないはずのすべての変数static。これにより、プログラムのグローバル名前空間の「破壊」も減少します。
JimmyB

2
「@演算子は、アドレス0x06の内部でPORTB値を格納するために使用されます」。本当に?以下のようなより多くのではありません「絶対メモリ・アドレス0x06での『PORTB』 @演算子は、変数を格納するために使用されますか」
Peter Mortensen

回答:


20

Cのキーワード「静的」には、2つの根本的に異なる意味があります。

スコープの制限

このコンテキストでは、「静的」と「extern」を組み合わせて、変数または関数名のスコープを制御します。Staticを使用すると、変数または関数の名前を単一のコンパイル単位内でのみ使用でき、コンパイル単位テキスト内の宣言/定義のに存在するコードでのみ使用できます。

この制限自体は、プロジェクトに複数のコンパイルユニットがある場合にのみ意味を持ちます。コンパイルユニットが1つしかない場合でも、それは機能しますが、それらの効果はほとんど無意味です(コンパイラーが生成したものを読み取るためにオブジェクトファイルを掘り下げるのが好きでない限り)。

前述のように、このコンテキストのこのキーワードは、他のコンパイルユニットで見つかった同じ名前で変数または関数名をリンク可能にすることによって、反対のことを行うキーワード 'extern'とペアになります。したがって、「静的」は、現在のコンパイル単位内で変数または名前を見つける必要があると見なすことができますが、「外部」は、クロスコンパイル単位のリンケージを許可します。

静的寿命

静的存続期間とは、変数がプログラムの存続期間全体に渡って存在することを意味します(ただし、どれだけ長いかを問いません)。「静的」を使用して関数内で変数を宣言/定義する場合、それは、最初の使用の前に変数が作成されることを意味します(つまり、私が経験するたびに、変数はmain()が開始する前に作成され、その後は破棄されません。関数の実行が完了し、呼び出し元に戻る場合でもそうではありません。また、関数の外部で宣言された静的ライフタイム変数と同じように、それらは同時に(main()が開始する前に)セマンティックゼロ(初期化が提供されていない場合)または指定された場合は指定された明示的な値に初期化されます。

これは、「auto」タイプの関数変数とは異なります。これは、関数に入るたびに新しく(または、as-if newの場合)作成され、関数が終了すると破棄されます(または、破棄された場合)。

関数の範囲に直接影響する、関数の外部の変数定義に「静的」を適用する影響とは異なり、「関数本体」内で関数変数を宣言すると、「静的」はスコープに影響を与えません。スコープは、関数本体内で定義されたという事実によって決定されます。関数内で定義された静的ライフタイム変数は、関数本体内で定義された他の「自動」変数と同じスコープを持ちます-関数スコープ。

概要

したがって、「静的」キーワードは、「非常に異なる意味」に相当するさまざまなコンテキストを持っています。このように2つの方法で使用された理由は、別のキーワードの使用を避けるためでした。(それについては長い議論がありました。)プログラマーはその使用を容認できると感じられ、言語の他のキーワードを回避することの価値は(他の議論よりも)より重要でした。

(関数の外部で宣言されたすべての変数は静的な有効期間を持ち、それを実現するためにキーワード「静的」を必要としません。したがって、この種類のキーワードを解放してそこでまったく異なるものを意味するために使用します:「1回のコンパイルでのみ可視ユニット。それは一種のハックです。)

特定の注記

static volatile unsigned char PORTB @ 0x06;

ここで「静的」という言葉は、リンカが複数のコンパイルユニットで見つかる可能性のあるPORTBの複数の出現を一致させようとしないことを意味すると解釈されます(コードが複数あると仮定します)。

PORTBの「場所」(または通常はアドレスであるラベルの数値)を指定するために、特別な(移植できない)構文を使用します。したがって、リンカにはアドレスが与えられ、そのためにアドレスを見つける必要はありません。この行を使用するコンパイルユニットが2つある場合、いずれにしても、それぞれが同じ場所を指し示すことになります。したがって、ここに「外部」というラベルを付ける必要はありません。

彼らが「外部」を使用した場合、問題が発生する可能性があります。その後、リンカは、複数のコンパイルで見つかったPORTBへの複数の参照を確認できます(照合を試みます)。それらのすべてがこのようなアドレスを指定し、アドレスが何らかの理由で同じではない[間違い?]場合、何をすべきですか?文句を言う?それとも?(技術的には、「のextern」で経験則のみということになるONEコンパイル単位が値を指定するであろうし、他のものはいけません。)

それを「静的」としてラベル付けする方が簡単であり、リンカーが競合を心配することを回避し、アドレスを不必要なものに変更した人のミスマッチアドレスのミスのせいにするだけです。

どちらの場合も、変数は「静的存続期間」を持つものとして扱われます。(そして「揮発性」。)

宣言はない定義が、すべての定義が宣言されています

Cでは、定義によってオブジェクトが作成されます。また、それを宣言します。しかし、宣言は通常(以下の箇条書きを参照)オブジェクトを作成しません。

以下は、定義と宣言です。

static int a;
static int a = 7;
extern int b = 5;
extern int f() { return 10; }

以下は定義ではなく、単なる宣言です。

extern int b;
extern int f();

宣言は実際のオブジェクトを作成しないことに注意してください。それらはそれに関する詳細を宣言するだけであり、コンパイラーはこれを使用して、適切なコードを生成し、必要に応じて警告およびエラーメッセージを提供できます。

  • 上記では、「通常」と忠告しています。場合によっては、宣言によってオブジェクトが作成される可能性があるため、リンカーによって(コンパイラーによってではなく)定義に昇格されます。したがって、このまれなケースでも、Cコンパイラーは宣言が単なる宣言であると見なします。宣言の必要なプロモーションを行うのは、リンカーフェーズです。この点に注意してください。

    上記の例で、「extern int b;」の宣言しかないと判明した場合は、リンクされたすべてのコンパイル単位で、リンカーは定義を作成する責任を負います。これはリンク時のイベントであることに注意してください。コンパイル中、コンパイラーは完全に認識しません。このタイプの宣言が最もプロモートされる場合、リンク時にのみ決定できます。

    コンパイラーは、「静的int a;」を認識しています。リンク時にはリンカによって昇格できないため、これは実際にはコンパイル時の定義です。


3
すばらしい答え、+ 1!マイナーポイントが1つだけあります。使用できる可能性externあり、より適切なCの方法になります。ヘッダーファイルの変数をプログラムに複数回含めるように宣言し、コンパイルするいくつかの非ヘッダーファイルで定義する正確に1回リンクされます。結局のところ、異なるcuが参照できる変数のインスタンスが1つだけであると想定されています。したがって、ここでの使用は、ヘッダーファイルに加えて別の.cファイルを必要としないようにするために取った一種のショートカットです。externPORTB static
JimmyB

また、関数内で宣言された静的変数は、関数呼び出し間で変更されないことに注意してください。これは、ある種の状態情報を保持する必要がある関数に役立ちます(これは、過去にこの目的で特に使用しました)。
Peter Smith

@ピーター私はそう言ったと思います。しかし、おそらくあなたが好きだったほどではないのですか?
-jonk

@JimmyBいいえ、「静的」のように動作する関数変数宣言では、代わりに「extern」を使用できませんでした。'extern'はすでに関数本体内の変数定義(定義ではない)のオプションであり、別の目的を果たします-関数の外部で定義された変数へのリンク時アクセスを提供します。しかし、私もあなたの考えを誤解している可能性があります。
jonk

1
@JimmyB Externリンケージは間違いなく可能ですが、「もっと適切」かどうかはわかりません。1つの考慮事項は、情報が翻訳単位で見つかった場合、コンパイラーがより最適化されたコードを出力できる可能性があることです。組み込みシナリオの場合、すべてのIOステートメントのサイクルを節約することは大きな問題になる可能性があります。
Cort Ammon

9

staticsは、現在のコンパイル単位(「翻訳単位」)の外には表示されません。これは同じファイルと同じではありません。

ヘッダーで宣言した変数が必要になる可能性のあるソースファイルにヘッダーファイルを含めること注意してください。このインクルードにより、ヘッダーファイルは現在の翻訳単位の一部になり、変数(のインスタンス)はその中に表示されます。


お返事をありがとうございます。「コンパイルユニット」申し訳ありませんが、わかりません。その用語について説明してください。別の質問をさせてください。別のファイル内に記述された変数や関数を使用したい場合でも、最初にそのファイルをメインのソースファイルに含める必要があります。次に、そのヘッダーファイルでキーワード "static volatile"を使用する理由を説明します。
Electro Voyager



3
@ElectroVoyager; static宣言を含む同じヘッダーを複数のcソースファイルに含めると、それらのファイルのそれぞれに同じ名前のstatic変数がありますが、同じ変数ではありません
Peter Smith

2
@JimmyBリンクから:Files included by using the #include preprocessor directive become part of the compilation unit.ヘッダーファイル(.h)を.cファイルに含める場合は、ヘッダーのコンテンツをソースファイルに挿入すると考えてください。これがコンパイルユニットです。その静的変数または関数を.cファイルで宣言した場合、それらは同じファイルでのみ使用でき、最後には別のコンパイル単位になります。
gustavovelascoh

5

コメントと@JimmyBの回答を説明的な例で要約してみます。

次の一連のファイルを想定します。

static_test.c:

#include <stdio.h>
#if USE_STATIC == 1
    #include "static.h"
#else
    #include "no_static.h"
#endif

void var_add_one();

void main(){

    say_hello();
    printf("var is %d\n", var);
    var_add_one();
    printf("now var is %d\n", var);
}

static.h:

static int var=64;
static void say_hello(){
    printf("Hello!!!\n");
};

no_static.h:

int var=64;
void say_hello(){
    printf("Hello!!!\n");
};

static_src.c:

#include <stdio.h>

#if USE_STATIC == 1
    #include "static.h"
#else
    #include "no_static.h"
#endif

void var_add_one(){
    var = var + 1;
    printf("Added 1 to var: %d\n", var);
    say_hello();
}

gcc -o static_test static_src.c static_test.c -DUSE_STATIC=1; ./static_test静的ヘッダーまたはgcc -o static_test static_src.c static_test.c -DUSE_STATIC=0; ./static_test非静的ヘッダーを使用して、コードをコンパイルおよび実行できます。

ここには、static_srcとstatic_testの2つのコンパイル単位があることに注意してください。もし、ヘッダの静的バージョンを(使用する場合-DUSE_STATIC=1)のバージョンvarsay_hello、各コンパイル単位のために利用できるようになり、これは、両方のユニットがそれらを使用するが、にもかかわらず、それを確認することができ、あるvar_add_one()関数がインクリメントその var主な機能は、印刷するときに、変数をその var変数、それはまだ64です:

$ gcc -o static_test static_src.c static_test.c -DUSE_STATIC=1; ./static_test                                                                                                                       14:33:12
Hello!!!
var is 64
Added 1 to var: 65
Hello!!!
now var is 64

ここで、非静的バージョン(-DUSE_STATIC=0)を使用してコードをコンパイルおよび実行しようとすると、変数定義が重複しているため、リンクエラーがスローされます。

$ gcc -o static_test static_src.c static_test.c -DUSE_STATIC=0; ./static_test                                                                                                                       14:35:30
/tmp/ccLBy1s7.o:(.data+0x0): multiple definition of `var'
/tmp/ccV6izKJ.o:(.data+0x0): first defined here
/tmp/ccLBy1s7.o: In function `say_hello':
static_test.c:(.text+0x0): multiple definition of `say_hello'
/tmp/ccV6izKJ.o:static_src.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
zsh: no such file or directory: ./static_test

これがこの問題を明確にするのに役立つことを願っています。


4

#include pic.hおおよそ「pic.hの内容を現在のファイルにコピーする」という意味です。その結果、インクルードするすべてのファイルpic.hは、独自のローカル定義を取得しますPORTB

たぶん、の単一のグローバル定義がないのか疑問に思うかもしれませんPORTB。その理由は非常に簡単です。グローバル変数は1つの Cファイルでしか定義できないため、PORTBプロジェクトの複数のファイルで使用する場合pic.hは、の宣言とその定義が必要にPORTBなります。各Cファイルに独自ののコピーを定義させると、作成していないプロジェクトファイルを含める必要がないため、コードの構築が容易になります。pic.cPORTB

静的変数とグローバルのもう1つの利点は、名前の競合が少なくなることです。MCUハードウェア機能を使用しない(したがってを含まないpic.h)ACファイルPORTBは、独自の目的で名前を使用できます。わざとそうするのは良い考えではありませんが、MCUに依存しない数学ライブラリなどを開発すると、そこにあるMCUの1つで使用されている名前を誤って再利用するのがいかに簡単であるかに驚くでしょう。


「そこにあるMCUの1つで使用されている名前を誤って再利用するのがいかに簡単であるかに驚くでしょう」 -すべての数学ライブラリが小文字の名前のみを使用し、すべてのMCU環境がレジスターに大文字のみを使用することを期待します名前。
vsz

@vsz LAPACK for oneは、歴史的なすべて大文字の名前でいっぱいです。
Dmitry Grigoryev

3

すでに良い答えはいくつかありますが、混乱の原因は簡単かつ直接対処する必要があると思います。

PORTB宣言は標準Cではありません。これは、PICコンパイラでのみ機能するCプログラミング言語の拡張です。PICはCをサポートするように設計されていないため、拡張機能が必要です。

の使用 staticここキーワード通常のコードでstaticそのように使用することがないため、混乱を招きます。グローバル変数の場合はextern、ではなくヘッダーで使用しますstatic。しかし、PORTB は通常の変数ではありません。これは、レジスタIOに特別なアセンブリ命令を使用するようコンパイラーに指示するハックです。PORTB staticを宣言すると、コンパイラーをだまして正しいことを実行させることができます。

ファイルスコープで使用する場合static、変数または関数のスコープをそのファイルに制限します。「ファイル」とは、Cファイルと、プリプロセッサーによってコピーされたすべてのものを意味します#includeを使用すると、コードがCファイルにコピーされます。そのためstatic、ヘッダーでの使用は意味がありません。1つのグローバル変数ではなく、#ヘッダーを含む各ファイルは、変数の個別のコピーを取得します。

一般的な考えに反して、static常に同じことを意味します:スコープが限定された静的割り当て。宣言される前後の変数は次のようになりますstatic

+------------------------+-------------------+--------------------+
| Variable type/location |    Allocation     |       Scope        |
+------------------------+-------------------+--------------------+
| Normal in file         | static            | global             |
| Normal in function     | automatic (stack) | limited (function) |
| Static in file         | static            | limited (file)     |
| Static in function     | static            | limited (function) |
+------------------------+-------------------+--------------------+

混乱を招くのは、変数のデフォルトの動作が変数の定義場所に依存していることです。


2

メインファイルが「静的」ポート定義を参照できるのは、#includeディレクティブが原因です。このディレクティブは、ヘッダーファイル全体をソースコードのディレクティブ自体と同じ行に挿入するのと同じです。

マイクロチップXC8コンパイラは.cファイルと.hファイルをまったく同じように扱うため、変数定義をどちらかに置くことができます。

通常、ヘッダーファイルには、他の場所で定義されている変数(通常は.cファイル)への「外部」参照が含まれています。

ポート変数は、実際のハードウェアと一致する特定のメモリアドレスで指定する必要がありました。したがって、実際の(外部ではない)定義がどこかに存在する必要があります。

Microchip社が実際の定義を.hファイルに入れることを選択した理由を推測することしかできません。おそらく、ユーザーが2つのファイル(.hと.c)ではなく1つのファイル(.h)だけを望んでいたと思われます(ユーザーにとって使いやすくするため)。

ただし、実際の変数定義をヘッダーファイルに入れ、そのヘッダーを複数のソースファイルに含めると、リンカは変数が複数回定義されていると警告します。

解決策は、変数を静的として宣言することです。その後、各定義はそのオブジェクトファイルに対してローカルとして扱われ、リンカーは文句を言いません。

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