第12回 変数のstatic修飾子

前回作成したプログラムを改良します。

第10回から第12回までの内容は、少し難しくなります。第13回以降は別の内容になりますので、すべて理解できなくても大丈夫です。ご自身で理解できるところまでチャレンジしてみましょう!

目次

前回のプログラムの問題点

前回までの記事で、スイッチを押したらLEDがピカっと1回だけ光るプログラムを作成しました。アルゴリズムの検討をして、それをうまくプログラミングできましたのでまったく問題ありませんよね。あとはスイッチが押されたら、押されたスイッチに対応して文字をPCに送るプログラムを書けば、オリジナルのキーボードが完成します。

でも問題がないのになんで「問題点」なんて話題にするのか不思議ですよね。確かに前回のプログラムで動作上の問題はないのですが、今後ご自身で規模が大きいプログラム作るようになると、ちょっとした問題が出てきます。今回の記事では、その問題点と解決策を習得します。

前回のプログラムをもう一度確認してみましょう。

前回のプログラム問題点

上の図にあるように、変数zenkaiに注目してみると、以下のような分かりづらさがあります。

  • グローバル変数にしているので、変数の宣言はプログラムの最初の方にあり、パッとみただけだとプログラムのどこで使われているのかわからない
  • loop関数だけ見ると、関数内で変数zenkaiが使われているものの、loop関数内にこの変数の宣言がない

問題点のポイントは「変数の宣言」と「変数が使われるところ」が離れているため、プログラムがわかりづらくなっているというところです。

この問題点を解決するには実際に変数が使われるところで宣言するしかありません。ところがloop関数内で宣言すると、loop関数の最後の「 } 」で用意した変数は捨てられてしまいます。loop関数内で宣言しても捨てられない変数って宣言できないのでしょうか。

C/C++言語では、このような時のために文法が用意されています。変数の宣言が関数内であっても、その関数の処理が終わったあと、つまりその関数の最後の「 } 」にきた時でも、変数を捨てないようにしてもらえる文法です。

static修飾子

変数を関数内で宣言すると、その変数の寿命(記憶域期間)はその関数が終わるまででした。

ところが関数内で変数を宣言するときに、宣言の先頭に「static」を付けると、その変数は関数の処理が終わっても捨てずにそのままにされます。

変数のstatic修飾子

この「static」の名称ですが、一般的には「static修飾子」と呼ばれます。

「static」は日本語で「静的」という意味ですが、ちょっとわかりづらいですよね。static修飾子をつけて宣言した変数は、関数の処理が終わってもずっとそのままじっと静かに存在している、ということで「静的」というイメージでしょうか。

一方で関数内の普通の変数は、その関数が終わると捨てられてしまいます。loop関数内の普通の変数は、loop関数が処理されるたびに用意されては捨てられる、という感じで動いてる感じですよね。そのような変数は「動的」と呼ばれています。

あと「修飾子」というとなんだか小難しい言葉ですが、「修飾」というのは上の例で説明すると、元々の宣言uint8_t zenkai;に対して修飾している、と考えたのでしょう。

日本語で考えると、例えば「きれい」という言葉に「とても」をつけると「とてもきれい」という言葉になります。この「とても」は日本語の文法では「修飾詞」と呼ばれていましたよね。「static」も変数宣言を修飾する、というイメージでこの言葉が使われているのだと思います。

なお、この「static」ですが、正式には「記憶域指定子」または「記憶域クラス指定子」と呼ばれています。なんだか疲れてしまいますね。ネットで検索するとわかりますが、ほとんど「static修飾子」と呼ばれていますので、こちらは忘れてしまっても大丈夫です。

それでは、実際に前回のプログラムを変更してみましょう。前回のプログラムの変数zenkaiの宣言をグローバル変数ではなく、loop関数内に移します。この変数は関数の処理が終わってもそのままにして欲しいので、以下のように先頭に「static」をつけます。

static uint8_t zenkai;

それでは、static修飾子を使用したloop関数を確認してみましょう。3行目で変数zenkaiをstaticとして宣言しています。

void loop() {
  
  static uint8_t zenkai; // 前回のスイッチの状態を保存しておく変数
  uint8_t konkai;

  // 左スイッチ状態を読み取る
  konkai = digitalRead(SWITCH_HIDARI);

  // スイッチ状態が、前回OFF、今回ONの時、スイッチが押されたと判断する
  if( (zenkai == OFF) && (konkai == ON) ) {
    // LEDを点灯
    digitalWrite(LED_BLUE, HIGH);
    // 一定時間待つ
    delay(TENTOU_JIKAN);
    // LEDを消す
    digitalWrite(LED_BLUE, LOW);
  }

  // 次回の処理のために、今回の値を前回の値として保存
  zenkai = konkai;
  
}

これでプログラムの変更は終わりました。

ところで、ちょっと気になるところがありませんか? loop関数はArduino Microの電源が入っている間、何度も何度も繰り返し実行されます。変数konkaiは、普通の変数宣言ですので、uint8_t konkai;で変数が用意されて、loop関数の最後の「 } 」でその変数は捨てられます。Arduino Microは、loop関数を処理するたびに、変数を用意して、それを捨てる、ということをしています。

一方、変数zenkaiは一度用意されたらloop関数の最後の「 } 」がきても捨てられることはなく、ずっと存在し続けます。でもloop関数は何度も何度も繰り返されるので、何度も何度もstatic uint8_t zenkai;が実行されてしまいますよね。なんだか心配…って感じがしませんか?

でも安心してください。Arduino Microはstaticの意味をちゃんとわかっていますので、内部では以下のように処理をしていますので大丈夫です。

  • 1回目のloop関数の処理のときにstatic uint8_t zenkai;の指示通り、ずっと存在する変数zenkaiを用意する
  • 2回目以降のloop関数の処理では、すでに1回目の処理で変数zenkaiを用意したので、この宣言は無視する

このような動作をしますので、安心して大丈夫です。

static変数の初期値

ところで、勘の鋭い方でしたら気付かれたかもしれません。普通の変数の宣言をした場合は、適当な数字が書かれた変数が用意される、と説明しました。今回変更したプログラムでは、static uint8_t zenkai;とただ宣言したので、適当な数字が用意されてしまうのではないか、という心配があります。

実は、static修飾子をつけた変数宣言の場合、0が代入されたstatic変数が用意されます。上のプログラムの例では、1回目のloop関数を処理するときに、0が代入されたstatic変数zenkaiが用意されます。

でも今回使用する変数zenkaiは、スイッチはOFFから始まりますので数字としては「1」が代入された変数を用意して欲しいところです。

このようにstatic変数を宣言するときに、最初の値を指定する方法も用意されています。

static uint8_t zenkai = 1;

このように宣言するときに「=」記号で値を代入すると、その値が代入されたstatic変数が用意されます。このように書いても、この変数宣言が実行されるのは、1回目のloop関数の処理の時で、2回目以降はこの宣言は無視されますので、loop関数の処理のたびに毎回1が代入されることはない、という点に注意してください。

staticのその他の働き

今回の記事では、変数宣言の修飾子として使用する「static」を説明しました。

これから他の人が作ったプログラムを見る機会もあると思いますが、この「static」は関数にも使用されます。関数の前に「static」をつけるわけですが、その時の「static」は今回説明したstatic変数とは別の意味を持ちますので注意してください。

関数に「static」をつけた場合の働きについては基礎編の範囲を超えますので説明は省略しますが、関数にstaticがついているプログラムを見かけた場合、変数のstaticと混同しないように注意してください。今回の説明したstaticの意味は、単に変数宣言の前に修飾した場合に限ります。

static修飾子を使用したスイッチ検知スケッチ

それでは、static修飾子を使用してスケッチをまとめましょう。以下のプログラムが問題なく動作するか実際に確認してみてください。

/*
 *  スイッチ制御プログラム改良版
 *  内容: スイッチを押すとピカッとLEDを光らせる
 */

// 青色LEDのピン接続番号
#define LED_BLUE  12

// 左側スイッチのピン接続番号
#define SWITCH_HIDARI  23

// スイッチの状態
#define OFF 1
#define ON  0

// 点灯時間(単位:ms)
#define TENTOU_JIKAN 50

void setup() {
  // LEDのピンを出力に設定
  pinMode(LED_BLUE, OUTPUT);

  // スイッチのピンを入力に設定
  pinMode(SWITCH_HIDARI, INPUT_PULLUP);

  // 最初はLEDをOFFに設定しておく
  digitalWrite(LED_BLUE, LOW);
}


void loop() {
  
  static uint8_t zenkai = OFF; // 前回のスイッチの状態を保存しておく変数
  uint8_t konkai;

  // 左スイッチ状態を読み取る
  konkai = digitalRead(SWITCH_HIDARI);

  // スイッチ状態が、前回OFF、今回ONの時、スイッチが押されたと判断する
  if( (zenkai == OFF) && (konkai == ON) ) {
    // LEDを点灯
    digitalWrite(LED_BLUE, HIGH);
    // 一定時間待つ
    delay(TENTOU_JIKAN);
    // LEDを消す
    digitalWrite(LED_BLUE, LOW);
  }

  // 次回の処理のために、今回の値を前回の値として保存
  zenkai = konkai;
  
}

更新履歴

日付 内容
2021.9.12 新規投稿
通知の設定
通知タイミング
guest
0 コメント
本文中にフィードバック
全てのコメントを見る
目次