第48回 補足 – 変数

今回は変数の扱いについて補足します。

目次

変数の復習

今までの記事で、変数の性質について詳しく説明していませんでしたので、この記事では変数についてもう少し掘り下げます。

C/C++言語では以下の変数型が用意されていましたよね。

変数の型

また、上の型の名前とは別に、変数のサイズ(メモ帳の大きさ)を数字で表現した変数型の書き方もありましたよね。

変数の型(stdint型)

この入門シリーズでは「uint8_t」などの変数型で宣言しますが、「unsigned char」などの変数型を使う人もいますので、両方の変数型を頭に入れておきましょう。

ところで、変数型によって扱える数字の範囲が異なりますが、そもそも数字の範囲を超えたらどうなるのでしょうか。

Arduino IDEのことだから、また例のごとく大怒りしそうですよね。命令の名前を1文字間違えただけでも大怒りするArduino IDEですよ。それが、変数型の数字の範囲を超えたら、それはもう大怒りするに違いありません。

この記事では、いくつかスケッチを作成して、変数の性質を調べていこうと思います。

変数型の範囲を超えたらどうなるか

変数の範囲を超えたらどうなるのか、以下の2つの観点で性質を調べてみたいと思います。

  1. 数値範囲をオーバーした場合どうなるか?
    例えば「uint8_t」型の変数は0〜255の範囲の数字しか扱うことができません。この変数に300を代入したり、「200 + 200」などのように結果が255を超えるような計算をするとどうなるのでしょうか。
  2. プラスの範囲の変数にマイナスを代入したらどうなるか?
    「uint8_t」型の変数はプラスの数しか扱えませんが、-100などのマイナスの数字を代入したり、「100 – 200」などのように結果がマイナスになるような計算をするとどうなるのでしょうか。

実際にこれらのことを確認するスケッチを作成して、Arduino Microで試してみましょう。

1. 数値範囲をオーバーした場合どうなるか?

まずはuint8_t型(0〜255)の変数に「300」を代入して、その変数をシリアルモニタに表示するスケッチを作成します。以下のように作成してみました。

/*
 * 変数の性質を確認する
 */

void setup() {

  // シリアルモニタ準備
  Serial.begin(9600);
  while(!Serial){
  }

  // 変数宣言
  uint8_t test;

  // testは0〜255なのに300を代入してどうなるか試してみる
  test = 300;

  // シリアルモニタに変数testの値を表示する
  Serial.print("変数testの値: ");
  Serial.println(test);
  
}

void loop() {
  // loop関数では何もしない
}

uint8_t型の変数「test」を宣言して、「test = 300;」というように扱える範囲を超える数字を代入してみました。さらにその値をシリアルモニタに表示してみます。命令を1文字間違えても怒るArduino IDEですので、まぁ、これも怒られるのでしょう。チェックボタンでスケッチに問題ないか確認してみましょう。

uint8_t型変数に300を代入

あれ? uint8_t型の変数に300を代入したら怒られるかと思ったら、「コンパイルが完了しました」と表示されて特に何も問題なくスケッチのチェックが終わりました。

これはかなりまずい状況です。というのは学生の皆さんはまだ経験がないかもしれませんが、いつもちょっとしたことで怒っている人が、明らかに怒る状況なのに怒らない、というのは怒りを通り越して別の次元にいってしまった、ということなんです。この状況になると謝ってもどうにもならない状況、というのが私の人生経験上、確実に言えることです。

それはさておき、Arduino Microの中では何が起こっているのでしょうか。上のスケッチはシリアルモニタに数字を表示するようにしていますので、シリアルモニタで変数「test」の値を確認してみましょう。

uint8_t型数値オーバーの結果

300を代入すると結果は44でした。これはどういうことなのでしょうか。

このことを理解するために、別のスケッチを作成して動作を確認してみます。

/*
 * 変数の性質を確認する
 */

void setup() {

  // シリアルモニタ準備
  Serial.begin(9600);
  while(!Serial){
  }

  // 変数宣言
  uint8_t test = 0;

  while( true ){
    // シリアルモニタに変数testの値を表示する
    Serial.println(test);

    // testの値を1増やす
    test++;

    // 100ms待つ
    delay(100);
  }

}

void loop() {
  // loop関数では何もしない
}

このスケッチはuint8_t型の変数testを使って、1を足しながら結果をシリアルモニタに表示する、という内容です。シリアルモニタには以下のよう0、1、2…と表示されるはずです。

0
1
2
3
4
5
6
7
.
.
.

変数testの範囲は0〜255ですので、255まで足し算したら次はどうなるかその部分を確認すると、

uint8_t型の255+1の結果

255に1を足すと0になっています。つまり、変数はその範囲を超えると最初に戻って0にしてしまうんです。先程のスケッチでは、uint8_t型のtestに300を代入しましたが、これは、255を超えています。255より大きい数はどうなるかというと、256は変数testにとっては0、257は1、ということでこれを続けていくと300は44になります。最初のスケッチの結果が44というのはこのような仕組みだったんです。

この例では「test = 300;」と直接300を代入しましたが、以下のように255を超える計算をしても、超えた分は0からカウントされます。以下の計算結果も44になります。

test = 100 + 200;

uint8_t型の変数に300を代入してもArduino IDEは怒りませんでした。実はこれは本当に怖いことなんです。プログラミングをしているといろいろな不具合に遭遇(そうぐう)します。不具合にはいろいろな原因がありますが、そのうちのひとつがこの変数の範囲を超えてしまうケースです。この記事の最後にミニチャレンジ課題があります。課題のスケッチはうまく動かない状態になっています。扱える数字の範囲を頭に入れて課題を解いてみてください。

ということで、変数の数値範囲をオーバーした場合このようなことがおきますので、プログラミングする場合は数字の範囲に十分注意するようにしましょう。

2. プラスの範囲の変数にマイナスを代入したらどうなるか?

次に、uint8_t型の変数にマイナスの数字を代入してみましょう。

次のようなスケッチを作成してみました。

/*
 * 変数の性質を確認する
 */

void setup() {

  // シリアルモニタ準備
  Serial.begin(9600);
  while(!Serial){
  }

  // 変数宣言
  uint8_t test = -1;

  // シリアルモニタに変数testの値を表示する
  Serial.print("変数testの値: ");
  Serial.println(test);

}

void loop() {
  // loop関数では何もしない
}

このスケッチはuint8_t型、つまり0〜255の数字を代入できる変数testに「-1」を代入してその値をシリアルモニタで確認する、という内容です。先程の数値範囲オーバーの時と同じく、スケッチをチェックしても何も指摘されません。

このスケッチをArduino Microボードに送ってシリアルモニタで結果を確認すると、

uint8_t型のマイナス代入

またまた不思議な結果になりました。-1を代入したのに結果は「255」です。なぜこのような結果になるのか、その背景を確認しておきましょう。

uint8_t型、int8_t型は両方とも256個の数字を扱うことができます。uint8_t型とint8_t型の変数が扱う数字を以下のような256個のマスがあるの表で考えてみます。

uint8_t・int8_t型変数

uint8_t型の変数は、この256個の数字のマスに0〜255の数字を割り当てています。

uint8_t型変数

一方でint8_t型の変数は、この256個の数字のマスに-128 〜 +127の数字を以下のように割り当てています。ちょっと並び方が変な気がしますよね。この理由を説明するには「2進数」というコンピュータ内部の理解が必要ですので、すみません、ここではこのようになっている、と理解しておいてください。

int8_t型変数

uint8_t型とint8_t型の数字の割り当てを並べると、以下のようになります。

uint8_tとint8_tの対応

0〜127の数字に関しては、uint8_t型もint8_t型も意味は同じですが、uint8_t型の128〜255は、int8_t型ではマイナスの数字の意味になってしまうんです。

つまり、uint8_t型の変数に「-1」を代入しても、uint8_t型にとっては「255」という意味なので、先程のスケッチではシリアルモニタに「255」と表示されてしまったわけです。マイナスの数字の扱いについてはちょっとややこしいですね。

使用する変数型

Arduinoの公式サイトにはArduinoの命令、関数のマニュアルがあります。そのマニュアルにはサンプルスケッチが掲載されていますが、多くのスケッチでは「int型」の変数が使用されています。

公式サイトの変数型はint型なので、int型を使うことが推奨されているのかな、と思ってしまいますよね。そこでスケッチで使用する変数型について補足します。

結論としては

  • 普通に変数を宣言する場合はint型で問題なし
  • ただし、必要な時に適切な変数型を使用できるように、変数型は常に意識すること

という感じです。それでは詳しく説明します。

このシリーズで使用している「int8_t」や「uint8_t」などは、扱える数字の範囲やプラス、マイナスの扱いに充分注意する必要があります。例えばマイナスの数字を扱う変数なのに「uint8_t」型で変数を宣言してしまうと、スケッチがうまく動きません。

一方で、変数を宣言するときに特に深く考えずに「int」型を使用すれば、「-32768 〜 32767」の数字を扱えるのでまず問題が起きることありません。

ということは、「int8_t」型や「uint8_t」型など、間違えると問題が起きる変数型を使うよりも、全部「int」型にしてしまった方が楽だし安全ですよね。

とはいっても、常に「int」型使うと問題が発生することがあります。「int」型はメモリを2バイト使用します。一方で「int8_t」型は1バイト使用します。例えば、以下のように何かのデータを記録するために配列を用意したとすると、

int data1[2000];  // 2000 x 2 = 4000バイト必要
int8_t data2[2000];  // 2000 x 1 = 2000バイト必要

というように、int型はint8_t型の2倍のメモリを使用してしまいます。記録する数字が気温の測定データなどのように「-128〜127」で収まるような場合、int8_t型で十分です。int型は「-32768 〜 32767」の範囲を扱えますが、ちょっともったいないですよね。

Arduino Microのメモリは約2500バイトしかありません。このように多くのデータを扱う場合は、変数型を意識しないとArduinoの能力を十分に活用することができなくなってしまいます。

以上のことから、Arduinoの公式サイトに掲載されている参考スケッチは「int」型の変数を使用してより安全なスケッチにしています。

このシリーズでは、間違えると問題が起きやすい変数型を使用していますが、変数型の理解を深めるために「int」は使用せず、「uint8_t」や「int8_t」などを使用しています。

ミニチャレンジ課題

変数の範囲について、実際のケースを確認してみましょう。

以下のスケッチは1秒に1回LEDを光らせて、5分経過したらブザーを鳴らすスケッチです。残念ながらこのスケッチはLEDの点滅を永遠に繰り返してブザーが鳴ることはありません。

このスケッチが正しく動作するように修正してみてください。

/*
 * ちゃんと動かない5分タイマー
 *    スタートボタンを押してタイマーをスタートすると
 *    時間になったらブザーが鳴るはずだけど、
 *    全然カウントが終わらないスケッチ
 */

// 秒表示LEDのピン番号
#define PIN_BYOU 12

// スピーカーのピン番号
#define PIN_SPEAKER 18

// スイッチのピン番号
#define PIN_SWITCH 23

void setup() {

  // LED接続ピンの設定
  pinMode(PIN_BYOU,   OUTPUT);

  // スピーカー接続ピンの設定
  pinMode(PIN_SPEAKER, OUTPUT);

  // スイッチ接続ピンの設定
  pinMode(PIN_SWITCH, INPUT_PULLUP);

  // LEDを消しておく
  digitalWrite(PIN_BYOU,   LOW);

}


void loop() {

  // 時間カウント用の変数
  uint8_t counter;

  // スタートスイッチが押されるまで待つ
  while( digitalRead(PIN_SWITCH) == 1 ) {
  }

  // タイマーカウント開始
  // 5分 = 60秒 x 5 = 300秒カウントする
  for( counter = 0; counter < 300; counter++ ) {
      digitalWrite(PIN_BYOU, HIGH);
      delay(50);
      digitalWrite(PIN_BYOU, LOW);
      delay(950);
  }

  // 時間になったらアラーム音を鳴らす
  while( true ) {
    tone(PIN_SPEAKER, 440);
    delay(60);
    noTone(PIN_SPEAKER);
    delay(60);
    tone(PIN_SPEAKER, 440);
    delay(60);
    noTone(PIN_SPEAKER);
    delay(60);
    tone(PIN_SPEAKER, 440);
    delay(100);
    noTone(PIN_SPEAKER);
    delay(900);
  }


}

更新履歴

日付 内容
2020.1.25 新規投稿
2021.8.27 新サイトデザイン対応
2022.2.18 誤字訂正
通知の設定
通知タイミング
guest
0 コメント
本文中にフィードバック
全てのコメントを見る
目次