Print this page

10 правил программирования на Си предотвращающих ошибки

30/07/2010 - 16:34

ПРАВИЛО #1 – ФИГУРНЫЕ СКОБКИ

   Блок программы, идущий после ключевых слов  if, else, switch, while, do и for следует всегда окружать фигурными скобками ({}), даже если он содержит только одиночные или пустые операторы. 

// Не следует так делать…
if (timer.done)
   // Одиночному оператору нужны скобки!       
   timer.control = TIMER_RESTART;

// А вот так правильно ...
while (!timer.done)
{
   // Даже пустой оператор должен быть окружён скобками. 
}

ПРАВИЛО #2 – Ключевое слово «const»

Ключевое слово const следует использовать:

- для объявления переменных, которые не должны меняться после инициализации,
- для определения передаваемых по ссылке параметров функции, которые не могут быть изменены,
- для определения полей в структурах и объединениях, которые не могут быть изменены
- в качестве альтернативы директиве #define при определении числовых констант.

const unsigned char
dirPort = 0xff;

Аргументация: Переменные объявленные с ключевым словом const будут защищены компилятором от непреднамеренных  изменений.

ПРАВИЛО #3 – Ключевое слово "static"

  Ключевое слово static следует использовать для описания всех функций и переменных, не используемых за пределами модуля, в котором они описаны.

static void InnerFunction(void)
{
   ….
}

Аргументация: Ключевое слово Си – static, имеет несколько значений. На уровне модуля глобальные переменные и функции, объявленные с ключевым словом static защищены от случайного доступа из других модулей. Это уменьшает связанность и способствует инкапсуляции.

ПРАВИЛО #4 – Ключевое слово "volatile"

Ключевое слово volatile нужно использовать везде, где уместно, включая:

- Объявление глобальной переменной доступной любому обработчику прерываний,
- Объявление глобальной переменной доступной двум или более задачам,
- Объявление указателя к отображаемым в памяти периферийным регистрам ввода/вывода

volatile unsigned int timer;

Аргументация: Правильное использование спецификатора volatile устраняет целый класс трудных для определения ошибок. Это ключевое слово сообщает компилятору, что переменная может быть изменена в любой момент без какого-либо действия со стороны ближайшего кода и компилятор не должен выполнять на ней некоторые оптимизации. {1}

ПРАВИЛО #5 – Комментарии

   Комментарии не должны быть вложенными и их не следует использовать для отключения блока программы, даже временно. Для временного отключения блока программы используйте директивы условной компиляции (например, #if 0 ... #endif).

// Так делать нельзя...
/*
 a = a + 1;
 /* comment */
 b = b + 1;
*/


// Так правильно...
#if 0
 a = a + 1;
 /* comment */
 b = b + 1;
#endif

Аргументация: Вложенные комментарии и закомментированный код могут позволить неожиданным отрывкам кода быть прокомпилированными.
(Первый пример, кстати, не будет компилироваться. Компилятор принимает за комментарий все, что находится между первыми символами /* и первыми встреченными символами */. Pashgan)

ПРАВИЛО #6 – Тип данных с фиксированной разрядностью

  Всякий раз, когда разрядность целочисленных типов, в битах или байтах, в программе существенна, следует использовать вместо char, short, int, long, или long long тип данных с фиксированной разрядностью. Знаковые и беззнаковые целочисленные типы с фиксированной разрядностью должны быть такими, как показано в таблице.

Стандарт для знаковых и беззнаковых целочисленных типов
с фиксированной разрядностью
Аргументация: Разрядность фундаментальных типов данных языка Си - char, short, int, long, и long long  зависит от реализации (от конкретного компилятора и аппаратной платформы), а это приводит к проблемам переносимости программ.  Стандарт 1999 года не решил эту проблему, но ввел одинаковые имена типов, показанных в таблице. Они заданы в новом заголовочном файле <stdint.h>. Эти имена надо использовать, даже если придется создать typedef`ы вручную.

ПРАВИЛО #7 – Поразрядные операторы       

   Ни один из поразрядных операторов (другими словами, &, |, ~, ^, <<, и >>) не должен использоваться для управления целочисленными данными со знаком.

// Так делать нельзя...
int8_t  signed_data = -4;
signed_data >>= 1;  // не обязательно -2

Аргументация: Си-стандарт не определяет основной формат данных со знаком и оставляет определение эффекта некоторых поразрядных операторов на усмотрение авторов компилятора.

ПРАВИЛО #8 – Целые числа со знаком и без

   Целые числа со знаком не должны сочетаться с целыми числами без знаков в сравнениях  или выражениях. Десятичные константы, не подразумевающие наличие знаков, должны быть описаны с 'u' на конце.

// Так делать нельзя...
uint8_t  a = 6u;
int8_t   b = -9;

if (a + b < 4)
{
   // если бы -9 + 6 было -3 < 4 как ожидалось.
   // была бы выполнена эта ветвь

}
else
{
   //но поскольку -9 + 6 это  (0x100 - 9) + 6 = 253
   //то будет выполнена эта ветвь

}

Аргументация: Некоторые детали управления целочисленными данными со знаком зависят от реализации (то есть от от конкретного компилятора). Кроме того, в результате смешивания знаковых и беззнаковых данных могут возникать информационно-зависимые ошибки.

ПРАВИЛО #9 – Параметризированные макросы против inline функций

Не следует использовать параметризированный макрос, если для выполнения той же задачи может быть написана inline функция. {2}

// Так делать нельзя...
#define MAX(A, B)   ((A) > (B) ? (A) : (B))
// ... если вместо этого вы можете сделать так.
inline int Max(int a, int b)

Аргументация: С использованием директивы препроцессора #define ассоциируется множество рисков, и многие из них связаны с созданием параметризированного макроса. Систематическое применение круглых скобок (как показано в примере) важно, но это не устраняет вероятность двойного инкремента - MAX(i++, j++). Другие опасности неправильного использования макроса включают сравнение данных со знаком и без, или любой тест данных с плавающей запятой.

ПРАВИЛО #10 – Оператор-запятая

Оператор запятая (,) не должен использоваться внутри описания переменной.

// Так делать нельзя…
char * x, y; // вы хотите, чтобы «y» был указателем, или нет?

Аргументация: Цена размещения каждого объявления  в отдельной строке низкая, а риск, что компилятор неправильно «поймет» ваши намерения высокий.

СНОСКИ

1.  Исходя из опыта консультирования множества компаний, я подозреваю, что подавляющее большинство встроенных систем содержит ошибки из-за нехватки ключевых слов volatile. Такие виды ошибок обычно обнаруживают себя как «глюки».
2.  Ключевое слово Си++ inline было добавлено в Си-стандарт в 1999-м году.

Michael Barr  "Bug-Killing Coding Standard Rules for Embedded C". Вольный перевод ChipEnable.Ru