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

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

目次

前回の記事の「疑問」

前回の記事で変数の特徴についていろいろ習得しました。

特に最後に説明した「グローバル変数」と「ローカル変数」はとても重要な考え方なので、今回の記事で理解を深めていきたいと思います。


説明に入る前に、前回の記事の「疑問」をもう一度確認しておきたいと思います。(前回の記事内容になりますので、このセクションは読み飛ばしていただいて構いません)

グローバル変数

グローバル変数は、「宣言した直後から、スケッチの最後まで使用できる」という特徴がありました。

ローカル変数

ローカル変数は、「宣言した変数が属する『 } 』まで使用できる」という特徴がありました。

グローバル変数とローカル変数に関する疑問

上の例で示した2つのスケッチは、変数countをそれぞれグローバル変数、ローカル変数で宣言したものですが、どちらも問題なく動作します

そうであれば、スケッチで使用する変数は最初の方でまとめて宣言してしまえばどこでも使用できるので、すべてグローバル変数で宣言してしまえば良いのでは?という疑問がありました。

ローカル変数で宣言すると、その変数が使用できるところは限られます。使用できないところで変数を操作しようとすると、例のごとくArduino IDEに怒られてしまいます。

でも実際のスケッチでは、グローバル変数とローカル変数は使い分けをします。

そこで今回の記事では、Arduinoボード本体が内部でどのように変数を扱っているのか調べて、グローバル変数とローカル変数をどのように使い分けるのが良いのか、感覚を身につけようと思います。

メモリ

Arduinoボード内部のようす

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

今まで、「変数が用意される」とか「メモ帳が用意される」という表現を使ってきました。

これから、Arduinoボード本体の内部では具体的にどのような処理がされているのか確認していきます。


Arduinoボード本体の中には、いろいろなデータを記憶しておく場所があります。その記憶場所は、Arduino Microの場合、次のようにボードの中央付近にある黒い四角い部品の中にあります。この部品はArduinoボードの中心的な役割を果たしていて「マイコン」(マイクロコンピュータの略)と呼ばれる部品です。

記憶場所のあるところ

データを記憶する場所」は「メモリ」と呼ばれています

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

メモリの種類

「メモリ」は大きく分けて2種類あります

ひとつは「スケッチを記憶しておくメモリ」、もうひとつは「変数を記憶しておくメモリ」です。

2種類のメモリ

これら2種類のメモリについてもう少し詳しく中身を見ていきましょう。

スケッチを記憶するメモリ

今までの記事で何度もスケッチをArduinoボード本体に送って動作確認しました。

Arduino IDEで作成したスケッチをArduinoボード本体に送ると、先ほどの「スケッチを記憶するメモリ」に保存されます

このメモリはArduino Microの電源をOFFにしても内容が保存されています

このように電源を切っても内容が保存されるメモリを「フラッシュメモリ」と呼びます。

一般的なPCでいうところの「SSD」に相当します。PCでアプリケーションをインストールすると「SSD」に記録されますよね。「SSD」は電源を切ってもデータは保存されるように、Arduinoボードの「フラッシュメモリ」も電源を切ってもスケッチは保存されます。

変数を記憶するメモリ

スケッチで宣言した変数は、「変数を記憶するメモリ」に保存されます。

今まで、「uint8_t count;とスケッチに書くとArduinoボードが「count」という名前のメモ帳を用意してくれる」という説明をしてきました。

uint8_t count;のArduinoボード内部の実際の動きとしては、「変数を記憶するメモリ」に変数を記憶しておく場所が用意されます

このメモリは電源をOFFにすると内容は消えてしまいます。このようなメモリを「RAM」と呼んだり、単に「メモリ」と呼んでいます。

2種類のメモリまとめ

ここまで、Arduinoボード内部にある2種類のメモリを確認してきました。ここで一度これらのメモリをまとめておきましょう。

メモリの種類利用目的性質
フラッシュメモリスケッチを記憶する電源OFFでも記憶している
RAM変数を記憶する電源をOFFにすると記憶内容は消える

メモリと変数の大きさ

一般的なPCのカタログなどを見ると「SSD:1TB、メモリ:16GB」などの説明があるのを見たことがあると思います。

同じようにArduinoボードのメモリの大きさ(容量)があります。また、変数もメモリ(RAM)に記憶されますので大きさ(サイズ)があります。そこで、次はメモリや変数の大きさについて確認します

メモリの大きさ

Arduinoボード本体の中には、フラッシュメモリとRAMの2種類のメモリがありますが、このメモリには大きさ(容量)があります。

最初に容量の単位を確認しましょう。メモリの容量は「バイト」という単位で数えます

PCのカタログなどで、SSD容量やRAM容量などのスペックで「バイト」という言葉が出てきますので、すでにご存知の方も多いですよね。

メモリの最小単位は1バイトで、Arduino Microの場合、フラッシュメモリは32,768バイト、RAMは2560バイトあります

Arduino Microのメモリ容量

PCの場合とくらべてずいぶん少ないですよね。PCのメモリで例えば16GB(16ギガバイト)の場合、バイトに直すと16,000,000,000バイトなります。Arduino MicroのRAMは2560バイトですので、PCのメモリは625万倍の容量です。なんだか想像つきませんね…

変数の大きさ

スケッチで変数を宣言すると、Arduinoボード内部ではRAMに変数を記憶する場所が用意されることを説明しました。

変数は、変数型(メモ用紙のサイズ)によって、その変数に代入できる数値の範囲が異なっていましたよね。

予想つくかもしれませんが、変数の型によって使用するRAMの大きさ、バイト数が異なっています。そこで、それぞれの変数型でRAMを何バイト使用するのか確認します。

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

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

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

もう少しRAMの使われ方を確認していきましょう。

今までuint8_t count;と宣言してcountという変数を使用しました。

uint8_t型の変数をRAMに記憶する場合、上の表を見ると1バイト必要なことがわかります。

Arduinoボード内部では次のようにcountという名前の変数を記憶するための場所が、RAM上に1バイト分用意されます。

RAMの使用例1

この状態でさらにuint16_t test;と宣言すると、次のようにRAM上に2バイト分のtestという名前の変数を記憶する場所が用意されます。

RAMの使用例2

いろいろな言葉や概念が出てきてちょっと難しくなってきましたよね。

これから、今まで出てきた言葉を使って、「グローバル変数・ローカル変数」と「RAM」の関係を見ていきます。

今回の記事では、これ以上新しい言葉は出てきませんのでご安心ください!

「グローバル変数・ローカル変数」と「RAM」の関係

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

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


「変数が用意されるタイミング」って言われても何だかピンときませんよね。

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

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

グローバル変数とRAM

変数countをグローバル変数で宣言した場合、ArduinoがRAMをどのように使うのかみていきます。

次のスケッチでは、変数countはどこの「 { 」「 } 」にも属していませんので、変数宣言直後からスケッチの最後まで使用できる「グローバル変数」です。

このように変数をグローバル変数として宣言すると、どのタイミングでRAMに記憶場所か用意されるか考えます。

Arduinoボードは、電源がONになって動作を開始すると最初にsetupの指示を実行します。

変数countに注目すると、この変数はsetupでもloopでもどこでも使用できる状態になっている必要がありますよね。

つまり変数countはsetupより前にRAMに記憶場所を用意しておく必要があるんです。別の言い方をすると変数countはArduinoボードの動作開始時にすでにRAMに用紙しておく必要があります

また、変数countは電源をOFFにするまでRAMに記憶する場所が用意されたままになります

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

グローバル変数は、Arduinoの動作開始直後から電源がOFFになるまで、RAMにずっと記憶場所が用意されている!

ローカル変数とRAM

次に、ローカル変数について見てみましょう。

変数が用意されるまで

次のように変数countをローカル変数で宣言した場合で考えていきます。

このように変数countをローカル変数として宣言すると、スケッチが動き始めた直後はRAMに何も用意されていません。(変数countはforの処理を実行するときに、RAMに記憶場所が用意されるためです)

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

スケッチが動き始めると、Arduinoボードはsetupの処理を始めます。

setupの処理では、pinModeで端子の設定をして、その後スイッチが押されるまで待ちます。

ここまでの処理では変数宣言は出てきていませんので、この段階でもRAMには変数の記憶場所は用意されません。


スイッチが押されると、setupwhileの処理が終わり、loopのの処理に入ります。

loopではforを処理しますが、forの初期化の部分のuint8_t count;で変数の宣言がされています。ここで初めて次のようにRAMにcountという変数が宣言されます

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

ここまでをまとめると、ローカル変数は宣言されたタイミングではじめてRAMに変数が用意される、ということです。

ではローカル変数の記憶場所は、いつまでRAMに用意されているのでしょうか?

変数が不要になったとき

ローカル変数countの使える範囲は、「forの初期化部分で宣言してから、forの最後の}まで」です。

裏を返せば、上の図にあるように、「forの最後の}」から先では変数countは使えません。つまり変数countは不要な範囲になります

そこで、Arduinoボードは、「forの最後の}」で、不要になったローカル変数countの記憶場所を消し去ります。この「RAMから記憶場所を消し去る」ことを「メモリを解放する」と呼ぶこともあります。メモリ解放後は次のように変数countの記憶場所はなかったことになります。

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

ここまでの内容をまとめると、ローカル変数は宣言したときにRAMに記憶場所が用意されて、変数が所属する}にくるとメモリが解放されることになります


グローバル変数とローカル変数の特徴は以上になります。

「グローバル変数・ローカル変数」と「RAM」のまとめ

今までの内容をまとめておきますね。

変数の種類RAMの記憶場所
グローバル変数Arduinoボード動作開始時に記憶場所が用意され、電源OFFまでずっと場所が確保されたままになる
ローカル変数変数宣言時に記憶場所が用意されて、変数が所属する}のタイミングで解放される

グローバル変数とローカル変数の使い分け

長々とグローバル変数とローカル変数の特徴についてみてきましたが、今度は使い分けについて確認していきましょう。

使い分けには絶対的なルールはないのですが、次のルールを目安にすると良いと思います。

グローバル変数とローカル変数の使い分け
  • グローバル変数にする理由がない限り、極力ローカル変数を使用する
  • ローカル変数を使う場合は、なるべく使う直前で宣言する

それぞれの使い方の目安について、その背景などを確認していきましょう!

❶ グローバル変数にする理由がない限り、極力ローカル変数を使用する

グローバル変数は、Arduinoボードの動作開始直後にメモリに用意されて、電源がOFFになるまで、記憶場所がずっとメモリに用意されたままになります。

つまり、グローバル変数が必要ない時でもずっとメモリを占有してしまうわけです。

そのため、グローバル変数は特に理由がない限り使用しないようにします。


それでは、グローバル変数にする必要がある場合、というのはどのようなケースなのでしょうか?

例えば次のようにsetupで変数を使用した何かの処理をして、その結果をloopでも使用する、というようなケースです。

グローバル変数の例

具体的な例としては、例えばsetupでユーザがボタンを押した回数をカウントしておき、loopではその押した回数に応じた処理をする、というような場合でしょうか。(あまり具体性がなくてすみません…!)

このような処理をしたい場合はグローバル変数を使用しないと実現できません。

もしsetupで変数宣言してしまうと、setupの最後の}でその変数の記憶場所は解放されてしまいます。loopの処理をするときには、RAM上にはその変数の形跡すらありません。


ここまでは「グローバル変数にする理由がある」ケースでした。

でも次のスケッチでは、変数countはローカル変数で十分ですが、グローバル変数でも問題なく動作します。

この例では確かにグローバル変数でも大きな問題はありません。

でも、グローバル変数にする必要がないものをグローバル変数にすると、動作しないことがあります。

そこで、具体例で説明します。

例えば、setuploopで多くの変数を利用した処理をする場合を考えてみます。

ちょっと無理矢理感がありますが、具体的な例としてsetuploopの両方で、異なるアラームメロディーを鳴らすために、多くの音程データが必要だとします。

このデータはそれぞれ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は何度も繰り返し実行しますので、また最初からメモリを使用して、解放して、ということを繰り返します。

グローバル変数で宣言した場合

それでは、setuploopで使用する変数をグローバル変数としたらどうなるでしょうか。

setup用の2,000バイトの変数とloop用の別の2,000バイトの変数をグローバル変数として宣言すると、グローバル変数として合計4,000バイトのメモリが必要になってしまいます。

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

つまり、Arduino Microで動作しないスケッチになってしまいます。


このように、特にグローバル変数にする必要性がない限り、なるべくローカル変数を使うようにします。

❷ ローカル変数を使う場合は、なるべく使う直前で宣言する

次のルールも絶対ではありませんが、ローカル変数はなるべく使う直前に宣言したほうが良い、というお話です。

LEDを一定回数点滅するように、スケッチは次のように書きました。

まさに使う直前に変数countを宣言しています。

でも、次のように変数countを使う場所から離れたところで宣言してみます。

特に問題はありませんよね。

でもこのスケッチを初めてみた場合、例えば52行目から始まるforでは変数宣言がありません。となると、forで使用している変数countはどのような型か知りたい場合、どこで宣言しているのか探す必要がありますよね。(検索機能を使えば探すことができますが、やはりちょっと面倒ですよね)

このような背景から、ローカル変数はなるべく使う直前に宣言したほうが、あとになってスケッチを読み返すときや、他の人がスケッチを読むときに、(少し)読みやすくなる、というメリットがあります。

Arduino IDEのメッセージ

最後に、Aruduino 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新規投稿
2021.8.25新サイトデザイン対応
2024.12.9説明内容補足
Arduino IDE2対応
通知の設定
通知タイミング
guest
0 コメント
新しい準
古い順 一番投票が多い
本文中にフィードバック
全てのコメントを見る
目次