Что произойдет, если возникнет ошибка времени выполнения?

Что произойдет, если в программе возникнет ошибка времени выполнения? Будет ли выполнение программы просто остановлено? Есть ли способ заставить Arduino сказать мне, в чем ошибка?

, 👍17


7 ответов


Лучший ответ:

21

Во-первых, давайте рассмотрим несколько примеров того, что может пойти не так.

Неинициализированные локальные переменные

void setup() {
  int status;
  pinMode(13, OUTPUT);
  digitalWrite(13, status);
} 

Как указал Эдгар Боне в комментариях, локальные переменные, такие как status в приведенном выше коде не инициализируются компилятором C++ неявно. Таким образом, результат приведенного выше кода не определен. Чтобы избежать этого, убедитесь, что вы всегда присваиваете значения своим локальным переменным.

С глобальными и статическими переменными все немного иначе:

Глобальные и статические переменные гарантированно инициализируются значением 0 по стандарту C.

Источник: Справочное руководство по AVR Libc — Часто задаваемые вопросы — Должен ли я инициализировать все свои переменные?

Это означает, что вам не нужно беспокоиться об их инициализации в 0 в вашем коде. На самом деле вам действительно следует избегать этого, так как инициализация может привести к трате памяти. Только инициализируйте их значениями, отличными от 0.

Переполнение памяти

int array[10];
int v = array[100];
array[-100] = 10;

Первая проблема здесь заключается в том, что вы не знаете, что будет присвоено v, но хуже всего то, что вы не знаете, что вы напутали с присвоением позиции -100 в array .

Перейти к недопустимой инструкции

void doSomething( void ) { 
    for (int i = 0; i < 1000; i++); 
}

void setup () 
{
    void (*funcPtr)( void );

    funcPtr = &doSomething;
    funcPtr(); // вызывает doSomething();

    funcPtr = NULL;
    funcPtr(); // неопределенное поведение
}

Первый вызов funcPtr() фактически будет вызовом doSomething(). Вызовы, подобные второму, могут привести к неопределенному поведению.

Другие плохие вещи, которые могут произойти

Ну, например, у вас может закончиться оперативная память. Что еще. В любом случае, я думаю, ваша программа продолжит работать, возможно, не так, как вы предполагали.

Виды защиты

В компьютерных системах подобные проблемы обычно решаются на разных уровнях:

  1. Компилятором
  2. Средой выполнения языка программирования (например, Java).
  3. Операционная система или процессор (если ваша память обращается к позиции за пределами адресного пространства, зарезервированного для вашей программы, ОС или процессор могут иметь механизмы безопасности для предотвращения этого)

Arduino имеет только ограниченную защиту компилятора и, вероятно, больше ничего. Хорошая новость заключается в том, что они не являются многозадачными, поэтому затрагивается только ваша программа. В любом случае любая из этих ошибок приведет к ошибочному поведению.

Ответы

Предполагается, что все проблемы, о которых я говорил выше, относятся к проблемам времени выполнения.

Что произойдет, если в программе возникнет ошибка времени выполнения?

Программа будет продолжена, и то, что произойдет, будет зависеть от побочных эффектов ошибки времени выполнения. Вызов нулевого указателя функции, вероятно, заставит программу перейти в неизвестное место.

Выполнение программы просто остановится?

Нет, это будет продолжаться так, как будто ничего экстраординарного не произошло, возможно, делая то, чего вы не планировали. Он может сбрасываться или действовать хаотично. Он может превратить некоторые входные данные в выходные и сжечь датчик или два (но это крайне маловероятно).

Есть ли какой-нибудь способ заставить Arduino сказать мне, в чем ошибка?

Я так не думаю. Как я уже говорил ранее, механизмов защиты там нет. Нет поддержки во время выполнения со стороны языка, нет ОС, нет аппаратных проверок доступа к памяти за пределами границ (загрузчик также не считается). Вам просто нужно быть осторожным со своей программой и, вероятно, установить свои собственные сети безопасности.

Причина отсутствия защиты, вероятно, заключается в том, что контроллеры Arduino слишком дешевы, имеют слишком мало памяти и не должны запускать ничего слишком важного (да, кажется, AVR где-то дает отказ от ответственности, чтобы вы не использовали микроконтроллеры). обычно используется Arduino в системах жизнеобеспечения).

,

Большой! Лучший ответ, который я когда-либо видел на Arduino.SE!, @The Guy with The Hat

Спасибо!! Я думаю, что мы должны стремиться давать как можно больше отличных ответов. Но меня немного беспокоит тот факт, что у нас не так много НАСТОЯЩИХ ЭКСПЕРТОВ по ЭЭ, которые могли бы посмотреть ответы, подобные моему, и найти какие-то вопиющие ошибки. На самом деле, именно поэтому я опубликовал ответ, хотя я мало знаю о микроконтроллерах AVR. Это чтобы увидеть, если мы получим кого-то, чтобы исправить это. Мы точно не хотим, чтобы умные пенсионеры вроде меня говорили что-то неправильное и оставались безнаказанными. Но это, вероятно, обсуждение для мета-сайта., @Ricardo

Не повредит ли это Arduino?, @Anonymous Penguin

@AnnonomusPerson - Единственным вероятным механизмом повреждения может быть код, который изменяет состояние вывода таким образом, что это приводит к конфликту на шине (например, ATmega пытается установить вывод на высокий уровень, в то время как внешнее оборудование устанавливает его на низкий уровень). Также возможно, что если есть внешнее оборудование, которым управляет ATmega, которое не может обрабатывать определенные состояния ввода управления, * внешнее оборудование * может вызвать какой-либо отказ и повреждение, но Arduino, к которому ничего не подключено, довольно пуленепробиваемый. Ардуино имеют последовательные резисторы для предотвращения возможных конфликтов на последовательных линиях., @Connor Wolf

@Ricardo - Один комментарий, который я хотел бы сделать, заключается в том, что неявно инициализированные переменные не являются * обязательно * неинициализированными. Переменные, определенные вне функций, обычно имеют так называемую «автоматическую продолжительность хранения», которая затем инициализируется по умолчанию до нуля. См. http://en.cppreference.com/w/cpp/language/default_initialization для получения дополнительной информации. Поведение при инициализации достаточно сложное, поэтому полагаться на него, вероятно, опасно, но делать общие операторы *вероятно* не очень хорошая идея., @Connor Wolf

Кроме того, SRAM инициализируется 0 при сбросе или запуске, поэтому вы можете сделать *некоторые* обоснованные предположения о неинициализированных переменных, если хотите жить в опасности. Вы не должны *полагаться* на такое поведение, но оно интересно., @Connor Wolf

Вот интересный пример того, что происходит, когда у вас заканчивается SRAM: http://electronics.stackexchange.com/questions/42049/arduino-serial-print-changes-behavior-of-program-undesireably/42069. По сути , стек затирает часть кучи или наоборот. Это может привести к интересным вещам, таким как повреждение некоторой части кадра стека (нарушение возврата функции и т. д.) или запись недопустимых данных в переменные., @Connor Wolf

Также актуален вопрос о рекурсии: http://arduinoprosto.ru/q/355/how-much-can-i-recurse-how-much-can-i-recurse-how-much-caqfsdrfw, поскольку рекурсия один из простых способов взорвать стек., @Connor Wolf

@FakeName - Верно! Я отредактирую свой ответ, чтобы исправить утверждение о неинициализированных переменных. Что касается других замечательных предложений, которые вы опубликовали, почему бы вам не дать им ответ, и я добавлю ссылку на него из своего? Рад видеть вас (или, может быть, просто фотографию вашего раздраженного кота) здесь., @Ricardo

@FakeName — Кстати, эта картинка всегда напоминает мне о великой *технике* кошачьего йодлинга из [Руководства инженера по кошкам](http://www.youtube.com/watch?v=mHXBL6bzAR4) на 4:57 мин., @Ricardo

Одно примечание: «Хорошая новость заключается в том, что вы напортачили только с ОЗУ, а не с EEPROM, поэтому ваша программа в безопасности». Программа, работающая на ATmega, хранится во флэш-памяти, а не в EEPROM. EEPROM обычно имеет размер всего 1-2 КБ и больше предназначен для хранения небольших объемов энергонезависимых данных (подумайте: настройки конфигурации пользователя, серийный номер и т. д.), а не фактического кода., @Connor Wolf

@ConnorWolf Верно! Хорошо подмечено. Только что отредактировал и исправил мой ответ., @Ricardo

компилятор C++ сделает это за вас. Он устанавливает его равным нулю. Локальные переменные не инициализируются. Я интерпретирую «статус» как локальный в вашем примере, поэтому это неправильно. не считается хорошей практикой полагаться на это поведение`, на самом деле **считается** хорошей практикой полагаться на это поведение для глобальных переменных, поскольку гарантируется, что они будут инициализированы нулями, а глобальные инициализирующие нуль в вашем коде могут тратить память (хотя современные компиляторы должны быть достаточно умны, чтобы этого избежать) см. http://www.atmel.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_varinit.html, @per1234

Вы написали: «_array[-100] = 10; [...] вы только испортили оперативную память [...], поэтому ваша программа безопасна». MCU, который отображается в памяти прямо под RAM на AVR. Это определенно _небезопасно_. «_[Вызов указателя функции NULL], что эквивалентно программной загрузке_». Не совсем. Программа перезапустится с самого начала: она инициализирует указатель стека, ОЗУ, вызовет main и т. д. Но, в отличие от программной загрузки, это _не_ сбрасывает регистры ввода-вывода в исходное состояние по умолчанию., @Edgar Bonet

@Connor Wolf: Вы написали: «_SRAM инициализируется до 0 при сбросе или запуске_». Это неправильно. В отличие от регистров ввода-вывода, SRAM не инициализируется аппаратно при запуске. Раздел BSS ОЗУ (и _только_ этот раздел) инициализируется нулем во время выполнения C. Раздел DATA инициализируется любым содержимым, запрошенным программистом. Вся оставшаяся оперативная память остается неинициализированной., @Edgar Bonet

@EdgarBonet - Очко принято. Жаль, что я не могу редактировать свои старые комментарии., @Connor Wolf

@EdgarBonet - Спасибо, что указали на проблему в моем ответе. Я прочитал предоставленную вами ссылку и попытался исправить утверждения о локальных переменных. Не могли бы вы проверить это? Спасибо!, @Ricardo

Ваше утверждение о перезапуске программы при вызове указателя функции NULL было правильным, по крайней мере, на AVR. Я просто указал, что перезапуск программы не совсем то же самое, что и сброс. Кстати, в этом процессе нет незаконных инструкций. «_Не могли бы вы проверить это?_» Я не могу найти ничего неправильного в вашем ответе., @Edgar Bonet


5

Для чего-то подобного вам понадобится аппаратный отладчик. Но обычно вы увидите, что программа ведет себя не так, как вы ожидаете, и вам придется просмотреть этот раздел кода, чтобы выявить проблему.

Распространенный/быстрый/простой способ сделать это — добавить операторы печати для вывода значений переменных или чего-то еще, чтобы вы знали, что программа без проблем доберется до этой точки в коде. Это поможет вам еще больше изолировать проблему.

Я полагаю, что VisualMicro имеет встроенные функции отладки.

,

3

Я бы предположил, что ЦП AVR не имеет инструментов обнаружения ошибок или восстановления. Он может просто остановиться или продолжать работать, игнорируя ошибку и последствия. Как сказал Сачлин, вы должны добавить в свою программу несколько операторов отладки, которые распечатывают данные в середине операции, чтобы проверить, работает ли она. Если вы используете эмулятор и устанавливаете точки останова, вы можете легко найти проблему.

,

9

Исключений во время выполнения нет. Есть только неопределенное поведение.

На самом деле исключений вообще нет. Если вы попытаетесь выполнить недопустимую операцию, ее результаты будут неизвестны.

Проверки во время выполнения вообще отсутствуют, за исключением того, что вы реализуете. Ваша программа работает на «голом железе». Это настольный эквивалент постоянной работы в кольце-0, потому что ATmega не у меня есть кольца.

,

6

Существует один механизм, который может вывести MCU из ошибочного состояния, и это сторожевой таймер. Если вы реализуете некоторый код, который будет многократно выполняться в цикле, который не будет выполняться дольше определенного фиксированного времени, вы можете установить это время как период наблюдения и включить таймер.

Затем вам придется неоднократно сбрасывать таймер в цикле. Если ваш код зависает в каком-то цикле условий, который никогда не завершится, то сторожевой таймер будет считать до нуля и в конечном итоге сбросить MCU.

Таким образом вы теряете данные, но если вы запускаете AVR WDT в режиме прерывания, вы можете сохранить некоторые данные перед сбросом MCU.

Таким образом, сторожевой таймер может защитить ваш код от случайных непреднамеренных бесконечных циклов.

Документация: AVR132: Использование усовершенствованного сторожевого таймера

,

-2

Arduino перезагрузится (т.е. перезапустит setup() и loop()).

,

Не обязательно. Ошибка во время выполнения может привести к тому, что программа зациклится без перезагрузки., @Nick Gammon


-1

Я написал среду *.ino:

// Среда тестирования для файлов эскизов Arduino.
// Адуино IDE 2.3.0
// Автор: Андерс Мунк
// Выпущено: 9 февраля 2024 г.

#include <iostream>

const bool LOW=false;
const bool HIGH=true;
const bool INPUT=false;
const bool OUTPUT=true;
const unsigned char LED_BUILTIN=13;

using namespace std;

void where(std::string hank)
{ cout <<"Where : "<<hank<<"\n"; }

int random(int max)
{ return std::rand(); }

unsigned long klokken=0;
unsigned long int millis()
{ klokken++;
  cout <<"When : "<< klokken <<"\n";
  return klokken;
}

class SERIAL
{ public:
    void begin(int Baud)
    { cout<<"Arduino sketch : Serial.begin()\n"; };
    void end()
    { cout<<"Arduino sketch : Serial.end()\n"; };
    void print(std::string A)
    { cout<<"Arduino sketch : print()\n"; };
    void println(std::string A)
    { cout<<"Arduino sketch : println()\n"; };
} Serial=SERIAL();

void digitalWrite(unsigned char A,bool S)
{ cout << "Arduino sketch : digitalWrite()\n"; }

void pinMode(unsigned char A,bool S)
{ cout << "Arduino sketch : pinMode()\n"; }

// Включите сюда файл эскиза Arduino:
#include "signal.ino"

int main()
{ setup();
  for(int i=0;i<100;i++){ loop(); };
  return 0;
}

Цель — получить сообщения об ошибках во время выполнения из эскиза *.ino.

Таким образом вы не получите полезной информации о времени, поскольку программа работает на другом процессоре.

Я запускаю его с помощью интегрированной среды разработки GPU C++.

Пожалуйста, рассматривайте мой код как предварительную специальную программу; Возможно, вам потребуется дальнейшее развитие для ваших нужд.

,

Понижение не мое. Вам нужно объяснить свой код и то, что он делает., @Rohit Gupta

Вопросу 10 лет. просто говорю, если ты не заметил, @Juraj