導入
関数を呼ぶたびに「前回どこまで進んだか」を覚えていてほしい、という場面があります。普通のローカル変数は呼び出しのたびに作り直され、関数を抜けると値が消えてしまいます。static を付けるだけで、ローカル変数に「値を覚えたままにする」という特別な性質を持たせられます。
説明
関数内の static ― 値を覚えているローカル変数
#include <stdio.h>
void increment(void) {
static int count = 0; /* 初期化は最初の呼び出し時に1回だけ */
count++;
printf("%d\n", count);
}
int main(void) {
increment();
increment();
increment();
return 0;
}
普通のローカル変数なら increment を呼ぶたびに count は毎回 0 に戻るはずですが、static を付けた変数は関数を抜けても値が消えず、次に呼ばれたときに前回の値から続きます。だから出力は 1・2・3 の3行になります。static なローカル変数の実体は、関数を呼ぶたびに積まれて消えるスタックではなく、プログラムが動いている間ずっと存在する領域に置かれます。
ファイルスコープの static と extern ― 公開する・しないの対比
static は、関数の外(ファイルの先頭など)に書いた変数・関数にも付けられます。この場合の意味は「値を覚える」ではなく、「このファイルの中だけで使える(他のファイルからは見えない)」という内部リンケージです。反対に、前のレッスンで見た extern は「他のファイルにも公開する」という外部リンケージの宣言でした。2つのファイルがある場面を想定すると、対比がはっきりします。
/* util.c */
static int internalCounter = 0; /* このファイルの中だけで使える。他の.cからは見えない */
int publicTotal = 0; /* static を付けなければ、既定で他のファイルにも公開される */
void bump(void) {
internalCounter++;
publicTotal += internalCounter;
}
/* main.c */
extern int publicTotal; /* util.c にある publicTotal を「借りてくる」宣言 */
/* extern int internalCounter; と書いても、util.c 側が static なのでリンクできずエラーになる */
同じ「ファイルの外側で static」でも、関数に付けたときは「このファイルの中だけで呼べる関数」という意味になります。プロジェクト内の他ファイルから呼ばれたくない補助関数に static を付けておくと、うっかり誤って外から使われる事故を防げます。
const ― 書き換え禁止の意図を示す
const を付けた変数は、初期化したあとに値を書き換えることができません。書き換えようとするコードは、実行前のコンパイルの時点でエラーになります。
#include <stdio.h>
int main(void) {
const double TAX_RATE = 0.1; /* この後、TAX_RATE は書き換えられない */
int price = 1000;
printf("%.0f\n", price * (1 + TAX_RATE));
return 0;
}
値そのものは普通の変数と同じですが、「この値はこの先変わらない」という意図をコンパイラと読む人の両方に伝えられるのが const の役割です。関数の引数を const char *name のように書けば、「この関数は name の中身を書き換えません」という約束にもなります。
volatile ― 最適化で消させない
volatile は、これまでの3つとは少し毛色が違います。コンパイラは通常、プログラムの見た目の動きが変わらない範囲で、無駄な読み書きを省略する最適化を行います。しかし、ハードウェアのレジスタや割り込みハンドラが書き換える変数など、「コンパイラの知らないところで値が変わりうる」変数に対してこの最適化をされると、値の変化を見逃してしまうことがあります。volatile int flag; のように付けると、「この変数は毎回メモリから読み直せ、最適化で省略するな」とコンパイラに伝えられます。組み込み開発やハードウェア制御でよく使われる修飾子で、通常のアプリケーションのコードではあまり出てきません。
やってみよう
increment の呼び出し回数を4回、5回と増やして出力がどう続くか確かめましょう。static int count = 0; から static を外すとどうなるか(毎回 1 だけが表示されるようになる)も試してみてください。
演習
static なローカル変数を使って、呼ぶたびに10ずつ増える通し番号を返す関数 nextId(void) を書いてください。1回目の呼び出しで 100、2回目で 110、3回目で 120 を返すようにし、main から3回呼んで、それぞれの戻り値を1行ずつ printf("%d\n", ...) で表示してください。
ヒント1を見る
static int id = 90; のように、最初に返したい値より10小さい値で初期化しておき、関数の中で先に id += 10; してから return id; すると、1回目の呼び出しで 100 になります。
ヒント2を見る
main の中で printf("%d\n", nextId()); を3回書きます。