C言語-6

C言語の学習を締めくくるために、C言語の型システムにおける核心的な概念である「型修飾子」と、プログラムの様々な部分を連携させる「記憶クラス指定子」について解説します。


11. 型修飾子(Type Qualifiers)

型修飾子は、変数の型(intcharなど)に特殊な性質や制約を追加するために使用されます。特にconstvolatileは、C言語による低レベルプログラミングや並行処理において不可欠です。

🔒 const(定数)

constは、変数が初期化された後、その値を変更できないように指定します。これにより、意図しない値の変更を防ぎ、安全性を高めます。

定数の宣言

C

#include <stdio.h>

int main() {
    // 1. 基本的な定数
    const int MAX_USERS = 100;
    // MAX_USERS = 200; // コンパイルエラー: const変数は変更できない

    // 2. ポインタとconst
    int num = 50;
    
    // 値がconst(ポインタが指す先の値は変更不可)
    const int *ptr_to_const = &num; 
    // *ptr_to_const = 60; // コンパイルエラー: 値は変更できない
    
    // ポインタ自体がconst(ポインタの指すアドレスは変更不可)
    int *const const_ptr = &num;
    *const_ptr = 70; // 値の変更は可能
    // int other = 80; const_ptr = &other; // コンパイルエラー: アドレスは変更できない

    printf("num: %d\n", num); // 出力: 70
    return 0;
}

💨 volatile(揮発性)

volatileは、変数がプログラムコードの外部要因(OS、ハードウェア、割り込み処理など)によって、いつ、どのように変更されるか予測できないことをコンパイラに伝えます。

  • 目的: コンパイラによる最適化(例えば、変数の値をレジスタにキャッシュすること)を抑制し、変数の値がメモリから毎回読み込まれるように強制します。
  • 用途: マルチスレッド処理での共有変数や、組み込みシステムでのハードウェアレジスタのアクセスに必須です。

C

// ハードウェアレジスタを指すポインタを想定
volatile int *io_register = (volatile int *)0x2000; 

int main() {
    // コンパイラは、この変数io_registerの値が、ループ中に外部から変更される可能性があると認識し、
    // 最適化を行わない(毎回メモリをチェックする)ようになる。
    volatile int flag = *io_register; 
    
    while (flag != 1) {
        flag = *io_register; // 毎回メモリから値を読み込む
    }
    return 0;
}

12. 記憶クラス指定子(Storage-Class Specifiers)

記憶クラス指定子は、変数のスコープ(有効範囲)と寿命(メモリに存在する期間)を制御するために使われます。

🌍 auto(自動記憶クラス)

ローカル変数(関数内で宣言された変数)は、デフォルトでauto記憶クラスになります。

  • スコープ: 変数が宣言されたブロック内{}内)のみ。
  • 寿命: 関数が呼び出されたときに作成され、関数を抜けるときに破棄されます。
  • 初期値: 明示的に初期化しない場合、**不定な値(ゴミ)**が入っています。

💾 static(静的記憶クラス)

ローカル変数にstaticを付けると、スコープはそのままに、寿命がプログラムの実行期間全体に伸びます。

  • スコープ: 変数が宣言されたブロック内のみ。
  • 寿命: プログラムの開始から終了までメモリに存在し続けます。
  • 特徴: 関数を抜けても値が保持され、初期化は一度だけ行われます。

C

#include <stdio.h>

void counter() {
    // 静的ローカル変数: 関数が呼び出されるたびに値が保持される
    static int count = 0; 
    count++;
    printf("カウント: %d\n", count);
}

int main() {
    counter(); // 出力: 1
    counter(); // 出力: 2
    counter(); // 出力: 3
    return 0;
}

🤝 extern(外部参照)

externは、「この変数は他のファイルで定義されているから、参照だけさせてほしい」とコンパイラに伝える役割を持ちます。

  • 用途: 複数のソースファイル間でグローバル変数を共有するときに使用します。

File1.c:

C

// グローバル変数を定義(実体)
int global_data = 50; 

File2.c:

C

// 外部ファイルで定義されたグローバル変数を参照
extern int global_data; 

void display_data() {
    printf("外部データ: %d\n", global_data);
}

💨 register(レジスタ記憶クラス)

変数へのアクセスを高速化するため、その変数をCPUのレジスタに格納するようコンパイラに要求します。ただし、コンパイラが実際にレジスタに割り当てるかどうかは保証されません。

  • 用途: ループ変数など、頻繁にアクセスされるローカル変数に使用されます(現代のコンパイラでは自動で最適化されるため、使用頻度は低いです)。

これで、C言語の基礎からポインタ、メモリ管理、そして高度な型制御の概念まで、幅広い知識を習得できました。これらの知識は、C++やObjective-C、さらにはシステムプログラミングの理解に役立つ強力な基盤となります。