第28回 変数の詳細2 〜メモリ〜

今回の記事では、Arduinoボードが変数をどのように処理しているのか習得して、変数の扱い方の理解を深めます。、

前回の「疑問」

前回の記事で変数の性質についていろいろ習得しました。特に最後に説明した「グローバル変数」と「ローカル変数」は重要な考え方なのでしっかり理解しておくようにしてください。

グローバル変数はどこの「 { 」「 } 」内にもなく、宣言した行以降どこでも使用できます。

グローバル変数の範囲

ローカル変数は、どこかの「 { 」「 } 」内で宣言され、宣言した行以降、「 } 」まで使用できます。

ローカル変数の範囲

ところで、キッチンタイマーの時間計測に使用する「count」という名前の変数は、ローカル変数で宣言してもグローバル変数で宣言しても問題なく動作します。

上の2つのスケッチは、変数「count」をグローバル変数、ローカル変数両方のパターンで宣言したものですが、どちらも問題なく動作します。

前回の記事の最後で出てきた疑問は、グローバル変数とローカル変数はどのように使い分ければいいのか、というものでした。今回の記事では、Arduinoボード本体が内部でどのように変数を扱っているのか調べて、変数の使い方の理解を深めます。

 

メモリ

以下のように変数を宣言すると、Arduinoボード本体の内部では「count」という名前を変数が用意されます。「count」というタイトルのメモ帳が準備されるイメージでしたよね。

Programming basic 1 28 memo pad

今まで、「変数が用意される」とか「メモ帳が用意される」という表現で説明してきました。これから、Arduinoボード本体の内部では具体的にどのような処理がされているのか確認していきます。

Arduinoボード本体の中には、いろいろなデータを記憶しておく場所があります。その記憶場所は、Arduino Microの場合、以下の部分に入っています。

記憶場所のあるところ

この記憶する場所は「メモリ」と呼ばれています。「メモリ」は日本語で「記憶」という意味ですが、普段から「メモリカード」など「メモリ」という言葉は出てきますのですでに使い慣れてるかもしれませんね。

このメモリは大きく分けて2種類あります。ひとつはスケッチを記憶しておくメモリ、もうひとつは変数を記憶しておくメモリです。

2種類のメモリ

これら2種類のメモリについてもう少し知っておきましょう。

今までの記事で何度もスケッチをArduinoボード本体に送って動作確認しました。Arduino IDEで作成したスケッチをArduinoボード本体に送ると、先ほどのスケッチを記憶するメモリに保存されます。このメモリはArduino Microの電源をOFFにしても内容が保存されています。このように電源を切っても内容が保存されるメモリを「フラッシュメモリ」と呼びます。

もうひとつの変数を記憶するメモリは、スケッチで変数を宣言するとこのメモリに変数が用意されます。「uint8_t count;」とスケッチに書くとArduinoボードが「count」という名前のメモ帳を用意してくれる、という説明をしました。具体的には、この変数を記憶するメモリに変数が用意されます。このメモリは電源をOFFにすると内容は消えてしまいます。このようなメモリを「RAM」「SRAM」、または単に「メモリ」と呼んでいます。

Arduinoボード本体の中には、このように2種類のメモリがありますが、このメモリには大きさがあります。

最初に大きさの単位を確認しましょう。メモリの大きさは「バイト」という単位で数えます。PCを購入する場合にもメモリ容量やSSD容量など出てきますのですでにご存知の方も多いですよね。メモリの最小単位は1バイトで、Arduino Microの場合、フラッシュメモリは32,768バイト、RAMは2560バイトあります。

Arduino Microのメモリ容量

スケッチで変数を宣言すると、RAMに変数が用意されることはわかりました。ところで、変数の型によってその変数に代入できる数字の範囲が異なっていましたよね。実は、変数の型によってRAMを使用するバイト数が異なるんです。そこで、それぞれの変数型でRAMを何バイト使用するのか確認します。

以下は変数型によって必要なRAMのバイト数です。

変数型ごとの必要バイト数

変数の型によって代入できる数字の範囲が異なっていましたが、使用するRAMのバイト数が異なっている、ということがわかりました。もう少しRAMの使われ方を確認していきましょう。

今まで「uint8_t count;」と宣言してcountという変数を使用しました。実際にはArduinoボード内部では以下のようにcountという名前の変数がRAMの1バイトを使用して変数が用意されていたわけです。

RAMの使用例1

この状態でさらに「uint16_t test;」と宣言すると、以下のようにRAMの2バイトを使用してuint16_t型の変数名「test」という変数が用意されます。

RAMの使用例2

だんだん難しくなってきましたが、まずはArduinoボード内部のメモリの構成、変数とRAMの関係をおさえておいてください。

 

グローバル変数/ローカル変数とRAMの関係

スケッチで変数を宣言すると、変数型に応じたバイト数でRAMに変数が用意されることはわかりました。

ところで、変数にはグローバル変数とローカル変数がありましたよね。これら2種類の変数について、RAMに変数が用意されるタイミングについて確認していきます。

「変数が用意されるタイミング」って言われても何だかピンときませんよね。だって変数宣言した時にRAMに変数が用意されるわけですから、「変数が用意されるタイミング」って言っても「変数宣言した時にRAMに用意されます」以上のことはなさそうです。

そこで、キッチンタイマーのスケッチを例に、グローバル変数とローカル変数がRAMをどのように使用しているのか詳しく見ていきます。

最初は以下のように変数「count」をグローバル変数で宣言した場合をみていきます。

グローバル変数のスケッチ

このように変数をグローバル変数として宣言すると、Arduinoボードの内部では、スケッチが動き始めた直後から、電源をOFFにするまでRAMに変数がずっと用意されたままになります。

グローバル変数のRAMのようす

グローバル変数は、宣言した以降の行でいつでも使えるわけですから、いつ使われてもいいように電源が入っている間、ずっとRAMにあります。

次に、以下のように変数「count」をローカル変数で宣言した場合をみていきます。

ローカル変数のスケッチ

このように変数countをローカル変数として宣言すると、スケッチが動き始めた直後はRAMに何も用意されていません。

ローカル変数のRAMのようす

スケッチが動き始めると、Arduinoボードはsetupの部分の命令の処理を始めます。pinMode命令で端子の設定をして、その後スイッチが押されるまで待ちますが、この間もRAMには変数が用意されません。

スイッチが押されると、setupのwhile構文の処理が終わり、loopの部分の繰り返し処理に入ります。loopの最初は「uint8_t count;」で変数の宣言がされますので、ここで初めてRAMにcountという変数が宣言されます。

ここまでの説明では、結局、変数が宣言されたタイミングでRAMに変数が用意される、というだけですよね。

ローカル変数はさらに重要なことがあります。この重要な点を理解するためにキッチンタイマーのスケッチに機能を追加します。

 

メモリの解放

現在のキッチンタイマーのスケッチは、スケッチが動き始めるとスイッチを確認し続け、スイッチが押されたらLEDの点滅を開始します。このような動作ですとスケッチが動き始めたかどうかわからないので、スケッチが動き始めたらLEDを3回点滅させるように機能を追加してみます。以下のようにスケッチに機能を追加してみました。

スケッチ開始時にLEDを3回点滅

このスケッチに出てくる変数を、Arduinoボードがどのように処理をしているか順を追って確認していきましょう。

Arduinoボードがこのスケッチの処理を開始すると、最初にsetup部分を上から順番に処理をしていきます。setupの処理開始時はメモリには何も変数が用意されていません。

最初に33行目と34行目のpinMode命令を実行します。この命名が終わると37行目で変数の宣言がありますので、Arduinoボードはこの時はじめてメモリ(RAM)に1バイトのcountという変数を用意します。

次に39行目から44行目のLEDを3回点滅する処理を実行し、スイッチが押されるまで47行目と48行目を実行します。つまり何もしないで待ちます。

ここでスイッチが押されると、while構文の処理は終わり、49行目の「 } 」でsetup部分が終わります。

先ほどメモリに用意した変数countはsetupの「 { 」と「 } 」の中で有効です。そのため、setupの最後の「 } 」の時に、先ほどメモリに用意した変数countは削除します。

このように、ローカル変数は必要なくなるとArduinoボードはメモリから削除して、元のメモリの状態に戻します。このように必要なくなったメモリの変数を元の状態に戻すことを「メモリを解放する」と呼んでいます。

引き続きこの後の処理をみていきます。

setup部分の処理が終わったため、Arduinoボードはloopの処理に入ります。

loopの最初では53行目で変数の宣言がありますので、Arduinoボードはメモリにcountという名前の変数を1バイト用意します。この変数「count」は先ほどsetup部分で使用した変数と同じ名前ですが、先ほどのcountとは別物です。

先ほどのsetup部分では、LEDを3回点滅処理をした後、変数countの値は3になっていますが、setupが終わるとメモリが解放されて変数countはなかったことになります。その後、loop部分で同じ名前の変数を宣言しても、また別物として用意されます。

このようにローカル変数は、宣言した以降の行で使用できますが、宣言した部分の「 } 」のところでメモリが解放される、という性質があります。

グローバル変数とローカル変数の特徴は以上になります。この特徴を理解した上で変数の使い分けを考えていきましょう。

 

変数の使い方

グローバル変数は、スケッチの動作開始と同時にメモリに用意されて、電源がOFFになるまでずっとメモリにあります。そのため、グローバル変数は特に理由がない限り使用しないようにします。

グローバル変数が必要な場合は、例えばsetup部分で変数を使用した何かの処理をして、その結果をloop部分でも使用する、というようなケースです。このような場合はグローバル変数を使用しないと実現できません。

グローバル変数の例

具体的な例としては、例えばsetup部分でユーザがボタンを押した回数をカウントしておき、loopではその押した回数に応じた処理をする、というような場合です。このような場合はグローバル変数にする必要があります。

キッチンタイマーの時間計測で使用した変数countはloop内で使用するだけですので、グローバル変数ではなく、ローカル変数を使用します。

そうはいってもタイマー時間計測の変数はグローバル変数でも動作するので問題ないのでは、と思ってしまいますよね。そこで極端な例で説明します。

例えば、setup部分とloop部分で多くの変数を利用した処理をする場合を考えてみます。具体的な例として、例えばsetup部分とloop部分の両方で、異なるアラーム音楽を鳴らすために多くのメロディー(音程)のデータが必要だとします。このデータは2,000バイトで、変数に音程のデータを代入してメモリに保存する場合を考えてみます。

Programming basic 1 28 large memory sample

Arduino Microボード本体のRAMは2,560バイトです。Arduinoボードがスケッチの処理を始めると、最初にsetup部分の命令を実行します。setup部分では変数2,000バイト分のメモリが必要になりますので、2,560バイトのRAMのうち、2,000バイト使用します。setupの処理が終わると、Arduinoボードはsetupの最後の「 } 」のところで2,000バイトのメモリを解放します。

次にArduinoボードは、loop部分の処理を開始します。このloop部分でも変数2,000バイトのメモリが必要になりますので、先ほどと同様に2,560バイトのRAMのうち、2,000バイト使用します。loopの処理が終わると、Arduinoボードはloopの最後の「 } 」のところで2,000バイトのメモリを解放します。loopは何度も繰り返し実行しますので、また最初からメモリを使用して、解放して、ということを繰り返します。

それでは、setupで使用するメモリとloopで使用する変数をグローバル変数としたらどうなるでしょうか。setup用の2,000バイトの変数とloop用の別の2,000バイトの変数をグローバル変数として宣言すると、グローバル変数として4,000バイトのメモリが必要になってしまいます。

Arduino Microボード本体のRAMは2,560バイトですので、グローバル変数としてメロディーのデータを用意することはできなくなってしまいます。

このように、理由がない限りローカル変数を使うようにします。

 

Arduino IDEのメッセージ

このメモリの情報については、Arduino IDEが教えてくれています。

キッチンタイマーのスケッチを開いて、検証ボタンを押してみてください。しばらく待っていると、ウインドウの下の方の黒い部分に情報が表示されますよね。

Programming basic 1 28 build message

1行目の「最大28672バイトのフラッシュメモリのうち、スケッチが4328バイト(15%)を使っています」はフラッシュメモリ、つまりスケッチを保存しておくメモリをどのぐらい使っているか、という情報です。

先ほど、Arduino Microのフラッシュメモリは32,767バイトある、と説明しました。でもArduino IDEのメッセージでは28,672バイトと言っています。なぜか4096バイト少ない値です。

これは、Arduino IDEはスケッチで書いたもの以外に、裏でいろいろな処理を追加していてそれが隠れているためです。Arduino Microボードに限らず、どのArduinoでも4096バイトArduino IDE(Arduinoシステム)が裏で使用しているため、フラッシュメモリの値はカタログに書かれたものより4096バイト少なくなっています。

またこのメッセージでは、キッチンタイマーのスケッチは4328バイトあることもわかります。使えるフラッシュメモリのうち15%しか使っていませんので、もっと長いスケッチが書けますね。

2行目の「最大2560バイトのRAMのうち、グローバル変数が149バイト(5%)を使っていて、ローカル変数で2411バイト使うことができます」という部分は、RAMが2,560バイトあって、グローバル変数は149バイト使っていることがわかります。キッチンタイマーのスケッチではグローバル変数は使ってないのに、なぜか149バイトも使っていることになっています。これも先ほどと同様に、Arduino IDE(Arduinoシステム)が149バイト分のグローバル変数を裏で使用しているためこのようになっています。

ところで、ローカル変数は何バイト使うか書かれていませんが、これはなぜかわかりますか?

ローカル変数は、実行している場所によって何バイト使うか変動するため、あらかじめ書くことができないためです。例えばsetup部分ではローカル変数を10バイト、loop部分ではローカル変数を100バイト使用する場合、setupを処理している時とloopを処理している時では必要なローカル変数のメモリが変わりますので、Arduino IDEの検証ボタンを押した時点ではローカル変数は何バイト必要、とは言えないためです。

それでは、最後にsetupとloopで使用している変数countをグローバル変数で宣言した場合、先ほどのメッセージはどうなるか確認してみましょう。

グローバル変数の使用メモリ

変数countはuint8_t型ですのでメモリは1バイト必要です。そのため先ほどの149バイトに1バイト追加した150バイトがグローバル変数で使用されることがわかります。

 

 

更新履歴

日付 内容
2019.9.16 新規投稿