コンテンツにスキップ

C言語/static

出典: フリー教科書『ウィキブックス(Wikibooks)』

static キーワードの理解と活用

[編集]

static キーワードの概要

[編集]

C言語における static キーワードは、文脈に応じて異なる意味と効果を持つ多目的な修飾子です。その主な機能は、変数やオブジェクトの寿命(ストレージ期間)と可視性(スコープ)を制御することです。static キーワードの複数の用途を理解することで、効率的で堅牢なプログラミングが可能になります。

static の文脈による意味の違い

[編集]

static キーワードは使用される文脈によって、以下の表のように異なる役割を持ちます。

使用場所 効果 主な目的
ローカル変数 変数の寿命をプログラム全体に拡張する 関数呼び出し間での値の保持
グローバル変数 変数の可視性をファイル内に限定する カプセル化とモジュール性の向上
関数 関数の可視性をファイル内に限定する 内部実装の隠蔽
クラスメンバー変数(C++) すべてのインスタンスで共有される変数を定義 クラス全体で共有される状態の管理
クラスメンバー関数(C++) クラスインスタンスに依存しない関数を定義 インスタンスに依存しない操作の実装

ローカル変数での static

[編集]

関数内で宣言された static 変数は、関数の呼び出しが終了しても値が保持されます。通常のローカル変数はスコープを出ると破棄されますが、static 変数はプログラムの実行が終了するまで存在し続けます。

#include <stdio.h>
void counter() {
    static int count = 0;  // 初期化は最初の呼び出し時のみ実行される
    count++;
    printf("この関数は %d 回呼び出されました\n", count);
}

int main() {
    for (int i = 0; i < 5; i++) {
        counter();
    }
    return 0;
}

実行結果:

この関数は 1 回呼び出されました
この関数は 2 回呼び出されました
この関数は 3 回呼び出されました
この関数は 4 回呼び出されました
この関数は 5 回呼び出されました

この例では、count 変数が static として宣言されているため、関数の呼び出しが終了しても値が保持され、次の呼び出し時に以前の値から継続されます。

static ローカル変数の初期化

[編集]

static ローカル変数は一度だけ初期化され、その後の関数呼び出しでは初期化処理はスキップされます。

#include <stdio.h>
#include <time.h>
int get_random_with_seed() {
    static int initialized = 0;
    
    if (!initialized) {
        printf("乱数ジェネレータを初期化します\n");
        srand(time(NULL));
        initialized = 1;
    }
    
    return rand() % 100;
}

int main() {
    for (int i = 0; i < 3; i++) {
        printf("乱数: %d\n", get_random_with_seed());
    }
    return 0;
}

実行結果:

乱数ジェネレータを初期化します
乱数: 42
乱数: 68
乱数: 35

この例では、乱数ジェネレータの初期化が最初の関数呼び出し時にのみ行われています。

グローバル変数とファイルスコープでの static

[編集]

ファイルスコープ(関数の外)で宣言された static 変数や関数は、そのファイル内でのみ可視となります。これはモジュール性を高め、異なるソースファイル間での名前の衝突を防ぎます。

static グローバル変数

[編集]

以下の例では、2つのソースファイルを使います。

module.c
#include <stdio.h>
static int module_counter = 0;  // このファイル内でのみアクセス可能

void increment_counter() {
    module_counter++;
}

void print_counter() {
    printf("カウンタの値: %d\n", module_counter);
}
main.c
#include <stdio.h>
// 関数のプロトタイプ宣言
void increment_counter();
void print_counter();

// module.c の module_counter には直接アクセスできない

int main() {
    print_counter();       // "カウンタの値: 0"
    increment_counter();
    print_counter();       // "カウンタの値: 2"
    return 0;
}

この例では、module_counter 変数は module.c ファイル内でのみアクセス可能です。main.c からは関数を通じて間接的にのみアクセスできます。

static 関数

[編集]

関数も static として宣言することで、そのファイル内でのみ可視となります。

utilities.c
#include <stdio.h>
// このファイル内でのみ呼び出し可能な補助関数
static void helper_function() {
    printf("補助関数が呼び出されました\n");
}

// 外部から呼び出し可能な公開関数
void public_function() {
    printf("公開関数を実行します\n");
    helper_function();  // 内部での補助関数の使用
}
main.c
#include <stdio.h>
// 関数のプロトタイプ宣言
void public_function();
// helper_function() にはアクセスできない

int main() {
    public_function();  // "公開関数を実行します" と "補助関数が呼び出されました" を表示
    // helper_function();  // コンパイルエラー
    return 0;
}

この例では、helper_function()utilities.c ファイル内でのみ呼び出し可能です。これにより、内部実装の詳細を隠蔽し、安全なAPIを提供できます。

実践的な使用例

[編集]

関数呼び出し回数の記録

[編集]

各関数の呼び出し回数を自動的に記録する例:

#include <stdio.h>
int factorial(int n) {
    static int call_count = 0;
    call_count++;
    
    printf("factorial関数の呼び出し回数: %d\n", call_count);
    
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

int fibonacci(int n) {
    static int call_count = 0;
    call_count++;
    
    printf("fibonacci関数の呼び出し回数: %d\n", call_count);
    
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

int main() {
    printf("5の階乗: %d\n", factorial(5));
    printf("7のフィボナッチ数: %d\n", fibonacci(7));
    return 0;
}

この例では、各関数内の static 変数が呼び出し回数を追跡しています。特に再帰関数では、呼び出し回数が想像以上に多くなることがわかります。

シングルトンパターンの実装

[編集]

static 変数を使用して、単一のインスタンスのみを持つオブジェクト(シングルトン)を実装する例:

#include <stdio.h>
#include <stdlib.h>
typedef struct {
    int id;
    char *description;
} Logger;

Logger *get_logger() {
    static Logger *instance = NULL;
    
    if (instance == NULL) {
        instance = (Logger*)malloc(sizeof(Logger));
        instance->id = 1;
        instance->description = "システムロガー";
        printf("新しいロガーインスタンスを作成しました\n");
    }
    
    return instance;
}

void log_message(const char *message) {
    Logger *logger = get_logger();
    printf("[Logger %d: %s] %s\n", logger->id, logger->description, message);
}

int main() {
    log_message("プログラムを開始します");
    log_message("処理を実行中...");
    log_message("プログラムを終了します");
    
    return 0;
}

この例では、get_logger() 関数内の static 変数を使用して、ロガーのシングルトンインスタンスを実装しています。

メモリ使用量の最適化

[編集]

一時的なバッファとして大きな配列が必要だが、毎回割り当てると非効率な場合:

#include <stdio.h>
#include <string.h>
void process_data(const char *input) {
    static char buffer[1024 * 1024];  // 1MB のバッファを一度だけ確保
    
    // バッファを使用してデータを処理
    strcpy(buffer, input);
    strcat(buffer, " - 処理済み");
    
    printf("処理結果: %s\n", buffer);
}

int main() {
    process_data("データ1");
    process_data("データ2");
    process_data("データ3");
    
    return 0;
}

この例では、static 配列を使用することで、関数が呼び出されるたびに大きなバッファを再割り当てするのを避けています。

複数のソースファイルと extern 宣言

[編集]

static グローバル変数との対比として、extern キーワードを使用して他のファイルで定義された変数にアクセスする方法を見てみましょう。

globals.c
// グローバル変数の定義
int shared_counter = 0;  // デフォルトでは外部リンケージを持つ

// このファイル内でのみアクセス可能
static int private_counter = 0;
module1.c
#include <stdio.h>
// 外部変数の宣言
extern int shared_counter;  // globals.c で定義された変数を参照

void increment_shared() {
    shared_counter++;
    printf("共有カウンタ: %d\n", shared_counter);
}
module2.c
#include <stdio.h>
// 同じ外部変数を参照
extern int shared_counter;

void double_shared() {
    shared_counter *= 2;
    printf("共有カウンタを2倍にしました: %d\n", shared_counter);
}
main.c
#include <stdio.h>
extern int shared_counter;

// 関数のプロトタイプ宣言
void increment_shared();
void double_shared();

int main() {
    printf("初期値: %d\n", shared_counter);
    increment_shared();
    double_shared();
    printf("最終値: %d\n", shared_counter);
    return 0;
}

この例では、shared_counter 変数が複数のファイル間で共有されていますが、private_counter 変数は globals.c ファイル内でのみアクセス可能です。

static と const の組み合わせ

[編集]

staticconst を組み合わせることで、ファイル内でのみアクセス可能な定数を定義できます。

#include <stdio.h>
// このファイル内でのみアクセス可能な定数
static const double PI = 3.14159265358979323846;
static const char *APP_NAME = "マイアプリケーション";

double calculate_circle_area(double radius) {
    return PI * radius * radius;
}

void print_app_info() {
    printf("アプリケーション名: %s\n", APP_NAME);
}

int main() {
    printf("円の面積: %.2f\n", calculate_circle_area(5.0));
    print_app_info();
    return 0;
}

この例では、PIAPP_NAME という定数がファイル内でのみアクセス可能です。これにより、定数の名前の衝突を避けつつ、ファイル内でのみ使用する定数を定義できます。

static の注意点と制限

[編集]

static キーワードを使用する際の注意点と制限を以下の表にまとめます。

注意点 説明
初期化 static 変数はプログラム開始時に一度だけ初期化され、動的な値での初期化には制限がある
メモリ使用量 static 変数はプログラム全体の実行中メモリを占有する
スレッドセーフティ 複数のスレッドから同時にアクセスされる場合、競合状態に注意が必要
単体テスト static 変数や関数は単体テストが難しくなる場合がある
再帰関数 再帰関数内で同じ static 変数が共有される点に注意

結論

[編集]

C言語における static キーワードは、使用される文脈によって異なる意味と効果を持つ多機能な修飾子です。ローカル変数の寿命を延長したり、グローバル変数や関数の可視性を制限したりする機能を持ち、適切に使用することでプログラムのモジュール性、カプセル化、効率性を向上させることができます。ただし、メモリ使用量やスレッドセーフティなどの側面には注意が必要です。コードの要件と設計に応じて、static キーワードの適切な使用を検討することが重要です。