---
tags: си, прога, ошибки
---
# Частые ошибки
Короче, в лабах из раза в раз встречаются одни и те же ошибки.
Поэтому перед лабой окиньте взором эту доку сверху вниз и проверьте, всё ли у вас на месте.
## 0. Компиляция
Используйте ключи `-Wall -Wextra -Werror -Wpedantic -fsanitize=address`.
Это поможет увидеть многие ошибки.
- `-Wall -Wextra -Wpedantic` — это значит включить тучу ворнингов
- `-Werror` — это сделать все ворнинги ошибками, чтобы вы точно их не пропустили (может бесить, но во благо!)
- `-fsanitize=address` будет орать, когда вы попытаете незаконно пересечь границу массива, записать что-то в никуда (или прочесть оттуда же), за`free`шить что-то и кучу всего.
- **На винде может не работать.** Можно в WSL попробовать гнать.
- К слову, чтобы отсечь варианты отсечения ноги, можно приправить опции следующим:
- `-fsanitize=leak` — поиск утечек памяти
- `-fsanitize=undefined` — поиск undefined behavior в коде
- А ещё `valgrind` есть, очень крутая вещь. Про утечки памяти пишет.
## 1. Перед основным кодом
- Дефайны/инклюды ненужные или закинутые в `.h` вместо `.c`
- Нет include guard в хедерах
- Как надо:
```c
code.h:
#ifndef CODE_H
#define CODE_H
// code
#endif // CODE_H
```
- Есть глобальные переменные
- Нужно:
```c
static const int FOO = 5;
#define FOO 5
```
- `static` обязателен, когда переменная **вне функций** объявляется, кроме случаев, когда это не так.
- Низя:
```c
int foo = 5;
```
- Это про переменные **вне функций**. Внутри можно и нужно так.
- Любые неявные константы (например, длина экспоненты, количество соседей в "Жизни" или одна и та же строка с ошибкой, которую вы выводите в трёх разных случаях) нужно объявить как константы / задефайнить
## 2. Функции
- Не должно быть длинных функций. Всё длинное стоит разбить на маленькие функции
- Функция должна заниматься небольшим числом вещей
- Каждая строка должна помещаться в экран
- Стандарт: 80 символов / строка. 81 — расстрел.
- Если что-то повторяется много раз — вынести как отдельную функцию
- Если вы передаёте множество связанных данных раз за разом, сделайте структуру, где всё это будет лежать.
## 3. Проверки
Как программисты, надо учитывать все случаи. Все. Пограничные тоже.
Можно некоторые проигнорировать из прагматическо-инженерных соображений (которые придётся доказывать, если спросят), но учесть нужно.
- Максимально вариативные тесты:
- целые числа
- дробные числа
- `NULL`
> Технически компилятор считает, что если мы разыменовываем переменную,
> то там не `NULL` обязательно (иначе UB), и оптимизирует соответственно.
>
> Но до проверки на `NULL` поинтер — это на самом деле тип, который
> может не иметь определённого значения.
> Поэтому при получении поинтеров нужен хотя бы `assert(NULL != ptr)`.
- обратить внимание на `fopen`: часто не проверяется возвращаемое значение
- NaN, infinity
- отрицательные числа
- экспоненты
- буквы
- строки
- пустая строка
- переполнение `int`/`long long`/`size_t`
- в том числе во время вычислений: в `(min + max) / 2` может быть переполнение при суммировании, даже если результат всегда влезет в результирующий тип
- в том числе при юзании литералов: `1 << 60` — выстрел в упор себе в ногу, потому что у `1` тип `int` длиной (обычно) 32 бита
- фиксить юзанием шестнадцатеричных литералов: `0x1`
- ноль и единица
- пограничные случаи (например, ровно `INT_MAX`)
- Используйте `<assert.h>` **только** для того, что точно не должно произойти. «Штатные» ошибки выводите, например:
- нельзя открыть файл
- неверные вводные данные
Список зависит от проги.
Интерфейс должен быть достаточно юзер-френдли
- Вместо одного `assert` со сложным условием лучше написать несколько.
Если сломается что-то одно, прога напишет строку с ошибкой, и будет проще чинить.
- Плохо:
```c
assert(NULL != left && NULL != right);
```
- Хорошо:
```c
assert(NULL != left);
assert(NULL != right);
```
- Сколько было вызвано `calloc`/`malloc`, столько же должно быть вызовов `free`
## 4. Небольшие упрощения
Золотое правило писателя криптосистем — **код должен быть скучным**.
Это значит, что не надо в коде юзать какие-то шаманские трюки, которые бы сводили с ума читателей.
Код должен быть последовательным и логичным.
```c
if (0 == foo);
if (!foo);
```
```c
void foo() {
if (true) {
return 1;
}
return 0;
}
/* Так понятней при чтении и можно использовать где угодно
*
* */
enum ERRORS {IS_OK, SOME_ERROR};
void foo() {
if (true) {
return SOME_ERROR;
}
return IS_OK;
}
```
## 5. Стиль кода
Писать код надо красиво и единообразно.
Если не знаете, как это, то поставьте форматтер.
- `clang-format` — универсальный вариант (можно настроить под себя)
- **Code::Blocks:** `Plugins` → `Source code formatter`
- **Sublime Text:** можно поставить любой плагин-форматтер
- **CLion:** форматтер, как обычно, встроен
Если осилите, включите автоформатирование при коммите.
## 6. Юзать нужные типы
- Если функции нужно неотрицательное число, то пишем `unsigned` (или `size_t`, если это размер / кол-во).
- `size_t` живёт в хедере `<stddef.h>`, но его ещё тащит `<stdlib.h>`.
- Счётчик в цикле тоже лучше `size_t`. Но осторожно с переполнением и циклами вида
```c
for (size_t i = NUMBER; i >= 0; i--) {
// code
}
```
- Если функция возвращает код ошибки, все их лучше запихать в `enum` и возвращать элементы енама.
- Указатели надо делать константными (кроме случаев, когда надо что-то менять): `void const *`, `int const *const *array`.
- Если куча одинаковых аргументов таскаются из функции в функцию, можно их запихать в один стракт и передавать уже его.
- В функцию стракты (особенно большие) передавать по указателю, иначе будут копироваться.
- Если по смыслу буля, юзать `bool` (живёт в `<stdbool.h>`).
- Функцию, которая юзается только в одном файле, нужно отметить `static`:
```c
static bool is_positive(int value) {
return value > 0;
}
```
- Функцию, которая юзается во многих, статиком не отмечать, и декларацию добавить в хедер
## 7. Файлы
- Просто для удобства закиньте пару файлов с примерами ввода в репозиторий.
Ускорит проверку на пару минут (и на формат, и на разнообразные данные, которые программа успешно скушает и начнёт обрабатывать)
- Прогу можно разбить на несколько файлов.
Например, в один файл поместить ваш вектор (структура данных такая), а в другом читать ввод.
## 8. Смысловое
- Каждая функция должна быть простой как три рубля. Не нужно впихивать несколько задач в одну функцию, в таком случае лучше поделить её на несколько.
- Логические выражения: избегать двойных отрицаний, использовать правила де Моргана. Воспринимать ДНФ/КНФ проще.
- Читать код должно быть легко. Будто книгу.
## Thoughts
Хотелось бы ещё сказать пару слов о:
- [ ] Если файл не открывается, то в fprintf(stderr, ...) пихать название файла;
- [ ] Больше комментировать;
- [x] Разбивать ассерты (если там несколько выражений через ИЛИ);
- [x] ~~perror, errno~~
- [x] указатель до assert на NULL — это опциональный тип, который может не иметь значения, и разыменовывать его — семантическая ошибка
- [x] ~~слои абстракций~~
- [x] разбивку проги на файлы
- [x] (мета) настроить форматтер кода в редакторе
- [x] bool
- [ ] pragma
- [ ] ссылку на разбор сложных типов
- [ ] оформить красиво
- [ ] пару слов о дефайнах
- [ ] SOLID, Open-close, KISS, DRY, DIE etc.
- [ ] typedef для удобства
- [ ] пару слов о сроках и их срывах