Данный пост представляет собой вольное пересказ ключевых понятий книги Michael Barr «Embedded C Coding Standard», изложенных в его выступлении на вебинаре в июне этого года (не знаю как поставить тэг «перевод») http://www.barrgroup.com/webinars/10rules/
Часть правил применима только к C++ и расширениям C, а часть и к стандарту языка.
Правило 1. Фигурные скобки { } ВСЕГДА должны обрамлять блок кода после операторов if,else,switch,while,do,for.
Следствие: Одиночный оператор и пустое выражение также должны быть обрамлены фигурными скобками.
Доказательство: рассмотрим простой пример кода
if (5 == foo) bar(); always_run();
Вроде бы ничего страшного, все понятно и ясно, но мы решили временно отключить вызов функции bar (к примеру, при поиске места сбоя программы).
if (5 == foo) // bar(); always_run();
, и совершенно неожидано почти перестала вызываться функция run, а если бы скобки были на месте, этого не произошло.
if (5 == foo) { // bar(); }; always_run();
Правило вроде очевидное и неоднократно встречается в различных источниках, но как же трудно ему следовать, ведь отнимается место на экране под пустую строку с закрывающей скобкой, а это место так жалко. (Заметим, что открывающую скобку я, в нарушение другого правила, которое в 10 лучших не входит, все-таки добавил к оператору условия и смог целую строку сэкономить). Небольшое замечание — я прекрасно понимаю, что строка с одинокой закрывающей скобкой ничего не стоит в смысле компиляции, но, как известно, мы пишем программы не для компьютеров, а для других людей, и мне, например, значительно понятнее код, когда я его могу увидеть весь на экране без необходимости роллирования.
Правило 2. Используйте ключевое слово const везде, где это возможно
Следствия: Используем при описании переменных, которые не будут изменяться после инициализации; при описании параметров функции, которые не должны модифицироваться; при описании полей структур и объединений, которые не могут модифицироваться (в том числе структур описания регистров устройств); как рекомендованная замену #define для числовых констант.
Доказательство: это ключевое слово не стоит ничего при исполнениии, в некоторых системах позволяет сэкономить оперативную память путем размещения в ПЗУ, позволяет компилятору обнаружить недопустимые операции с данными, дает читателю дополнительную информацию, занимает немного места (не требует отдельной строки) — короче, нет никаких оснований пренебрегать этим правилом./*1/
char const *gp_model_name = “Acme 9000”; int const g_build_number = CURRENT_BUILD_NUMBER(); char *strncpy (char *p_dest, char const *p_src, size_t count); size_t const HEAP_SIZE = 8192;
Правило 3. Используйте ключевое слово static везде, где это возможно.
Следствие: Использовать для ВСЕХ нелокальных переменных и функций, которые не должны быть видны где либо, кроме модуля, их декларирующего.
Доказательство: не стоит ничего при исполнении, улучшает инкапсуляцию и защиту данных, защищает переменную с длительным временем жизни от ошибок в написании имени, *1.
static uint32_t g_next_timeout = 0; static uint32_t add_timer_to_active_list(void)
Правило 4. Используйте ключевое слово volatile там, где это нужно (но не где только можно — примечание переводчика).
Следствие: используем при описании глобальных переменных, которые могут быть модифицированы подпрограммой обслуживания прерываний; при описании глобальных переменных, модифицируемых более чем одной задачей; при описании регистров внеших устройсв, отображенных на память.
Доказательство: несколько замедляет исполнение команд с такими переменными (вот откуда примечание), но позволяет избежать труднообнаружимых ошибок, дает дополнительную информацию читателю, *1.
uint16_t volatile g_state = SYSTEM_STARTUP; typedef struct { ... } my_fpga_t; my_fpga_t volatile *const p_timer = ...
Правило 5. Комментарии не должны использоваться для отключения блока кода, даже временно.
Следствие: Используйте директиву условной компиляции препроцессора; не используйте вложенные комментарии, даже если ваш диалект С это позволяет; используйте систему контроля версий для экспериментов с кодом; не оставляйте закомментированый код в релизе.
Вроде бы правило понятно и несложное к выполнению, но рука так и тянется вставить /* и */, (тем более, что другое правило — комментариии при помощи // я уже принял) — надо бороться с вредной привычкой.
#if 0 a = a + 1; #endif
Правило 6. Когда длина, в битах или байтах, целых чисел существенна для программы, используйте типы данных с определенной длиной (С99 типы).
Следствие: Ключевые слова short и long не должны никогда использоваться; ключевое слово char применяем только для определения строк.
Доказательство: ничего не стоит при исполнении, облегчает переносимость, *1.
#include < stdint.h > uint16_t data;
Правило 7. Никогда не применяйте битовые операции ( &, |, ~, <<, >> ) к знаковым типам.
Следствие: Вам не придется исследовать неопределенное поведение этих операций.
Доказательство: ничего не стоит при исполнении, облегчает переносимость, *1.
Правило 8. Никогда не смешивайте знаковые с беззнаковыми типами в выражениях или сравнении.
Следствие: Цифровые константы беззнакового типа должны иметь постфикс u.
Доказательство: ничего не стоит при исполнении, вам не придется помнить правила привеления типов, *1.
int s = - 9; unsigned int u = 6u; if (s + u < 4) { // эта ветка НЕ будет исполнена, хотя -9+6=-3 < 4, как мы могли бы ожидать } else { // А вот эта ветка - будет }
Правило 9. Вместо применения параметризованных макросов следует использовать inline функции.
Следствие: в сочетании с правилом 2 исключает применение #define чуть менее, чем полностью (остаются параметры компиляции).
Доказательство: макросы неотлаживаемы в принципе и это очень веский аргумент, отсутствует контроль типов при вызове макроса, возможны побочные эффекты при модификации параметра, но некоторые С компиляторы воспринимают директиву inline как пожелание, а не как руководство к действию — в результате время исполнения может возрасти. Тем не менее, настоятельно рекомендуется к применению.
#define SQUARE(A) ((A)*(A)) inline uint32_t square(uint16_t a) { return (a * a); }
Правило 10. Декларируем каждую переменную на одной строке.
Следствие: Запятая недопустима в списке переменных.
Доказательство: вам не придется помнить, насколько действует *, улучшается читаемость кода.
Тем не менее, на мой взгляд, самое неоднозначное правило, поскольку увеличивает видимый размер кода. Даже если декларации и собраны в начале модуля, (а есть еще и рекомендации объявлять локальные переменные непосредственно перед использованием), удлинение кода лично я к лучшим сторонам отнести не могу, поэтому продолжил бы использовать объявления вроде
int i,j,k;
, но, конечно, следующая строка неприемлема
int *pi, pj;
Подводя итог, можно сказать, что перед нами способ несколько укоротить имеющуюся у нас веревку, при этом не стреноживая нас до невозможности движения вообще. Конечно, до фанатизма в следовании рекомендациям доходить не следует, просто надо иметь в виду, что тут как в Уставе — каждая строчка написана кровью людей, которые делали по-своему. При этом ни одно из правил в противоречие со здравым смыслом не входит, скорее, они иногда конфронтируют с въевшимися привычками, совершенно не обязательно, что рациональными.
ссылка на оригинал статьи http://habrahabr.ru/post/226495/
Добавить комментарий