グローバル変数

C言語のグローバル変数についての解説です。

グローバル変数を使わないC言語プログラミングを推奨

最初に書いておくと、グローバル変数は使わないでC言語プログラミングを行うことを強く推奨します。

自分でライブブラリやアプリケーションを作成する場合は、グローバル変数を使わずに、プログラミングできます。

自分で作成していないライブラリが、ライブラリンのインターフェースとして、グローバル変数を使うことを要求していた場合は、グローバル変数を使う必要がありますが、自作のC言語プログラミングであれば、グローバル変数を使わずに、プログラミングできます。そのような場合でも、グローバル変数へのアクセスを、極力さけるための工夫をします。

グローバル変数の危険性

グローバル変数の危険性というのは、一言で書くと、グローバル変数はどこからでも変更可能だということです。

さて、プログラミングが100万行あったとして、何かの間違いで、どこかの場所でグローバル変数が変更されました。

さて、皆さんは、この箇所を、苦労して探したいですか? それとも、最初からリスクをなくしておきますでしょうか?

C99対応のC言語入門では、最初からリスクをなくしておく方法を強くお勧めしています。

でもどうしてグローバル変数があふれているの?

でも、どうして、結局のところ、世の中に出回っているプログラミングは、こんなにもグローバル変数にあふれているのでしょうか?

500行のときは、グローバル変数でも、確認するのは簡単でした。次の担当の人は、前に書いてあったものを、コピーして、真似しました。その場しのぎとして、とても楽です。プログラムは、次第にどんどん大きくなっていきます。

気づけば、1万行に達しています。

書き換えようと思っても、リファクタリングの時間は与えてもらえません。そのような交渉をする権限も雰囲気もありません。

次の要件を達成するための現実的な策は、前のものをコピペして、少し変えて使うことです。そして、あまりにもリファクタリングを怠っていたので、プログラミングを書く作業時間は、どんどん伸び、試験の期間に到達しています。試験の期間のぎりぎりで、夜10時までの残業をこなして、やっと、プログラムが書き終わりました。さて、そのようなシステムは、信頼性の高いシステムでしょうか?

グローバル変数を使わないでプログラミングする手法

グローバル変数を使わないでプログラミングする手法について紹介します。

整数定数はenum

整数定数を使いたい場合は、列挙型enumを使います。

浮動小数点定数は定数マクロ

浮動小数点定数を使いたい場合は、#defineによるマクロの記述を行います。

文字列定数は静的ローカル変数

文字列定数を使いたい場合のテクニックをローカル変数の解説の静的ローカル変数の部分に書いています。

文字列定数の配列は静的ローカル変数

文字列定数の配列を使いたい場合のテクニックをローカル変数の解説の静的ローカル変数の部分に書いています。

プログラムの開始から終わりまで同じ値を保持したい

この場合も静的ローカル変数が使えます。

複数の関数からまとまったデータにアクセスしたい

グローバル変数を使いたくなる動機は、必要なデータだけ関数を引数に渡すのがめんどうで、複数の関数からまとまったデータにアクセスしたい場合があるからです。

必要なデータだけを関数に渡すのは、リエントラントで最も安全な方法ですが、少し大きなアプリケーションを作る場合には、面倒で現実的ではないのです。

このような場合は、C言語にオブジェクト指向の考え方を取り入れましょう。オブジェクトにデータを保存して、アクセスするようにします。

グローバル変数の文法の解説

ここまでにグローバル変数の「ヒヤリ、はっと」を感じてもらって、文法の解説をします。

グローバル変数の宣言

関数外で宣言した変数は、グローバル変数になります。グローバル変数は0(すべてのビットが0)で初期化されます。値が不定であるローカル変数とは、この点で異なります。

グローバル変数は、プログラムは始まりから、終わりまで有効です。グローバル変数は、プログラムのすべての位置から見えます(ファイル外の場合は、externすれば)。

#include <stdio.h>
#include <stdint.h>

// グローバル変数の宣言
int32_t MYAPP_GLOBAL_VAR;

// 中身を出力
int main(void) {
  printf("%d\n", MYAPP_GLOBAL_VAR);
}

出力結果です。

0

C言語には、関数の外には、式を書くことはできませんが、グローバル変数を初期化するための右辺の項が書けるという仕様があります。

#include <stdio.h>
#include <stdint.h>
#include <string.h>

// グローバル変数の宣言と初期化
int32_t MYAPP_GLOBAL_VAR_CONST1 = 5;
int32_t MYAPP_GLOBAL_VAR_CONST2 = sizeof(6);
int32_t MYAPP_GLOBAL_VAR_NO_CONST = strlen("AAABBB");

// 式を書いた場合は文法エラー
// strlen("AAABBB");

// 中身を出力
int main(void) {
  printf("%d\n", MYAPP_GLOBAL_VAR_CONST1);
  printf("%d\n", MYAPP_GLOBAL_VAR_CONST2);
  printf("%d\n", MYAPP_GLOBAL_VAR_NO_CONST);
}

gccでの出力結果です。初期化が、定数でなかった場合は、警告がでます。

a.c:8: warning: initializer element is not constant
a.c:8: warning: (near initialization for ‘MYAPP_GLOBAL_VAR_NO_CONST’)
5
4
6

これは、仕様として覚えておいて、もし定数を使いたい場合は、上記で紹介したグローバル変数を使わない方法をお勧めします。

他のオブジェクトファイルで定義されたグローバル変数にアクセスする

C言語にはファイルスコープという概念があり、他のオブジェクトファイルに記述されているグローバル変数は、そのままでは見えません。他のオブジェクトファイルにあるグローバル変数にアクセスしたい場合は、extern宣言を使用します。

# 他のファイルに記述されているグローバル変数にアクセスできるようにする
extern OUTER_GLOBAL_VAR;

グローバル変数を他のオブジェクトファイルからアクセスできなくする

グローバル変数を他のオブジェクトファイルからアクセスできなくするにはstatic修飾子を使います。static修飾子を使うと、このファイル(オブジェクトファイルになったと想像して)の中だけで、使用できます。

#include <stdio.h>
#include <stdint.h>

// このファイル(オブジェクトファイルになったと想像して)の中だけで有効なグローバル変数の宣言
static int32_t MYAPP_GLOBAL_VAR;

// 中身を出力
int main(void) {
  printf("%d\n", MYAPP_GLOBAL_VAR);
}

グローバル変数を実際に使ってしまった場面があれば教えてください

はい。正直に告白します。

static修飾されたグローバル変数を使ってしまった場合のことを話します。

プログラミング言語の処理系でのことですが、関数のIDを関数名から取り出すロジックがあります。関数名から関数のIDを取り出す処理は、線形探索で、毎回IDを取り出していては、非常にパフォーマンスのコストがかかります。

さらに、関数のIDは、同じオブジェクトファイルの中で、数十回から百回程度、利用されることが想定されています。

関数のIDは、プログラミングが始まってから終わりまで、変わることはありません。一度だけ取得して設定しておけば、それをずっと使えます。

このような用途を満たすために、static修飾されたグローバル変数を使いたくなり、実際に、static修飾されたグローバル変数を使ってしまいました。

関連情報