MQL4に限った事ではありませんが、変数配列の基本を抑えていないと致命的な不具合が発生します。
具体的にはバッファオーバーフローによる不具合ですが、IT企業で実際にプログラミングしているプログラマーでさえこの不具合を頻繁に発生させています
それだけ理解していない人が多いという事です(IT企業は慢性人手不足の為、未経験者の外注がわけも分からずプログラミングしているという問題もありますが)。
MQL4プログラミングで変数配列を使うことは避けて通れない為、初心者の方は変数配列の基本を必ず理解して下さい。
このページの内容が理解出来なかった人が多数いるみたいですので補足説明です。
なるべくプログラミング未経験者でも分かり易いように図解付きで簡単に説明していますが、全く理解できないかもしれません。
大事な所なので本当はちゃんと理解してから先に進んで欲しいのですが、もし理解できなかったらとりあえず斜め読みだけしておいて下さい。
次のページ以降から動的配列という言葉が出てきた時に「なんか F2.変数配列について のページで説明されてたような・・・」と思い出して、その都度見直して頂ければ大丈夫です。
多分、実際にカスタムインジケータを作成しながら再度見直した時に理解できると思います。
本題(カスタムインジケータの作成)に辿り着く前にこんな所で挫折して欲しく無いので、
理解できなかったら今はとりあえず斜め読みだけで大丈夫です。
こんな序盤で挫折しないで先に進んでみて下さい。
バッファオーバーフローの例
バッファオーバーフローは初心者の頃はよくやらかしてしまうと思います。
人様や企業からお金を頂いて作っているプログラムでは無いので、初心者は気にしないで大丈夫です。
MT4のログで発生箇所を教えてくれるので、自分で修正して問題解決出来るようになればOKです。
ただ、もし作ったインジケータ等を有料公開して、バッファオーバーフローを発生させたらもの凄く叩かれると思います。
無料公開なら「ごめんね、すぐ直すね、てへぺろ (・ω<) 」で済みますが、有料の場合は金額の大小に関わらず責任が伴います。
もし有料公開するなら責任を持って動作テストをしっかり行う必要があります
(世の中には納期優先で不具合が大量にある事を知っていながら揉み消して発注元にソフトを納品してしまう凶悪な企業が実在しますが、
そんなIT企業みたいにはならないようにしましょう)。
■MT4のエラーログ
MT4はこのようにバッファオーバーフローが発生している場所を親切に教えてくれます。
ログをみると、ソースコードの17行目の12列目で配列範囲外アクセス(array out of range)のエラーが発生している事を教えてくれます。
■バッファオーバーフローのソースコード
17行目の12列目で test_arr配列の範囲外のメモリ領域に0を代入しようとしています。
プログラミング未経験者の人は、今時点では何がなんだか分からないと思います。
このページの内容を最後まで読み終わると、17行目の12列目のソースコードの何が間違いなのか分かると思います。
変数宣言について
変数配列の説明の前に、
まずは変数宣言について説明をします。
何故こんな初歩的な説明をするかというと、実際にIT企業で客先常駐請負で常駐している外注プログラマーが、
「えっ?変数宣言って何!?」という耳を疑うような事を聞いてきたからです。
日常生活で身の回りにある電子機器やスマホアプリのプログラムは、こういう初心者外注プログラマーがわけも分からずプログラミングしているので、この事実を知ってしまうとあらゆる製品を使いたくなってしまいます。
変数宣言の例:
int temp_int;
これは
int型の変数で、temp_intという名称の変数を宣言しています。
変数を使う為にはこのように変数の名称と、変数の型を宣言しなければなりません。
int型というのは整数の値を扱う型です。
temp_intはプログラマーが任意で決めます、好きな名前を付けて下さい。
ちなみに
intはintegerの略で、integerは整数を意味します。
この変数というものはデータを保管する入れ物のようなもので、変数の値は変化させる事が出来ます。
小学生の算数の授業で1次関数というのを習ったと思います。
こんな数式に見を覚えがあると思います。
上記グラフの1次関数式は
になります。
この式のyとxが変数だと思って下さい。
実際のプログラミングでも同じ用に計算式を使って変数の値を変更します。
変数の値が整数しか使わない場合は基本的に
int型で宣言しますが、
小数点以下を含む値を使う場合は
double型で宣言する必要があります。
逆に小数点以下を含む値をint型変数に代入する事は出来ません(実際には代入されますが、値がint型にキャストされて小数点以下の値は丸められ意図しない動作を引き起こします)。)
なぜ整数と、小数点を含む場合で違う型宣言しなければならないかを説明すると長くなってしまうので、
詳細はWikipediaで
IEEE 754を検索して調べて下さい。
簡単に説明するとコンピュータのデータは2進数のデジタルデータの為、小数点のデータを表現する為にIEEE 754という規格を用いている為、
int型とは互換性が一切無いからです。
簡単に説明し過ぎて訳が分からないと思いますので、
とにかく
整数はintの型を使う、小数点が含まれている場合はdoubleの型を使うとだけ覚えて下さい。
メモリマップについて
変数宣言について理解できたと思いますので、本題の変数配列について説明をしたいのですが、その前にメモリマップの説明をしなければなりません。
コンピュータは基本的にメモリにデータを展開してそのデータを取り扱います。
そのデータがメモリ上のどこで展開されているかをメモリマップで説明します。
まさか「メモリって何?」という人はいないとは思いますが、
もし分からない場合はやはりWikipediaで
記憶装置を検索して調べて下さい。
Windowsのタスクマネージャーで確認できるあのメモリです。
MT4でメモリを使い切る事は無いと思うので余談なのですがこのメモリの使用率が限界に近づくと、メモリに展開できなかったデータはストレージ(HDDやSSD)に展開されて、
ストレージが仮想メモリとして使用され
処理が劇的に遅くなります。
パソコンの中身を見たことがある人は見慣れていると思いますが、
これがデスクトップPCのメモリです。
写真のメモリは一世代前のDDR3規格のメモリです。2020年にはDDR5規格のメモリが出るみたいです。
メモリマップはだいたいこのような形式で書き表されます。
(残念ながらMT4にメモリマップを閲覧する機能はありません)
数字の先頭についている[0x]は16進数表記という意味です。
横軸の値は1桁目の値を示しています。
「16進数って何?」という人が結構いたので
H1.コンピュータが扱う2進数と16進数についてページを追加しました。
そちらを参考にして下さい。
例えば赤色のアドレスは0x0010 + 0x1 = 0x0011になります。
例えば緑色のアドレスは0x0040 + 0xA = 0x004Aになります。
メモリマップの見方が理解出来たと思いますので、変数宣言をすると変数がどのように配置されるかを説明します。
上記のように変数宣言すると以下のようにメモリに展開されます。
(static修飾子については気にしないで下さい。static修飾子を付ける事でローカル変数でもビルド時に展開アドレスを確定させる為に付けています)
int型は4バイトサイズなので、int型変数を一つ宣言すると4バイトのメモリを使います。
(例なのでtemp_int1がアドレス0x0000に展開されていますが、この先頭アドレスは実際には違う値になります)
各変数には初期値を設定していますので、メモリマップ上のデータは以下のようになります。
MT4では見れませんが、
VisualStudioで実際のメモリマップを閲覧すると
このようになります。
初心者の方でも理解できるように極力簡単に説明していますが、ここまでついてこれているでしょうか?
MT4ではメモリマップを閲覧する事が出来ない為、このメモリマップを実際に見る事は出来ませんが、
実際こんな感じでメモリが使われている事を頭の中でイメージ出来るかどうかが非常に重要です。
変数配列宣言と配列アクセス
変数配列簡単に説明すると変数の配列なのですが、
言葉で説明するよりメモリマップを見ると理解し易いと思います。
変数配列の宣言方法は、変数名の後に[](大括弧)を付けて、大括弧内に要素数を書きます。
int型は4バイトデータですが、メモリマップを見ると16バイトのメモリが確保されています。
これは要素数が4つのint型配列を宣言したので、
4バイト(int型のサイズ) X 4要素 = 16バイト
の領域が確保されたからです。
このように、宣言数に要素数を決めた変数配列宣言を静的配列宣言と呼びます。
静的配列宣言は、宣言した時点で要素数分のメモリ領域が確保されるので、直ぐに使えます
(正確にはプログラムがロードされた時点で静的領域メモリに展開される)。
恐らく今の時点では何を言っているか分からないと思います、次の動的配列宣言の説明を見ると違いが理解出来ます。
メモリ領域が確保されましたが、配列をそのまま使う事は出来ません。
配列のデータにアクセスするは、配列のインデックスを指定する必要があります。
例えば1つ目の要素にアクセスするにはインデックス0を指定します。
インデックス指定する方法は、変数名の後に[](大括弧)を付けて、大括弧内にインデックス値を書きます。
array_int[0] = 0x11111111;
インデックスは0から始まるので注意して下さい。
初心者は頻繁に間違えるので要注意です。
次の例は、全ての配列の値を設定します。
array_int[0] = 0x11111111;
array_int[1] = 0x22222222;
array_int[2] = 0x33333333;
array_int[3] = 0x44444444;
気がついたと思いますが、
最後の要素のインデックスが3になっています。
初心者はインデックス4を指定してしまうので要注意です。
MT4では範囲外のインデックス4にアクセスするとエラーになります。
要素数を4つで宣言したのに、何故インデックス4を指定するのが悪いのか?
インデックスが0から始まっている時点で察しがつくと思いますが、
↓のメモリマップを見て下さい。
インデックス指定でアクセスしている配列要素の場所はこのようになっています。
インデックス4でアクセスした場合、静的配列宣言で確保したメモリ領域外にアクセスする事になり,
メモリデータ破壊に繋がり意図しない動作を引き起こす事になります。
何故インデックスが0から始まるのか?
このインデックスというのはアドレスのオフセット値だからです。
簡単に説明し過ぎて分かり難いですね。
array_int配列の先頭アドレスからのオフセット値で、
この配列はint型なのでオフセット値 X 4バイト(int型のサイズ)オフセットします。
オフセット値なので、インデックス0はオフセット0のアドレスの配列にアクセスする事になります。
静的変数配列宣言の場合、配列の先頭アドレスと、1つ目の配列要素のアドレスは基本的に同じになります。
動的配列宣言とメモリ動的確保
動的配列は宣言時に配列領域が確保されず、アドレスを確保する領域だけが確保されます。
配列宣言しただけでは配列領域が確保されない為、別途配列領域を確保する必要があります。
言葉で説明しただけでは分かり難いと思いますので、メモリマップを使いながら説明します。
変数配列の宣言方法は、変数名の後に[](大括弧)を付けて、大括弧内に要素数を書きません。
4バイトの領域が確保されていますが、この状態ではまだ配列にアクセスする事は出来ません。
ちなみに動的配列宣言の場合、変数の型に関わらず4バイトの領域が確保されます。
double型(8バイト)の動的配列宣言でも4バイトの領域が確保されます。
動的配列は動的配列領域を確保しない限りアクセス出来ません。
動的配列領域の確保は
ArrayResize関数を使用します。
上記の配列サイズは要素数なので、変更する配列の型に依存します。
配列サイズと予備サイズの分の領域がヒープ領域に確保されます。
ヒープ領域は動的に確保されたメモリが使う領域です。
array_intはint型(4バイト)なので、
4バイト(int型) X ( 4 + 4 ) = 32バイト
32バイト分の領域が確保されます。
ただし、予備サイズ領域にはアクセス出来ないので、実際にアクセス出来るのは
4バイト(int型) X 4 = 16バイト
までです。
静的配列とは異なり、配列のアドレスと、1つ目の配列要素のアドレスは異なります。
array_intには動的確保された領域の先頭アドレス(0x3000)が保存されます。
つまり、動的確保した領域がどこにあるかを記録しています。
ちなみに予備サイズ領域のメモリは確保されていますが、予備サイズ領域にアクセスした場合エラーになります。
予備サイズ領域は、再び動的配列のサイズを(大きく)変更した時に使用されます。
予備サイズ領域を越える大きなサイズを指定した場合、領域の再配置が行われる場合があります。
メモリマップの全体
プログラムが動作する時、使用されるメモリ領域はだいたい4つあります(実際にはもうちょっと細かく別れています)。
プログラムがロードされると、
コード領域にプログラムが展開され、
静的領域に
静的変数や
グローバル変数が展開されます。
ヒープ領域とスタック領域はプログラム実行時に使われます。
ヒープ領域はプログラムで動的確保した変数で使用します。
スタック領域は
ローカル変数等で使用します。