[SEI CERT C Coding Standard](https://wiki.sei.cmu.edu/confluence/display/c/SEI+CERT+C+Coding+Standard) был разработан по результатам исследования стандарта языка С и списка уязвимостей [Common Weakness Enumeration](https://cwe.mitre.org/index.html). Все указания на неопределенное поведение (UB) в функциях библиотеки языка С и конструкциях языка были оформлены как правила и рекомендации.
Некоторые правила очевидны (проверять деление на 0), некоторые могут проявиться только при очень странных действиях программиста (копирование FILE), про некоторые правила уже упоминалось ранее (размер массива для храненеия строки).
Аналогичный стандарт [MISRA С](https://www.mathworks.com/help/bugfinder/misra-c-2023-reference.html) используется при разработке кода для микроконтроллеров транспортных средств и вводит жесткие ограничения с целью обеспечения большей переносимости кода (int32_t вместо int, запрет setlocale), улучшения его читабельности (обязательные скобки в логических условиях и ``{}`` во всех операторах), множество запретов на потенциально опасные действия (использование динамической памяти, рекурсия, операция запятая).
#### Препроцессор
* Предпочитайте встроенные или статические функции макросам с параметрами
``#define ABS(x) (((x) < 0) ? -(x) : (x))``\
заменить на\
``inline int abs(int x) { return x<0?-x:x; }``\
и ``_Generic``, если нужна функция для аргументов разных типов.
* Используйте круглые скобки внутри макросов вокруг имен параметров
* Списки замены макросов должны быть заключены в круглые скобки
* Не используйте имена стандартных заголовочных файлов для своих файлов
* Понимать порядок замен макросов при сцеплении или создании строковых констант
* Используйте в заголовочном файле защиту от повторного включения
* Избегайте использования повторяющихся вопросительных знаков
Вплоть до последней версии C23 препроцессор выполнял замену последовательностей из 3 символов (триграфов), начинающихся с ??, на символы, отсутствующие на некоторых национальных клавиатурах. Так как замена происходит везде, даже в комментариях и строках, это может привести к неожиданным результатам. Хотя в С23 триграфы убрали, есть возможность включить их опцией компилятора.
```run-c
#include <stdio.h>
int main() {
int x=0; // Почему 0??/
x=1;
printf("x=%d??!??/n", x); // Вывод x=0| Для вывода ??! написать "x=%d?""?!\n"
}
```
* Гарантируйте уникальность имен заголовочных файлов
* Не заменяйте безопасные функции устаревшими или не рекомендуемыми функциями
* Оберните макросы с несколькими операторами в цикл do-while
```c
int x, y, tmp;
if (x<y) SWAP(x, y); else ++x;
// неправильно
#define SWAP(x,y) tmp = x; x = y; y = tmp
// if (x<y) tmp = x; x = y; y = tmp; else ++x; -- ошибка компиляции
// неправильно
#define SWAP(x,y) { tmp = x; x = y; y = tmp; }
// if (x<y) { tmp = x; x = y; y = tmp; } ; else ++x; -- ошибка компиляции
// правильно
#define SWAP(x,y) do { tmp = (x); (x) = (y); (y) = tmp; } while(0)
// if (x<y) do { tmp = (x); (x) = (y); (y) = tmp; } while(0); else ++x; -- ОК
```
* Не завершайте определения макросов точкой с запятой
* Не определять небезопасные макросы
* Используйте стандартные предопределенные макросы для проверки версий компилятора и наличия дополнений в стандартной библиотеки
* Не создавайте Unicode-идентификаторы с помощью сцепления в макросах
```c
#define NAME(n) \u04##n
NAME(01) // предполагается результат \u0401, но ...
```
* Избегайте побочных эффектов в аргументах макросов
``ABS(++n)`` увеличивает n на 2
* Не используйте команды препроцессора внутри вызовов функций, которые являются макросами
#### Объявления
* Константная квалификация неизменяемых объектов
* Не определяйте повторно имена переменных в блоках
* Используйте визуально отличные идентификаторы
* Используйте ``static_assert`` для проверки значения константного выражения
* Не объявляйте более одной переменной на одно объявление
```c
// неправильно
char* s, c; // какой тип с?
// правильно
char* s;
char c;
```
* Используйте typedef только для типов, не являющихся указателями
* Используйте осмысленные символьные константы для представления литеральных значений
* Включайте соответствующую информацию о типе в деклараторы функций
* Правильно кодируйте отношения в определениях констант
* Объявляйте функции, возвращающие errno, с возвращаемым типом errno_t
* Поддерживайте контракт между автором и вызывающей стороной вариативных функций
* Поймите проблемы с типами, связанные с вариативными функциями
* Реализуйте абстрактные типы данных с использованием непрозрачных типов
* Объявляйте параметры функции, которые являются указателями на значения, не измененные функцией, как константные
* Объявляйте объекты или функции файловой области, которым не требуется внешняя связь, как статические
* Используйте «L», а не «l» для обозначения длинного значения
* Остерегайтесь неправильно скомпилированных переменных с квалификацией volatile
* Не начинайте целочисленные константы с 0 при указании десятичного значения
* Минимизируйте область действия переменных и функций
* Явно укажите void, когда функция не принимает аргументов
* Поймите хранение составных литералов
* Используйте volatile для данных, которые не могут быть кэшированы
* Гарантируйте уникальность взаимно видимых идентификаторов
* Объявляйте объекты с соответствующей продолжительностью хранения
* Объявляйте идентификаторы полностью перед их использованием
* Не объявляйте идентификаторы с конфликтующими свойствами
* Не объявляйте и не определяйте идентификаторы, начинающиеся с ``_`` или ``__``
* Используйте правильный синтаксис при объявлении поля -- массива с изменяемым размером.
```c
// неправильно
struct ArrayStruct {
int size;
int data[1];
};
// правильно
struct ArrayStruct {
int size;
int data[];
};
...
struct ArrayStruct *a=malloc(sizeof(ArrayStruct)+n*sizeof(int));
a->size=n;
```
* Избегайте утечки информации при передаче структуры через доверительную границу
* Не создавайте несовместимые объявления одной и той же функции или объекта
```C
// a.h
extern int i;
int f(int a);
// a.c
// без #include "a.h"
short i;
long f(long a) { return a * 2; }
```
* Не объявляйте переменные внутри оператора switch перед первой меткой case
#### Вычисления
* Используйте скобки для указания приоритета операции
* Помните о поведении логических операторов AND и OR
* Не предполагайте, что размер структуры равен сумме размеров ее членов
* Не отбрасывайте квалификацию const
* Не умаляйте преимущества констант, предполагая их значения в выражениях
* Убедитесь, что арифметика указателей используется правильно
* Используйте sizeof для определения размера типа или переменной
* Не зависьте от порядка вычисления подвыражений или порядка, в котором возникают побочные эффекты
* Не делайте предположений относительно расположения структур с битовыми полями
* Не игнорируйте значения, возвращаемые функциями
* Обрабатывайте операторы отношения и равенства так, как если бы они были неассоциативными
* Остерегайтесь повышения целых чисел при выполнении побитовых операций с целыми типами, меньшими int
* Не ставьте точку с запятой на той же строке, что и оператор if, for или while
* Не сравнивайте указатели функций с константными значениями
* Используйте фигурные скобки для тела оператора if, for или while
* Выполняйте явные проверки для определения успеха, истинности и ложности, а также равенства
* Не зависьте от порядка вычислений для побочных эффектов
* Не обращайтесь к volatile-переменной через указатель без volatile
* Не читайте неинициализированную память
* Не разыменовывайте нулевые указатели
* Не приводите указатели к более строго выровненным типам указателей
* Вызывайте функции с правильным количеством и типом аргументов
* Не обращайтесь к переменной через указатель несовместимого типа
* Не изменяйте константные объекты
* Не сравнивайте данные с выравниванием
* Избегайте неопределенного поведения при использовании указателей с ограничением
* Не полагайтесь на побочные эффекты в операндах sizeof, _Alignof или _Generic
* Не выполняйте присваивания в операторах выбора
* Не используйте побитовые операции ``&``, ``|``, ``~`` вместо логических ``&&``, ``||``, ``!``
* Не вызывайте va_arg с аргументом неправильного типа
#### Целые числа
* Поймите модель данных, используемую вашей реализацией
* Используйте size_t для всех целочисленных значений, представляющих размер объекта
* Поймите правила преобразования целых чисел
* Применяйте ограничения для целочисленных значений, полученных из ненадежных источников
* Не используйте функции ввода для преобразования символьных данных, если они не могут обработать все возможные входные данные
* Используйте только явно знаковый или беззнаковый тип char для числовых значений
* Убедитесь, что все целочисленные значения находятся в диапазоне
* Убедитесь, что константы перечисления соответствуют уникальным значениям
* Не предполагайте положительный остаток при использовании оператора %
* Не делайте предположений о типе простого битового поля int при использовании в выражении
* Используйте побитовые операторы только для беззнаковых операндов
* Избегайте выполнения побитовых и арифметических операций с одними и теми же данными
* Используйте intmax_t или uintmax_t для форматированного ввода-вывода для определенных программистом целочисленных типов
* Не делайте предположений о представлении знаковых целых чисел
* Определяйте целочисленные константы независимым от реализации образом
* Вычисляйте целочисленные выражения большего размера перед сравнением или присвоением этому размеру
* Убедитесь, что операции с целыми числами без знака не отбрасывают разряды
* Убедитесь, что преобразования целых чисел не приводят к потере значащих разрядов или неправильной интерпретации данных
* Убедитесь, что операции с целыми числами со знаком не приводят к переполнению
* Убедитесь, что операции деления и остатка не приводят к ошибкам деления на ноль
* Не сдвигайте значение на отрицательное число бит или на число бит, большее или равное числу бит, которые существуют в операнде
* Используйте правильную точность целых чисел
* Для преобразования указателя в целое число или целого числа в указатель используйте ``uintptr_t``
#### Вещественные числа
* Понимание ограничений чисел с плавающей точкой
* Будьте осторожны при перестановке выражений с плавающей точкой
* Избегайте использования чисел с плавающей точкой, когда требуются точные вычисления
* Обнаружение и обработка ошибок с плавающей точкой
* Проверка входных данных с плавающей точкой на наличие исключительных значений
* Не используйте денормализованные числа
* Преобразование целых чисел в числа с плавающей точкой для операций с плавающей точкой
* Приведение возвращаемого значения функции, которая возвращает тип с плавающей точкой
* Не используйте переменные с плавающей точкой как счетчики циклов
* Предотвращайте или обнаруживайте ошибки диапазона в математических функциях
* Убедитесь, что преобразования с плавающей точкой находятся в пределах диапазона нового типа
* Сохраняйте точность при преобразовании целочисленных значений в тип с плавающей точкой
* Не используйте представления объектов для сравнения значений с плавающей точкой
#### Массивы
* Понять, как работают массивы
* Не применять оператор sizeof к указателю при получении размера массива
* Явно указывать границы массива, даже если они неявно определены инициализатором
* Не формируйте и не используйте указатели или индексы массива за пределами границ
* Убедитесь, что размер для массивов переменной длины находится в допустимом диапазоне
* Не вычитайте и не сравнивайте два указателя, которые не ссылаются на один и тот же массив
* Не прибавляйте и не вычитайте целое число к указателю на объект, не являющийся массивом
* Гарантируйте, что библиотечные функции не формируют недопустимые указатели
* Не прибавляйте и не вычитайте масштабированное целое число к указателю
#### Строки
* Представляйте символы, используя соответствующий тип
* Примите и внедрите последовательный план управления строками.
* Обеззараживание данных, передаваемых в сложные подсистемы
* Не обрезайте случайно строку
* Используйте обычный символ для символов в базовом наборе символов
* Используйте указатели на const при обращении к строковым литералам
* Не думайте, что strtok() оставляет строку синтаксического анализа неизмененной
* Используйте интерфейсы проверки границ для манипулирования строками.
* Используйте управляемые строки для разработки нового кода манипуляции строками.
* Не предполагайте числовых значений для выражений с типом char
* Не объединяйте разные типы строковых литералов
* Не указывайте границу символьного массива, инициализированного строковым литералом
* Не пытайтесь изменять строковые литералы
* Гарантируйте, что хранилище для строк имеет достаточно места для символьных данных и нулевого символа
* Не передавайте ненулевую последовательность символов библиотечной функции, которая ожидает строку
* Преобразуйте символы в тип unsigned char перед преобразованием в большие целые числа
* Аргументы функций обработки символов должны быть представлены как тип unsigned char
* Не путайте узкие и широкие строки символов и функции
#### Динамическая память
* Выделяйте и освобождайте память в одном модуле, на одном уровне абстракции.
* Сохраните новое значение в указателях сразу после free().
* Немедленно привести результат вызова функции выделения памяти к указателю на выделенный тип.
* Очистите конфиденциальную информацию, хранящуюся в повторно используемых ресурсах.
* Остерегайтесь выделения памяти нулевого размера
* Избегайте выделения больших стеков
* Убедитесь, что конфиденциальные данные не записываются на диск.
* Убедитесь, что аргументы calloc() при умножении не отбрасывают разряды
* Определите и используйте функцию проверки указателя
* Не предполагайте бесконечное пространство кучи
* Рассмотрите возможность использования цепочки goto при выходе из функции в случае ошибки при использовании и освобождении ресурсов.
* Не обращайтесь к освобожденной памяти
* Освобождайте динамически выделенную память, когда она больше не нужна
* Динамически выделяйте и копируйте структуры, содержащие гибкий элемент массива
* Освобождайте только динамически выделенную память
* Выделяйте достаточно памяти для объекта
* Не изменяйте выравнивание объектов вызовом realloc()
#### Ввод и вывод
* Будьте осторожны, используя функции, которые используют имена файлов для идентификации.
* Канонизировать имена путей, происходящие из подозрительных источников.
* Не делайте предположений относительно fopen() и создания файлов.
* Идентификация файлов с использованием нескольких атрибутов файлов
* Создавайте файлы с соответствующими правами доступа.
* Будьте осторожны при вызове метода удаления() для открытого файла.
* Будьте осторожны с двоичными данными при передаче данных между системами.
* Будьте осторожны при использовании функции rename().
* Будьте осторожны при указании параметра режима fopen().
* Никогда не отталкивайте ничего, кроме одного прочитанного символа.
* Понять разницу между текстовым режимом и двоичным режимом с файловыми потоками
* Убедитесь, что файловые операции выполняются в защищенном каталоге
* Не полагайтесь на конечный нулевой символ при использовании fread()
* Никогда не ожидайте, что fwrite() прервет процесс записи на нулевом символе
* Не используйте fseek() и ftell() для вычисления размера обычного файла
* Избегайте непреднамеренного усечения при использовании fgets() или fgetws()
* Не создавать временные файлы в общих каталогах
* Закрывать файлы перед порождением процессов
* Не выходить с несброшенными данными в stdout или stderr
* Не открывать файл, который уже открыт
* Исключить пользовательский ввод из строк формата
* Не выполнять операции на устройствах, которые подходят только для файлов
* Различать символы, считанные из файла, и EOF или WEOF
* Не предполагать, что fgets() или fgetws() возвращают непустую строку при успешном выполнении
```c
if (fgets(buf, sizeof(buf), stdin)) {
buf[strlen(buf) - 1] = '\0'; // buf[-1] при buf[0]=='\0'
}
```
Можно создать файл с '\0' программно и перенаправить ввод. В Linux можно ввести '\0' с клавиатуры как Ctrl-Shift-2 (^@).
* Не копируйте объект FILE
* Не выполняйте попеременный ввод и вывод из потока без промежуточного вызова fflush или позиционирования (fseek, fsetpos)
* Сбрасывайте строки при сбое fgets() или fgetws()
* Не вызывайте getc(), putc(), getwc() или putwc() с аргументом потока, имеющим побочные эффекты
* Закрывайте файлы, когда они больше не нужны
* Используйте только значения для fsetpos(), которые возвращаются из fgetpos()
* Избегайте условий гонки TOCTOU при доступе к файлам
* Не осуществляйте доступ к закрытому файлу
* Используйте допустимые строки формата
#### Окружение
* Не делайте предположений о размере переменной среды
* Остерегайтесь нескольких переменных среды с одинаковым эффективным именем
* Очищайте среду при вызове внешних программ
* Не изменяйте объект, на который ссылается возвращаемое значение определенных функций
* Не полагайтесь на указатель окружения после операции, которая может сделать его недействительным
* Все обработчики выхода должны завершаться return
* Не вызывайте system()
* Не храните указатели, возвращаемые определенными функциями
#### Обработка сигналов
* Маскируйте сигналы, обрабатываемые непрерываемыми обработчиками сигналов
* Изучите детали реализации, касающиеся сохранения обработчика сигналов
* Избегайте использования сигналов для реализации обычной функциональности
* Вызывайте только асинхронно-безопасные функции в обработчиках сигналов
* Не обращайтесь к общим объектам в обработчиках сигналов
* Не вызывайте signal() из прерываемых обработчиков сигналов
* Не возвращайте из обработчика сигналов исключений
#### Обработка ошибок
* Примите и реализуйте последовательную и всеобъемлющую политику обработки ошибок
* Используйте ferror() вместо errno для проверки ошибок потока FILE
* Избегайте внутриполосных индикаторов ошибок
* Используйте обработчики ограничений времени выполнения при вызове интерфейсов проверки границ
* Выберите подходящую стратегию завершения
* Независимый от приложения код должен обеспечивать обнаружение ошибок, не диктуя обработку ошибок
* Поймите поведение завершения assert() и abort()
* Предпочитайте функции, которые поддерживают проверку ошибок, эквивалентным функциям, которые не поддерживают
* Установите errno в ноль перед вызовом библиотечной функции, известной тем, что она устанавливает errno, и проверяйте errno только после того, как функция вернет значение, указывающее на сбой
* Не полагайтесь на неопределенные значения errno
* Выявляйте и обрабатывайте стандартные ошибки библиотеки
* Обнаруживайте ошибки преобразования строки в число
#### Интерфейсы
* Функции должны проверять свои параметры
* Избегайте размещения строк в памяти непосредственно перед конфиденциальными данными
* Функции, которые считывают или записывают в массив или из него, должны принимать аргумент для указания исходного или целевого размера
* Создавайте согласованные интерфейсы и возможности для связанных функций
* Предоставляйте согласованный и удобный механизм проверки ошибок
* Используйте совместимые параметры массива
* Обеспечьте безопасность типов
* Совместимые значения должны иметь один и тот же тип
* API должны иметь параметры безопасности, включенные по умолчанию
#### Распараллеливание
* Получайте и освобождайте примитивы синхронизации в одном модуле, на одном уровне абстракции
* Не используйте volatile в качестве примитива синхронизации
* Обеспечьте видимость при доступе к общим переменным
* Присоединяйтесь к потокам или отсоединяйтесь от них, даже если их статус выхода не важен
* Не выполняйте операции, которые могут блокироваться, удерживая блокировку
* Убедитесь, что каждый мьютекс переживет данные, которые он защищает
* Убедитесь, что составные операции над общими переменными являются атомарными
* Не предполагайте, что группа вызовов независимых атомарных методов является атомарной
* Избегайте проблемы ABA при использовании алгоритмов без блокировок
* Очищайте хранилище, специфичное для потока
* Не уничтожайте мьютекс, пока он заблокирован
* Предотвращайте гонки данных при доступе к битовым полям из нескольких потоков
* Избегайте условий гонки при использовании библиотечных функций
* Объявляйте объекты, совместно используемые потоками, с соответствующими сроками хранения
* Избегайте взаимоблокировки, блокируя в предопределенном порядке
* Обертывайте функции, которые могут ложно проснуться в цикле
* Не вызывайте signal() в многопоточной программе
* Сохраняйте безопасность и жизнеспособность потока при использовании условных переменных
* Не присоединяйтесь и не отсоединяйтесь от потока, который ранее был присоединен или отсоединен
* Не ссылайтесь на атомарную переменную дважды в выражении
* Обертывайте функции, которые могут ложно завершиться сбоем в цикле
* Не допускайте гонок данных в многопоточном коде
#### Разное
* Стремитесь к чистой компиляция при высоких уровнях предупреждений
* Стремитесь к логической завершенности
* Используйте комментарии последовательно и в читаемой форме
* Не манипулируйте значениями типа time_t напрямую
* Остерегайтесь оптимизаций компилятора
* Обнаружение и удаление мертвого кода
* Кодировка символов: в целях безопасности используйте подмножество ASCII.
* Кодировка символов: проблемы, связанные с UTF8
* Включите диагностические тесты с использованием assert
* Обнаружение и удаление кода, который не имеет никакого эффекта или никогда не выполняется
* Обнаружение и удаление неиспользуемых переменных
* Не вводите ненужные зависимости от платформы
* Не зависьте от неопределенного поведения
* Завершите каждый набор операторов, связанных с меткой case, оператором break
* Будьте осторожны при работе с конфиденциальными данными (например, пароли) в программном коде
* Для функций, возвращающих массив, предпочтительнее возвращать пустой массив, чем пустое значение
* Не используйте оператор switch для передачи управления в сложный блок
* Используйте надежные условия завершения цикла
* Используйте setjmp(), longjmp() безопасно
* Остерегайтесь различий в библиотеках и языках, специфичных для разных поставщиков
* Не использовать устаревшие или не рекомендуемые функции
* Не используйте небезопасные или слабые криптографические алгоритмы
* Не используйте функцию rand() для генерации псевдослучайных чисел
* Правильно задавайте начальные значения для генераторов псевдослучайных чисел
* Не передавайте недействительные данные в функцию asctime()
* Убедитесь, что управление никогда не достигнет конца непустой функции
* Не обрабатывайте предопределенный идентификатор как объект, если он может быть реализован только как макрос
* Не вызывайте va_arg() для va_list, имеющего неопределенное значение
* Не нарушайте ограничения
* Никогда не кодируйте конфиденциальную информацию напрямую