Как стать автором
Обновить

Почему embedded-разработчикам следует использовать статический анализ кода

Время на прочтение5 мин
Количество просмотров12K
Всего голосов 57: ↑53 и ↓4+49
Комментарии74

Комментарии 74

Вот, наконец-то SonarQube упомянули! Когда можно будет сравнить ваш java анализатор с его бесплатным и встроенным?
PVS-Studio не анализирует Java код
Да, вы правы. Но была публикация «PVS-Studio 2018: CWE, Java, RPG, macOS, Keil, IAR, MISRA» где анонсировали возможное появление анализатора для java. Мы дискутировали в комментариях, что эта ниша уже насыщена достаточно качественными бесплатными анализаторами.
Так ведь и ниша C++ насыщена бесплатными анализаторами (Cppcheck, Clang, ...). :)
Надо пообщаться с коллегами, почему они остановили свой выбор именно на pvs-studio для C++. Возможно причина не только в хорошем маркетинге. Статьи тут очень часто отличные!
К сожалению, на Java направление у нас пока выделено очень мало ресурсов. Так что ничего пока сказать не могу. Сейчас мы сосредоточены над C++: поддержкой macOS, классификацией предупреждений согласно CERT, внедрением в ядро символьных вычислений, embedded.
Уверен, что с embedded вашей компании лучше удастся продавать анализатор! Рынок важен с точки зрения надежности и высокой стоимости доставки обновлений. В области статического анализа Java кода достаточно высокая конкуренция…
Как будто в области статичекого анализа для C++ конкуренции нет… :)
List of tools for static code analysis: C, C++; Multi-language.
Это же отлично, что упоминаете удобный инструмент статического анализа кода и continuous code quality! По прочтению этой публикации почему-то вспомнил ваш комментарий:
sonar — использовали в одном из проектов. Не впечатлил.
Время пришло. Приглашаю скачать PVS-Studio для Java и попробовать проверить свои проекты. :)
Может быть вы объясните это острое нежелание показывать цены у отечественных производителей? Почему надо обязательно писать менеджеру, чтобы просто узнать цену?
Отечественность производителя здесь ни при чём, тем более, что мы ориентированы на иностранных клиентов. Есть классы продуктов, где невозможно просто написать цену, так как она формируется из количества лицензий, скидок при покупке на несколько лет, наценки реселлера и так далее. По этой же причине, вы не увидите цены для таких статических анализаторов как Coverity, Klocwork, Parasoft.

Ориентировочную же цену вполне можно прикинуть, изучив страницу "Купить".
НЛО прилетело и опубликовало эту надпись здесь
Нет. Непонятно, к чему придраться. Можно конечно сделать жуткое эмпирическое правило, основанное на названии переменной в которой есть «average», но на практике, это всё равно не будет работать. Динамический анализ здесь тоже не поможет. Подобные ошибки следует выявлять юнит-тестами. Именно из-за разнообразия ошибок, все эти методологии поиска ошибок не конкурируют, а дополняют друг друга.

Профилактикой может быть правило в стандарте кодирования компании или просто любовь к написанию качественного кода, что не позволяет использовать такие явные константы как 3, 4. Использовалась бы именованная константа, и беды не было.

P.S. Есть ещё доказательство корректности программ и соответствующие инструменты, но это слишком медленно и дорого для подавляющего большинства проектов. Формализация (описание) как работает функция занимает больше, чем сама функция. Плюс там есть масса сложностей, с такими языками как C++.
НЛО прилетело и опубликовало эту надпись здесь
Нерационально обсуждать тему, что всё плохо и что всё тлен. Давайте лучше по возможности доносить до программистов мысль, что качество кода — это очень важно, особенно когда речь идёт о управлении чем-то внешним. Я, например, в статьях рассказываю об одной из методологий — статическом анализе кода. Предлагаю и другим нести свет в царство тьмы. Особенно бывает темно как раз в мире embedded-систем. Кстати, у меня уже была заметка на эту тему: "Заземлённые указатели".
Кто бы рассказал, как это самое unit-тестирование сделать для встраиваемых проектов (для обычного PC, как я понимаю, это может делать некий фреймворк или IDE (я нифига не нашёл в инете unit-тестирование не с помощью фреймворков и не для абстрактной простой программы. Так оно для меня и осталось загадкой...)? Иначе протестировать сотню функций, делая по программе-тесту на каждую (100 штук!) я даже не представляю как! )? Вот есть у меня программа для микроконтроллера, которая, вообще говоря, нифиговая в принципе и в логике работы и в размерах (потому что это прошивка одного из 4 взаимодействующих между собой через ДОЗУ контроллеров платы прибора). Я бы с радостью тестировал бы всё это, но как? Там даже не Си++, а просто Си.
НЛО прилетело и опубликовало эту надпись здесь
Я не понимаю вот что: если я каждую функцию/класс обложу тестами с окружением всего, что ей нужно, то тест окажется больше самой программы в несколько раз/десятки рах! А привязку к HAL придётся делать указателями на функции HAL (иначе их не подменить).
Вот сейчас для проекта у меня ПО микроконтроллера как раз и пишется на обычном PC (под QNX) на Си с обвязкой на Си++ с имитацией драйверами всего окружения аппаратуры и имитацией HAL. Всё вроде бы хорошо, но один из процессоров необычный (отечественный аналог TMS середины 80-х) — у него байт 32 бита (да, и sizeof(char)=1 и char=32 бита), компилятор с Си 89 (а может и раньше) и глючной работой кое-чего в Си ( скажем, int a[2]={1,2}; компилируется, но инициализация не выполняется :) ). Как это всё поведёт себя на реальном процессоре — вопрос.

А что касается тестов вообще, я не понимаю, как всё это обложить тестами правильно. Писать ли для каждой функции/класса отдельную программу? Тогда программ будет масса — ведь каждому классу нужно имитировать окружение. Или тест надо встраивать в саму программу — это приведёт к тому, что программа резко распухнет и будет сложно разделить тестовую часть от штатной. А проверка логики работы будет заключаться во взаимодействии классов и потребует имитации этой же логики в тесте (иначе как проверить, что логика отработала верно). Вот всего этого я и не понимаю.
НЛО прилетело и опубликовало эту надпись здесь
Да, это нормально, так и должно быть.


Я ж застрелюсь. :)

Не совсем понял, что вы понимаете под «отдельной программой»,


Я имею в виду, что нужно ли делать отдельный тестовый стенд (программу) для каждого класса. Тесты же нужно откуда-то вызывать.

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


Спасибо за информацию. :) Статьи я читал, но всё равно масса непоняток для реального проекта остаётся.

У одной единицы — одна ответственность,


Часто при таком подходе трудно запомнить связи между сущностями. Скажем, у меня есть менеджер управления электропитанием, менеджер времени, менеджер имитации ошибок, менеджер обмена с аппаратурой и так далее. Так вот, удобно их связывать воедино с помощью одного класса-системы. Иначе связь между этими менеджерами трудно отследить (а через класс системы — просто — все обращаются к нему и он переадресует).
НЛО прилетело и опубликовало эту надпись здесь
Спасибо, стало понятнее. :)
Если выполняете юнит тестирования то почитайте про fake, mock, stub, dummy. К тому же код должен быть организован с использованием dependency injection что упростит тестирование.
К сожалению, всё это всего лишь общие фразы. Без внятного объяснения реализации на настоящем примере ( без каких-либо вспомогательных фреймворков и программ ) это бесполезно. Потому я и не понимаю, как это реально делать.
Знаю что есть книжка:
«Test-Driven Development for Embedded C by James W. Grenning»
Возможно вам поможет разобраться.
Спасибо!

Ну кстати шанс как раз есть — юнит тесты мало того что писать надо, но ещё и код делать тестируемым, а с анализатором нужно просто запустить его.

> Использовалась бы именованная константа, и беды не было.

Даже с именованной константой никто не застрахует от возможности написать что-то вроде:
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 и прочее — просто надо внимательно подходить к написанию кода (не исключая использования статических анализаторов).
Во-первых, мир embedded очень разный. Где-то 8KiB памяти и жалкое подобие С89. Где-то 64-х битовые процессоры и гигабайты RAM.

Во-вторых, сможете показать, чем этот код на современном C++ уступает по эффективности/ресурсоемкости коду на C89/C99?
Насчет того, что разный, совершенно согласен. Но гигабайты RAM — это уже не совсем эмбеддед… Хотя, естесственно, и такой компьютер может быть встроен в какой-нибудь прибор. Правда, там все ограничения, типично ассоциируемые со встроенными решениями, уже неактуальны.

сможете показать, чем этот код на современном 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 ничего нет. Ну чтож, бывает. Сейчас так. Пройдет немного времени и будет не так.
Ну, это мы говорим об элементарных примерах. Но да, можно вообразить себе архитектуру, компилятор для которой не поддерживает даже #define. И вот там придется использовать числа в коде.

Да я прочел. Правда, меня берут сомнения в том, что можно сделать компилятор 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.
Да вы бы давно уже на godbolt.org сходили бы.
Мы обсуждаем конкретный код вычисления среднего значения массива или написание компилятора с целевой архитектурой, у которой 32 байта RAM и килобайт FLASH?

Если про конкретный код, то он не представляет никаких проблем и любой компилятор спокойно выдаст оптимальный по размеру/скорости (в зависимости от флагов) код. И вообще любой код, который не далеко ушёл от чистого 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, то почему бы им не воспользоваться? А если нет, то может стоит поставить вопрос о приобретении лицензии? Можно, конечно, и без колёс толкать телегу, говоря, что так бесплатно, но с колёсами удобнее.
В общем, «потому же, что и не-embedded разработчикам, но с поправкой на embedded специфику».

По делу: ваш блог стоит читать уже хотя бы ради КДПВ!) Ну и образцы косяков культуру кода поднимают.
Отличная иллюстрация зла магических чисел получилась.

Да надо было


const size_t ASize = 3;
uchar A[ASize];
...

Или enum, что лучше? Можно и sizeof но с ним легко выстрелить себе в голову.

Примером может служить использование 32-битного типа time_t

Просто ради полноты картины, компиляторы поновее уже могут определять time_t как 64-битный тип (gcc 7.x для arm-none-eabi, например, определяет как __int_least64_t).

НЛО прилетело и опубликовало эту надпись здесь
for (uchar i = 0; i != SOME_ARRAY_SIZE; i++)…

Такой цикл у меня одного вызывает сомнение?

Выглядит странно. А если ещё вдруг захочиться пропустить несколько элементов в теле цикла сделав там i += x то всё.

Отчасти, это дело вкуса. Например, я обычно пишу как раз !=. Причина: единообразие циклов, использующих итераторы или простые целочисленные типы. Тем не менее, я понимаю, что это плохо и наверное откажусь от такой практике. Например, CERT однозначно не рекомендует использовать != (MSC21-C).
Спрашивал как-то одного товарища из авионики про анализаторы. Ответ был простой: не используем поскольку все использованные инструменты также должны быть сертифицированны.
Странный ответ. А я думал, что это царство DO-178C, Misra и подобных стандартов. И они просто обязаны использовать инструментарий для статического анализа кода.
НЛО прилетело и опубликовало эту надпись здесь
Наконец то свершилось, PVS для IAR.
Но
Сразу же всплыла ложка дегтя
В моем проекте нашлась ошибка:
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 это уже довольно древний спор на тему того что правильнее — sizeof(тип) или sizeof(переменная).

Если кодстайл принуждает к 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 разработчики очень хотят pvs-studio, но разработчки pvs-studio не хотят собирать софт под x86, а на x86_64 не работает сборка. Замкнутый круг.
А почему не работает сборка? Как я представляю, вы используете некий кросс-компилятор для сборки кода для встраиваемого устройства. В чем состоит затруднение запускать этот кросс-компилятор под управлением 64-битной операционной системе? В общем, прощу развить мысль.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий