MQL4プログラムの代表的なエラーとその対処法

MetaQuotes | 12 10月, 2015

概論

MQL4言語の新バージョンのコンパイラを使用する場合、いくつかの古いプログラムはエラーを返すことがあります。

旧バージョンのコンパイラでは、プログラムを強制終了しないために、多くのエラーは実行環境で処理され、動作の停止には至りませんでした。例えば、ゼロ除算エラーや配列の範囲を越えるなどは重大なエラーです。この重大なエラーがプログラムの強制終了をもたらします。特定の変数の値におけるいくつかの状態でのみ、これらのエラーは現れますが、これらの状況について知り、正しく処理することは必要です。

新しいコンパイラのおかげで、実際のエラーソースと潜在的エラーソースを見つけ、コードの質を向上させることができます。

この記事では、古いプログラムのコンパイル時に起りうるエラーと、その対処法について検証していきます。

  1. コンパイルエラー
  2. ランタイムエラー
  3. コンパイラの警告

1. コンパイルエラー

コードにエラーがある場合、プログラムをコンパイルできません。

全てのエラーを完全に制御する為に、ディレクティブによって設定されるコンパイルのstrictモードを使用することをお勧めします。

#property strict

Strictモードはエラーの検索を大幅に容易にします。


1.1. 予約語と一致する識別子

もし変数名や関数名は予約語のうちの1つと一致する場合、

int char[];  // 不正確
int char1[]; // 正確
int char()   // 不正確
{
 return(0);
}
コンパイラがエラーメッセージを出します。

図1. 『Unexpected token』エラーと『Name expected』エラー

このエラーを修正するには、変数や関数の名前を修正する必要があります。

1.2. 変数名や関数名の特殊文字

もし変数や関数の名前に特殊文字($、@など)が含まれている場合

int $var1; // 不正確
int @var2; // 不正確
int var.3; // 不正確
void f@()  // 不正確
{
 return;
}
コンパイラがエラーメッセージを出します。

図2. 『Unknown symbol』エラーと『Semicolon expected』エラー

このエラーを修正するには、変数や関数の名前を修正する必要があります。


1.3. Switchオペレータの利用で発生したエラー

コンパイラの旧バージョンでは、switchオペレータの式や定数でどんな値でも使用することが可能でした。

void start()
  {
   double n=3.14;
   switch(n)
     {
      case 3.14: Print("Pi");break;
      case 2.7: Print("E");break;
     }
  }

新しいコンパイラでは、switchオペレータの式や定数は整数でなければならないため、そういった構成を使用するとエラーが発生します。

図3. 『illegal switch expression type』エラーと『constant expression is not integral』エラー

このような場合は、数値の明示的な比較を使用することができます。例えば、

void start()
  {
   double n=3.14;
   if(n==3.14) Print("Pi");
   else
      if(n==2.7) Print("E");
  }

1.4. 関数の戻り値

void以外の全ての関数は、宣言型の値を返す必要があります。例えば、

int function()
{
}
コンパイルの厳格(strict)モードでは以下のエラーが発生することがあります。

図4. エラー『not all control paths return a value』

デフォルトのコンパイルモードでは、コンパイラは警告を発します。

図5. 警告「not all control paths return a value」

関数の戻り値が宣言と一致しない場合、

int init()                         
  {
   return;                          
  }

コンパイルのstrictモードで以下のエラーが発生します。

図6. エラー『function must return a value』

デフォルトのコンパイルモードでは、コンパイラは警告を発します。

図7. 警告『return - function must return a value』

これらのエラーを修正するには、関数のコードに型に応じた戻り値を伴うreturnステートメントを追加する必要があります。

1.5. 関数の引数の配列

関数の引数の配列は、参照渡しのみで引き渡されます。

double ArrayAverage(double a[])
{
 return(0);
}
コンパイルの厳格(strict)モードでは、このコードは、以下のエラーをもたらします。

図8. コンパイラエラー『arrays passed by reference only』

デフォルトのコンパイルモードでは、コンパイラは警告を発します。

図9. 警告『arrays passed by reference only』

これらのエラーを修正するには、&接頭辞を配列の名前の前に付けて、明示的に配列の参照渡しを指示する必要があります。

double ArrayAverage(double &a[])
{
 return(0);
}

定数配列は(Time[]Open[]High[]Low[]Close[]Volume[])参照渡しをすることができないことに注意してください。例えば、呼び出しで、

ArrayAverage(Open);

コンパイルのモードに関わらず、エラーをもたらします。


図10. エラー『Open' - constant variable cannot be passed as reference』

これらのエラーを修正するには、定数配列から必要なデータをコピーする必要があります。

   //--- オープン価格の値を保存する配列
   double OpenPrices[];
   //--- OpenPrices[]配列にオープン価格の値をコピーする
   ArrayCopy(OpenPrices,Open,0,0,WHOLE_ARRAY);
   //--- 関数を呼び出す
   ArrayAverage(OpenPrices);


2. ランタイムエラー

ランタイムエラー(英:runtime errors)とは、プログラムコードの実行中に発生するエラーのことです。このようなエラーは、通常、プログラムの状態に依存し、変数の誤った値と関係しています。

例えば、変数が配列要素のインデックスとして使用される場合、その負の値は必然的に配列の範囲外への越出につながります。


2.1. 配列の範囲の越出(Array out of range)

このエラーは多くの場合、インディケータバッファにアクセスした場合に、インディケータ内に起こります。IndicatorCounted()関数は、最後のインディケータ呼び出しの後、確定したバーの数を返します。以前にすでに計算されたバーでのインディケータの値は、再計算の必要はなく、計算のスピードアップの為には、最後の方のいくつかのバーを処置するだけで十分です。

この最適化計算を行う方法を使う多くのインディケータは、以下のようになります。

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int start()
  {
   //--- 時に計算の為にN以上のバーが必要(例えば100)      
   if (Bars<100) // もしチャート上にこのような数のバーがない場合(例えば、MNの時間軸)  
     return(-1); // 計算を終了し、終了する
   //--- 最後のインディケータの呼び出し時から変更のないバーの数
   int counted_bars=IndicatorCounted();
   //--- エラー時は終了する
   if(counted_bars<0) return(-1);
      
   //--- サイクルの計算が始まるバーのポジション
   int limit=Bars-counted_bars;
   //--- counted_barsが0となると、サイクルの始めのポジションから1を引く必要がある   
   if(counted_bars==0) 
     {
      limit--;  // counted_bars==0で配列の範囲を越えない為に
      //--- 計算では履歴の中の10個のバーに変位が使用されるので、最初の計算時にこの変位を追加する
      limit-=10;
     }
   else //--- インディケータはすでに以前に計算されている、counted_bars>0
     {     
      //--- 最後のバーの為のインディケータの値の更新が保証されるように、再呼出し時、limit値を1増やす
      limit++;
     } 
   //--- 計算の主要サイクル
   for (int i=limit; i>0; i--)
   {
     Buff1[i]=0.5*(Open[i+5]+Close[i+10]) // 履歴の5と10に入るバーの数が使われる
   }
}

多くの場合、counted_bars==0の時に誤った処理が見受けられます。(最初のポジションのlimitを、1+可変サイクルの最大インデックスの値まで減少させる必要があります)

Start()関数実行時に、0からBars()-1までのインディケータバッファの配列要素に取り掛かることができる点を覚えておいてください。インディケータバッファではない配列で作業する必要がある場合、現在のインディケータバッファの大きさに応じたArrayResize()関数を使用し、そのサイズを増やす必要があります。アドレス指定の為の要素の最大インデックスは、引数としてのインデックスバッファの1つのArraySize()関数の呼び出しでも取得することができます。


2.2. ゼロ除算(Zero divide)

エラー『ゼロ除算』は、割り算実行時に除数がゼロと等しい場合に発生します。

void OnStart()
  {
//---
   int a=0, b=0,c;
   c=a/b;
   Print("c=",c);
  }

このスクリプトを実行すると、『Experts』タブにエラーと、プログラム終了についてのメッセージが表示されます。

図11. エラー『Zero divide』についてのメッセージ

通常、このようなエラーは、任意の外部データの値によって除数の値が決まる場合に発生します。例えば、もし取引のパラメータが分析される場合で、開いているオーダーがない場合、有効マージンの値は0と等しくなります。別の例:分析されるデータがファイルから取り込まれる場合、ファイルがない場合には正しい動作は保証されません。このため、このような場合を考慮に入れ、正しく処理できるように努めることが望ましいです。

最も簡単な方法は、除算を行う前に、除数がゼロでないかをチェックし、不正確なパラメータ値についてのメッセージを表示することです。

void OnStart()
  {
//---
   int a=0, b=0,c;
   if(b!=0) {c=a/b; Print(c);}
   else {Print("Error: b=0"); return; };
  }

その結果、重大なエラーは発生しませんが、不正確なパラメータ値についてのメッセージが表示され、除数がゼロというメッセージが表示され、作業が終了します。


図12. 不正確な除数値についてのメッセージ


2.3. 現在のシンボルのためにNULL値の代わりにゼロ値を使用する

コンパイラの旧バージョンでは、金融商品の指定を必要とする関数に引数として、ゼロを使用しました。

例えば、現在のシンボルのためのテクニカルインディケータMoving Averageの値は、次のように求めることができます。

AlligatorJawsBuffer[i]=iMA(0,0,13,8,MODE_SMMA,PRICE_MEDIAN,i);    // 不正確
新しいコンパイラでは、現シンボルの値には、明示的にNULLを指定する必要があります。
AlligatorJawsBuffer[i]=iMA(NULL,0,13,8,MODE_SMMA,PRICE_MEDIAN,i); // 正確

また、Symbol()関数やPeriod()関数を使用して、現シンボルとチャート期間を指定することができます。

AlligatorJawsBuffer[i]=iMA(Symbol(),Period(),13,8,MODE_SMMA,PRICE_MEDIAN,i); // 正確


2.4. Unicode形式の文字列と、DLLでのその使用

文字列はUnicode文字のシーケンスを表しています。

この事実を考慮し、これに対応するWindowsの機能を使用する必要があります。このように、InternetOpenA()とInternetOpenUrlA() の代わりに、wininet.dllライブラリ関数を利用するときに、InternetOpenW()とInternetOpenUrlW()を呼び出す必要があります。

MQL4では内部文字列構造が変更されたため(現在は12バイトを使用)、DLLへの文字列の引渡しはMqlString構造体を使用する必要があります。

#pragma pack(push,1)
struct MqlString
  {
   int      size;       // 32ビットの整数には、文字列バッファの為に割り当てられたサイズが含まれている
   LPWSTR   buffer;     // LPWSTR   buffer文字列を含む、32ビットのバッファアドレス
   int      reserved;   // 保存されていない32ビット整数は使用しない
  };
#pragma pack(pop,1)


2.5. ファイル共有

新しいMQL4では、ファイルを開く時に、共有モードにするには、FILE_SHARE_WRITEとFILE_SHARE_READのフラグを明示的に指定する必要があります。

これらがない場合、ファイルの専有者によって閉じられるまで、他の誰かにファイルを開けられないように、排他モードでファイルが開かれます。

例えば、オフラインチャートを使用する場合、明示的にファイル共有を指定する必要があります。

   // 1-st change - add share flags
   ExtHandle=FileOpenHistory(c_symbol+i_period+".hst",FILE_BIN|FILE_WRITE|FILE_SHARE_WRITE|FILE_SHARE_READ);
詳細はこちら「新しいMQL4のオフラインチャート」


2.6. DateTimeの変換特徴

DateTimeのタイプの文字列への変換は、コンパイルモードによって異なります。

  datetime date=D'2014.03.05 15:46:58';
  string str="mydate="+date;
//--- str="mydate=1394034418" - 古いコンパイラ、#property strictディレクティブのない新しいコンパイラ 
//--- str="mydate=2014.03.05 15:46:58" - #property strictディレクティブを持つ新しいコンパイラ
例えば、ファイル名にコロン(:)が含まれているファイルを使用する試みはエラーとなします。


3. コンパイラの警告

コンパイラの警告は、エラーメッセージではなく、情報としての性質を持ち、エラーの発生源を示すので、これらは修正をした方がいいです。

クリアなコードは、警告を含むことはありません。


3.1. グローバル変数とローカル変数の名前の交差

グローバル変数とローカル変数で同じ変数名を使われている場合、

int i; // グローバル変数
void OnStart()
  {
//---
   int i=0,j=0; // ローカル変数
   for (i=0; i<5; i++) {j+=i;}
   PrintFormat("i=%d, j=%d",i,j);
  }

コンパイラは警告を発し、宣言されたグローバル変数がある列番号を表示します。

図13. 警告『declaration of '%' hides global declaration at line %』

これらの警告を修正するには、グローバル変数の名前を修正する必要があります。


3.2. タイプ不一致

新バージョンのコンパイラでは、タイプ一致の操作が導入されました。

#property strict
void OnStart()
  {
   double a=7;
   float b=a;
   int c=b;
   string str=c;
   Print(c);
  }

タイプ不一致である場合、コンパイラのstrictモードでは、以下の警告メッセージが表示されます。


図14. 警告「possible loss of data due to type conversion」と「implicit conversion from 'number' to 'string'」

この例では、コンパイラは、異なるデータタイプの割り当て時とint型のstringへの変換時に、正確性が欠如する可能性について警告します。

修正をするためには、明示的なタイプの整理を使用する必要があります。

#property strict
void OnStart()
  {
   double a=7;
   float b=(float)a;
   int c=(int)b;
   string str=(string)c;
   Print(c);
  }

3.3. 未使用変数

プログラム内に使われていないコード(余分なもの)がある変数の存在は、あまり良いことではありません。

void OnStart()
  {
   int i,j=10,k,l,m,n2=1;
   for(i=0; i<5; i++) {j+=i;}
  }
このような変数についてのメッセージは、コンパイルのモードに関係なく表示されます。

図15. 警告『variable '%' not used』

図15. 警告『variable '%' not used』

修正のためには、使用していない変数をプログラムのコードから削除する必要があります。


まとめ

この記事では、エラーを含んでいる古いプログラムのコンパイル時に起こりうる典型的な問題について検証しました。

いずれにしても、プログラムのデバッグ時には、コンパイルのstrictモードを使用することをお勧めします。