Comments 202
Плохой код найти легко, а Вы лучше соберите "парад" хорошего кода.
Насчёт п. 1, такое есть во многих старых и не очень системных h-файлах. Может, есть какая-то причина?
Я общался с автором кода. Он сказал, что просто не знал, что в языке программирования Си есть такая вещь, как перечисления (enums).
При этом у него 15+ лет российского опыта программирования.
Привычка с эпохи DOS и Unix. До C89 не было enum, и до сих пор иногда нет в компиляторах, написанных на колонках.
Причина очень простая -- искать проще. Вот представь, у тебя 50000 сообщений, сгруппированных по интервалам. Приходит сообщение 12345... Помогите Даше-путешественнице найти, что это за сообщение в enum.
Элементарно. Если данный enum предполагается выводить в сообщениях, то достаточно явно определить значения каждого члена enum. Проблема высосана из пальца.
А Даше большой привет)
Вообще, иногда важно, чтобы константы просто были разными. Например, если они - идентификаторы каких-нибудь внутренних для программы сущностей и никогда не выходят наружу. Тогда простого enum-а вполне достаточно.
А иногда возникают дополнительные потребности. Например, может захотеться, чтобы эти номера были стабильными. Скажем потому, что они используются в формате файла или в сетевом протоколе и предполагается некоторая совместимость между версиями. Или вот как тут, привели пример с константами, которые надо искать по номеру (хотя в лог лучше бы не лениться и писать по именам, если речь, конечно о логе).
Тогда да, или enum с явными значениями, или #define. Сишный enum тут не обладает, к сожалению, особыми преимуществами перед #define потому, что он без писка автоматически преобразуется компилятором в любой другой целочисленный тип, поэтому особого контроля не добавляет. Ну разве что отладчик, если повезет, скажет не только чиселку но и её название.
4--Статические функции в заголовочном файле.
Это зря вырезал из контекста. Там всё правильно, ниже идут реализации функций, чтобы компилятор мог их инлайнить, а если не смог, то линкер не ругался на множественные определения функций.
7--Отсутствие имен переменных в определении структуры справа.
Какой-то ты безграмотный сишник. Справа не определение структуры, а инициализация. И в структуре не переменные, а поля или члены, кому как нравится.
Зря наехал на этот код. Для тебя, наверно, будет откровением, что не так много проектов используют C99, не везде он доступен.
9--Размер типа битового поля не должен превышать ожидаемый размер всей структуры
А тут ты с размаху сел в лужу. Тип битового поля может быть только int, unsigned int, _Bool или _BitInt(n).
The following usages of bit-fields causes undefined behavior:
Whether types other than
int
,signed int
,unsigned int
,_Bool
(since C99), and (possiblyunsigned
)BitInt(N)
(since C23) are permitted.
10--Несовпадение типов в битовых полях.
А здесь что не так? Продолжение бултыхания в 9?
Если речь зашла о нелепости, то что можно сказать о нумерации в этой статье?
0, 1, 16, 18, 2, 3, 17, 4, 5, 6, ...
Это такая головоломка на поиск закономерностей?
Да и два дефиса после номера странно смотрятся.
Насчёт п.14, вот тут пишут наоборот, что не надо all caps:
DATA_TYPE MODULE_variableZero;
Ну это явно обфускация такая, а не просто недочитал книжку "Лучшие практики написания кода".
#define CAN_ID_FORMAT_STANDARD (0U)
А здесь что не так? В файлах хедеров, описывающих регистры, пины и прочие кишки микроконтроллера, можно увидеть точно такие же отдельные строчки #define. И выглядят они, если честно, приятнее перечислений.
Если это линейная функция, то её следует вычислять по формуле.
Если на вычисления есть процессорное время, то конечно лучше вычислять. А если времени нет? Я не думаю, что написавший это программист забыл формулу графика прямой линии из учебника для начальной школы.
Ещё может быть прикол, например, что дискретность или система команд поменяется в другой ревизии чипа, и 0дБ будет соответствовать уже не 0x6, а, например, 0x7FFF. С перечислением такое можно обойти почти без костылей. Как вариант, конечно, макрос или constexpr, но первое не особо чисто, а второе — не факт что компилятор поддерживает.
Есть и более тривиальный случай: этот код может быть предназначен для использования инженерами, работающими с данными ЦАП — и хорошо если электронщиками, а не какими-нибудь звукоинженерами, которые от программирования совсем далеки.
Тогда решение даже в одном конкретном чипе параметры вводить через абсолютно точно понятные им дефайны наиболее верное.
А ещё вычисленные таблицы можно загнать во Flash, которого в микроконтроллерах свободного обычно остаётся много, а вот пара операций деления/умножения может работать дольше, чем чтение из Flash.
Если на вычисления есть процессорное время, то конечно лучше вычислять. А если времени нет?
То нужен препроцессор, который развернёт формулу.
Добавлю еще, что все эти системные макросы в хедерах микроконтроллеров не просто так макросами сделаны. Они пишутся не руками, получаются автоматически из RTL.
Парад Нелепого Си Кода
Вас Тоже Покусали Англоязычные Газетчики, Пишущие Все Слова В Заголовке С Заглавной?
Мне как то раз подкинули legacy программу на Qt - дописывать. Файлов 50, и всего один комментарий:
Вот такой:
// Плохо!
Статейка просто переполнена пафосом... Да и компетенции автора тоже есть некоторые вопросы по отдельным пунктам... Мда ...
Тут половина кода выдрана из контекста. Вполне возможно, что многое сделано с целью оптимизации.
К enum'у не применить #ifdef. Операция деления очень дорогая - тем более в таком виде (если оптимизатор ее не уберет).
Пишу на языке, в котором нет enum'ов. Так без лишних проблем пишутся константы:
subtype t_log_level is pls_integer range 1..9;
c_log_level_error constant t_log_level := 1; -- critical error
c_log_level_warn constant t_log_level := 2; -- less critical error
c_log_level_info constant t_log_level := 4; -- default level if debugging is enabled (for example, used by apex_application.debug)
c_log_level_app_enter constant t_log_level := 5; -- application: messages when procedures/functions are entered
c_log_level_app_trace constant t_log_level := 6; -- application: other messages within procedures/functions
c_log_level_engine_enter constant t_log_level := 8; -- Application Express engine: messages when procedures/functions are entered
c_log_level_engine_trace constant t_log_level := 9; -- Application Express engine: other messages within procedures/functions
в котором нет enum'ов
... но есть range, который в данном случае enum заменяет. Но только в данном случае – попробуйте теперь написать тип, который принимает только три значения 2, 13, -56
А вообще при обычном использовании enum вообще не должно быть маппинга на конкретные числа. И ваш случай выглядит таким. На плюсах это было бы
enum class t_log_level {
kError,
kWarn,
kInfo,
kAppEnter,
kAppTrace,
kEngineEnter,
kEngineTrace,
}
О, на Аде кто-то нынче пишет не just for fun?
В legacy с 20 летний историей можно накопать много странных решений. Но, во первых, всему есть причина, даже странному. Во вторых,, это же работающий код ;)
Я тоже делаю печать в лог из прерывания на микроконтроллерах . Лог пишется в специальную неблокирующую очередь, без него трудно отладить сложный протокол вроде юсб.
Про прерывания, которые генерируют прерывания - так это надо еще посмотреть, что конкретно скрывается за LOG_DEBUG(). Хорошо и правильно написанная система логирования такое вполне себе позволит. Особенно если учесть, что это DEBUG, и, видимо, где-то рядом есть LOG_ERROR(), LOG_INFO(), LOG_MESSAGE(), а сама LOG_DEBUG() будет работать в исключительных случаях.
Про нежелание использовать перечисления - в мире embedded до сих пор встречаются компиляторы, которые не везде умеют enum'ы. Препроцессор просто переносимее. Та же история с битовыми полями. Они мало того, что не везде есть, так еще и от компилятора к компилятору правила выравнивания и упаковки меняют. С ними, к счастью, проблемы реже случаются. Особенно последние лет пять, но тоже бывает.
Про подавляющее большинство текста статьи - бессмысленный и беспощадный корпоративный CodingStyle. Написанный человеком, не просто с синдромом самозванца, а таковым самозванцем являющимся. Особенно если их в конторе двое-трое и они как-то договариваются. Как по мне, так лучший вариант был и остается здесь. Но корпоративные требования - это не про самодокументрующий код. Это про doxygen и его аналоги. Впрочем, в каждой избушке своя слезливая и кровавая история почему так. При этом она всегда по сути административная, а не техническая. Как и вся эта статья.
Если бы код просто отдавался на ревью коллеге, и это ревью реально делалось, то и статьи бы не было. Одно из двух - или embedder в единственном экземпляре (привет BusFactor == 1), или есть бог, и есть подмастерья. В любом случае это чисто административная проблема. Да и многое из написанное вскроется если банально перестать подавлять предупреждения компилятора (а лучше включить их параноидальный вывод). Но чистая сборка по каким-то причинам не в чести. Не хочется думать о том, что причина в квалификации. Спишем на вечно горящие сроки.
P.S.
Я видел много разного. Включая
#include "file.c"
И даже знаю места, где даже это вполне себе оправдано. В бесконечной вселенной возможно все. К этому стоит относиться проще.
10+ лет опыта программирования микроконтроллеров на российском рынке - это скорее минус, чем плюс. Встречал мало подобных личностей, которые вообще бы умели программировать. Кодировать - да. Особенно учитывая специфику HW разработки и её относительную простоту.
Даже на, казалось бы, простой вопрос - что такое указатель никто ответить не может. Все повторяют глупость про адрес в памяти.
Поскольку я с той стороны силы, отвечу так: ресурсы масс-маркет микроконтроллеров за 20 лет увеличились от силы раз в 5, и функциональность embedded решений вполне себе современная и покрывает бизнес-функции.
А Windows, браузеры, сайты и т.п. расперло безмерно. Так что кто умеет и кто не умеет программировать и, внезапно, проектировать архитектуры, вопрос как минимум дискуссионный.
ресурсы масс-маркет микроконтроллеров за 20 лет увеличились от силы раз в 5
soc с линухом внутри это ещё микроконтроллер или уже нет? ;) Закон Мура он везде одинаковый.
Раньше микроконтроллером считали чип с процесором, памятю, и переферией на одном кристале.
Если проводить параллели, то SOC с линуксом сейчас - это 386EX и альтернативные х86 c QNX в конце 90-х ... сердине 2000-х, так что вполне себе параллели. Это не микроконтроллеры, но тем не менее ограничения там пожестче чем на ПК.
Я больше к тому, что ограниченность ресурсов и в разы более долгий срок жизни железа довольно сильно дисциплиниует программистов.
ограниченность ресурсов
Безграничных ресурсов нет нигде.
в разы более долгий срок жизни железа
Ну, написали в далёком 2000 году программу, залили в контроллер, и она там крутится 20 лет. Можно сдуть пыль из компа с Windows 98 - doom там будет бегать с той же скоростью.
Программиста дисциплинирует исключительно административное внушение.
Программиста дисциплинирует исключительно административное внушение
предпочитаю таких "программистов" на работу не принимать
code style - согласен, административное внушение в некоей степени
корпоративная культура - согласен, административное внушение в чистом виде
писать грамотный код - или ты обучен и он у тебя воспитан в крови и в голове, или давай до свидания
Программиста дисциплинирует исключительно административное внушение.
Гораздо лучше его дисциплинирует обязательный для применения всеми линтер и форматер.
Вот. Вот. Золотые слова.
Форматтер как раз НЕ дисциплинирует, потому что делает эту работу за человека.
Человек привыкает видеть код, сформатированный определенным образом, и начинает сам подстраиваться под этот стиль. В т.ч., учитывает свойственные стилю особенности и ограничения (например, можно писать очень длинные выражения или заводить переменные для промежуточных результатов; если после форматирования длинные выражения выглядят не очень, это несколько склоняет программиста и использованию временных переменных).
В том, что само по себе расставление скобочек и отступов автоматиризованно, не вижу ничего плохого. Зачем тратить время живого человека на то, что может сделать машин?
Всё верно, это должна делать машина - правда, лишь в той мере, в какой ей положено - но не ПОСЛЕ написания кода, а прямо во время. То есть, этим должен заниматься редактор. Ну либо сам программист вручную, если не может настроить свой редактор/IDE на стиль конкретного проекта. Но ни в коем случае не какие-то исправления потом - к этому есть вопросы как минимум с этической точки зрения. И с административной, о которой я и говорил - возникает отношение "а зачем стараться, если всё равно переформатируют", отсюда нечто противоположное дисциплине, см. "теория разбитых окон".
Что касается "привыкает видеть" - не, это так не работает в мало-мальски сложных проектах. Ведь как я сам могу работать в разных проектах, где принят разный стиль, так и это может быть соответственно требованиям. Например, у нас на работе используется ряд opensource-проектов, код которых патчится. Соответственно, в каждом из таких мест будет свой стиль - апстриму же не продиктуешь, проще оформить патч
обязательный для применения всеми
То есть кто-то принял административное решение, обязательное для всех подчинённых.
Лучший способ отбить охоту придумывать дебильные требования code-style - попросить автора написать linter на это его новое правило.
Да с чего бы это он везде одинаковый? Вот когда встанет (а это уже вот-вот случится), то встанет везде, это да.
soc с линухом внутри это ещё микроконтроллер или уже нет?
Скорее нет чем да.
SoC обладает MMU и крутит Linux.
А в MCU MMU нет. MCU в 99% крутят какую-н RTOS или вообще суперцикл.
Даже на, казалось бы, простой вопрос - что такое указатель никто ответить не может. Все повторяют глупость про адрес в памяти.
Ну да, ну да... Важно понимать кому и какой вопрос вы задаете. С позиции embedder'a это ЕДИНСТВЕННЫЙ правильный ответ. Потому как попробуйте объяснить все остальные сущности языком ассемблера. А embedder - это тот, кто даже когда пишет на Rust (не говоря уже о С), должен понимать как именно написанное будет выглядеть на ассемблере, в какую сторону и с какой скоростью заставит по плате электроны бежать.
А судя по содержанию первого абзаца, у меня к вам единственный вопрос... Как корона? На ушки не давит?
Даже на, казалось бы, простой вопрос - что такое указатель никто ответить не может. Все повторяют глупость про адрес в памяти.
Я вот тоже не назову сходу точное определение указателя.
Я бы ответил, что это переменная, которая хранит адрес на память. (По сути адрес в памяти). Никакой глупости тут нет.
Однако это не помешало мне запрограммировать 65+ прошивок с указателями в которых прошли модульные тесты. Включая реализацию связанных списков.
В справочнике по Си написано, что указатель - это ссылка на объект или функцию. Такое абстрактное определение понимание сути совершенно не увеличивает.
С точки зрения компилятора, это что‐то вроде пары из адреса и объекта, которому этот адрес принадлежит. Например, посмотрите как clang компилирует следующую программу:
int main(void) {
int a = 0;
int b = 1;
*(&a + (&b - &a)) = 2;
return b;
}
Она возвращает 1, потому что по мнению оптимизатора вы не можете взять адрес объекта a
и перейти из него в объект b
: это разные объекты, живущие в разных реальностях. Это имеет физический смысл при использовании чего‐то вроде CHERI, но, т.к. почти никто не использует такие процессоры, почти всё время это просто ещё один пример использования UB для оптимизации.
Это так же пример, почему вы больше не можете считать, что указатель — это просто адрес в памяти: современные компиляторы так больше не считают.
Хороший контрпример.
И как же звучит самое грамотное определение указателя в Си?
Так «ссылка на объект или функцию» — нормальное определение. Вы не можете посмотреть на него и сказать «если я прибавлю к этой ссылке 10, то я попаду в соседний объект». Интереснее вопрос «а что такое объект»: если я правильно понимаю логику компиляторов, это будет что‐то вроде «кусок памяти, выделенный для одного экземпляра типа».
а как объяснить что такое точка и прямая в геометрии?
По моему это из той области когда попытка объяснять фундаментальные, базовые, аксиоматичные понятия приводит к появлению оторванных от жизни теорий основанных на таких объяснениях. По моему просто не надо этим заниматься и не поощрять такие попытки.
Она возвращает 1, потому что по мнению оптимизатора вы не можете взять адрес объекта a и перейти из него в объект b: это разные объекты, живущие в разных реальностях.
Фуф!. Как хорошо, что я собираю прошивки с флагом -O0.

Ну то есть в зависимости от версии и флагов компилятора ваши UB могут сломаться. Или не сломаться, как повезёт.
Для того, чтобы это определить меня есть модульные тесты.
https://habr.com/ru/articles/698092/
Я свои прошивки вообще на ассемблере часто пишу. Это позволяет мне делать интересные вещи вроде контроля одиночных сбоев в регистрах общего назначения: попробуйте убедить компилятор C в том, что ему нужно поместить глобальную переменную в регистр. Последний раз как я пытался это сделать, прошивка почему‐то не заработала, хотя компилятор вроде поддерживал привязку переменной к регистру.
Результат также быстрее, чем -O0. (Про остальные -O такого не скажу, но мои прошивки не делают некоторые вещи, которые делает startup: не копируют в ОЗУ значения разных переменных, обычно не инициализируют стэк.)
Как на ассемблере ARM Cortex-M будет выглядеть функция заполнения области RAM памяти под стек значениями 0xCA?
Нужно раскраcить стек именно в Reset_Handler до вызова функции main().
Так как main уже начнет использовать стек.
[окраска памяти (memory painting)]
Это нужно для последующего анализа глубины заполнения стека после прогона модульных тестов.
Я для заполнения памяти использовал следующие макросы:
.macro MOV32 reg, val /* ▶2 */
movw \reg, :lower16:\val
movt \reg, :upper16:\val
.endm
.macro INIT_MEM start, len, value, tmpreg1, tmpreg2, tmpreg3 /* ▶2 */
.if \len > 0
MOV32 \tmpreg1, \start
MOV32 \tmpreg2, \start + \len
MOV32 \tmpreg3, \value
_init_mem_loop_\start:
str \tmpreg3, [\tmpreg1], 4
cmp \tmpreg1, \tmpreg2
it ne
bne _init_mem_loop_\start
.else
nop.w
nop.w
nop.w
nop.w
nop.w
nop.w
_init_mem_loop_\start:
nop.w
nop
nop
nop
.endif
.endm
Это не самый оптимальный код, какой может быть¹, но он работает.
Вообще на хабре где‐то была хорошая статья про ARM ассемблер, но я что‐то не могу её найти. Поисковик, тем не менее, находит достаточно статей про ARM ассемблер, в том числе упоминающий инструкцию it… (ne|…)
: кроме сей инструкции ассемблер для меня особо ничем не примечателен. (В примере выше, насколько я знаю, я мог бы её и опустить, но вот, например, в макросе проверки памяти на ОС есть кусок вида
eors r1, r1, \tmpreg3
itt ne
strne \tmpreg3, [\tmpreg1]
blne count_ones
, где с ней просто удобнее.)
¹ Особенно учитывая, что .else
ветка существует исключительно чтобы я мог ради интереса посмотреть на diff
между результатом дизассемблирования почти одной и той же прошивки, скомпилированной под разные STM’ки и не получить 100500 отличий в адресах инструкций.
попробуйте убедить компилятор C в том, что ему нужно поместить глобальную переменную в регистр.
А позвольте полюбопытствовать - а зачем? В первом приближении компилятор упирается совершенно справедливо. Лишаться одного из регистров и усложнять себе жизнь - так себе идея. Для нее нужно очень веское обоснование. Вариант "потому что я так хочу" - это безусловно путь в ассемблер. Но и там это решение выглядит очень сомнительным, и требующим серьезного обоснования.
Что до -O0, то это не самое хорошее решение. Особенно не стоит кичиться тем, что "как хорошо, что я его использую". Да, в хорошо написанном коде оптимизатору особенно развернуться негде, потому от -O0 вреда мало, но... Временами хочется именно небольшой ассемблерной оптимизации, а опускаться на уровень ниже - это усложнять читабельность кода. Особенно не самыми подготовленными коллегами. И да, в большинстве случаев при не агрессивной оптимизации, она может сделать именно это.
Под агрессивной оптимизацией, я в первую очередь понимаю произвольный инлайнинг, произвольное разбиение на функции и изменение последовательности присвоений - три всадника апокалипсиса для Embedder'ов. Остальное - скорее вопрос качества самого кода. В частности приведенный пример... Я не знаю кодогенерацию CLANG (как-то не особенно доводилось с ним работать), но... Он лучшая иллюстрация к заголовку статьи. А еще - а что выведет CLANG с ключами аналогичными (-Wextra -Pedantic)? Неужели этого будет мало для того, что бы заподозрить странности с тем кодом и пересмотреть это решение?
А позвольте полюбопытствовать - а зачем? В первом приближении компилятор упирается совершенно справедливо.
Я написал ответ на вопрос «зачем» в той части предложения, что вы почему‐то решили не цитировать.
Подождите... Я перечитал исходное сообщение еще раз. Я правильно понимаю, что то что я упустил это "контроль одиночных сбоев в регистрах общего назначения"? Это какие-то специальные тестовые задачи для работы в условиях плохой с плохой электромагнитной или механической обстановкой? Но вроде бы эти моменты должны решаться другими методами.
Да и в целом всегда есть вопрос - ну хорошо, я вижу отравленный регистр и что? Как адекватно на это должна реагировать программа в контроллере? В целом я бы с радостью почитал статью от профи, если это действительно часть вашей работы. Ибо иногда с подобным приходится сталкиваться, но эти вопросы в основном решаются со схемотехником. И в целом это вроде бы выглядит логично.
Я работаю в испытательном центре, занимающемся испытаниями на радиационную стойкость, а не разрабатываю аппаратуру. Т.е. мне нужно предоставлять данные для ответа на вопросы вида «насколько вероятно, что прилетевшая в схему частица вызовет сбой в определённом блоке» (блоки в основном ограничиваются разными видами памяти), а не собственно как‐то компенсировать результат этого сбоя.
Соответственно, всё, что мои прошивки делают — это крутят определённый набор тестов, в то время как сама схема стоит с открытым кристаллом и подвергается бомбардировке тяжёлыми заряженными частицами (ТЗЧ), обычно ионами благородных газов из ускорителя. (Или ещё чем и не обязательно с открытым кристаллом, но конкретно одиночные сбои интересны при испытаниях на ТЗЧ.)
Потом, на основе моих данных, кто‐то будет решать, что всё‐таки нужно делать, чтобы его изделие нормально работало в космосе. Но это вопрос не ко мне.
Если вы будете писать как я, то прилетающие частицы вас не особо будут беспокоить — но у меня для стабильности работы тестовой прошивки есть по сути одно и не очень полезное (для реальных прошивок) правило — не используйте никакую память, кроме flash, а если очень нужно — используйте мало памяти и не держите там ничего долго. Ну и по возможности не оставляйте в прошивке код для перезаписи flash, результат случайного прыжка туда вам не понравится.
бомбардировке тяжёлыми заряженными частицами (ТЗЧ), обычно ионами благородных газов из ускорителя
Интересно, а где Вы в России ускоритель видели?
Что Вас удивляет? Ускорители ЛЯР ОИЯИ вполне себе живут и здравствуют, можете поискать, где — именно они используются для испытаний на воздействие ТЗЧ. Есть медицинский ускоритель протонов в Обнинске. А если вам всего лишь нужно ускорить электроны и потом превратить их в гамма‐излучение, то такие устройства в принципе относительно часто встречающиеся, небольшие — могут поместиться в вашу комнату (если не считать свинца, из‐за которого ваша комната рухнет на соседа снизу или подвал ещё до установки в неё ускорителя и того, что размещать ускоритель в жилом помещении вам наверняка нельзя) — и некоторые даже используются для промышленной дезинфекции.
Спасибо. Да, теперь все стало на свои места. Действительно очень специфическая задачи, требующие специфических решений. Безусловно важные задачи. И для них вполне уместны очень специальные решения.
Правда стоит ли их использовать, скажем так, в обычных проектах (что бы это не значило) - вопрос. Мне кажется что нет. Какой смысл ограничивать себе доступные ресурсы? С другой стороны, безусловно, идея "здорового минимализма" не просто имеет право на жизнь, а весьма востребована. Так что, как всегда - вопрос баланса. А уж где тот баланс каждый сам для себя решает.
есть по сути одно и не очень полезное (для реальных прошивок) правило — не используйте никакую память, кроме flash, а если очень нужно — используйте мало памяти и не держите там ничего долго.
интересно, а вы исправляющие ошибки коды не используете, помехозащищающее кодирование? Насколько я понимаю это должна быть прям вот ваша основная тема?
Если оно присутствует в железе, то приходится. Если нет, то не имеет смысла, содержимое памяти, в которой проверяется наличие ОС — это всегда какая‐то простая функция вида f(адрес), во многих случаях даже f(_) = const. Исправляющие ошибки коды будут использовать разработчики аппаратуры, после расчёта вероятности получения сбоя.
С ECC в данном случае работать не удобно. Дело в том, что в космосе вам, в зависимости от орбиты, может прилетать одна частица в месяц/неделю/день/… — ECC с таким легко справляется. На испытаниях в сторону микросхемы может лететь до несколько десятков тысяч частиц в секунду (в саму микросхему попадёт, если я не ошибаюсь, порядка на два—три меньше, пропорционально её площади) — чем больше, тем меньше мы стоим на ускорителе и меньше платим за его время. Если память без ECC, то увеличение числа частиц в разумных пределах мало что качественно меняет — вероятность пропустить сбой из‐за того, что в одном месте произошло два сбоя за цикл ФК низкая, а несколько сбоев рядом никак не отличаются от нескольких сбоев далеко друг от друга.
Если же память с ECC, то подача слишком большого числа частиц приведёт к неизбежному заключению «ECC не работает», что, возможно, не соответствует реальности. Но при этом ECC действительно может не работать — одна частица вполне может сбить одновременно пачку ячеек; разработчики схемы должны позаботится о том, чтобы это было не проблемой, сделав логически близкие адреса физически не такими близкими, но мы — испытательный центр — должны как‐то проверить, что они действительно это сделали.
Схемы с такой памятью также могут по‐разному реагировать на сбои — не все, к примеру, предоставляют счётчик скорректированных ошибок.
В общем, я скорее рад, что микросхемы со встроенным ECC мне приходят нечасто.
Если оно присутствует в железе, то приходится. Если нет, то не имеет смысла, содержимое памяти
я просто однажды занимался тестированием внешней микросхемы SDRAM-памяти в которой глюки с битами проскакивали, так там очень пригодились коды исправляющие ошибки. Вообще не понятно как вы фиксируете-отлавливаете ошибки с памятью если такие коды не используете. Это вроде как само сабой разумеется при тестировании памяти, то есть при проверках что прочитается то, что туда записали. Исправляющие ошибки коды позволяют формировать с одной стороны как бы псевдо случайные данные близкие к данным используемым в реальной работе, а с другой стороны локализовать ошибку если она вдруг появится, ну и техника работы с такими кодами очень проработана - известна.
Если вы еще так не пробовали могу проконсультировать по методике.
Вообще не понятно как вы фиксируете-отлавливаете ошибки с памятью если такие коды не используете.
Если содержимое памяти — это функция от адреса (или, тем более, константа), то нужно просто запихать во flash‐память эту функцию (константу) и сравнивать содержимое памяти с ней. Если использовать исправляющие коды и случайные данные, то, во‐первых, код проверки и, соответственно, цикл ФК будет занимать больше времени. Во‐вторых, инициализация памяти будет занимать больше времени. В‐третьих, как я написал, сбоев будет много, но при этом заранее не известно, сколько в одном регионе накопится за цикл ФК — сколько сбоев должны выдерживать исправляющие ошибки коды — тем более, учитывая, что в схемах, где об ОС не думали при разработке могут найтись и множественные логически близко расположенные сбои (когда одна частица сбивает сразу несколько бит).
Исправляющие ошибки коды позволяют формировать с одной стороны как бы псевдо случайные данные близкие к данным используемым в реальной работе, а с другой стороны локализовать ошибку если она вдруг появится, ну и техника работы с такими кодами очень проработана - известна.
Знание, что именно должно лежать в памяти работает не хуже для локализации. И в реальной работе в памяти будет так же не случайный шум, а почти наверняка что‐то вроде массивов каких‐нибудь объектов и у каждого объекта схожий префикс. (К примеру, ASCII текст имеет бит 7 равный 0, а цифры конкретно вообще имеют четыре одинаковых наиболее значащих бит, у чисел с плавающей точкой в наиболее значащих битах расположены знак и экспонента.)
Если использовать исправляющие коды и случайные данные
нет данные не случайные, данные псевдо-случайные.
Смысла в исправляющих кодах тогда ещё меньше. Я не решаю вычислительные задачи, я чем‐то заполняю память. Если вычисление эталонного содержимого памяти занимает меньше времени, чем подсчёт и коррекция ошибок с использованием кодов коррекции ошибок, то при проверке его можно просто вычислить ещё раз. Если больше, то мне и так сложно понять, зачем тратить время на вычисление кодов коррекции при инициализации, сложные вычисления собственно содержимого памяти увеличат время инициализации ещё больше.
Кстати, ещё в копилку, почему писать программную реализацию ECC в моём случае — плохая идея: вообще‐то начальное заполнение памяти происходит во время облучения — т.е. когда в памяти активно появляются новые ОС, мешая всему подряд, но особенно любым операциях, использующим большие куски ОЗУ. Точнее, это не всегда так, только почти всегда — если в схеме возникают только ОС в памяти, память инициализируется только до облучения, но помимо одиночных сбоев в микроконтроллерах часто есть ещё такие интересные вещи как функциональные сбои, требующие произведения сброса (скорее всего, те же одиночные сбои, но просто где‐то внутри) и тиристорные эффекты, требующие выключения и включения питания.
---
Хотя я сейчас подумал, что какая‐то программная коррекция ошибок может пригодиться в тех случаях, когда я храню в ОЗУ саму исполняемую программу, что происходит, когда flash просто нет. У меня было, кажется, что‐то вроде одной такой схемы когда‐то давно и ОС смотреть там было сложно (и то, что никакой коррекции ошибок, кроме сброса при зависании, я не добавил, не помогло).
Но, я думаю, если только интерфейс, по которому в ОЗУ загружается программа, не будет очень медленным и безальтернативным, вместо кодов коррекции ошибок я скорее сделаю что‐то вроде перезаписи ОЗУ с программой данными, полученными повторно по этому же или более быстрому интерфейсу. А если он будет — то можно просто записать в ОЗУ три/пять/семь/… копий программы и использовать мажорирование, полагаясь на малую вероятность получить один и тот же сбой в двух/трёх/четырёх/… далеко расположенных местах сразу — не так сбоеустойчиво (в худшем случае) и экономно по памяти, как, скажем, коды Рида‐Соломона, но зато коррекция ошибок требует меньше времени и меньше иструкций.
И в любом случае, коррекция ошибок в таких схемах будет работать только если ошибка возникла в том коде, что должен быть выполнен после коррекции ошибок, и в тот момент времени, когда коррекция ошибок ещё не произошла. Нужно будет подумать, как её вставить так, чтобы увеличить вероятность, что инструкции с ошибками не будут выполнены, с учётом того, что больше инструкций и больше времени — больше ошибок.
я вижу что ваши рассуждения опираются на какую-то специфику, которую я не знаю. Поэтому наверно мои советы вам, действительно не подходят. Но, думаю, ничего плохого нет если я вам подкинул идею которая вам сейчас не пригодилась, всегда полезно посмотреть на проблему как бы со стороны. Удачи и успехов!
Хотите пример из совершенно долбанутой сферы, но уж куда деваться? Есть такая штука, eBPF, у неё верификатор в ядре, так вот он с оптимизатором clang нередко не дружит совершенно - я даже тикет в последнем заводил. Приходится ручками что-то подправлять, чтобы оно смогло пройти верификацию (причем каждый из этих двух компонентов вроде бы в своем праве, а страдает программист)...
[…] изменение последовательности присвоений - три всадника апокалипсиса для Embedder'ов
Меня тут заинтересовало, если ли в Си какой-то аналог std::memory_order_relaxed (?). Например, чтобы такой код не ломался:
uint16_t ADC_Convert(void)
{
ADCSRA |= 1<<ADSC; // start conversion
while ((ADCSRA&(1<<ADSC)) == 1){} // wait for the conversion to finish
// порядок чтения этих
// двух регистров менять нельзя
uint8_t adcl = ADCL; // read ADCL register
uint8_t adch = ADCH; // read ADCH Register
uint16_t val = ((adch<<8)|adcl)&0x3FF; // combine into single 10 bit value, 0x3FF-> 0b11 1111 1111
return val;
}
Давайте послушаем что скажут другие. Я, к сожалению, не владею плюсами, потому не уверен что правильно понял вопрос. А как понял отвечаю.
Скорее всего универсального решения нет. Но типовым вариантом будет использование volatile для adcl и adch. Это подрезает крылья большинству оптимизаторов. Из менее очевидного и сильно хуже переносимого - volatile применительно к функциям, #pragma с локальным изменением параметров компиляции, специфичные ключи сборки для файла. В конце концов тот же ассемблер.
И да - здесь ужас в той строке, которая "wait for the conversion to finish". Не надо так. Даже в учебных примерах. Потому как код из них так или иначе оказывается в изделиях.
Спасибо за ответ, да, думаю volatile для переменных поможет.
Цикл будет крутиться 20-30 тактов (при первом запуске - до 120), но согласен, правильнее сделать через прерывание, а вот как раз код в прерывании хочется сделать как можно быстрее.
А можно несколько провокационный вопрос? Скажите, а зачем делать код в прерывании максимально быстрым? Есть какая-то сакральная идея в соотношении времени работы прошивки в фоне к времени работы в обработчиках прерываний? Откуда отношение к прерываниям, как к туалету - без них плохо, но и засиживаться не комильфо.
Как по мне, так единственное что нельзя в прерываниях - это подобные конструкции с while(). Все остальное - да на здоровье. Более того, идеально в фоне только сторожевой таймер сбрасывать. А все остальное исключительно по событиям.
чтобы несколько источников прерывания меньше влияли друг на друга
Про все архитектуры не скажу, но в AVR (тех самых 8-ми битных) во время нахождения в обработчике прерывания другие прерывания не обрабатываются. И другие прерывания, которые тоже случились, будут ждать обработки. Подробнее не отвечу, извините.
Да, и тут понятно. Если события будут возникать быстрее, чем их можно обработать - то как ни крутись ничего не получится. И при таком раскладе часть событий будет неизбежно теряться.
Но если события возникают с допустимой интенсивностью то есть ли какая-то разница между тем, что 1% времени работы будет в обработчике, 9% времени в фоне и 90% в ожидании. Или 10% в обработчике и 90% в ожидании, а фона не будет совсем. Так или иначе, а процессор свои 10% времени потратит на обработку, ибо все равно придется исполнить инструкции, связанные с событием. Так есть ли разница, будут ли они исполнены в одном или другом контексте?
Поправлюсь - мы говорим о контроллерах, и о коде выполняющемся непосредственно на них, без использования RTOS или чего-то подобного. Там несколько другая история. В первую очередь связанная с тем, что все пользовательские задачи работают в фоне, а весь сервис в прерываниях.
По AVR - ну, с некоторыми ограничениями, там вполне можно сделать вложенные прерывания. Как и запретить оные на Cortex'ах. Тут вопрос скорее в принципе. Понятно, во времена MSDOS и всех учебниках писали - не висите в прерывании, выполняйте обработчик максимально быстро. И было понятно чем это вызвано. "Зависание" в обработчике оператором субъективно воспринималось как "торможение". У микроконтроллера, как правило, нет оператора. Есть только последовательность событий, на которые надо успевать реагировать. И в этом смысле, парадигма с длинным бесконечным циклом в main() - не самое лучшее решение. Оно как раз откладывает время обработки (в пределе на пробежку по всему циклу). Хотя, безусловно, оно более простое и интуитивно понятное.
Ещё в прерывании не стоит использовать fpu. Так как дольше будет происходить смена контекста.
И да - здесь ужас в той строке, которая "wait for the conversion to finish". Не надо так. Даже в учебных примерах. Потому как код из них так или иначе оказывается в изделиях.
Нет в этом никакого ужаса. Абсолютно легальная конструкция, сферу применимости которой необходимо понимать точно так же, как необходимо понимать сферу применимости прерываний.
Более того, в любом микроконтроллере внутри вагон событий, которые устанавливают где-то какой-то флаг, но не вызывают прерывание в принципе.
Так что бессмысленный ужас есть только в религиозной установке «всё надо делать только на прерываниях!».
Надо же - а я ведь не сказал к какой именно части той строки претензия. Флаги в аппаратуре вполне легальная конструкция. Кто же спорит. Претензии не к ним. И даже не к прерываниям, как таковым. Претензии к конструкции "{}" в теле цикла.
Вы можете назвать сферу применимости, где НУЖНО так вот бездумно жрать такты и миллиамперы от источника? Да так, чтоб именно нужно и любое другое решение при любом раскладе оказывается менее правильным? Я - нет.
Можно ли так? Можно - кто же спорит. Можно и микроскопом гвозди забивать. И на болид Formula-1 колеса от велосипеда прикрутить. Если понимать последствия таких решений. Только вот решение - оно на виду, а лонгрид с описанием.... Его еще осилить надо. Потому и говорю - даже в примерах не надо. Как минимум воткните внутрь какой-нить printf() - все какая-никакая работа между тупым опросом флагов.
Ну и в целом. У кого из нас "религиозные установки", и есть ли они в принципе - это отдельный вопрос. Как и то, является ли панк-культура культурой. Предлагаю в эти дебри не лезть. Не стоит оно того.
Вы можете назвать сферу применимости, где НУЖНО так вот бездумно жрать такты и миллиамперы от источника?
Пока у вас управление питанием на данном микроконтроллере не реализовано — вы эти такты и миллиамперы жрёте совершенно одинаково независимо ни от чего.
А воткнуть сюда жрущий ещё и память printf, чтобы он зачем-то делал какую-то бессмысленную работу ради работы — вот это и есть «религиозные установки». Вот за такое и надо прямо по рукам бить, за подход «не могу объяснить зачем, но наверчу сюда что-нибудь».
Вы можете назвать сферу применимости, где НУЖНО так вот бездумно жрать такты и миллиамперы от источника?
spinlock, там тоже "тупой" поллинг флага.
Посмотрите сюда. Да, не самый хороший источник, но тем не менее. Не пустой цикл, а возможность отдать ресурс другим задачам. Примерно так и в уважающих себя RTOS. А их (те самы RTOS) я выносил за скобки несколькими сообщениями выше.
Тролить изволите?
Конечно, если ради единственной задачи делается пулинг то разницы никакой. Но много вы такого знаете? А то, что по факту только прерывания могут как-то вмешаться в этот цикл в процессе работы и позволить использовать те самые такты и миллиамперы более правильно - это факт.
А раз так или иначе происходит смешивание парадигм, то разве не резонно по максимуму использовать ту, которая расходует ресурсы более эффективно. Да, согласен - все надо сравнивать. Скажем RAM для асинхронного варианта надо больше. Не всегда такое возможно на конкретно взятой аппаратуре. Да, сложность во всех проявления - от создания до сопровождения.
Но если от RAM никуда не уйти, то остальное прямое следствие запроса от разработчиков.
по максимуму использовать ту, которая расходует ресурсы более эффективно
Мне как-то рассказывали про студента, на защите неудачно употребившего это слово, отчего до того момента один не проявлявший большого интереса к защищаемой работе член комиссии внезапно ожил и попросил описать конкретные критерии эффективности.
Нет, студента даже отбили и что-то хорошее поставили в итоге.
Но так-то дедушка был абсолютно прав. Да и сам я студентов честно предупреждаю, что язык их — враг их, и на экзамене валить я буду в основном вопросом «почему?».
Вы не со студентом разговариваете. Ваш авторитет преподавателя здесь не работает.
В реальности язык твой - друг твой. Чем раньше ты обозначишь видимые проблемы, тем меньшей кровью их получится решить. К сожалению, у современных выпускников навык коммуникации тоже не очень. И его тоже приходится развивать. Не в последнюю очередь благодаря такому вот отношению преподавателей. Но это тема другого разговора.
Для ученых все ясно: не изобретай лишних сущностей без самой крайней необходимости. Но мы-то с тобой не ученые. Ошибка ученого – это, в конечном счете, его личное дело. А мы ошибаться не должны. Нам разрешается прослыть невеждами, мистиками, суеверными дураками. Нам одного не простят: если мы недооценили опасность. И если в нашем доме вдруг завоняло серой, мы просто не имеем права пускаться в рассуждения о молекулярных флюктуациях – мы обязаны предположить, что где-то рядом объявился черт с рогами, и принять соответствующие меры, вплоть до организации производства святой воды в промышленных масштабах. И слава богу, если окажется, что это была всего лишь флюктуация, и над нами будет хохотать весь Всемирный совет и все школяры в придачу…
А и Б Стругацкие. Жук в муравейнике.
Какая мне разница, есть у вас студбилет или истёк давно, если вы с апломбом лезете обсуждать тему, представление о которой у вас сформировано википедией и какими-то малоосмысленными догматами? Профессионала от студента отличает умение эффективно решать практические задачи — вы им, судя по вашим рассказам, не обладаете.
Впрочем, работал у меня однажды такой программист. Прочитал много книг, очень любил рассуждать, как правильно писать код — настолько, что младших по возрасту этими рассказами в какой-то момент начал явно задалбывать уже.
В первом же полностью самостоятельном проекте на этом месте родил невероятное страшилище, такое, что прямо кровь из глаз.
Тоже ведь не студент был.
Без контекста эта критика ниочом. Может это Arduino core, там нет управления питанием. Или этот код выполняется раз в несколько минут, и эти несколько тактов проца не стоят времени программиста.
Я даже могу привести пример, когда не просто можно, а нужно написать обработчик АЦП без прерываний :)
И в этом вечная проблема с читателями википедий — они какие-то магические заклинания в голове несут, но откуда эти заклинания и зачем вообще нужны, понятия не имеют.
Я даже могу привести пример, когда не просто можно, а нужно написать обработчик АЦП без прерываний :)
Если огласите, то будут шансы на то, что я соглашусь.
Второстепенная функция АЦП (какое-нибудь измерение напряжения батарейки, температуры или что-то ещё малосущественное), основная риалтайм-функция на прерываниях от таймера, ОСРВ без иерархии прерываний.
И на кой чёрт вам в этом вносить дополнительный джиттер влезающими не к месту прерываниями АЦП? Чтобы что?
Да, но... Нет.
Да, если это студенческая лабораторная на кафедре. В задачу которой входит показать, что в реальной жизни можно и нужно использовать оба подхода.
Нет, если это реальная задача из реальной жизни. Потому как в реальной жизни ждиттер-критичные операции и ОСРВ без иерархии прерываний - очень плохо совместимые понятия. Потому, что тактовая сетка ОСРВ в разы крупнее, чем время выполнения обработчика АЦП, а подавляющее большинство ее системных вызовов привязано к той самой сетке. Потому как второстепенность функции - это всегда про частоту опроса и только. А она как раз лучше регулируется будучи запускаемой от таймера. А еще время для выполнения данного обработчика, даже с учетом накладных на вызов и возврат, на пару сдвигов и умножений для приведения к удобному виду - ноль без палочки. А еще проекты реального мира, как котята - имеют привычку подрастать и обзаводиться новым функционалом. А его проще добавлять именно асинхронным вариантом.
По итогу - ну да, безусловно. Можно натянуть сову на глобус. В лучших традициях гос. заказа - написать ТЗ так, чтоб никаких других вариантов не осталось. И их действительно не останется. Но если подходить к вопросу проектирования так, как положено. При этом иметь соответствующие ручки в процессе выбора и утверждения технического решения. Вот тогда опросных решений стоит (по возможности) избегать. И не делить функции устройства на основные и вспомогательные (типа отвалится, и пес с ней). Все, написанное в ТЗ должно быть реализовано в полном объеме с заданной надежностью.
С таким подходом Arduino не было бы. Иногда так делать не нужно, конечно же.
Нет, если это реальная задача из реальной жизни. Потому как в реальной жизни ждиттер-критичные операции и ОСРВ без иерархии прерываний - очень плохо совместимые понятия. Потому, что тактовая сетка ОСРВ в разы крупнее, чем время выполнения обработчика АЦП, а подавляющее большинство ее системных вызовов привязано к той самой сетке
Вы этот набор слов опять где-то в википедии нашли?..
Я даже затрудняюсь интерпретировать, при чём тут «тактовая сетка ОСРВ». Особенно если мы возьмём ОСРВ без тактовой сетки.
Мою квалификацию, к счастью. оцениваете не вы а заказчики. И это хорошо. Потому как иначе, пожалуй, я бы умер с голоду ;-) Ваше право оценивать мою квалификацию исходя из моих комментариев. Но это ровно так же работает в обратную сторону.
Момент второй - я выше писал про ОСРВ и выносил их за скобки, ограничиваясь Bare Metall. Но мы ж как всегда - здесь читаем, здесь не читаем, здесь селедку заворачиваем. Вообще говоря, ОСРВ в контроллерах должна иметь весьма серьезные обоснования. Чаще всего, при ее наличии, или контроллер решает не свойственные ему задачи (опять же - что бы это не значило), или ей закрываются дыры, которые другим путем решать разработчику кажется нецелесообразным (что в общем случае далеко не всегда верно). Я, за более 15 лет практики, видел всего пару проектов (и принимал в них участие) где она оправдана. Это были реально крупные проекты с большим количеством разработчиков. В остальных случаях она была избыточна и приносила проблем больше, чем решала.
Момент третий - накладные расходы ОСРВ, завязанные на взаимодействие ее составных частей, в любом случае будут сильно большими, чем простой (снова - что бы это не значило) обработчик прерываний от АЦП. Что же до ОСРВ вообще без тактовой сетки (читай основного таймерного или иного регулярного прерывания, по которому осуществляется переключение контекстов задач) - ну я просто оставлю без комментариев. И да, в хорошей ОСРВ контексты переключаются не только по таймеру - это тоже не новость. Но работу планировщика по их переключению никто не отменял. Это далеко не на несколько процессорных тактов действие.
По всему остальному - я никого и ни за что не агитирую и никому своих подходов не навязываю. Все что я делаю - это делюсь опытом. А опыт этот в подавляющем большинстве случаев и формируется как исправление последовательного подхода или той его части, которая кричит что "обработчик прерывания должен быть максимально коротким". Именно этот подход в подавляющем большинстве случаев и приводит к тому, что опрос флагов никуда не девается - просто меняется его контекст.
Боже упаси учить умных. У них надо учиться. Главное, стараться не оказаться в Крыловской басне про кукушку и петуха. При чем с любой стороны процесса. Умные и без меня прекрасно справляются. Вообще ругать коллег по цеху - явный признак проблем с квалификацией. А подходы в мире есть разные. У каждого есть свои плюсы и минусы. И самое главное - эти плюсы и минусы понимать и грамотно учитывать. Но тут, безусловно, важен контекст. По этой части наши с вами взгляды сходятся. Что совсем не удивительно.
Хорошо и адекватно написанный код при любом уровне оптимизации работает предсказуемо и одинаково.
А вот если при О0 всё хорошо, но при каком-нибудь Оz падает, говорит о кривости кода и того кто это писал.
В целом да, но как любое категоричное утверждение, это весьма спорно.
Если Oz влияет на кодогенерацию, то с позиции embedded он точно будет работать не одинаково. Такты, время... Другое дело, одинаково с точностью до итогового результата. Это да.
Ну и (не будем показывать пальцем на IAR), но некоторые особенно упертые оптимизаторы так усиленно борются за размер или скорость, что временами очень успешно ломают остальное. По итогу или осознанный выбор ограничений оптимизатора, или код на ассемблере (слава богу они не SUN и ассемблерные вставки даже без volatile не оптимизируют). Первое не подпадает под "любой Oz", второе сильно роняет уровень восприятия кода.
Но в целом да - именно так.
интересные вещи вроде контроля одиночных сбоев в регистрах общего назначения
Есть и интересные регистры специального назначения. PC, sp, управление прерываниями, периферией, етц. В них сбоев не бывает?
Обычно сбои там или приводят к функциональному сбою (== что‐то не работает, но почему именно мы не знаем) и написание прошивки на ассемблере никак с ними не поможет, либо тестирование сбоев в них на ассемблере и на C особо не отличается и соответственно не входит в список причин, по которым я использую ассемблер.
Фуф!. Как хорошо, что я собираю прошивки с флагом -O0.
Это говорит лишь о том, что Вы не умеете программировать на Си. Хорошо написанный код не сломается от оптимизации, и не сломается от того, что его перекомпилировали под другой процессор (другую систему команд).
Я бы предпочёл смотреть на это с несколько другой точки зрения: компилятор имеет право кешировать (например, в регистрах) значения переменных, в норме хранящихся в памяти, и существует вполне определенный набор способов дать ему понять, что его закешированная копия больше не актуальна.
Это оставляет за указателем простую семантику адреса в памяти и в то же время вполне ясно объясняет, почему не надо так писать, как в приведенном Вами примере.
Вот что в Си добавилось в относительно недавнее время,это понимание, что к процессору может быть припаяно несколько разных видов памяти, образующих совершенно независимые адресные пространства. Особенно часто это встречается на практике в виде т.наз. гарвардской архитектуры, в которой код (функции) и данные (переменные) живут в разной памяти, но при этом численные диапазоны валидных адресов могут перекрываться.
Поэтому стандарт не разрешает сравнивать указатели на функции и на данные с единственным исключением: NULL, он и в Африке NULL.
А что насчёт сравнения указателей на переменные и на константы?
По-моему независимые адреса были как раз давно, в 8-битных процессорах (в тех же PIC), просто потому что там особо не разгуляешься. И компиляторы костылили эти адресные пространства с разной степенью успешности (memory model и так далее). А в 32-битных mcu этой проблемы нет - вполне можно сказать что флеш у нас идет с 0x8000000, рам с 0x2000000, а внешняя память с 0xA000000 и никакого пересечения не будет.
10+ лет опыта программирования микроконтроллеров на российском рынке - это скорее минус, чем плюс. Встречал мало подобных личностей, которые вообще бы умели программировать. Кодировать - да. Особенно учитывая специфику HW разработки и её относительную простоту.
Буквально вчера за чаепитием слушал, как коллеги выбирают автомобиль.
Русского автопрома все чураются , как огня. Плюются и проклинают разработчиков АвтоВАЗза.
К китайцам относятся нейтрально, но ждут положительных отзывов.
Корейца готовы купить тотчас же, как будет скидка.
Японца отрывают с руками, даже поддержанного 10+ лет пробегом. Буквально занимают у родственников по полмиллиона, чтобы скорее купить себе японский автомобиль.
10+ лет опыта программирования микроконтроллеров на российском рынке - это скорее минус, чем плюс. Встречал мало подобных личностей, которые вообще бы умели программировать. Кодировать - да. Особенно учитывая специфику HW разработки и её относительную простоту.
Как сказал мой знакомый (инженер возрастом 50+ лет):
"В России только тогда будет хорошая инженерная школа и хорошие автомобили, когда сами разработчики и их руководство будут обязаны ездить только на тех автомобилях, которые они сами разрабатывают."
Это исключает возможность использовать перечисления внутри структур
А как это связано? Перечисления это тип данных, что мешает их объявлять перед структурами?
Как и всякие константы, экземпляры перечислений тоже лучше прописывать заглавными буквами.
Есть такая печальная практика. А на самом деле заглавными буквами нужно писать МАКРОСЫ. Отличить макросы от остального кода задача поважнее, чем отличить константы от переменных.
Для констант есть популярный стиль kSomeConstant. Для перечислений тоже. Либо вообще никак не отделять константы от переменных.
Что точно не нужно делать – это писать константы, как макросы.
Я сторонник приравнивать макросы к функциям / методам. Естественно, когда они взаимозаменяемы (геттеры/сеттеры итп). Видеть в коде рядом Фунции () и МАКРОСЫ () не очень приятно
А какая мотивация приравнивать макросы к конструкциям языка? Директивы препроцессинга это по сути отдельный язык для генерации кода C. Препроцессинг выполняется до компиляции.
https://ru.wikipedia.org/wiki/Препроцессор_Си
void SetSomeValue (SomeStuct str, Value val) {str.val = val; }
#define SetSomeValue(str, val) do {(str).val = (val); } while (0)
Делают одно и тоже, только второй вариант абсолютно бесплатный. Даже инлай-фунции не бесплатные
А потягайтесь-ка с GPT!
Правильность утверждений:
«Делают одно и то же» — нет, в текущем виде функция и макрос делают не одно и то же.
Вариант с функцией принимает структуру по значению, и изменение будет потеряно после выхода из функции.
Вариант с макросом изменяет исходную структуру напрямую, т.к. подставляется непосредственно в место вызова. Использование конструкции do {...} while(0) позволяет корректно использовать этот макрос внутри сложных конструкций (if, for и т.п.).
«Второй вариант абсолютно бесплатный» — это справедливо, так как макрос не требует вызова функции, не требует размещения параметров на стеке и т.п.
«Даже inline-функции не бесплатные» — верно. Inline-функции хоть и встраиваются компилятором напрямую в код, но формально не обязаны это делать всегда. Они могут порождать вызовы, в то время как макрос гарантированно встраивается препроцессором.
Ну вообще-то, стандарт Си определяет ожидаемое поведение препроцессора cpp (не оговаривая при этом, является ли он отдельной программой или встроен в компилятор) и ничего не говорит об M4
В стандарте отладочной информации DWARF есть кое-какие телодвижения, позволяющие сделать макросы понятными отладчику. Глубоко я эту тему не копал и особо подробностями не владею, но само по себе это как бы намекает, что препроцессор Си - всё же часть языка, пусть и несколько отдельная.
Потому что перечисления по сути являются константами.
Нет. Перечисления это тип данных https://ru.wikipedia.org/wiki/Перечисляемый_тип
А вот значения, которые этот тип определяет, это да, константы типа перечисление.
Но вы же не называете структуру константой, если в ней определены константы?
Было бы классно, если все эти примеры сопровождались рекомендациями о том, как делать лучше или, наоборот, когда такой подход уместен. Ну и заодно небольшим описанием контекста - какой компилятор и стандарт, например, и предполагается ли в дальнейшем этот код компилировать как C++.
До ANSI C enum
не было, может собираться это все будет на самодельном компиляторе доперестроечных времен, который диалект K&R поддерживает в лучшем случае, а само изделие доставляется до конечного пользователя и его товарищей в радиусе нескольких метров за 2 минуты.
3--Излишние повторные имена в перечислениях и структурах
Когда я говорил про рекомендации, то имел ввиду такое. Положим есть невероятно надуманный сферический код:
Скрытый текст
/* 100500 typedefs above */
typedef struct bar_tag{
int data;
int len;
} bar;
typedef struct foo_tag{
struct foo_tag* head;
int data;
} foo;
/* 100500 typedefs below */
Консистентность кода будет лучше, если убрать повторные имена?
Скрытый текст
typedef struct{
int pos;
int len;
} bar;
typedef struct foo_tag{
struct foo_tag* head;
int some_data;
} foo;
typedef struct{
int data;
char* key;
} baz;
8--Статические функции в заголовочном файле.
Будет уместно если заголовочный файл - это header-only библиотека. Например, вот так это используется в stb_image.h. Думаю, что ее разработчики Си все-таки знают.
17--Перечисления маленькими буквами. Как и всякие константы, экземпляры перечислений тоже лучше прописывать заглавными буквами. Чтобы не путать с переменными.
Зависит от соглашения и контекста. В K&R константы, определяемые как макросы через #define
, писали в верхнем регистре (т.к. макросы могут вести себя в коде как "Кузькина мать"), так же делали и с константами через enum
.
Если использовать enum по назначению - для именования одного варианта из определенного множества от 0 до N (а не для именования магических констант в духе 0xFFAA13U
), то можно, наверное и в нижнем регистре писать. Пример из той же stb_image, та же история и в коде второго Quake.
Как выше писали, в плюсовом коде очень даже распространенное явление, вот из DagorEngine (пишут и так, и так) или из CryEngine (венгерская нотация).
лучшее что есть на сегодня это подход Солярис, он хорошо документирован, по теме stb команде разработчиков или разработчику так проще, меньше суеты, по разделению кода, почему-то я на С++ и пересев на С всё таки понял что файлы надо разделять, я написал либу математики, и прям специально её разделил, и даже проверял линковал и в С++ и в С, по крайенжину не знаю, еще хочу сказать по битовой ситуации, смотрите, представьте, есть функционал, он собран, внутри собирается состояние исходя из битов и это не надо пересобирать достаточно только указать какие биты используются
Много спорных пунктов. Претензии к транслиту, например, - чистой воды вкусовщина.
И вообще, добрее надо быть к людям
Транслит это не только признак деревенщины , транслит не находится утилитой grep.
А зачем вы ищете код утилитой grep? Пользоваться IDE у вас запрещено?
Ответ прост. Чтобы делать grep поверх grep
Пользоваться IDE у вас запрещено?
Наоборот. К сожалению разрешено.
Почему Сборка с Помощью Есlipse ARM GCC Плагинов это Тупиковый Путь
В вашем примере fizical - это даже не транслит, в русском языке нет слова физикал (physical) или ложикал, т.е. это обычное незнание английского языка.
транслит не находится утилитой grep
ШТА? О_о
Всё это уже потеряло смысл.
Берёте VS Code. Ставите в него GitHub Copilot c GitHub Copilot Chat и забываете о всех проблемах стиля и синтаксиса и языка.
Я уже пару месяцев перешел со всех коммерческих редакторов на VS Code с Copilot на базе ChatGPT o1 и Claude 3.7 Sonnet .
Он мне код инициализации регистров того же CAN пишет прямо со скриншота описания этих регистров в pdf даташите ! Причем в формате, который я ему говорю и с такими коментариями, которые я бы сам так четко не написал.
А как он пишит коменты к шапкам функций! Прямо читает мысли. Просто фантастика!
Сейчас Claude 3.7 Sonnet итеративно мне нагенерил функций с автоматами состояний для работы по SPI с цепочкой внешних расширителей IO за полчаса. С учетом таймаутов, логами, мьютексами и прочей мишурой. Сам бы такое кодил и отлаживал пару дней. Масса нагенеренных функций на более чем 500 строк не имела ни одной ошибки!
Хочешь он тебе перечисления сделает, хочешь дефайнами все выполнит. Выкинет все идиотские коменты, вставит правильные.Там это вообще плёвое дело переделать исходники под любой формат и стиль. Тот же .clang-format - это детский лепет по сравнению с GitHub Copilot.
Словом не стоит чинить деревянную телегу, когда все уже на электромобилях.
Вы встречали нелепый Си код?
Вы обратились по адресу, я его создаю)))
Я долгое время негодовал по поводу того, что часто приходится копаться в плохо оформленном коде от коллег или в коде из интернета.
По-моему, это не самый лучший способ разводиться на эмоции. Как по мне, то весь С / С++ код, в Интернете, оформлен неряшливо. Ну, или, точнее, будет сказать, не так, как мне нравится. Поэтому, если нужно использовать чей-то опенсорс, то, по возможности, я его рефакторю, а то и делаю полную пересборку.
Например, как-то, мне понадобилось подключить возможность управления видео, в своей программе «МедиаТекст», на C++ / WTL (она не опубликована, но, скриншот можно посмотреть здесь: http://scholium.webservis.ru/Pics/MediaText.png ). Для этого использовал исходник FFPlay.c. Однако, с пол-пинка, Линуксовский си не подключается к «приплюснутым» «Форточкам». Пришлось, переделывать FFPlay.c в FFPlay.cpp, заодно конвертировать си-шные функции и данные в классы и делать прочий марафет. На этом фоне «лепость» или «нелепость» источника уже не имеет особого значения ибо перестройка там очень существенная. Главное, что возможность работы, со всем возможным медиа-контентом, я получил и своих целей достиг.
Данная программа, для других, не слишком актуальна, поэтому я ее, пока, не публикую. Мне она нужна для подготовки данных для моей обучающей программы https://habr.com/ru/articles/848836/ .
Вот только, что придумал открытку для текста.
Можно дарить программистам в качестве ачивки. "За Нелепый Код"

Начнём с того, что ничего нелепого в большинстве приведённых примеров попросту нет.
«Нелепое» — это тот вырвиглазный ужас, который вы на блок-схемах в своих статьях рисуете, зачем-то пытаясь пятью размерами шрифта запихнуть в каждый прямоугольник три вагона абсолютно ненужной для блок-схемы информации.
А здесь приведён совершенно рядовой, обычный и понятный код, единственная причина называния которого «нелепым» — ваше неуёмное желание через графоманию строить из себя высокопрофессионального программиста, учащего окружающих, как правильно делать всё.
Научите, пожалуйста, своих программистов в яНДЕКСЕ пользоваться оператором switch.
Вот код из яндекс.драйв.

Файл взят отсюда https://habr.com/ru/news/712888/
О, удивительно, но что в ответ вы сможете только наехать на Яндекс — я предсказал ещё до того, как нажал «Отправить» на предыдущем комментарии. При чём он тут? Это Яндекс заставляет вас рисовать самые нелепые блок-схемы из всего, что я видел в жизни?
Вы промахнулись постом, Олег. Если хотите поговорить про правила компоновки блок-схем вам сюда https://habr.com/ru/articles/667030/
Тут же идет речь про нелепый код.
Здесь нет нелепого кода. Здесь есть графомания с необоснованными придирками от человека, который в первую очередь сам не умеет делать оформление не то что красиво, а хотя бы осмысленно.
Где-нибудь на пикабу эта статья смотрелась бы органичнее.
Здесь нет нелепого кода.
Если воспринимать приведенные исходники просто, как последовательность символов, то вы безусловно правы.
блок-схемах в своих статьях рисуете, зачем-то пытаясь пятью размерами шрифта запихнуть в каждый прямоугольник
Акела промахнулся статьей на хабре.
И тупо написал не туда.
"Нелепое» — это тот вырвиглазный ужас, который вы на блок-схемах в своих статьях рисуете, зачем-то ."
Инспекция аппаратного дизайна это требование международного автомобильного стандарта функциональной безопасности продукта ISO26262-5
Называется "Hardware design walk-through". Хочешь, не хочешь, а надо. Стандарт ISO26262-5 требует.
В основном Вы придираетесь. Код как код, не образец прекрасного стиля, но и не ужас-ужас-ужас.
Некоторые вещи, которые Вам так не нравятся, имеют свою причину. Например, в языке не всегда была инициализация структур с указанием полей по именам. Некоторый код был написан до её появления, и так с тех пор и остался.
Там да, можно было промахнуться. Мы с этим жили...
if(0) и if(1)- это самый правильный способ добавлять код для локальной отладки. Основная цель - этот код должен компилироваться каждый раз правильно.
Если сделать #if 0 (или #ifdef DEBUG_MY_CODE ) - такой код может сломаться, но вы об этом не узнаете. А когда начнёте отладку - придётся исправлять код.
if(0) и if(1)- это самый правильный способ добавлять код
Вы рассуждаете, как разработчик в GUI-IDE, где создать ещё одну похожую сборку это задача класса epic!
Надо просто собирать код скриптами, делать несколько похожих сборок с разными конфигами, собирать и следить за успехом на сервере сборки Jenkins.
Рекомендую этот текст
Почему важно собирать код из скриптов
Ваше решение с дополнительной сборкой - тоже хорошо. Для меня есть 2 причины "за if(0)":
if(0) позволяет обнаружить ошибку сразу же, пока разработчик в контексте.
разработчики просто забывают запустить дополнительный
make with-debug-print
и несобирающиеся коммиты уезжают на сервер сборки, а оттуда прилетает красное письмо. В итоге - потери времени, дополнительные коммиты (ну или rebase)
Но в целом я согласен, лучше избегать if(0), особенно если это не отладка, а какой-то код, который решили временно отключить.
Если сделать #if 0 (или #ifdef DEBUG_MY_CODE ) - такой код может сломаться, но вы об этом не узнаете
Любопытно, почему вдруг.
Потому что если он не компилируется, то когда кто-то внесет ломающее изменение, он никак и не узнает, что оно ломает код под #if 0
А зачем кому то вносить изменения в код под #if ? Почему кому-то не внести изменения в код под if (0) и сломать его логику?
Имеется в виду что кто-то поменяет код несколькими строками выше (переименует локальные переменные и т.д.) и код внутри #if 0 перестанет компилироваться, а узнаем мы об этом только когда придет пора отлаживать.
В моем понимании такие трюки не должны покидать комп разработчика.
в смысле "удалять этот код когда проблема решилась а потом писать заново если опять возникла"?
Зависит от того, проблема решилась сама или вы ее таки решили. Если проблема решена вами - она не должна появиться снова. Ну и воще, для кого тесты придумали? ;)
А когда начнёте отладку - придётся исправлять код.
Да еще и парить себе моск, то ли проводить эти правки через code review отдельным коммитом (на который кто-то должен тикет написать), то ли проводить в общей куче под тем тикетом, ради которого вы туда и полезли, то ли плюнуть на весь этот геморрой и оставить потомкам как есть.
Зря столько критики в адрес макросов. Неопытные программисты думают что весь код это только в .c и .h файлах - но это не так.
Очень часто надо задать конфигурацию проекта и окружения сборки. Потом всё это пропихнуть в код. Вот макросы позволяют сделать нечто подобное.
где-то в скрипте сборки:echo "#define MY_MACROS_VARIABLE "\"$(uname)\" > my_project_config.h
потом в коде:
#include "my_project_config.h"
printf("%s",MY_MACROS_VARIABLE);
В современных автосборщиках и IDE это делается из коробки потому и не видно что происходит.
9--Треугольные функции из вложенных if if if if if if
вполне нормально когда вам надо описать работу какого-то протокола и учесть обработку всех ошибок. Попробуйте во многопоточном коде пропустить хоть одно условие - всё рухнет.
Как-то мне пришлось патчить древнюю библиотеку fontconfig. Так вот там помимо пачки условий еще куча goto операторов.
my_project_config.h
Вы в курсе, что макросы вообще-то можно передавать через ключи компилятора с префиксом -Dxxx?
Конечно можно. Но я говорил про скрипты сборки до этапа компиляции. Когда надо сгенерировать полотна кода из конфигурационных макросов. Или предлагаете пихать кучу переменных в командную строку.
Очень часто можно увидеть в исходниках крупных проектов пустые заголовочные файлы или с пустыми макросами как раз для этих целей. Человек не знающий таких деталей может просто удалить эти файлы для оптимизации и красивости.
Был у меня случай. Через make собиралось, а отдельный файл не компилировался. Да, пришлось добавлять:
#[cfg(all(target_family = "unix", target_pointer_width = "64"))]
fn main() {
println!("cargo:rerun-if-changed=src/file.c");
cc::Build::new()
.define("UNIX", Some("1"))
.define("HAVE_LIMITS_H", Some("1"))
.define("HAVE_STDINT_H", Some("1"))
.define("SIZEOF_VOID_P", Some("8"))
Так не предлагается не учитывать обработку ошибок - можно сделать её иначе, а не треугольником. Например, return или goto. И тогда главная ветвь будет линейна, а не вот это вот, напоминающее типичную проблему интерфейсов по отображению тредов сообщений деревьями (ага, даже Хабр её не может решить нормально).
3 пункт.
Видимо вы не совсем понимаете всей прелести использования именованных структур и перечислений при описании типов-псевдонимов с помощью typedef.
В отличии от безымянных это позволяет делать неполное описание типа, обычно называю forward declaration, а не включать весь заголовок, например в хедер, что может существенно уменьшить связность кода и увеличить скорость сборки, что весьма важно для больших проектов.
может существенно уменьшить связность кода
И что в этом хорошего, если структура определена а разных местах кода?
Определена структура только в одном. Почитайте и поймите forward declaration. Все вопросы отпадут
Тут бы пример.
неполное описание типа
У вас есть тип в одном хедере
typedef struct types_truct_s
{
uint32_t field1;
bool field2;
char field3[16];
} types_truct_s;
Так вот если вы пишите в каком-то другом хедере прототип функции где используется указатель на этот тип
void func(types_truct_s * arg);
Можно не инклудить весь первый большой хедер в этом, а инклудить его только в сишном файле. А сам тип описать вот так
typedef struct types_truct_s types_truct_s;
Что мы имеем в такой компоновке: в хедерах минимум инклудов, все инклуды в сишниках. Благодаря этому если есть модуль который зависит от второго файла, но не использует эту функцию, он не будет инклудить первый хедер со структурой.
Стоит отметить, что это не сработает, если будет требоваться передать или вернуть структуру по значению. Суть в том, что компилятор когда пробегается по файлу видит что есть такой тип и он где-то определен. Это условно похоже на то как работают описания и определения функций (прототипы и тела)
Главный принцип: меньше инклудов в хедерах. При таком подходе не будет каскадной зависимости одного модуля от тысячи других. Заодно ускорится сборка проекта.
Еще применяют для приватности кода в библиотеках. Не получиться пользоваться не публичными методами, так как часть структур обьявлена в сишниках.
Скорее всего это про другое.
Что такое непубличные методы в сишниках? Те которые не должны торчать наружу из этого модуля? Так почему бы их не сделать статическими и не высовывать прототипы в хедер?
я имел ввиду forward declaration.
Например либа libwebsockets, у нее часть структур в публичном хидере обьявлена без содержимого. А полное обьявление уже в private-lib-core.h.
Связность не прямая, а через длинную цепочку инклудов.
Ну вот сразу же - пункт 2 явно от незнания, во-первых, не все компиляторы умеют enum'ы, во-вторых, есть ситуации, когда оно проще будет майнтениться именно дефайнами (уже привели примеры про сетевые протоколы).
Пункты 16,20 тоже незнание - это практически наверняка ветки, сделанные из отладки или подобных целей, чтобы не выкидывать код.
Лучшие Образцы Си Кода