Files
eduplan-cpp-eltex/diag/tasks/03_ring/task.md
T

105 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Задача 03 — кольцевой буфер (C++)
> Разминка уровня «основная»: структура маленькая, но встречается везде. Ориентир — 40–60 минут.
## Глава I. Общая информация
- **Цель:** научиться писать структуру с фиксированной памятью и без сдвигов элементов.
- **Почему это в Eltex:** приём/передача пакетов, буферы DMA, очереди между потоками —
всё это кольцевые буферы. Понимание wrap-around и «полный/пустой» — прямой вопрос на
собеседовании.
- **Что сдаётся:** `solution.cpp`, проходящий `python3 grade.py 03`.
## Глава II. Что нужно знать до старта
Идея: массив фиксированного размера + два индекса. `head` — куда писать, `tail` — откуда
читать. Когда индекс доходит до конца, он возвращается в начало: `idx = (idx + 1) % capacity`.
Сдвигать элементы не нужно никогда — в этом весь смысл.
Ловушка, из-за которой задача попадает в собеседования: **как отличить пустой буфер от
полного, если оба индекса совпали?** Варианты: хранить счётчик `size`, либо оставлять одну
ячейку свободной. В нашем интерфейсе есть `size()`, поэтому проще хранить счётчик.
## Глава III. Задание
```c++
class RingBuffer {
public:
explicit RingBuffer(size_t capacity);
~RingBuffer();
bool push(int v); // false, если буфер полон (перезапись НЕ делать)
bool pop(int& out); // false, если буфер пуст
size_t size() const;
size_t capacity() const;
bool empty() const;
bool full() const;
};
```
Требования:
- O(1) на `push`/`pop`, никаких сдвигов элементов;
- память выделяется один раз в конструкторе и освобождается в деструкторе;
- корректное поведение после заворачивания индексов (записали до конца, продолжили с начала);
- двойного освобождения быть не должно.
## Глава IV. Ступени
**Ступень 1 (10 минут).** Поля: указатель на массив, `capacity_`, `size_`, `head_`, `tail_`.
Конструктор выделяет массив и обнуляет поля. Деструктор освобождает. Если хочешь короче —
`std::vector<int>` вместо ручного `new[]`, тогда деструктор не нужен вовсе.
**Ступень 2 (10 минут).** `empty()` — это `size_ == 0`, `full()` — `size_ == capacity_`.
Проверь на буфере ёмкости 1: после одного `push` он полон, после одного `pop` пуст.
**Ступень 3 (15 минут).** `push`: если полон — вернуть `false` и ничего не менять; иначе
записать в `head_`, сдвинуть `head_ = (head_ + 1) % capacity_`, увеличить `size_`, вернуть `true`.
**Ступень 4 (15 минут).** `pop`: если пуст — `false`; иначе отдать `buffer_[tail_]`, сдвинуть
`tail_`, уменьшить `size_`, вернуть `true`.
**Ступень 5.** Проверь руками wrap-around: ёмкость 4, положи 4 элемента, сними 2, положи
ещё 3 — все значения должны идти в правильном порядке. Затем `python3 grade.py 03`.
## Глава V. Критерии приёмки
`PASS` от `grade.py 03`: сборка без предупреждений, ASAN/UBSAN чистые, все проверки `ok`,
включая порядок FIFO и корректный wrap-around.
## Глава VI. Подсказки (после первой попытки)
<details>
<summary>Значения после заворота идут не в том порядке</summary>
Проверь, что `pop` читает именно `tail_`, а `push` пишет именно в `head_`, и что оба индекса
инкрементируются **после** операции. Классическая ошибка — сдвинуть `head_` до записи.
</details>
<details>
<summary>Перезапись при полном буфере</summary>
Ты не проверяешь `full()` в `push`. По условию перезапись запрещена: полный буфер возвращает
`false` и не портит старые данные.
</details>
<details>
<summary>ASAN: утечка или двойное освобождение</summary>
Если память выделена через `new[]`, освобождать нужно `delete[]` (не `delete`). Проще взять
`std::vector<int>` — тогда вопрос исчезает.
</details>
## Глава VII. Частые ошибки
- Сдвиг элементов вместо индексов (теряется весь смысл O(1)).
- Путаница «пусто/полно» при совпавших индексах.
- `%` на каждом шаге там, где можно было обойтись условием — не ошибка, но на собеседовании
спросят «а быстрее можно?» (ответ: `if (++idx == cap) idx = 0;`).
- Копирование объекта без правила трёх → двойное освобождение. Если сдаёшь с ручным `new[]`,
запрети копирование (`= delete`).
## После сдачи
Разбор: как устроены буферы в драйверах (ring buffer с head/tail для DMA), почему в
многопоточном варианте нужны атомарные индексы и `memory_order`.