Comments 55
Мы не хотим, чтобы из-за программной ошибки какой-нибудь рентгеновский аппарат облучал пациентов дозой в 20 000 рад, поэтому обычных «повседневных» правил уже недостаточно. На кону стоят человеческие жизни и огромные деньги, и необходимо включать дотошность. Здесь и выходят на сцену остальные правила MISRA:Можно ли соблюсти все правила MISRA и все равно облучить пациентов повышенной дозой?
for (int i = 0; i < n; ++i);
{
do_something();
}
Ну прямо с субботней проверки лабораторных, только было что-то типа этого
if (true && false);
{
//делать что-то если true
}
Студентка час искала, почему у неё внутрь скобок заходит..., потом позвала меня, я в начале тоже в шоке был :), но быстро догадался…
Для этого сказал, чтобы перед точкой запятой пробел ставили всегда, тогда её видно из далека.
if (true && false) ;
{
//делать что-то если true
}
Ну и конечно статический анализатор помогает, даже казалось бы в общем-то очень понятных вещах, иногда находит то, чего так просто и не поймешь.
Кстати, у этой «концепции» есть и другие преимущества, например, более компактное представление кода, уменьшение длины простыней и больше кода одновременно на экране для (визуального) анализа.
Однако, справедливости ради, -Wall -Wextra -Wpedantic покажут практически тоже. А вот эту троицу настоятельно рекомендую всем. Как минимум для своего кода (а лучше и для библиотек — чтоб потом сюрпризом не было).
P.S.
В одной из stm32'ых программ была забавная конструкция
while(any_condition);
{
}
Все съели. И только GCC с этой троицей ругнулся. Ошибок нет, но неаккуратность присутствует.
Это вроде не ошибка. Это такой способ показать пустой цикл. Видел не раз в их исхолниках.
Конечно не ошибка.
Только вот обратите внимание на точку с запятой после while(). Т.е. оператор цикла закрыт и он пустой прямо в первой строке. В итоге, если кто-то задумает наполнить цикл вписав код в пустой операторный блок, то будет сильно удивлен тому, что он не будет выполняться.
Что-то лишнее. Или операторный блок или точка с запятой. И, судя по вашему комментарию, это точно надо было исправлять.
Каждый switch должен заканчиваться default
Вы привели замечательный пример устарелости MISRA.
Намного лучше не писать default, а перечислять все варианты enum'а, чтобы получить ошибку, если кто-то добавит новый элемент (предупреждения ведь интерпретируются как ошибки, мы ведь надёжное ПО пишем, правда?)
enum class E
{
Var1,
Var2,
Var3
};
int foo(E e)
{
switch(e)
{
case E::Var1: return 1;
case E::Var2: return 2;
}
return -1;
}
Godbolt
Приходилось мне переводить MISRA C++ для создания стандарта предприятия. Крайне двойственные ощущения: есть хорошие, действительно важные правила, которые следует использовать, а есть вот такая устарелая некомпетентность с default.
В целом, конечно, прочитать нужно, но считать эти правила чем-то непогрешимым не следует.
Правила CERT понравились куда больше.
2. Память контроллера, не поверите, может быть испорчена в рантайме. Или даже не память — некорректное значение может прийти через USART, например.
Вкратце: не всегда switch делается по членам enum'а.
enum не всегда подходит: иногда удобно делать switch по константам, связанным с содержимым аппаратных регистров
Да вот только правило говорит нам, что следует всегда использовать default. Я и указываю на излишнюю догматичность и даже вредность этого правила для случая enum.
Память контроллера, не поверите, может быть испорчена в рантайме
Действительно не поверю. Если программа гадит не пойми куда, то речи о надёжном ПО быть не может. И уж точно от этого не спастись с помощью default.
Или даже не память — некорректное значение может прийти через USART, например
Конечно, всё, что пришло извне, должно подвергаться проверке. Только при чём тут default?
Да вот только правило говорит нам, что следует всегда использовать default.
Лучше перебдеть.
Действительно не поверю. Если программа гадит не пойми куда ...
Хехе, сразу видно программиста не-железячника.
Программа тут не при чем. Может прилететь частица и изменить бит в памяти (нормальная ситуация для аппаратуры на спутниках), может случиться скачок/просадка напряжения и повредить участок FLASH/EEPROM (например, если BOD настроен неверно и запись в NVM прошла при нештатном напряжении) или область RAM, может быть повреждена линия к внешней микросхеме памяти, и еще куча вариантов.
Только при чём тут default?
Это самый простой способ отловить неожиданное значение.
Лучше перебдеть
Покажите пример с «лучше». Пока что только я показал пример, почему лучше без default :)
Хехе, сразу видно программиста не-железячника
Это очевидно — в этой ветке я единственный, кто последовательно, логично и с примерами отстаивает свою позицию.
Программа тут не при чем. Может прилететь частица и изменить бит в памяти (нормальная ситуация для аппаратуры на спутниках), может случиться скачок/просадка напряжения и повредить участок FLASH/EEPROM (например, если BOD настроен неверно и запись в NVM прошла при нештатном напряжении) или область RAM, может быть повреждена линия к внешней микросхеме памяти, и еще куча вариантов.
Иными словами, вы назвали следующие смертные грехи: 1) неправильный выбор элементной базы для указанных в ТЗ условий эксплуатации, 2) нарушение технологии, 3) неправильная эксплуатация, повлекшая физическое повреждение, а так же 4) ещё какую-то кучу.
Вы правда хотите какой-то программой исправить вышеозначенные косяки аппаратуры, на которой эта же программа и исполняется? Если да, то я прошу вас назвать вуз, который выдал вам диплом, а так же организацию, в которой вы работаете.
Это самый простой способ отловить неожиданное значение
Вы таки продемонстрируете код?
я единственный, кто последовательно, логично и с примерами отстаивает свою позицию.
Переход на аргументы ad hominem плохо сочетается с перечисленными добродетелями. :)
Вы правда хотите какой-то программой исправить вышеозначенные косяки аппаратуры, на которой эта же программа и исполняется?
В реальном мире никогда не будет идеальных условий эксплуатации — просто примите это. Причем чем специфичнее применение, тем условия, как правило, тяжелее. И программа, по-возможности, должна если и не спасать положение целиком, то хотя бы минимизировать финальный ущерб. Это достигается не только программными средствами, конечно; критичные функции всегда защищаются аппаратно, насколько это возможно.
Кстати, от выбивания битов в памяти не имунна полностью и «правильная» по вашему мнению элементная база. Да, в технологии с диэлектрическими подложками тиристорного эффекта не будет, но вероятность повреждения памяти заряженными частицами все равно есть. Предлагаете делать на лампах? Вот они да, 100% защищены от всех эффектов.
Сразу скажу, что свинцовая или какая-то другая защита не спасет. Попадание частицы с космическим уровнем энергии в материал вызывает лавину вторичных частиц, что усугубляет проблему. Как видите, есть много нюансов.
Жизнь сложна и многообразна; защищаться воплями «ЭТО НАРУШЕНИЕ ПРАВИЛ ЭКСПЛУАТАЦИИ ВЫ САМИ ВИНОВАТЫ!!!1111» — так себе вариант. Надо предполагать работу аппаратуры и в нештатных условиях. В том числе и не предусмотренных ТЗ, но вероятных, в том числе и в результате возможного раздолбайства.
Вы таки продемонстрируете код?
Эээ, вам написать switch, в котором в блоке default осуществляется вызов обработчика ошибки?
В реальном мире...
Т.е. вы таки собираетесь с помощью default исправлять аппаратуру, на котором этот default исполняется?
Ну, в самом деле, назовите уже вуз и место работы — надо же знать куда нельзя отправлять учиться детей, и с чьей продукцией нельзя связываться.
Эээ, вам написать switch, в котором в блоке default осуществляется вызов обработчика ошибки?
И показать, почему это будет лучше, чем без default.
Кстати, если вы так настаиваете, я тоже перейду на личности и спрошу — а вы сами далеко ушли от Arduino? Похоже нет.
И показать, почему это будет лучше, чем без default.
То, что наличие дополнительной обработки ошибок лучше, чем ее отсутствие, не очевидно?
По-моему, вы не читаете мои сообщения
К сожалению, я их читаю. К сожалению, потому что ничего в них дельного нет.
Использование default — маленький кирпичик, который может повысить надежность
Очередное голословное утверждение без кода.
Кстати, если вы так настаиваете, я тогда тоже перейду на личности и с прошу — а вы сами далеко ушли от Arduino? Похоже нет.
Я не в курсе, что здесь личного. К Arduino я никогда не прикасался и не собираюсь.
То, что наличие дополнительной обработки ошибок лучше, чем ее отсутствие, не очевидно?
Вы не показываете, где есть эта самая дополнительная обработка.
До c++17 можно было написать что-то вроде
auto invalid = E(42);
И попасть в default ветку. И это не было UB!
Конечно, никто так не пишет, но память в МК — не идеал надёжности
До c++17 можно было написать что-то вроде
auto invalid = E(42);
И попасть в default ветку. И это не было UB!
Это и сейчас можно. Поэтому необходимо в правилах предупредить именно эту ошибку, а не залеплять потом дыры с помощью default. Например, как в CERT.
Но и в CERT не идеально. На современных плюсах я бы предпочёл что-то вроде
enum class variants
{
var1,
var2,
var3,
...
varN
};
std::optional<variants> from_int(int value)
{
const auto e{static_cast<variants>(value)};
switch(e)
{
case variants::var1: [[fallthrough]];
case variants::var2: [[fallthrough]];
case variants::var3: [[fallthrough]];
...
case variants::varN:
return e;
}
return {};
}
память в МК — не идеал надёжности
И вы считаете, что такие МК можно использовать в критических системах, для которых и создана MISRA?
Назовите хоть один МК, который полностью защищён от повреждений памяти.
Вы тоже собираетесь исправлять аппаратные проблемы программой, которая на этой же аппаратуре исполняется?
Ну, вообще говоря отчасти это так и делается. Например если нет аппаратного скраббера, то делается программный, кроме того многие ошибки в аппаратуре только выставляются, но обрабатываются софтом.
Как верно выше заметили — безсбойной аппаратуры не бывает, но число и опасность сбоев можно сильно понизить за счет ряда решений, в основном аппаратных, но софт — тоже всегда часть этого решения.
Но вообще я другое хотел спросить. Вот вы выше привели пример, как бы вы использовали switch, в нем нет дефолта, да, но простите — в чем было бы отличие, если бы все-таки был default, в котором как раз было бы return {}?
Ну, вообще говоря отчасти это так и делается. Например если нет аппаратного скраббера, то делается программный
Вы не видите разницы между программной эмуляцией и исправлением непредсказуемых косяков железа с помощью программы, на этом же железе исполняемой?
кроме того многие ошибки в аппаратуре только выставляются, но обрабатываются софтом
Так выставляемые ошибки предсказуемы! И потому, внезапно, являются частью гарантий/контракта этой аппаратуры. А вы тут пропагандируете новое слово в надёжности, когда исправляются непредсказуемые ошибки, нарушающие гарантии.
Как верно выше заметили — безсбойной аппаратуры не бывает, но число и опасность сбоев можно сильно понизить за счет ряда решений, в основном аппаратных, но софт — тоже всегда часть этого решения.
Я уже устал отвечать на подобные размышлизмы. Знаю одно — я не хотел бы быть вашим коллегой.
Но вообще я другое хотел спросить. Вот вы выше привели пример, как бы вы использовали switch, в нем нет дефолта, да, но простите — в чем было бы отличие, если бы все-таки был default, в котором как раз было бы return {}?
А вы пробовали не спрашивать, а прочитать ветку, в которую постите? Вот прямо мой первый комментарий, там ясно написано:
чтобы получить ошибку, если кто-то добавит новый элемент
Вы не видите разницы между программной эмуляцией и исправлением непредсказуемых косяков железа с помощью программы, на этом же железе исполняемой?
Гм, вот что я точно не вижу, так это суть выражаемой в данном предложении мысли. Можете развернуть ее более понятно?
А вы тут пропагандируете новое слово в надёжности, когда исправляются непредсказуемые ошибки, нарушающие гарантии
Щито? Во-первых я тут пока ничего не пропагандировал, а во-вторых я похоже опять должен вас спросить — что вы имеете в виду, какие такие непредсказуемые ошибки?
Я уже устал отвечать на подобные размышлизмы. Знаю одно — я не хотел бы быть вашим коллегой.
Ну, при таком уровне аргументации я вас на работу и не возьму никогда.
А вы пробовали не спрашивать, а прочитать ветку, в которую постите? Вот прямо мой первый комментарий, там ясно написано:
Это я прочитал, но это касается только компилтайма. А вы там дальше уже про корректный враппер интов для рантайма пишете, и именно про него я спросил. То, что он также наследует желаемое вами поведение в компилтайме, я никак не оспариваю. Впрочем, поскольку обработка в рантайме в конечном итоге важнее всего, то в общем вариант с default все так же достаточен для решения основной задачи.
Впрочем, поскольку обработка в рантайме в конечном итоге важнее всего, то в общем вариант с default все так же достаточен для решения основной задачи
Вариант без default позволяет получить при компиляции ошибку и тем самым предупредить некорректное поведение во время исполнения.
В то же время, и вариант без default, и вариант с default одинаково ведут себя на всех входных данных (хотите оспорить это утверждение примером входного int с разным поведением двух вариантов?).
Внимание, вопрос: так почему же нужно выбрать вариант с default?
Вы должны гарантировать безошибочность написание текста исходного кода за все время. Мало того, вы так же должны доказать правильность работы компилятора, например, у нас имеется правило запрета использования прописной буквы я даже в комментариях, вы знаете откуда это идёт, думаю что нет. Вы можете написать правильно работающую программу, но вот использовать вам ее не разрешат, так как вы не сможете доказать ее правильность.
ваша ошибка в том, что считаете что сертифицируется ПО, а на самом деле сертифицируется процесс создания ПО
Серьёзно? Я где-то говорил про сертификацию?
И да, ПО всё же верифицируется.
вы так же должны доказать правильность работы компилятора
Нет, такого доказывать не надо. Надо доказать, что программа, скомпилированная данным компилятором, работает правильно.
Например, КТ-178 вещает следующее:
Компилятор считается пригодным для создания программного продукта после успешного завершения верификации данного продукта.
у нас имеется правило запрета использования прописной буквы я даже в комментариях, вы знаете откуда это идёт, думаю что нет
Боитесь разных символьных наборов на разных устройствах или ещё какой-то идиотизм. Но у меня другой вопрос: а расставлять знаки препинания как попало вас тоже заставляют?
Вы можете написать правильно работающую программу, но вот использовать вам ее не разрешат, так как вы не сможете доказать ее правильность.
Ценная мысль, надо бы отлить в граните.
Но зачем вы написали весь этот ваш комментарий?
Да вот только правило говорит нам, что следует всегда использовать default. Я и указываю на излишнюю догматичность и даже вредность этого правила для случая enum.
В случае, если enum содержит, например, пару десятков значений, а реально полезны (как-то обрабатываются) из них в конкретном switch штук пять — перечисление всех возможных значений в switch вряд ли можно назвать чистым и читаемым кодом. Указание только обрабатываемых значений и обработка по default, по мне, выглядят куда более читабельными.
Конечно, всё, что пришло извне, должно подвергаться проверке. Только при чём тут default?
Как я понял, речь о том, что значение переменной в памяти может измениться/побиться, причём может получиться совершенно произвольное значение, не соответствующее ни одному из используемых в enum (т.е. при использовании enum со значениями 1, 2, 3 переменная может оказаться равной, скажем, 23). В таком случае наличие case со всеми значениями enum и без default в конце не приведёт к срабатыванию ни одного case (значение-то совсем кривое). И вот тут default с «аварийной» обработкой будет очень кстати.
Поэтому лично я обеими руками за default всегда и везде.
В случае, если enum содержит, например, пару десятков значений, а реально полезны (как-то обрабатываются) из них в конкретном switch штук пять — перечисление всех возможных значений в switch вряд ли можно назвать чистым и читаемым кодом. Указание только обрабатываемых значений и обработка по default, по мне, выглядят куда более читабельными.
Если у вас enum с парой десятков значений или используется как флаги (что очень любят), то код ваш уже говно — можете использовать default.
Как я понял, речь о том, что значение переменной в памяти может измениться/побиться, причём может получиться совершенно произвольное значение, не соответствующее ни одному из используемых в enum (т.е. при использовании enum со значениями 1, 2, 3 переменная может оказаться равной, скажем, 23). В таком случае наличие case со всеми значениями enum и без default в конце не приведёт к срабатыванию ни одного case (значение-то совсем кривое). И вот тут default с «аварийной» обработкой будет очень кстати.
Поэтому лично я обеими руками за default всегда и везде.
Да, вон там ниже HighPredator, тоже такой же рукастый, обосрался на простейшем примере — полюбопытствуйте.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum values
{
one,
two,
three,
four,
five
};
int foo(void)
{
return rand() % (five + 1);
}
int main(void)
{
enum values f = foo();
printf("Original value = %d\n", f);
char c;
memset(&c, 0xFF, 2 * sizeof(c));
printf("New value = %d\n", f);
switch (f)
{
case one:
case two:
case three:
case four:
case five:
printf("Within range\n");
break;
default:
printf("Out of range\n");
break;
}
return 0;
}
Идея простая: из-за ошибки в коде, повреждается переменная и по факту начинает содержать значения не из перечисления. В таких случаях, дефолтный блок, написанный осмысленно, будет выступать барьером против неопределенного поведения в процессе работы приложения в рантайме. А оно бы и получилось в данном варианте, ведись работа с переменной дальше с ожиданием, что значения всегда будут в диапазоне.Без проблем. Предположим, есть следующий код
Да, давайте предположим, и даже запустим. Предупреждения компилятора как бы намекают, но мы ведь считаем, что косяк запрятан далеко и так просто его никакой анализатор не увидит, правда?
Идея простая: из-за ошибки в коде, повреждается переменная и по факту начинает содержать значения не из перечисления. В таких случаях, дефолтный блок, написанный осмысленно, будет выступать барьером против неопределенного поведения в процессе работы приложения в рантайме
Идея идиотская, а запуск показывает, что вашим default даже подтереться нельзя, не то что «барьером против неопределенного поведения» сделать.
А оно бы и получилось в данном варианте
А неопределённое поведение и так случилось — в момент записи в память. И default здесь не поможет. Идея написать такой код, который будет устойчив к неопределённому поведению C/C++ такая же дебильная, как идея ораторов выше исправлять кодом аппаратные ошибки.
Неопределённое поведение потому и неопределённое, что не определено :)
А оно бы и получилось в данном варианте, ведись работа с переменной дальше с ожиданием, что значения всегда будут в диапазоне
Вы бы предыдущую дискуссию хоть бы почитали. В том то и дело, что я нигде не предлагал всегда считать, что значение в допустимом диапазоне.
Наверное, это сложная для вас мысль, потому я повторю её иначе: не писать default и всегда считать, что переменная в допустимом диапазоне — это две большие разницы.
Хотя выше уже есть мой код, иллюстрирующий эти слова, только сегодня и только сейчас я специально для вас продемонстрирую этот трюк ещё раз, предварительно пофиксив ваш код: невероятный трюк.
И хотя тут нет претензий на
А вот полагаюсь я на то, что значение enum корректное (т.е. является одним из его элементов), только тогда, когда 1) оно уже было проверено при поступлении на вход программы (из сети, консоли, файлов и т.п.) и 2) это enum class в C++. Потому что, как и было показано вашим замечательным примером, от неопределённого поведения ничего не спасёт, разве что свечка в церкви и санитайзер на CI, а присвоить случайно число в enum class переменную нельзя. Таким образом останутся только логические ошибки, которые ловятся тестами.
А почему плохо засунуть return -1 в default секцию?
Статическое объявление всего — вообще первая особенность программирования под железо. Во-первых, в 90% случаев отсутствует ОС, так что по умолчанию следить за памятью нечему, а реализации менеджера памяти в стандартной библиотеке не слишком оптимальны. При этом действительно хорошая реализация malloc() может занимать места столько же, сколько вся программа.
Ну и я уже не говорю о том, что выделение памяти, действительно, недетерминированная операция; нет никакой гарантии, что в нужный момент окажется доступным нужный объем памяти.
Я им сразу говорил, что строки должны лежать в progmem и там же должна быть таблица с указателями на эти строки. Но нет, мы типа умные и за всем сами следим. В итоге потеряли два дня, зато вроде урок усвоили.
Со строками в progmem я знаком, а вот с таблицей указателей на эти строки, нет.
Не могли бы Вы привести коротенький пример как это реализуется?
Наверное можно было не экономить эти 200 байт в RAM (указатели на что-то типа 100 строк), но раз уж занялись перфекционизмом, то почему бы не потренироваться.
static const char idx2msg[][LOG_MSG_SIZE] = {
{"I'm_still_alive"},
{"Event_Offset_SRCppm_Trim_Jitter_DSPppm"},
{"--------------------------------------"},
{"ARM_MATH_LENGTH_ERROR"}
};
enum msg_idx {
LOGMSG_IM_ALIVE = 0,
LOGMSG_PPS_HDR0,
LOGMSG_PPS_HDR1,
LOGMSG_PROC_RATIO
};
В другом проекте у нас строки хранятся прямо в вызове функции печати, это-то понятно как работает. А вот таблицу найти не могу, сорри. Если найду, то напишу.
DisplayControl::instance().printLinePgm( 0, PSTR( "Восстановление настроек по умолчанию" ), TextAlignment::AlignLeft );
microsin.net/programming/avr/avrstudio-gcc-progmem.html
Добрый вечер! Спасибо за статью...
Вы написали:
"…
Дело в том, что не существует какого-то специального «аттестата» о том, что ваш код соответствует MISRA. Как оговаривает сам стандарт, отслеживание кода на соответствие должно выполняться двумя сторонами: заказчиком ПО и поставщиком ПО. Поставщик разрабатывает ПО, соответствующее стандарту, и заполняет необходимые документы. Заказчик же со своей стороны должен удостовериться, что данные из этих документов соответствуют действительности.
..."
Скажите пожалуйста, если исполнитель не предоставляет исходный код, то как заказчик проверит на соответствие стандарту?
См. PVS-Studio 7.16, взятие рубежей: MISRA C, Visual Studio 2022, .NET 6.
Что такое MISRA и как её готовить