2019年4月12日金曜日
MQL4 で配列用メモリを動的確保する方法
プログラムの動作中に配列長が決まるということはよくあります なお、[CArray](https://www.mql5.com/en/docs/standardlibrary/datastructures/carray) 系のライブラリを使っても可変長配列を実現できます 。 > 参考 > > [MQL4 で可変長配列を使用する方法 | Strategy of C](https://strategyofc.blogspot.com/2018/10/mql4.html) ### ArrayResize() の基本の使い方 以下は動的に配列長を 1000 にした例です。 実際に使用する場合は、1000 の部分が変数になることが多いかと思います。 ```mq4 int arr[]; ArrayResize(arr, 1000); ``` ### Static Array には適用しない方が良い 公式ドキュメントには ArrayResize() は [Dynamic Array](https://docs.mql4.com/basis/types/dynamic_array) にしか適用できないと書いてあります。 > The function can be applied only to dynamic arrays ところが、以下のように Static Array に対して適用した場合もコンパイルは通ってしまいます。さらに、一見想定通りの動作をします。 しかし、この使用方法は何らかの問題 を起こす可能性があるので、避けるべきでしょう。 ```mq4 // やってはいけない(と思う) int arr[50]; // Static Array Print("Is this a dynamic array = ", ArrayIsDynamic(arr) ? "Yes" : "No"); // No ArrayResize(arr, 1000); Print("Is this a dynamic array = ", ArrayIsDynamic(arr) ? "Yes" : "No"); // Yes ``` > 参考 > > [Dynamic Array Object - Data Types - Language Basics - MQL4 Reference](https://docs.mql4.com/basis/types/dynamic_array) ### 頻繁にサイズを変更する場合 メモリの動的確保は大抵負荷の高い処理になります。 つまり、頻繁にメモリサイズを変更すると動作が重くなります。 これを避けるため、先に大きめのメモリ領域を確保しておくという機能が用意されています。 頻繁に `ArrayResize()` を呼ぶ場合は利用すると良いでしょう。 ```mq4 int arr[]; // 3番目のオプション引数で追加のメモリ確保が可能 ArrayResize(arr, 1000, 1000); // 物理メモリは 2000 確保されるが、配列のサイズは 1000 Print("ArraySize = ", ArraySize(arr)); // 1000 for (int i = 1; i < 3000; i++) { ArrayResize(arr, i, 1000); // i が既に確保されている 2000 以下の場合、配列のサイズだけが変更される(動作が速い) } ``` ### 配列のサイズを変更したらデータはどうなるか? 配列サイズを変更した際のデータの扱いについて、公式ドキュメントを検索してみたのですが、仕様としてはっきり定義されているわけではなさそうです。 [ArrayInitialize()](https://docs.mql4.com/array/arrayinitialize) に以下の記述があったくらいでしょうか。 > Initialization of the array using ArrayInitialize(array, init_val) doesn't mean the initialization with the same value of reserve elements allocated for this array. > At further expanding of the array using the ArrayResize() function, the elements will be added at the end of the array, their values will be undefined and in most cases will not be equal to init_value. 意訳してみました。 > ArrayInitialize(array, init_val) で初期化しても、(ArrayResize() の3番目の引数で指定した)予約領域が初期化されるわけではありません。 > ArrayResize() で領域を拡張した際、末尾に追加された値は不定で、ほとんどの場合 init_value にはなりません。 逆に言うと、既に使用済みの領域は値を保持すると言えるかもしれません。 しかし、そこまではっきりと書いてあるわけでもありません。 一応、以下のような実験をしてみたところ、値は保持されていましたが、仕様が曖昧である以上、不定であると考えた方が安全かと思います。 ```mq4 ArrayResize(arr, 10); for (int i = 0; i < 10; i++) { arr[i] = i * 10; } // 拡張する ArrayResize(arr, 20); for (int i = 0; i < 10; i++) { Print(i, ": ", arr[i]); // 0 ~ 90 値は保持されていた } ``` ### 動的メモリを解放する [ArrayFree()](https://docs.mql4.com/array/arrayfree) の説明によると、明示的に動的メモリを解放する必要はあまり無いようです。 ただ、以下のようなローカル変数はどうなるのかよくわかりません。 スコープから抜けたら自動的に解放されるのかもしれません 。 そもそも、配列をローカルで動的確保するのはパフォーマンス的にどうよ?と思いますが、 スコープを狭く安全な実装にするためには、こちらが良いケースもあるかもしれません。そういった場合、`ArrayFree()` を呼んでおいた方が安全かとは思います。 ```mq4 void my_func(int size) { int arr[]; ArrayResize(arr, size); // 何か処理 ArrayFree(arr); } ```。 そういった場合、C++ では `new` 演算子で動的にメモリを確保するわけですが、MQL4 では `new` 演算子で配列用のメモリを確保することは出来ません。 その代わり、[ArrayResize()](https://docs.mql4.com/array/arrayresize) を利用することでメモリを動的確保することができます。