名前空間
変種
操作

スコープ

提供: cppreference.com
< c‎ | language

C のプログラムに現れるすべての識別子は、ソースコード中のスコープと呼ばれる連続または非連続な一部分でのみ、可視になります (使用できます)。

あるスコープにおいて、ひとつの識別子が複数のエンティティを指示することがありますが、それはそれらのエンティティが異なる名前空間に存在する場合だけです。

C には4種類のスコープがあります。

  • ブロックスコープ
  • ファイルスコープ
  • 関数スコープ
  • 関数プロトタイプスコープ

目次

[編集] ネストしたスコープ

同じ識別子によって表される2つの異なるエンティティが同時にスコープ内にあり、それらが同じ名前空間に属する場合、それらのスコープはネストされており (ネスト以外の形式のスコープのオーバーラップはできません)、内側のスコープに現れた宣言が外側のスコープに現れた宣言を隠蔽します。

// この例における名前空間は普通の識別子です。
 
int a;   // 名前 a のファイルスコープはここで始まります。
 
void f(void)
{
    int a = 1; // 名前 a のブロックスコープはここで始まります。 ファイルスコープの a は隠蔽されます。
    {
      int a = 2;         // 内側の a のスコープはここで始まります。 外側の a は隠蔽されます。
      printf("%d\n", a); // 内側の a がスコープ内です。 2 を表示します。
    }                    // 内部の a のブロックスコープはここで終わります。
    printf("%d\n", a);   // 外側の a がスコープ内です。 1 を表示します。
}                        // 外側の a のスコープはここで終わります。
 
void g(int a);   // 名前 a は関数プロトタイプスコープを持ちます。 ファイルスコープの a は隠蔽されます。

[編集] ブロックスコープ

複文 (関数の本体を含みます) の中、または、ifswitchforwhile、または do-while 文内に現れるあらゆる式、宣言、または文の中 (C99以上)、または関数定義の仮引数リストの中で宣言されたあらゆる識別子のスコープは、宣言の地点で始まり、それが宣言されたブロックまたは文の終わりで終わります。

void f(int n)  // 関数引数の n のスコープが始まります。
{         // 関数本体が始まります。
   ++n;   // n はスコープ内にあり、関数引数を参照します。
// int n = 2; // エラー、同じスコープで識別子を再宣言することはできません。
   for(int n = 0; n<10; ++n) { // ループローカルな n のスコープの始まり。
       printf("%d\n", n); // 0 1 2 3 4 5 6 7 8 9 を表示します。
   } // ループローカルな n のスコープが終わります。
     // 関数引数の n がスコープに戻ってきます。
   printf("%d\n", n); // 引数の値を表示します。
} // 関数引数の n のスコープが終わります。
int a = n; // エラー、名前 n はスコープ内にありません。

C99 未満では、選択文および繰り返し文はそれ自身のブロックスコープを持ちませんでした (複文を使用した場合は、その複文がブロックスコープを持ちます)。

enum {a, b};
int different(void)
{
    if (sizeof(enum {b, a}) != sizeof(int))
        return a; // a == 1
    return b; // C89 では b == 0、 C99 では b == 1
}
(C99以上)

ブロックスコープの変数は、デフォルトでは、リンケージを持たず、自動記憶域期間を持ちます。 非 VLA なローカル変数の記憶域期間はブロックに入ったときに始まりますが、宣言に遭遇するまでその変数はスコープ内になく、アクセスできないことに注意してください。

[編集] ファイルスコープ

あらゆるブロックおよび仮引数リストの外側で宣言されたあらゆる識別子のスコープは、宣言の地点で始まり、翻訳単位の終わりで終わります。

int i; // i のスコープが始まります。
static int g(int a) { return a; } // g のスコープが始まります (注: a はブロックスコープです)。
int main(void)
{
    i = g(2); // i および g はスコープ内です。
}

ファイルスコープの識別子は、デフォルトでは、外部リンケージ静的記憶域期間を持ちます。

[編集] 関数スコープ

関数内で宣言された{rlp|statements#Labels|ラベル}}は、その関数内にあらゆる場所でスコープ内です (宣言の前でも後でも、いかなるネストしたブロック内でも)。 注: ラベルは、コロン文字の前に未使用の識別子が現れることによって、暗黙に宣言されます。

void f()
{
   {   
       goto label; // label はスコープ内です (たとえ宣言より前でも)。
label:;
   }
   goto label; // ラベルはブロックスコープを無視します。
}
 
void g()
{
    goto label; // エラー、 label は g() のスコープ内にはありません。
}

[編集] 関数プロトタイプスコープ

定義でない関数宣言の仮引数リスト内で導入された名前のスコープは、その関数宣言子の終わりで終わります。

int f(int n,
      int a[n]); // n はスコープ内であり、第1引数を参照します。

宣言に複数の宣言またはネストした宣言がある場合、スコープは最も近い囲っている関数宣言子の終わりで終わります。

void f ( // 関数名 f はファイルスコープです。
 long double f,            // 引数 f のスコープが始まり、ファイルスコープの f は隠蔽されます。
 char (**a)[10 * sizeof f] // f はスコープ内である第1引数を参照します。
);
 
enum{ n = 3 };
int (*(*g)(int n))[n]; // 引数 n のスコープは配列宣言子内の
                       // 関数宣言子の終わりで終わり、
                       // グローバルの n がスコープ内になります。
// (この宣言は int 3個の配列へのポインタを返す関数へのポインタを宣言します)

[編集] 宣言の地点

構造体、共用体、および列挙のタグは、そのタグを宣言する型指定子内のそのタグの出現直後から始まります。

struct Node {
   struct Node* next; // Node はスコープ内であり、この構造体を参照します。
};

列挙定数のスコープは、列挙子リスト内の列挙子の出現直後から始まります。

enum { x = 12 };
{
    enum { x = x + 1, // コンマまでは新しい x はスコープ内ではありません。 x は 13 に初期化されます。
           y = x + 1  // 新しい列挙子 x がスコープ内です。 y は 14 に初期化されます。
         };
}

それ以外のあらゆる識別子のスコープは、その宣言子の直後、初期化子 (もしあれば) の前から始まります。

int x = 2; // 最初の x のスコープが始まります。
{
    int x[x]; // 新たに宣言された x のスコープは宣言子 (x[x]) の後から始まります。
              // 宣言子内では外側の x が未だスコープ内です。
              // これは int 2個の VLA 配列を宣言します。
}
 
unsigned char x = 32; // 外側の x のスコープが始まります。
{
    unsigned char x = x;
            // 内側の x のスコープは初期化子 (= x) の前から始まります。
            // これは内側の x を値 32 で初期化するのではなく、
            // 内側の x を内側の x 自身の値 (不定値) で初期化します。
}
 
unsigned long factorial(unsigned long n)
// 宣言子の終わり。 factorial はこの地点からスコープ内です。
{
   return n<2 ? 1 : n*factorial(n-1); // 再帰呼び出し。
}

特別なケースとして、識別子の宣言でない型名のスコープは、型名内のもし省略されなかったならばその識別子が現れたであろう場所の直後から始まるとみなされます。

[編集] ノート

C89 未満では、外部リンケージを持つ識別子は、たとえブロック内で導入されたときでも、ファイルスコープを持ち、そのため、 C89 コンパイラは、スコープ外に出た外部識別子の使用を診断することは要求されませんでした (そのような使用は未定義動作です)。

C では、ループ本体内のローカル変数は for ループの初期化子節内で宣言された変数を隠蔽できます (それらのスコープはネストします) が、 C++ ではそれはできません。

C++ と異なり、 C には構造体スコープはありません。 構造体/共用体/列挙宣言内で宣言された名前は、その構造体宣言と同じスコープになります (ただしデータメンバはそれ自身のメンバ名前空間に入ります)。

struct foo {
    struct baz {};
    enum color {RED, BLUE};
};
struct baz b; // baz はスコープ内です。
enum color x = RED; // color および RED はスコープ内です。

[編集] 参考文献

  • C11 standard (ISO/IEC 9899:2011):
  • 6.2.1 Scopes of identifiers (p: 35-36)
  • C99 standard (ISO/IEC 9899:1999):
  • 6.2.1 Scopes of identifiers (p: 29-30)
  • C89/C90 standard (ISO/IEC 9899:1990):
  • 3.1.2.1 Scopes of identifiers

[編集] 関連項目

スコープC++リファレンス