2019年4月12日金曜日

MQL4 で配列用メモリを動的確保する方法

プログラムの動作中に配列長が決まるということはよくありますMQL だとそう多くはないかもしれないですが。

そういった場合、C++ では `new` 演算子で動的にメモリを確保するわけですが、MQL4 では `new` 演算子で配列用のメモリを確保することは出来ません。

その代わり、[ArrayResize()](https://docs.mql4.com/array/arrayresize) を利用することでメモリを動的確保することができます。



なお、[CArray](https://www.mql5.com/en/docs/standardlibrary/datastructures/carray) 系のライブラリを使っても可変長配列を実現できますこれらのクラス内では `ArrayResize()` 等を使っています。

> 参考
>
> [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) の説明によると、明示的に動的メモリを解放する必要はあまり無いようです。

ただ、以下のようなローカル変数はどうなるのかよくわかりません。
スコープから抜けたら自動的に解放されるのかもしれませんC++ と異なる手段で動的配列確保を実装している理由かも。

そもそも、配列をローカルで動的確保するのはパフォーマンス的にどうよ?と思いますが、
スコープを狭く安全な実装にするためには、こちらが良いケースもあるかもしれません。そういった場合、`ArrayFree()` を呼んでおいた方が安全かとは思います。

```mq4
void my_func(int size) {
    int arr[];
    ArrayResize(arr, size);

    // 何か処理

    ArrayFree(arr);
}
```