Comments 74
List of tools for static code analysis: C, C++; Multi-language.
Вот, наконец-то SonarQube упомянули!Кстати, про SonarQube мы упомянули не в первый раз:
- Проверяем проект PascalABC.NET с помощью плагинов для SonarQube: SonarC# и PVS-Studio
- Контролируем качество кода с помощью платформы SonarQube
- PVS-Studio как плагин для SonarQube
sonar — использовали в одном из проектов. Не впечатлил.
Ориентировочную же цену вполне можно прикинуть, изучив страницу "Купить".
Профилактикой может быть правило в стандарте кодирования компании или просто любовь к написанию качественного кода, что не позволяет использовать такие явные константы как 3, 4. Использовалась бы именованная константа, и беды не было.
P.S. Есть ещё доказательство корректности программ и соответствующие инструменты, но это слишком медленно и дорого для подавляющего большинства проектов. Формализация (описание) как работает функция занимает больше, чем сама функция. Плюс там есть масса сложностей, с такими языками как C++.
Вот сейчас для проекта у меня ПО микроконтроллера как раз и пишется на обычном PC (под QNX) на Си с обвязкой на Си++ с имитацией драйверами всего окружения аппаратуры и имитацией HAL. Всё вроде бы хорошо, но один из процессоров необычный (отечественный аналог TMS середины 80-х) — у него байт 32 бита (да, и sizeof(char)=1 и char=32 бита), компилятор с Си 89 (а может и раньше) и глючной работой кое-чего в Си ( скажем, int a[2]={1,2}; компилируется, но инициализация не выполняется :) ). Как это всё поведёт себя на реальном процессоре — вопрос.
А что касается тестов вообще, я не понимаю, как всё это обложить тестами правильно. Писать ли для каждой функции/класса отдельную программу? Тогда программ будет масса — ведь каждому классу нужно имитировать окружение. Или тест надо встраивать в саму программу — это приведёт к тому, что программа резко распухнет и будет сложно разделить тестовую часть от штатной. А проверка логики работы будет заключаться во взаимодействии классов и потребует имитации этой же логики в тесте (иначе как проверить, что логика отработала верно). Вот всего этого я и не понимаю.
Да, это нормально, так и должно быть.
Я ж застрелюсь. :)
Не совсем понял, что вы понимаете под «отдельной программой»,
Я имею в виду, что нужно ли делать отдельный тестовый стенд (программу) для каждого класса. Тесты же нужно откуда-то вызывать.
Вообще, про тестирование есть довольно много литературы и обучающих курсов, загляните, если интересно.
Спасибо за информацию. :) Статьи я читал, но всё равно масса непоняток для реального проекта остаётся.
У одной единицы — одна ответственность,
Часто при таком подходе трудно запомнить связи между сущностями. Скажем, у меня есть менеджер управления электропитанием, менеджер времени, менеджер имитации ошибок, менеджер обмена с аппаратурой и так далее. Так вот, удобно их связывать воедино с помощью одного класса-системы. Иначе связь между этими менеджерами трудно отследить (а через класс системы — просто — все обращаются к нему и он переадресует).
Ну кстати шанс как раз есть — юнит тесты мало того что писать надо, но ещё и код делать тестируемым, а с анализатором нужно просто запустить его.
Даже с именованной константой никто не застрахует от возможности написать что-то вроде:
for (int i = 0; i <= NUM_ELEMENTS; ++i) {… }
array<uchar, 3> A;
...
const auto average = accumulate(begin(A), end(A), 0) / A.size();
Чтобы разработчик задумался и переписал бы код, например, так:
Да-да, это очень правильно. Только мы сейчас об эмбеде, а не о модном и гламурном мире C++ '11 и прочих F#.
Если вы пишете для встроенной системы, то хороший тон — соблюдать правила если не C89, то максимум C99. Это гарантирует наибольшую переносимость между разными компиляторами и архитектурами. Так что забудьте про auto и прочее — просто надо внимательно подходить к написанию кода (не исключая использования статических анализаторов).
Во-вторых, сможете показать, чем этот код на современном C++ уступает по эффективности/ресурсоемкости коду на C89/C99?
сможете показать, чем этот код на современном C++ уступает по эффективности/ресурсоемкости коду на C89/C99?
Начнем с того, что этот код не скомпилируется под, скажем, SDCC… Насчет Cosmic — тоже не уверен. Пробовать лень. Если попробуете — расскажите.
типично ассоциируемыеЗдесь же код обсуждается, а не заблуждения на счет того, что считается embedded, а что нет.
Начнем с того, что этот код не скомпилируется под, скажем, SDCC…Ну возьмите компилятор, который поддерживает C++11. В чем код на C++ будет уступать коду на C?
Кстати говоря, подобный код и на C++98 можно писать, только там не будет auto и array/begin/end нужно будет брать не из stdlib. Принцип остается тем же.
Если же ваш поинт был в том, что где-то в embedded кроме допотопных компиляторов C89 ничего нет. Ну чтож, бывает. Сейчас так. Пройдет немного времени и будет не так.
Здесь же код обсуждается, а не заблуждения на счет того, что считается embedded, а что нет.
Самый главный признак «embedded» в том, что там уже нельзя обсуждать сферический код в вакууме. Сразу возникают вопросы — для какой архитектуры, для какого конкретно компилятора, что там с поддержкой прерываний и пр.
Ну возьмите компилятор, который поддерживает C++11.
Покажите мне компилятор для, например, STM8, который поддерживает C++11, и будем сравнивать. Пока я не знаю, где такой взять. :)
Самый главный признак «embedded» в том, что там уже нельзя обсуждать сферический код в вакууме.
Т.е. обсуждение этого кода в принципе лишено смысла, т.к. нет ответов на вопросы:
для какой архитектуры, для какого конкретно компилятора, что там с поддержкой прерываний и пр.Я правильно понимаю вашу логику?
Покажите мне компилятор для, например, STM8, который поддерживает C++11, и будем сравнивать.Прочтите, пожалуйста, то, что вам пишут. В частности:
Если же ваш поинт был в том, что где-то в embedded кроме допотопных компиляторов C89 ничего нет. Ну чтож, бывает. Сейчас так. Пройдет немного времени и будет не так.
Да я прочел. Правда, меня берут сомнения в том, что можно сделать компилятор C++11 для устройства с тридцатью двумя байтами RAM и килобайтом FLASH.
Правда, меня берут сомнения в том, что можно сделать компилятор C++11 для устройства с тридцатью двумя байтами RAM и килобайтом FLASH.Какие сложности? На этом же устройстве будет работать не компилятор, а собранная им программа. Как, собственно, и в случае с C-компилятором.
Какие сложности?
Это уже к m08pvv, например. :) Должен признать, что я не специалист в теории внутренней работы компиляторов.
я не специалист в теории внутренней работы компиляторов.
Я тоже не специалист по компиляторам, но за любые приятности языка приходится платить размером кода и занимаемой памяти.
компилятор C++11 для устройства с тридцатью двумя байтами RAM и килобайтом FLASH
Если ограничения настолько суровые, то проблема не в том чтобы написать компилятор, а в том, чтобы написать программу, которая запустится. Компилятор будет стабильно выдавать программу с потреблением RAM > 32 байт и/или общим размером > килобайта.
Можно, конечно, заставить компилятор оптимизировать данный код по размеру, но вряд ли экономически оправдано писать C++ компилятор для устройств с 32 байтами RAM и килобайтом FLASH, ибо под такие устройства скорее уж на их ассемблере писать надо.
Я тоже не специалист по компиляторам, но за любые приятности языка приходится платить размером кода и занимаемой памяти.Может хоть вы покажете, чем придется платить в приведенном C++ном коде?
Если под «приятностями» понимаются вещи, которые требуют поддержки в run-time, вроде исключений, RTTI и динамической памяти, то такие вещи для сильно ограниченных по ресурсам устройств просто отключаются.
Может хоть вы покажете, чем придется платить в приведенном C++ном коде?
Ну так, для этого надо раздобыть компилятор с поддержкой C++11 хотя бы для AVR или на худой конец Cortex, написать тестовую программу, дизассемблировать ее и посмотреть.
Покажите мне компилятор с хорошей поддержкой C++11 хотя бы для какой-нибудь встроенной архитектуры, и будем сравнивать. Я не знаю такого.
динамической памяти
Динамическое выделение памяти прямо запрещается MISRA.
Если про конкретный код, то он не представляет никаких проблем и любой компилятор спокойно выдаст оптимальный по размеру/скорости (в зависимости от флагов) код. И вообще любой код, который не далеко ушёл от чистого C, тоже не будет представлять проблем.
Если же про 32 байта RAM, то проблемы будут начиная с виртуальных методов (32 байта RAM очень быстро будут съедены таблицами виртуальных методов и лишними указателями) и прочего. Теоретически, вполне можно и это всё тоже оптимизировать (на таком микроконтроллере никто же не будет ничего подгружать динамически, поэтому вполне можно на этапе компиляции избавиться вообще от всего), но опять всё упирается в вопрос о целесообразности написания C++ компилятора для такой целевой архитектуры. В большинстве случаев, на таких микроконтроллерах решаются задачи, для которых чистого C хватает более чем.
Мы обсуждаем конкретный код вычисления среднего значения массива или написание компилятора с целевой архитектуройНет, скорее обсуждаем разумность применения C++ в embedded. С одной простой демонстрацией.
И вообще любой код, который не далеко ушёл от чистого C, тоже не будет представлять проблем.По-вашему, показанный код с использованием accumulate не далеко ушел от чистого C? И шаблонный array из C++ — это все тот же чистый C?
Если же про 32 байта RAM, то проблемы будут начиная с виртуальных методов (32 байта RAM очень быстро будут съедены таблицами виртуальных методов и лишними указателями) и прочего.Ну и какого прочего? Для совсем уж ограниченных условий RTTI, исключения и поддержка динамической памяти из run-time просто изымаются. Использовать ООП с наследованием и полиморфизмом никто не заставляет. А если виртуальные методы не используются, то платить за них не нужно. Что еще?
Нет, скорее обсуждаем разумность применения C++ в embedded. С одной простой демонстрацией.
С++ — это просто инструмент, для многих платформ и задач он вполне подходит и я этого не отрицаю.
По-вашему, показанный код с использованием accumulate не далеко ушел от чистого C? И шаблонный array из C++ — это все тот же чистый C?
Компилятор спокойно может из этого кода получить не менее эффективный код, чем из аналогичного на C, так что для меня они эквивалентны.
Использовать ООП с наследованием и полиморфизмом никто не заставляет.
А что использовать от C++? auto — сахар, который никак не влияет. Шаблоны — легко сожрать весь доступный килобайт FLASH. Вообще, под такую платформу даже на чистом C будут проблемы, т.к. подключив какую-нибудь библиотеку легко выйти за допустимые ограничения по памяти (килобайт FLASH — это катастрофически мало для многих библиотек).
Если же никаких шаблонов, никаких исключений, никаких RTTI, осторожная работа с памятью, то для многих контроллеров можно писать и на C++
для многих платформ и задач он вполне подходит и я этого не отрицаю.Тут скорее вопрос в том, что в большинстве случаев (возможно, подавляющем) C++ оставляет меньший простор для ошибок, в сравнении с чистым C.
А что использовать от C++?А вот те же шаблоны. То, что они раздувают код — это предания из 90-х. Если не верите, то спросите себя: насколько раздувают код те же std::array или тот же std::accumulate. Тот же auto вполне себе не сахар, а средство борьбы со сложностью и с забывчивостью разработчиков (классический пример: какой тип возвращает strlen()?). Ссылки.
И, если не брать случай с 32 байтами RAM, в которых и на нормальном С не попрограммируешь, то единственное преимущество чистого C — это пока еще отсутствие C++ компиляторов для каких-то отдельных платформ. Ну и традиции, типа «диды писали...» :)
C++ оставляет меньший простор для ошибок, в сравнении с чистым C.
Согласен.
А вот те же шаблоны. То, что они раздувают код — это предания из 90-х. Если не верите, то спросите себя: насколько раздувают код те же std::array или тот же std::accumulate.
Зависит от использования. При неразумном использовании будет съедать место на FLASH. Да и сама stl будет съедать место. Придётся компилятору постараться и выкинуть всё лишнее, что не используется, а то и вовсе в несколько проходов оптимизировать, чтобы получить код, который укладывается в заданные ограничения.
единственное преимущество чистого C — это пока еще отсутствие C++ компиляторов для каких-то отдельных платформ
Под ARM более менее хорошо с компиляторами C++, а вот остальной зоопарк — вендоры не спешат делать компиляторы C++, т.к. под их микроконтроллеры большинство пишет на ассемблере или на C, а пишут они потому, что если есть возможность писать на C++, то результат требует доработки напильником или даже серьёзного разговора с компилятором. В итоге имеем порочный круг.
Ну и традиции, типа «диды писали...» :)
Компилятор C++ в среднем есть более сложная программа, чем компилятор C, и в сообществе разработчиков встроенных систем укоренились мифы (часть из которых до сих пор правда) о том, что иногда надо плясать с бубном, чтобы компилятор выдал то, что надо (особенно если весьма жёсткие ограничения по памяти или ещё какие). Поэтому, скорее всего, выбор и идёт в сторону проверенного и безотказного инструмента. Но вполне возможно, что в недалёком будущем это изменится, так как принципиальных проблем нет.
Я не знаю, кто определяет эти правила хорошего тона, но надо этих товарищей вытащить из криокамеры и сказать что сейчас уже две тысячи восемнадцатый на дворе. :) Прекрасно сейчас используют C++ в embedded, в том числе для мелких контроллеров.
Я не знаю, кто определяет эти правила хорошего тона
Те, например, для кого SDCC — не «а что это такое?», а суровая реальность.
Сочувствую, конечно. Но мне кажется, что это ошибка — жаловаться, что C++ код не компилируется компилятором языка C. Если для вашей платформы нет компилятора C++ (я так понимаю, что вы для STM8 разрабатываете?) это не значит, что весь embedded такой. Вы сами выше написали, что нельзя обсуждать сферический код в вакууме без привязки к конкретике — и теперь сами же обобщаете правила кода для STM8 на всю область. А если найдётся контроллер, для которого только ассемблер есть — будем считать, что писать на C в embedded правило плохого тона? :)
P.S.: Только что посмотрел — вроде IAR для STM8 вполне себе может C++.
и теперь сами же обобщаете правила кода для STM8 на всю область
Я лишь о том, что решать задачи надо минимально необходимыми средствами, чтобы код (хотя бы не привязанный к железу напрямую) работал на максимальном количестве платформ. Иначе может быть очень обидно, когда нужная библиотека существует, и вроде бы в плане ресурсоемкости самого алгоритма должна бы работать на целевом чипе, но написана на языке/диалекте, который не поддерживается используемым компилятором, и потому требует правки, сравнимой с переписыванием.
я так понимаю, что вы для STM8 разрабатываете?
Я разрабатываю, в зависимости от необходимости, для STM8, MSP430, AVR и STM32, иногда с привлечением CPLD Altera. :)
Справедливости ради, прямо сейчас у меня на столе лежит плата, относящаяся к текущему проекту, и выполнена она на основе STM32F051R8T6.
вроде IAR для STM8 вполне себе может C++.
Только вы знаете, сколько стоит IAR? :) А вот Cosmic и SDCC бесплатны.
Понятное дело, что для хобби разницы в, э-э-э, некотором смысле, нет, но когда это продукт, то лучше использовать официально бесплатные инструменты.
когда это продукт, то лучше использовать официально бесплатные инструменты.
Скорее не бесплатные, а лицензионные. Если у фирмы есть лицензия на IAR, то почему бы им не воспользоваться? А если нет, то может стоит поставить вопрос о приобретении лицензии? Можно, конечно, и без колёс толкать телегу, говоря, что так бесплатно, но с колёсами удобнее.
По делу: ваш блог стоит читать уже хотя бы ради КДПВ!) Ну и образцы косяков культуру кода поднимают.
Примером может служить использование 32-битного типа time_t
Просто ради полноты картины, компиляторы поновее уже могут определять time_t
как 64-битный тип (gcc 7.x для arm-none-eabi, например, определяет как __int_least64_t
).
Такой цикл у меня одного вызывает сомнение?
Выглядит странно. А если ещё вдруг захочиться пропустить несколько элементов в теле цикла сделав там i += x
то всё.
Но
Сразу же всплыла ложка дегтя
В моем проекте нашлась ошибка:
V512. A call of the 'Foo' function will lead to a buffer overflow or underflow.
Перехожу в описательную часть с примерами таких ошибок и вижу пример от PVS-Studio как делать не надо:
Sample N2.
#define CONT_MAP_MAX 50
int _iContMap[CONT_MAP_MAX];
memset(_iContMap, -1, CONT_MAP_MAX);
In this sample, the size of the buffer to be filled is also defined incorrectly.
Вместо этого предлагается такой код (как «надо делать» по версии PVS):
This is the correct version:
#define CONT_MAP_MAX 50
int _iContMap[CONT_MAP_MAX];
memset(_iContMap, -1, CONT_MAP_MAX * sizeof(int));
Вот тут то и закралось в голове: а что, если завтра в таком проекте мне необходимо будет исправить
int _iContMap[CONT_MAP_MAX];
на
char _iContMap[CONT_MAP_MAX];
???
Выводы для сотрудников PVS-Studio:
Проверяйте примеры, которые выдаете за эталон «Как надо»
Выводы для всех остальных:
Не копируйте бездумно то решение, которое выдают за эталон.
Если кодстайл принуждает к sizeof(тип), то приводят примеры как у вас.
int a;
memset(&a, 0, sizeof(int));
Меняем тип в объявлении переменной
char a;
memset(&a, 0, sizeof(int));
и теперь memset пишет вне переменной.
А если кодстайл принуждает к sizeof(переменная), то пример такой:
Было:
int a[100];
memset(a, 0, sizeof(a));
Делаем выделение памяти динамическим:
int* a = (int*)malloc(100);
memset(a, 0, sizeof(a));
Теперь память недозаполняется.
Получается как не пиши код, можно придраться.
Есть, кстати, трюк, который позволяет хоть немного, но защититься от проблемы со взятием sizeof() от указателя вместо массива. У гугла в коде такое можно встретить:
template <typename T, size_t N>
char (&ArraySizeHelper(T (&array)[N]))[N];
#define arraysize(array) (sizeof(ArraySizeHelper(array)))
И дальше можно использовать arraysize() — он не скомпилируется, если ему дать указатель.
Почему embedded-разработчикам следует использовать статический анализ кода