下载MetaTrader 5

MQL4程序的常见错误以及如何避免它们

26 三月 2014, 09:21
MetaQuotes Software Corp.
2
9 346

介绍

一些较旧的程序可能在新版本的MQL4编译器中返回错误。

为了避免关键的程序完成,以前版本的编译器在运行环境中处理了许多错误。例如,除数为零或数组越界都是严重错误,并通常会导致应用程序崩溃。这些错误只在一些状态下针对某些变量值而发生。阅读这篇文章了解如何处理这样的情况。

新的编译器可以检测实际或潜在的错误源并提高代码质量。

在这篇文章中,我们讨论了旧程序编译过程中检测到的可能出现的错误,以及解决这些问题的方法。

  1. 编译错误
  2. 运行时间错误
  3. 编译器警告

1编译错误

如果程序代码中包含错误,则它不能被编译。

要完全控制所有的错误,建议使用严谨的编译模式,它通过以下指令来设置:

#property strict

这种模式大大简化了故障排除。


1.1. 与关键字一致的标识

如果变量或函数的名称与其中一个关键字一致

int char[];  // incorrect
int char1[]; // correct
int char()   // incorrect
{
 return(0);
}

编译器会返回一个错误信息:

图1. 错误“unexpected token(非预期标记)”和“name expected(预期名称)”

图1. 错误“unexpected token(非预期标记)”和“name expected(预期名称)”

要解决这个错误,您需要使用变量或函数的正确名称。


1.2. 变量和函数名的特殊字符

如果变量或函数名称中包含特殊字符($,@,点):

int $var1; // incorrect
int @var2; // incorrect 
int var.3; // incorrect
void f@()  // incorrect
{
 return;
}

编译器会返回一个错误信息:

图2. 错误“unknown symbol(未知交易品种)”与“semicolon expected(预期分号)”

图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(非法switch表达式类型)”和“constant expression is not integral(常量表​​达式不是整数)”

图3. 错误“illegal switch expression type(非法switch表达式类型)”和“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. 函数返回值

除了空值外的所有函数都应该返回声明的类型值。例如:

int function()
{
}

在严谨的编译模式下发生错误:


图4. 错误“not all control paths return a value(并非所有的控制路径返回一个值)”

图4. 错误“not all control paths return a value(并非所有的控制路径返回一个值)”

在默认的编译模式下,编译器会返回一个警告:

图5. 警告:“not all control paths return a value(并非所有的控制路径返回一个值)”

图5. 警告:“not all control paths return a value(并非所有的控制路径返回一个值)”

如果函数的返回值与声明的不匹配:

int init()                         
  {
   return;                          
  }

在严格的编译中会检测错误:

图6. 错误“function must return a value(函数必须返回一个值)”

图6. 错误“function must return a value(函数必须返回一个值)”

在默认的编译模式下,编译器会返回一个警告:

图7. 警告 'return - function must return a value(回报 - 函数必须返回一个值)”

图7. 警告 'return - function must return a value(回报 - 函数必须返回一个值)”

要解决这样的错误,添加带有相应类型返回值的return操作符到函数代码。



1.5. 函数参数数组

在函数参数,数组现在只引用传递。

double ArrayAverage(double a[])
{
 return(0);
}
在严谨的编译模式下,该代码将导致错误:

图8. 编译器错误“arrays passed by reference only(数组只引用传递)”

图8. 编译器错误“arrays passed by reference only(数组只引用传递)”

在默认的编译模式下,编译器会返回一个警告:

图9. 编译器警告“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(‘打开’ - 常量变量不能引用传递)

图10. 错误'Open' - constant variable cannot be passed as reference(‘打开’ - 常量变量不能引用传递)

为了避免这些错误,从常量数组​​复制所需的数据:

   //--- an array that stores open price values
   double OpenPrices[];
   //--- copy the values of open prices to the OpenPrices[] array
   ArrayCopy(OpenPrices,Open,0,0,WHOLE_ARRAY);
   //--- function call
   ArrayAverage(OpenPrices);



2. 运行时间错误

程序代码执行过程中出现的错误称为运行时间错误。这种错误通常是依赖于程序的状态,并与变量的不正确的值相关联。

例如,如果变量用作数组元素的索引,其负值将不可避免地导致数组超出范围的错误。


2.1. 数组超出范围

访问指标缓冲区时常常在指标中发生这个错误。该IndicatorCounted()函数返回自上次指标调用的不变的柱数。先前计算的柱的指标值不需要重新计算,所以为了更快的计算,您只需要处理最后的几个柱。

大部分使用这种计算优化的方法的指标看起来如下:

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int start()
  {
   //--- some calculations require no less than N bars (e.g. 100)      
   if (Bars<100) // if less bars are available on a chart (for example on MN timeframe)    
     return(-1); // stop calculation and exit

   //--- the number of bars that have not changed since the last indicator call
   int counted_bars=IndicatorCounted();
   //--- exit if an error has occurred
   if(counted_bars<0) return(-1);
      
   //--- position of the bar from which calculation in the loop starts
   int limit=Bars-counted_bars;

   //--- if counted_bars=0, reduce the starting position in the loop by 1,   
   if(counted_bars==0) 
     {
      limit--;  // to avoid the array out of range problem when counted_bars==0
      //--- we use a shift of 10 bars back in history, so add this shift during the first calculation
      limit-=10;
     }
   else //--- the indicator has been already calculated, counted_bars>0
     {     
      //--- for repeated calls increase limit by 1 to update the indicator values for the last bar
      limit++;
     } 
   //--- the main calculation loop
   for (int i=limit; i>0; i--)
   {
     Buff1[i]=0.5*(Open[i+5]+Close[i+10]) // values of bars 5 and 10 bars deeper to history are used
   }
}

通常counted_bars==0的情况处理不当(初始限制持仓应该通过等于相对循环变量的1 +最大指数的值来降低)。

另外,请记住,在执行start()函数的时候,我们可以从0到Bars ()-1的访问指标缓冲区的数组元素。如果您需要使用无指标缓冲区的数组,那么按照指标缓冲区的当前大小使用ArrayResize()函数来增加其大小。也可以通过调用用作参数的指标缓冲区的 ArraySize()来获得元素地址的最大指数。


2.2. 除数为零

当除法运算中除数为零时则会发生零除的错误:

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

当您运行这个脚本时,专家选项卡会出现一条错误的消息,并且程序关闭:

图11. 错误消息“zero divide(除数为零)”

图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. 不正确的除数消息

图12. 不正确的除数消息


2.3. 当前字符用0替代NULL

在旧版本的编译器中0(零)可用作满足金融工具规范的函数参数。

例如,当前交易品种的移动平均线技术指标的值可能被要求如下:

AlligatorJawsBuffer[i]=iMA(0,0,13,8,MODE_SMMA,PRICE_MEDIAN,i);    // incorrect

在新的编译器中您应该明确地指定NULL来规定当前的交易品种:

AlligatorJawsBuffer[i]=iMA(NULL,0,13,8,MODE_SMMA,PRICE_MEDIAN,i); // correct

此外,当前交易品种和图表周期可使用Symbol()Period()函数来指定。

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


2.4. Unicode字符串和它们在DLL中的使用

字符串现在表示为Unicode字符序列。

记住这一点,并使用适当的Windows函数。例如,使用wininet.dll库来替代 InternetOpenA() 和InternetOpenUrlA(),您应该调用InternetOpenW() 和InternetOpenUrlW()。

字符串的内部结构在MQL4中(现在只需要12个字节)发生了变化,当传递字符串到DLL时应使用MqlString结构:

#pragma pack(push,1)
struct MqlString
  {
   int      size;       // 32 bit integer, contains the size of the buffer allocated for the string
   LPWSTR   buffer;     // 32 bit address of the buffer that contains the string
   int      reserved;   // 32 bit integer, reserved, do not use
  };
#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 date=D'2014.03.05 15:46:58';
  string str="mydate="+date;
//--- str="mydate=1394034418" - old compiler, no directive #property strict in the new compiler
//--- str="mydate=2014.03.05 15:46:58" - new compiler with the directive #property strict

例如,尝试使用文件名中包含冒号的文件会导致错误。


3. 编译器警告

编译器警告是信息性而非错误的讯息,但它们指出了可能的错误来源。

一个清晰的代码不应该包含警告。


3.1. 全局和局部变量名称一致

如果全局和局部各级变量具有相似的名称:

int i; // a global variable
void OnStart()
  {
//---
   int i=0,j=0; // local variables
   for (i=0; i<5; i++) {j+=i;}
   PrintFormat("i=%d, j=%d",i,j);
  }

编译器会显示指出全局变量的声明行号的警告:

图13. 警告“declaration of '%' hides global declaration at line %(声明'%'隐藏在行%的全局声明)”

图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);
  }

在严谨的编译模式下如果类型不匹配则编译器会显示警告:

Figure 14. Warnings "possible loss of data due to type conversion" and "implicit conversion from 'number' to 'string'

图14. 警告“possible loss of data due to type conversion(由于类型转换可能丢失数据)”和“implicit conversion from 'number' to '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('%'变量未使用)”

要修复它,需要从代码中移除未使用的变量。


结论

本文描述了包含错误的旧程序的编译过程中可能出现的常见问题。

在所有情况下,建议使用严谨的编译模式来调试程序。


p align="center"// stop calculation and exit br/pnbsp;span class="keyword"

本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/1391

最近评论 | 前往讨论 (2)
Little---Prince
Little---Prince | 24 11月 2015 在 04:34
  外汇黄金技术分析交流,群号是375124107,进群验证777,谢谢配合  
Bing Liu
Bing Liu | 25 12月 2015 在 02:43
不错,看了之后终于知道怎么解决这些报警了,谢谢高手指点
升级至MetaTrader 4 Build 600及更高版本 升级至MetaTrader 4 Build 600及更高版本

新版MetaTrader 4客户端的新版本拥有用户数据存储的更新结构。在早期版本中所有程序,模板,配置文件等都被直接存储在程序端的安装文件夹。现在,特定用户所需的所有必要的数据都存储在一个单独的称为数据文件夹的目录中。阅读文章来寻找常见问题的答案。

新MQL4中的离线图表 新MQL4中的离线图表

更新的MQL4具有存储历史数据的新格式,并提供相应的MqlRates结构,便于时间,开盘,最低,最高,收盘和交易量值的存储。多年来,交易者们已经开发出收集和存储他们在HST文件中的数据,用于生成离线图表的MQL4应用。我们可以向您保证,所有先前编译的EX4文件在新的MetaTrader 4程序端将以之前相同的方式工作。

为什么8月1日MetaTrader 4升级至最新版本很重要? 为什么8月1日MetaTrader 4升级至最新版本很重要?

自2014年8月1日起,将不再支持低于build 600的MetaTrader 4桌面程序端。 然而,许多交易人仍然使用过时的版本而没有意识到更新平台的功能。我们在开发上做了很大的努力,愿陪伴交易人继续前进,而放弃之前的版本。在本篇文章中,我们描述了新版MetaTrader 4的优势。

我们如何开发MetaTrader 信号服务和群组交易 我们如何开发MetaTrader 信号服务和群组交易

我们持续加强信号服务,完善机制,添加新的功能并修复缺陷。2012年的MetaTrader信号服务和当前的MetaTrader信号服务就像两个完全不同的服务。目前,我们正在实施 虚拟主机云服 务,它由一个服务器网络组成用来支持特定版本的MetaTrader客户端。若要从MetaTrader客户端以最小的网络延迟租用程序端虚拟副本,直接 到达他们交易商的交易服务器,交易人将只需完成5个步骤。