---
# System prepended metadata

title: Частые ошибки
tags: [' прога', ' ошибки', си]

---

---
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 для удобства
- [ ] пару слов о сроках и их срывах