Namespaces
Variants

Scope

From cppreference.net

identifier は、Cプログラム内に現れる際に、その scope と呼ばれる、ソースコード内の連続的とは限らない一部の領域でのみ visible (つまり、使用可能)となります。

スコープ内では、識別子が複数のエンティティを指定できるのは、それらのエンティティが異なる name spaces にある場合のみです。

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を隠蔽する

ブロックスコープ

複合文( compound statement )の内部で宣言された識別子のスコープは、関数本体、 if switch for while 、または do-while 文内に現れる任意の式、宣言、または文 (C99以降) 、または function definition のパラメータリスト内で宣言された識別子のスコープは、宣言の地点から始まり、それが宣言されたブロックまたは文の終端で終了します。

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; // b == 0 in C89, b == 1 in C99
}
(C99以降)

ブロックスコープ変数はデフォルトで リンケージを持たず 自動記憶域期間 を持つ。非VLAローカル変数の記憶域期間はブロックに入った時点で開始されるが、宣言が現れるまでは変数はスコープ内になくアクセスできないことに注意。

ファイルスコープ

任意のブロックまたはパラメータリストの外側で宣言された識別子のスコープは、宣言の位置から始まり、翻訳単位の終わりまで続きます。

int i; // iのスコープ開始
static int g(int a) { return a; } // gのスコープ開始(注: "a"はブロックスコープを持つ)
int main(void)
{
    i = g(2); // iとgはスコープ内
}

ファイルスコープの識別子はデフォルトで external linkage static storage duration を持ちます。

関数スコープ

関数内で宣言された ラベル(およびラベルのみ) は、その関数内のすべての場所、すべてのネストされたブロック内、自身の宣言の前後でスコープ内にあります。注記:ラベルは、任意の文の前のコロン文字の前に、それ以外では未使用の識別子を使用することで暗黙的に宣言されます。

void f()
{
   {   
       goto label; // 後で宣言されていてもスコープ内のラベル
label:;
   }
   goto label; // ラベルはブロックスコープを無視する
}
void g()
{
    goto label; // エラー: g()のスコープ内にラベルが存在しない
}

関数プロトタイプスコープ

定義ではない function declaration のパラメータリストで導入された名前のスコープは、関数 declarator の終端で終了します。

int f(int n,
      int a[n]); // nはスコープ内にあり、最初のパラメータを参照します

宣言内に複数の宣言子または入れ子になった宣言子がある場合、スコープは最も近い外側の関数宣言子の終わりで終了することに注意してください:

void f ( // 関数名 'f' はファイルスコープ
 long double f,            // 識別子 'f' がスコープ内に入り、ファイルスコープの 'f' は隠蔽される
 char (**a)[10 * sizeof f] // 'f' はスコープ内の最初のパラメータを参照
);
enum{ n = 3 };
int (*(*g)(int n))[n]; // 関数パラメータ 'n' のスコープは
                       // その関数宣言子の終端で終了
                       // 配列宣言子内では、グローバル n がスコープ内
// (これは3つのintの配列へのポインタを返す関数へのポインタを宣言)

宣言のポイント

構造体、共用体、列挙体のタグのスコープは、タグを宣言する型指定子内でタグが現れた直後に始まります。

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' がまだスコープ内にある
              // これは2つの int からなる VLA 配列を宣言する
}
unsigned char x = 32; // 外側の 'x' のスコープ開始
{
    unsigned char x = x;
            // 内側の 'x' のスコープは初期化子 (= x) より前に開始
            // これは内側の 'x' を値32で初期化せず、
            // 自身の不定値で内側の 'x' を初期化する
}
unsigned long factorial(unsigned long n)
// 宣言子終了、この時点から 'factorial' がスコープ内に存在
{
   return n<2 ? 1 : n*factorial(n-1); // 再帰呼び出し
}

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

注記

C89以前では、外部リンケージを持つ識別子はブロック内で導入された場合でもファイルスコープを持ち、そのため、C89コンパイラはスコープ外となったextern識別子の使用(そのような使用は未定義動作です)を診断する必要はありません。

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

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

struct foo {
    struct baz {};
    enum color {RED, BLUE};
};
struct baz b; // bazはスコープ内
enum color x = RED; // colorとREDはスコープ内

参考文献

  • C23規格 (ISO/IEC 9899:2024):
  • 6.2.1 識別子、型名、複合リテラルのスコープ (p: 未定)
  • C17規格 (ISO/IEC 9899:2018):
  • 6.2.1 識別子のスコープ (p: 28-29)
  • C11規格 (ISO/IEC 9899:2011):
  • 6.2.1 識別子のスコープ (p: 35-36)
  • C99規格 (ISO/IEC 9899:1999):
  • 6.2.1 識別子のスコープ (p: 29-30)
  • C89/C90規格 (ISO/IEC 9899:1990):
  • 3.1.2.1 識別子のスコープ

関連項目

C++ドキュメント for スコープ