Типичные ошибки в программах на MQL4 и методы их устранения
Введение
При использовании новой версии компилятора языка MQL4 некоторые старые программы могут выдавать ошибки.
В старой версии компилятора во избежание критического завершения программ многие ошибки обрабатывались средой исполнения и не приводили к остановке работы. Например, деление на ноль или выход за пределы массива являются критическими ошибками и обычно приводят к аварийному завершению. Проявляются такие ошибки лишь в некоторых состояниях при определенных значениях переменных, однако о таких ситуациях следует знать и корректно их обрабатывать.
Новый компилятор позволяет обнаружить реальные или потенциальные источники ошибок и повысить качество кода.
В этой статье мы рассмотрим возможные ошибки, возникающие при компиляции старых программ, и методы их устранения.
1. Ошибки компиляции
При наличии ошибок в коде программа не может быть скомпилирована.
Для полного контроля всех ошибок рекомендуется использовать строгий режим компиляции, который устанавливается директивой:
#property strict
Этот режим значительно упрощает поиск ошибок.
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; }
то при строгом режиме компиляции возникает ошибка:
Рис.6. Ошибка "function must return a value"
В режиме компиляции по умолчанию компилятор выводит предупреждение:
Рис.7. Предупреждение 'return - function must return a value"
Для исправления таких ошибок в код функции нужно добавить оператор возврата return c возвращаемым значением соответствующего типа.
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() в соответствии с текущим размером индикаторных буферов. Максимальный индекс элемента для адресации также можно получить вызовом ArraySize() с одним из индикаторных буферов в качестве аргумента.
2.2. Деление на ноль (Zero divide)
Ошибка "Zero divide" возникает в случае, если при выполнении операции деления делитель оказывается равен нулю:
void OnStart() { //--- int a=0, b=0,c; c=a/b; Print("c=",c); }
При выполнении данного скрипта во вкладке "Эксперты" возникает сообщение об ошибке и завершении работы программы:
Рис.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. Использование 0 вместо NULL для текущего символа
В старой версии компилятора допускалось использование 0 (нуля) в качестве аргумента в функциях, требующих указания финансового инструмента.
Например, значение технического индикатора 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. Строки в формате Unicodе и их использование в DLL
Строки теперь представляют собой последовательность символов Unicode.
Следует учитывать этот факт и использовать соответствующие функции Windows. Например, при использовании функций библиотеки wininet.dll вместо InternetOpenA() и InternetOpenUrlA() следует вызывать InternetOpenW() и InternetOpenUrlW().
В MQL4 изменилась внутренняя структура строк (теперь она занимает 12 байт), поэтому при передаче строк в DLL следует использовать структуру MqlString:
#pragma pack(push,1) struct MqlString { int size; // 32-битное целое, содержит размер распределенного для строки буфера 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); }
В строгом режиме компиляции при несоответствии типов компилятор выводит предупреждения:
Рис.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'
Для исправления нужно убрать неиспользуемые переменные из кода программы.
Выводы
В статье рассмотрены типичные проблемы, с которыми могут столкнуться программисты при компиляции старых программ, содержащих ошибки.
Во всех случаях при отладке программ рекомендуется использовать строгий режим компиляции.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Потому, что вы пробегаете в цикле по количеству баров, которые есть на текущем графике, но на младшем тайме баров меньше, и при запросе большего количества чем есть - получаете ошибку.
Большое спасибо, Виталий!
Все равно не понял как это исправить. Прописал вместо Bars - "iBars(NULL,PERIOD_M5)" Не помогло
Не понимаю почему на младшем тайме баров меньше? Наоборот ведь больше?! В 1 баре м15 три по м5. Как сделать, чтоб работало, подскажите. Не могу врубиться. С циклами пока проблемыПотому, что вы пробегаете в цикле по количеству баров, которые есть на текущем графике, но на младшем тайме баров меньше, и при запросе большего количества чем есть - получаете ошибку.
Оказалась не в этом ошибка
Оказалась не в этом ошибка
Если ошибку нашли, то может стоит сообщить остальным где она была? Кто-то может так же столкнуться с ней, а тут уже ответ есть вместо простой констатации факта.
Вы правы. Обращение к High[], Low[] и Bars возвращает значения с текущего таймфрейма. А нужно запрашивать с желаемого:
Читаните
С более полным исходным кодом разобрались в сервисдеске.
Вот ссылка, объясняющая проблему https://support.microsoft.com/ru-ru/help/172190/windows-nt-contains-file-system-tunneling-capabilities
При удалении файла в виндовс и создании такого же с тем же именем средсвами мкл, дата создания будет выдаваться первого файла.
Винда кеширует файлы