ИУП: интенсив 7 дней + диагностический пакет (32 вопроса, 9 задач с автопроверкой)

This commit is contained in:
Kodlo-chan
2026-09-23 16:05:09 +07:00
commit 4cd63b3d4d
40 changed files with 2464 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
diag/solutions/
diag/submissions/
diag/dist/
diag/results_*.json
diag/tasks/*/t_0*
diag/tasks/07_epoll/solution
diag/tasks/09_gdb/crash_checked
diag/tasks/08_bash/big.log
diag/tasks/08_bash/empty.log
*.o
+62
View File
@@ -0,0 +1,62 @@
# Индивидуальный учебный план — CONTEXT
Лейн: HQ (Kodlo group, -1003909355979), топик `🎓 Индивидуальный учебный план` (thread 1419).
Создание/пересоздание: `~/.hermes/scripts/lane_topic_create.py ~/.hermes/eduplan_lane.json "🎓 Индивидуальный учебный план"`.
Отправка в лейн: `~/.hermes/scripts/send_lane.py ~/.hermes/eduplan_lane.json "текст" [файл ...]`.
Заведён 22.09.2026 по запросу Туры.
## Вводные (ответы Туры, 23.09.2026)
1. Ученик — сам Тура (человек в студии), не внешний пользователь.
2. Предмет: программирование, приоритет — алгоритмы и структуры данных.
Цель-ориентир: вакансия **«Разработчик C/C++», Eltex (Новосибирск), hh #136775547**.
3. Формат результата: всё вместе — трек с контрольными точками, расписание,
подборка материалов, проверка знаний.
4. Критерий выполнения: тесты + выполненные практические задачи.
5. Дополнительно: знания «обширные, но нужно плотно повторить и укрепиться перед
собеседованием»; нужна диагностика текущего уровня.
6. **Срок: собеседование максимум через неделю, подготовка фуллтаймом (~8 ч/день).**
Поэтому основной рабочий документ — 7-дневный интенсив `WEEK7.md`, а `PLAN.md`
(полный трек M1–M10) переведён в пост-офферный режим: хвост M2, M6, M7, M8, M9.
## Вакансия-ориентир (данные hh, вытащено 23.09.2026 через chromium --dump-dom)
- Работодатель: «Предприятие ЭЛТЕКС» (Eltex), Новосибирск. Опыт: 1–3 года.
Формат: офис/гибрид (hh в тексте: «рассматриваем только офисный формат»). Полный день.
- Ключевые навыки hh: `C/C++`, `Linux`, `TCP/IP`.
- Продукт: телеком-оборудование (Ethernet-коммутаторы, сервисные маршрутизаторы,
Wi-Fi, GPON, IoT), embedded C/C++.
- Задачи: userspace-приложения/библиотеки/утилиты; bringup устройств (uboot → стабильная
ОС); драйверы устройств и модули ядра Linux.
- Требования:
- C/C++ уверенно; **базовые алгоритмы и структуры данных**; многопоточные и сетевые
приложения; bash-скрипты.
- Коммерческая разработка под Linux.
- L2/L3-сети, OSI, TCP/IP, работа с сетевым оборудованием и анализаторами трафика.
- Железо/электроника (для аппаратных проектов): схемотехника, измерительное/паяльное,
чтение принципиальных схем.
- Git, Docker, GDB.
- Плюс: опыт разработки устройств на SoC (uboot/linux, драйверы).
## Артефакты лейна
- `/home/pi/eduplan/PLAN.md` — трек подготовки (модули, контрольные точки, артефакты,
материалы, ритм).
- `/home/pi/eduplan/diag/` — диагностический пакет: квиз (32 вопроса, 4 домена) +
9 машинно проверяемых практических задач + автооценка (`grade.py`).
`diag/submissions/` (0777) — инбокс для файлов-ответов Туры.
- `/home/pi/eduplan/diag/dist/eduplan_diag.tar.gz` — тот же пакет для скачивания в TG.
- `/home/pi/eduplan/WEEK7.md` — интенсив на 7 дней (D0 23.09 — D7 30.09), три блока в дне,
у каждого дня контрольная точка. Выдача дня в лейн: крон `d3af678622f3`
(`eduplan-intensive-daily`, 09:00 NSK, no_agent, скрипт `~/.hermes/scripts/eduplan_day.py`,
читает WEEK7.md, 8 срабатываний).
## Статус
23.09.2026: вводные получены, вакансия разобрана, план и диагностика собраны.
Диагностика проверена на двух прогонах: заглушки → 0/9 задач PASS, эталонные решения
(`diag/solutions/`, в руки не отдавать) → 9/9 PASS; квиз на верных ответах даёт 32/32.
Отложенные прогоны: `python3 grade.py` (все), `python3 quiz.py --answers "1-3 2-4 ..."`.
Ждём прохождения диагностики — по её результатам интенсив калибруется (какие дни усилить,
что срезать). Срок известен: неделя, фуллтайм. Открытый вопрос: реальные часы в день и
дата самого собеседования (для мок-сессий на D7).
+160
View File
@@ -0,0 +1,160 @@
# ИУП: подготовка под «Разработчик C/C++» (Eltex, Новосибирск)
> **Актуальный режим (23.09.2026):** собеседование ≤ 1 недели, подготовка фуллтаймом —
> рабочий документ сейчас `WEEK7.md` (интенсив 7 дней). Этот файл — полный трек на
> пост-офферный период и калибровка по диагностике.
Цель: пройти собеседование на вакансию hh #136775547 (Eltex, embedded C/C++, Linux, TCP/IP)
и подтвердить уровень практикой, а не самооценкой.
Принципы плана:
- каждая точка трека закрывается артефактом, у которого есть машинная или ревью-проверка;
- материалы — первопартийные (официальные доки, man-страницы, книги авторов стека);
- теории ровно столько, сколько нужно для практики: сначала задача, потом разбор;
- ритм недельный, с обязательным «выходом» (код/разбор) каждую неделю;
- ревизия плана каждые 4 недели по факту прохождения.
## Диагностика (вход)
`/home/pi/eduplan/diag` — квиз по 4 доменам (C/C++, алгоритмы, Linux/OS, сети) и
9 практических задач (биты, связный список, ring buffer, разбор IPv4-пакета,
ДП, потоки, epoll-сервер, bash, отладка gdb).
Результат задаёт: с каких модулей стартовать, какие закрывать «по касательной»,
а какие — с нуля. Пока уровня не знаем, все модули помечены как «полный».
## Трек модулей
Обозначения: **К** — контрольная точка, **A** — артефакт.
Оценка «сдал»: артефакт собирается и проходит автопроверку/ревью; провал — точка не закрыта.
### M0. Диагностика — 1 день
- A: `diag/results_quiz.json`, `diag/results_tasks.json`.
- К: пройден квиз (≥ 24/32 верных, иначе модуль-дыра уходит в план) и 9 задач.
### M1. Системный C — 1 неделя
- Память и владение: malloc/free, утечки, double free, dangling, переполнение буфера.
- Указательная арифметика, выравнивание, структуры и padding, bitfields, union.
- UB: переполнение знаковых, strict aliasing, неинициализированная память, порядок
вычислений; как ловить — `-Wall -Wextra -fsanitize=address,undefined`.
- Строки/буферы, `memcpy` vs `strcpy`, `restrict`, endianness, work with bytes.
- A: 20 задач (массивы, битовые операции, парсинг бинарных структур) + конспект по UB.
- К: A собирается с `-Wall -Wextra -Werror` и проходит автопроверку.
- Материалы: cppreference (C), man-страницы 3 раздел, lkmpg (глава про кодинг-стиль
и ошибки), OSTEP (глава про адресное пространство и malloc).
### M2. Алгоритмы и структуры данных — 4–6 недель (приоритет по запросу)
Порядок — от «спрашивают почти всегда» к «глубина»:
1. Сложность, амортизация, оценка на салфетке.
2. Массив/строка: два указателя, скользящее окно, префиксные суммы, бинарный поиск
(в т.ч. lower_bound руками).
3. Сортировки: понимание всех, stability, компараторы; встроенная сортировка с ключом.
4. Хеш-таблицы: коллизии, открытая адресация vs цепочки, нагрузка; unordered_map.
5. Связные списки: разворот, поиск цикла (Флойд), пересечение, k-й с конца.
6. Стек/очередь/ring buffer, монотонный стек/дек.
7. Бинарные деревья и BST, обходы (рекурсия/итерация), LCA; куча и priority_queue
(heapify O(n), top-K).
8. Графы: представления, BFS/DFS, топосорт, компоненты, Дейкстра (неотрицательные веса),
union-find.
9. ДП: базовые состояния и переходы, задача о рюкзаке, LIS O(n log n), разбор «как
доказывать корректность и считать сложность».
10. Битовая арифметика, математика, строковые алгоритмы (хеш/префикс-функция — по остатку).
- A: 60 задач (Яндекс-тренировки по алгоритмам + LeetCode/Codeforces-набор), 3 «живых»
разбора вслух с замером времени, файл-трекер с датами и статусами.
- К: 60% задач решены без подсказки за целевое время; 3 разбора приняты на ревью.
- Материалы: официальные Яндекс Тренировки по алгоритмам (бесплатно), cppreference,
OSTEP — как базис по структурам; Кормен — как справочник, не как старт.
### M3. C++ как язык — 2 недели
- RAII, Rule of 0/3/5, move-семантика, `std::move` vs перемещение, noexcept.
- Умные указатели: unique_ptr/shared_ptr/weak_ptr, циклы ссылок.
- Шаблоны и STL: контейнеры, итераторы, алгоритмы, лямбды, `auto`, structured bindings.
- Исключения и гарантии безопасности (basic/strong), RAII-обёртки.
- Виртуальность, vtable, виртуальный деструктор, slicing.
- A: своя реализация `vector`/`unique_ptr` + мини-библиотека утилит.
- К: сборка с `-Wall -Wextra -Werror` под ASAN/UBSAN, тесты проходят; ревью кода.
- Материалы: cppreference (эталон синтаксиса/поведения), Core Guidelines (C++ Core
Guidelines — официальный свод), книги автора языка (Страуструп) — выборочно.
### M4. Многопоточность — 2 недели
- std::thread, mutex/lock_guard, condition_variable, атомики и memory_order, барьеры.
- Гонки, дедлоки, ложное пробуждение, паттерны: producer/consumer, пул потоков,
thread-safe singleton, reader-writer.
- Инструменты: ThreadSanitizer, `-pthread`, helgrind — по возможности.
- A: bounded blocking queue + пул потоков с тестами, гонки ловятся TSan.
- К: TSan чистый на 4×4 потоков/20k элементов, счётчики совпадают.
- Материалы: cppreference (atomic/thread), документация компилятора по санитайзерам.
### M5. Linux: системное программирование — 3 недели
- Процессы: fork/exec/wait, зомби, сигналы и маски, exit-коды.
- Файловый ввод-вывод: open/read/write/lseek/mmap, буферизация, права.
- IPC: pipes, FIFO, unix-сокеты, shared memory.
- Мультиплексирование: select → poll → epoll (LT/ET), неблокирующие сокеты.
- Сокеты: TCP/UDP клиент-сервер, SO_REUSEADDR, разбор ошибок errno.
- /proc, /sys, ulimit, cgroups (обзор), systemd-юниты (обзор).
- A: TCP-эхо-сервер на epoll с поддержкой многих соединений + клиент; конспект по
системным вызовам с man-страницами.
- К: автотест поднимает сервер, гоняет 3 соединения, проверяет эхо; ревью кода.
- Материалы: man7.org (man-страницы, разделы 2/3/7), LKMPG, OSTEP (процессы/потоки),
Beej's Guide to Network Programming.
### M6. Сети L2/L3 и анализ трафика — 3 недели
- OSI/TCP-IP стеки; инкапсуляция; MTU/фрагментация.
- L2: Ethernet, MAC, ARP, VLAN 802.1Q, STP (обзор), коммутация, learning/broadcast.
- L3: IPv4-заголовок и контрольная сумма, маски/подсети, маршрутизация, ICMP, TTL.
- L4: TCP (handshake, состояния, окно, ретрансмиссии, таймеры), UDP, порты, NAT.
- Прикладные: DNS, DHCP, HTTP-минимум.
- Инструменты: tcpdump, Wireshark, tcpreplay-разбор дампов, iperf.
- A: 5 письменных разборов дампов (ARP-запрос, VLAN-кадр, TCP-handshake, ретрансмиссия,
ICMP unreachable) + свой дамп, снятый своей программой (`tcpdump`).
- К: разборы приняты на ревью; в каждом названы поля и вывод — что происходит в сети.
- Материалы: RFC 791/793 (первоисточники), man-страницы сокетов, Wireshark wiki.
### M7. Инструменты и инженерная гигиена — параллельно с M1–M6
- git: ветки, rebase, cherry-pick, bisect, stash, worktree, конфликты.
- gdb: breakpoints/watch/backtrace, разбор падений, core dumps, отладка потоков.
- Сборка: Makefile руками → CMake, флаги, санитайзеры, препроцессор.
- Docker: образ под сборку/тесты, воспроизводимое окружение.
- bash: пайплайны, регулярки, awk/sed на уровне «разобрать лог», set -euo pipefail.
- A: скрипт `check.sh` (сборка+тесты+санитайзеры), разбор одного реального падения в gdb,
Makefile+CMake для своих задач.
- К: скрипт зелёный на чистом клоне; падение разобрано письменно.
### M8. Ядро и embedded (плюс к вакансии) — 2 недели
- Модуль ядра: сборка, insmod/rmmod, printk, параметры, /proc.
- Символьный драйвер: file_operations, копирование user↔kernel.
- Device Tree (обзор), cross-compile (aarch64), uboot и bringup (обзор).
- A: рабочий модуль + символьный драйвер, собранные под текущее ядро (или в QEMU).
- К: модуль грузится и выгружается, драйвер отдаёт данные `cat`-ом.
- Материалы: LKMPG (sysprog21.github.io/lkmpg), docs.kernel.org, эликсир (исходники ядра),
kernel.org driver-api.
### M9. Электроника (если проект аппаратный) — 1–2 недели
- Основы схемотехники: питание, делители, подтяжки, транзистор как ключ.
- Интерфейсы: UART/I2C/SPI, уровни, осциллограмма.
- Чтение принципиальной схемы, работа мультиметром/осциллографом, пайка.
- A: письменный разбор одной схемы из открытого проекта + замеры (если есть доступ к
железу студии).
- К: разбор принят на ревью; термины и режимы интерфейсов объяснены без подглядывания.
### M10. Мок-интервью и выход — финальные 2 недели
- 3 живые сессии: (1) алгоритмы — 2 задачи вслух с замером, (2) C/C++ глубина + Linux,
(3) сети L2/L3 + анализ дампа.
- Резюме под вакансию + сопроводительное с готовностью к офисному формату.
- A: 3 записи/протокола сессий с разбором ошибок; резюме и cover под Eltex.
- К: не больше 1 грубой ошибки на сессию; резюме отревьюено.
## Ритм
- Будни: 1.5–2 ч практики (задачи) + 30 мин разбора теории под сегодняшние задачи.
- Выходные: 3 ч — «грязный» модуль недели (драйвер/сервер/дампы) с артефактом.
- Каждую неделю — отчёт: что закрыто, что нет, где упёрся; каждые 4 недели — ревизия
плана (пункты переехать/выбросить/добавить).
- Правило: не начинать новый модуль, пока не закрыта контрольная точка предыдущего;
исключение — M7 (идёт параллельно).
## Порядок запуска (после диагностики)
Старт — с M2 (приоритет Туры) с параллельным добором M1 там, где диагностика показала
дыры; M3/M4/M5 — по мере закрытия M2; M6/M7/M8/M9/M10 — по срокам собеседования.
Если собеседование ближе 6 недель — сжатый вариант: M2 (ядро) + M5/M6 + M7 + M10,
остальное после оффера.
+40
View File
@@ -0,0 +1,40 @@
# ИУП: подготовка к собеседованию Eltex (C/C++, алгоритмы, Linux, сети)
Личный учебный план и диагностический пакет. Репозиторий обновляется по ходу недели.
## Что внутри
- `WEEK7.md` — **рабочий документ**: интенсив на 7 дней (D0 23.09 → D7 30.09), три блока
в дне (алгоритмы / системное / практика+разбор), у каждого дня контрольная точка.
- `PLAN.md` — полный трек M1–M10 на пост-офферный период (модули, артефакты, материалы).
- `CONTEXT.md` — вводные, разбор вакансии (Eltex, hh #136775547), статус лейна.
- `diag/` — диагностический пакет:
- `quiz.py` + `quiz.json` — 32 вопроса по 4 доменам (C/C++, алгоритмы, Linux, сети);
- `tasks/01..09` — 9 практических задач с автопроверкой (ASAN/UBSAN, python-драйверы);
- `grade.py` — прогон задач с автооценкой;
- `questions_export.txt` — вопросы квиза текстом (можно отвечать строкой в чате).
- `TRACKER.md` — дневник: что решено, где встал, сколько минут.
## Как начать
```bash
git clone https://git.kodlo.art/Kodlo/eduplan-cpp-eltex.git
cd eduplan-cpp-eltex/diag
python3 quiz.py # квиз, ответы — номер варианта
python3 grade.py # все 9 задач (с санитайзерами)
python3 grade.py 01 04 # выборочно
```
Нужны `g++` (C++17), `gcc`, `python3`; для задачи 07 — только Linux-сокеты,
для 08 — bash/awk/sort. Никаких внешних зависимостей.
## Правила честности
Эталонных решений в репозитории нет — они лежат только на машинке студии, чтобы
автопроверка не протухла. Задача 09 (`answer.txt`) — часть оценки: важен ход разбора.
## Как отдавать результаты
- `python3 grade.py --submit` — складывает `results_*.json` и свои файлы решений рядом;
- либо коммит в ветку `answers/<день>` и push (доступ на запись у аккаунта `tura`);
- либо файлами в чат / в топик лейна.
+23
View File
@@ -0,0 +1,23 @@
# Трекер интенсива
Пиши по факту, не по плану. «Где встал» важнее «сколько сделал» — по этому я калибрую
следующий день.
| День | Дата | Задач решено | Код дня | Задача вслух (время) | Где встал / что не понял |
|------|------|--------------|---------|----------------------|--------------------------|
| D0 | 23.09 | | | | |
| D1 | 24.09 | | | | |
| D2 | 25.09 | | | | |
| D3 | 26.09 | | | | |
| D4 | 27.09 | | | | |
| D5 | 28.09 | | | | |
| D6 | 29.09 | | | | |
| D7 | 30.09 | | | | |
## Слабые темы (по диагностике и по ходу)
- ...
## Вопросы, которые хочу разобрать со студией
- ...
+145
View File
@@ -0,0 +1,145 @@
# ИУП — интенсив 7 дней (фуллтайм, собеседование ≤ 1 неделя)
Срок: собеседование максимум через неделю, подготовка — фуллтайм (~8 ч/день).
Цель интенсива: закрыть ровно то, что спросят на входе в Eltex (C/C++, базовые алгоритмы и
структуры данных, Linux, L2/L3-сети, инструменты), и не утонуть в глубине.
## Правила интенсива
1. Каждый день — три блока: **утро (3 ч) алгоритмы**, **день (3 ч) системное/язык**,
**вечер (2 ч) практика + разбор**. Блок не начинается, пока не закрыта вчерашняя точка.
2. День закрыт, только если: решены задачи дня **без подсказки**, написан/починен код дня,
сделана **одна задача вслух на таймер** (10 мин: 2 мин на разбор + код + сложность).
3. Всё пишем, а не читаем: теория — только под конкретную задачу, максимум 40 минут.
4. Трекер: `/home/pi/eduplan/diag/submissions/` или файл у себя — что решено, где встал,
сколько минут. Это вход для калибровки на следующий день.
5. Что осознанно НЕ лезет в неделю: полноценное ядро (модуль+драйвер), электроника,
Docker-продакшн. Ядро — обзорно на D6, электроника — после оффера (см. `PLAN.md`).
6. Если день провален — не догоняем всё, а переносим хвост в D7 (день повтора) и режем
второстепенное (M8-обзор, docker).
Материалы: cppreference (C и C++), man7.org (разделы 2/3/7), LKMPG, docs.kernel.org,
OSTEP (главы про процессы/память), Beej's Guide to Network Programming, RFC 791/793,
Wireshark wiki. Задачи: Codeforces с фильтром по тегам и рейтингу 800–1400
(`https://codeforces.com/problemset?tags=two+pointers,800-1400` — менять тег),
Яндекс Тренировки по алгоритмам (бесплатные контесты с разборами,
`https://yandex.ru/yaintern/training/algorithm-training`), классика LeetCode — по названию
(ссылки на leetcode.com открываются только в браузере).
---
## D0 — 23.09, диагностика (3–4 ч)
- Квиз: `python3 quiz.py` (или ответы строкой в чат), 32 вопроса, 4 домена.
- Задачи: `python3 grade.py` — 9 задач с автопроверкой (ASAN/UBSAN).
- Разбор промахов вместе со мной, калибровка: какие дни усилить, что срезать.
- **Точка:** есть `results_quiz.json` и `results_tasks.json`, названы 3 слабейших темы.
## D1 — 24.09: массивы/строки + память в C + процессы Linux
- Утро (3 ч) — алгоритмы: два указателя, скользящее окно, префиксные суммы, бинарный
поиск и `lower_bound` руками. Задачи: Two Sum, Valid Palindrome, Container With Most
Water, Longest Substring Without Repeating Characters, Binary Search + 6–8 задач с
Codeforces по тегам `two pointers` и `binary search`, рейтинг 800–1400.
- День (3 ч) — C и память: указатели и арифметика, выравнивание и padding, UB
(signed overflow, strict aliasing, неинициализированная память), malloc/free,
переполнение буфера; сборка с `-Wall -Wextra -Werror -fsanitize=address,undefined`.
Практика: переписать `tasks/01_bits` и `tasks/03_ring` на чистом C (без C++-обёрток),
прогнать под санитайзерами.
- Вечер (2 ч) — Linux: `fork/exec/wait`, зомби, сигналы, коды возврата. Практика: свой
мини-шелл (fork + exec + wait, обработка `exit`) — это прямой вопрос на собеседовании.
- **Точка:** 10+ задач решены; мини-шелл запускает `ls`/`cat`; ASAN чистый; лог дня.
## D2 — 25.09: хеш-таблицы и сортировки + C++ (RAII, владение, STL) + git/Makefile
- Утро (3 ч) — алгоритмы: хеш-таблицы и множества (коллизии, открытая адресация vs
цепочки), сортировки и компараторы, stability. Задачи: Group Anagrams, Top K Frequent
Elements, Two Sum II, Merge Intervals, Sort Colors + 6–8 задач с тегами `hashing`,
`sortings`.
- День (3 ч) — C++: RAII, правило 0/3/5, move-семантика и `std::move`, умные указатели
(unique/shared/weak, циклы ссылок), контейнеры STL и итераторы, лямбды, исключения и
гарантии безопасности. Практика: свой `unique_ptr` и свой `vector` (упрощённый), класс-
владелец с правилом пяти; сборка с `-Werror`.
- Вечер (2 ч) — git (ветки, rebase, конфликты, `bisect`) и сборка: Makefile руками, затем
CMake для своих задач. Практика: репозиторий с 3 задачами, где сборка/тесты — одной
командой.
- **Точка:** мини-библиотека собирается с `-Werror`; git-сценарии (rebase, bisect) пройдены
руками; 10+ задач.
## D3 — 26.09: списки/стек + многопоточность + gdb
- Утро (3 ч) — алгоритмы: связные списки (разворот, цикл Флойда, k-й с конца), стек/
очередь/ring buffer, монотонный стек. Задачи: Reverse Linked List, Merge Two Sorted
Lists, Linked List Cycle, Valid Parentheses, Daily Temperatures + 6–8 задач с тегами
`linked list`, `stack`.
- День (3 ч) — многопоточность: `std::thread`, mutex/`lock_guard`, `condition_variable`
и predicate-цикл, атомики и `memory_order`, гонки, дедлоки, ложные пробуждения;
паттерны producer/consumer и пул потоков. Практика: довести `tasks/06_threads` до
продакшн-вида + написать пул потоков. TSan на этой машине недоступен (VMA) — проверяем
инвариантами + ASAN, в логе это помечено.
- Вечер (2 ч) — gdb: breakpoints, `watch`, `bt`, `frame`, отладка потоков, разбор
падения. Практика: `tasks/09_gdb` + своё падение (переполнение буфера) с письменным
разбором.
- **Точка:** 10+ задач; пул потоков проходит счётчики 4×4×20k; падение разобрано в gdb
письменно.
## D4 — 27.09: деревья/куча + Linux-системное (epoll) + L2/L3
- Утро (3 ч) — алгоритмы: бинарные деревья и BST, обходы (рекурсия и итерация), куча и
`priority_queue`, heapify за O(n), top-K, LCA. Задачи: Invert Binary Tree, Validate
Binary Search Tree, Binary Tree Level Order Traversal, Kth Largest Element, Lowest
Common Ancestor + 6–8 задач с тегами `trees`, `heaps`.
- День (3 ч) — Linux: файловый ввод-вывод (`open/read/write/lseek`), `mmap`,
мультиплексирование `select → poll → epoll` (LT/ET), неблокирующие сокеты,
`SO_REUSEADDR`, обработка `EAGAIN/EINTR`. Практика: `tasks/07_epoll` — эхо-сервер
довести до вида «не падает под нагрузкой» + клиент, нагрузочный тест своими руками.
- Вечер (2 ч) — сети L2/L3: OSI и TCP/IP, инкапсуляция, Ethernet (14 байт заголовка),
MAC, ARP, VLAN 802.1Q, IPv4-заголовок и контрольная сумма, маски и подсети, ICMP.
Практика: `tasks/04_ipv4` + два письменных разбора дампов (`tcpdump`: ARP-обмен и
TCP-handshake).
- **Точка:** сервер держит 3+ одновременных соединения и не течёт по дескрипторам;
2 разбора дампов письменно; 10+ задач.
## D5 — 28.09: графы + TCP глубоко + bash/proc
- Утро (3 ч) — алгоритмы: представления графа, BFS/DFS, топологическая сортировка,
компоненты связности, Дейкстра (неотрицательные веса), union-find. Задачи: Number of
Islands, Course Schedule, Clone Graph, Network Delay Time, Redundant Connection + 6–8
задач с тегами `graphs`, `dfs and similar`, `dsu`.
- День (3 ч) — сети глубже: TCP-автомат состояний, окно и подтверждения, ретрансмиссии и
таймеры, закрытие соединения, UDP, порты, NAT, DNS/DHCP минимум; работа с
`tcpdump`/Wireshark. Практика: 3 письменных разбора дампов (ретрансмиссия, ICMP
unreachable, DNS) + свой захват трафика своей же программой.
- Вечер (2 ч) — Linux-быт: `/proc`, `ulimit`, systemd-юниты обзорно; bash (пайплайны,
`set -euo pipefail`, awk/sed для логов). Практика: `tasks/08_bash` + свой скрипт
диагностики сети/логов.
- **Точка:** 8+ задач; 3 разбора дампов; скрипт работает на логе в 100k строк за секунды.
## D6 — 29.09: ДП + ядро/embedded обзорно + docker + резюме
- Утро (3 ч) — алгоритмы: ДП — состояния и переходы, рюкзак, LIS за O(n log n), строковые
задачи. Задачи: Climbing Stairs, House Robber, Coin Change, Longest Increasing
Subsequence, Edit Distance, Word Break + 5–6 задач с тегом `dp`, рейтинг 800–1400.
- День (3 ч) — ядро и embedded обзорно: модуль ядра (сборка, insmod/rmmod, printk,
параметры, `/proc`), символьный драйвер (`file_operations`), device tree и cross-compile
— по касательной, ровно чтобы отвечать словами. Практика: hello-модуль + простейший
символьный драйвер (в QEMU, если на этой машине сборка не встанет).
- Вечер (2 ч) — docker (образ под сборку/тесты) и **резюме + сопроводительное под Eltex**
(учесть: рассматривают офисный формат).
- **Точка:** модуль грузится/выгружается; черновик резюме отревьюен.
## D7 — 30.09: повтор, два мок-интервью, выход
- 2 ч — утренний повтор: только слабые места из трекера (не всё подряд).
- 3 ч — мок-интервью №1: алгоритмы, 2 задачи вслух с таймером, я веду и разбираю.
- 2 ч — мок-интервью №2: C/C++ + Linux + сети (вопросы по кругу, включая разбор дампа).
- 1 ч — вопросы работодателю, легенда по опыту, финальная вычитка резюме.
- **Точка:** ≤ 1 грубая ошибка на сессию; резюме и сопроводительное готовы.
---
## После собеседования
Незакрытое (полный трек `PLAN.md`): M2 хвост (битовые/строковые алгоритмы, математика),
M6 глубина (STP, маршрутизация, HTTP), M7 полностью (Docker в бою, bisect на реальном
репозитории), M8 ядро (полноценный драйвер, uboot/bringup), M9 электроника.
+54
View File
@@ -0,0 +1,54 @@
# Диагностика уровня — пакет (C/C++, алгоритмы, Linux, сети)
Собран под вакансию Eltex «Разработчик C/C++» (Новосибирск, hh #136775547).
Цель — не «проверить себя на ощупь», а получить машинную оценку по доменам и список дыр.
## Что внутри
- `quiz.json` + `quiz.py` — 32 вопроса по 4 доменам (C/C++ и память, алгоритмы и структуры
данных, Linux/ОС, сети L2/L3 и TCP/IP). 24+ верных — уверенный вход.
- `tasks/` — 9 практических задач, у каждой автопроверка:
| # | Задача | Что проверяется |
|---|--------|-----------------|
| 01 | `01_bits` | битовые операции, без встроенных popcount |
| 02 | `02_list` | связный список: разворот, середина, цикл (Флойд) |
| 03 | `03_ring` | кольцевой буфер, wrap-around, ASAN |
| 04 | `04_ipv4` | разбор IPv4-заголовка из буфера + контрольная сумма |
| 05 | `05_lis` | НВП за O(n log n), тест на 100 000 элементов |
| 06 | `06_threads` | ограниченная очередь: mutex/condvar, ThreadSanitizer |
| 07 | `07_epoll` | TCP-эхо-сервер на epoll: несколько соединений, SIGTERM |
| 08 | `08_bash` | разбор лога доступа: TOTAL/TOP-3/5XX, 110k строк |
| 09 | `09_gdb` | отладка ASAN-падения + письменный разбор в `answer.txt` |
- `grade.py` — прогон задач с автооценкой (`PASS/FAIL`, число проверок, время).
- `solutions/` — **эталонные решения, в руки не отдавать**: они лежат здесь, чтобы
автопроверка не «протухла» (эталон → все PASS, заглушка → FAIL).
## Как проходить
```bash
cd diag
python3 quiz.py # вопросы по одному; ответы — номер варианта 1-4
python3 quiz.py --export # вывести вопросы текстом (можно отвечать в чате)
python3 grade.py # все задачи (с ASAN/UBSAN/TSAN)
python3 grade.py 01 04 # только выбранные
python3 grade.py --fast # без санитайзеров, быстрее
python3 grade.py --submit # результаты + свои файлы в submissions/
```
Порядок: сначала квиз, потом задачи с 01 по 09. Зависание = FAIL (есть таймауты).
Правила честности: без встроенных `popcount`/`reverse`-хелперов, без копирования чужих
решений, `answer.txt` в задаче 09 — обязательная часть (оценивается ход разбора).
## Что считается результатом
- `results_quiz.json`: балл по каждому домену + разбор промахов с пояснениями.
- `results_tasks.json`: по задаче — PASS/FAIL, число проверок, время, полный лог сборки.
- Итог и решение по плану (с каких модулей стартовать) собираются по этим двум файлам.
## Публикация и доступ
- Пакет в каталоге: `/home/pi/eduplan/diag` (мир читает; каталог доступен на исполнение).
- Инбокс для ответов: `/home/pi/eduplan/diag/submissions/` (0777).
- Архив для скачивания: `/home/pi/eduplan/diag/dist/eduplan_diag.tar.gz`.
+158
View File
@@ -0,0 +1,158 @@
#!/usr/bin/env python3
"""Автопроверка практических задач диагностики.
Использование (из каталога diag):
python3 grade.py # проверить все задачи (с санитайзерами)
python3 grade.py 01 04 07 # только указанные
python3 grade.py --fast # без ASAN/UBSAN/TSAN (быстрее, слабее)
python3 grade.py --submit # после проверки скопировать ответы в submissions/
Что требуется от решающего: файл solution.cpp / solution.sh / crash.c + answer.txt,
каждый в своём каталоге tasks/<NN_имя>/.
Результат: results_tasks.json (по задаче: pass, сколько проверок, время, лог).
"""
import json
import os
import shutil
import subprocess
import sys
import time
HERE = os.path.dirname(os.path.abspath(__file__))
TASKS = os.path.join(HERE, "tasks")
SUB = os.path.join(HERE, "submissions")
CXX = os.environ.get("CXX", "g++")
SAN = "-fsanitize=address,undefined -fno-omit-frame-pointer"
FAST = False
# имя задачи: (тип, что запускать, таймаут, флаги компиляции)
SPEC = {
"01_bits": ("cpp", "test_bits.cpp", 180),
"02_list": ("cpp", "test_list.cpp", 180),
"03_ring": ("cpp", "test_ring.cpp", 180),
"04_ipv4": ("cpp", "test_ipv4.cpp", 180),
"05_lis": ("cpp", "test_lis.cpp", 300),
"06_threads": ("cpp", "test_threads.cpp", 600), # собирается с TSan, не с ASAN
"07_epoll": ("bin", "test_epoll.py", 300),
"08_bash": ("bash", "test_bash.py", 300),
"09_gdb": ("py", "test_gdb.py", 300),
}
def sh(cmd, cwd, timeout):
t0 = time.time()
try:
r = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True, timeout=timeout)
return r.returncode, r.stdout + r.stderr, time.time() - t0, False
except subprocess.TimeoutExpired as e:
out = (e.stdout or b"").decode(errors="replace") if isinstance(e.stdout, bytes) else (e.stdout or "")
return None, out + "\n[TIMEOUT] превышено время %s c" % timeout, time.time() - t0, True
def run_task(name, root):
d = os.path.join(root, name)
kind, test, timeout = SPEC[name]
log = []
rc = None
timed_out = False
if kind == "cpp":
flags = ["-std=c++17", "-O2", "-Wall", "-Wextra", "-g"]
extra = []
if name == "06_threads":
flags = ["-std=c++17", "-O1", "-Wall", "-Wextra", "-g", "-pthread"]
extra = [] if FAST else ["-fsanitize=thread"]
elif not FAST:
extra = SAN.split()
cmd = [CXX] + flags + (extra or []) + [test, "-o", "t_" + name]
rc, out, secs, timed_out = sh(cmd, d, timeout)
log.append("$ " + " ".join(cmd) + "\n" + out)
if rc != 0:
return False, log, secs, timed_out
rc, out, secs2, timed_out = sh(["./t_" + name], d, timeout)
log.append("$ ./t_" + name + "\n" + out)
secs += secs2
if "unsupported VMA" in out and not FAST:
# ThreadSanitizer не поддерживает этот размер адресного пространства
# (Raspberry Pi / ядро 6.18, 47-бит VMA) — откатываемся на ASAN.
cmd2 = [CXX, "-std=c++17", "-O1", "-Wall", "-Wextra", "-g", "-pthread"] \
+ SAN.split() + [test, "-o", "t_" + name]
rc, out2, s, to = sh(cmd2, d, timeout)
log.append("$ " + " ".join(cmd2) + "\n# TSan недоступен на этой платформе, откат на ASAN\n" + out2)
if rc == 0:
rc, out2, s2, to = sh(["./t_" + name], d, timeout)
log.append("$ ./t_" + name + "\n" + out2)
secs += s2
elif kind == "bin":
# epoll-сервер: solution.cpp -> бинарь solution, затем python-тест
cmd = [CXX, "-std=c++17", "-O2", "-Wall", "-Wextra", "-g",
"solution.cpp", "-o", "solution"]
rc, out, secs, timed_out = sh(cmd, d, timeout)
log.append("$ " + " ".join(cmd) + "\n" + out)
if rc != 0:
return False, log, secs, timed_out
rc, out, secs2, timed_out = sh([sys.executable, test], d, timeout)
log.append("$ python3 " + test + "\n" + out)
secs += secs2
else: # bash / py — тест сам всё делает
if name == "08_bash":
os.chmod(os.path.join(d, "solution.sh"), 0o755)
rc, out, secs, timed_out = sh([sys.executable, test], d, timeout)
log.append("$ python3 " + test + "\n" + out)
body = log[-1]
passed = (rc == 0) and ("FAIL" not in body) and ("ALL PASS" in body)
return passed, log, secs, timed_out
def main():
global FAST
args = [a for a in sys.argv[1:]]
FAST = "--fast" in args
do_submit = "--submit" in args
names = [a for a in args if a.isdigit() or a in SPEC]
if names:
names = [n for n in SPEC if n.split("_")[0] in names or n in names]
else:
names = list(SPEC)
results = {}
print(f"{'задача':12s} {'итог':7s} {'проверок':9s} {'FAIL':5s} {'время':8s}")
for n in names:
ok, log, secs, to = run_task(n, TASKS)
body = log[-1]
n_ok = body.count("ok ")
n_bad = body.count("FAIL ")
results[n] = {
"pass": ok, "checks_ok": n_ok, "checks_fail": n_bad,
"seconds": round(secs, 2), "timeout": to, "log": log,
}
print(f"{n:12s} {'PASS' if ok else 'FAIL':7s} {n_ok:<9d} {n_bad:<5d} {secs:8.2f}")
total = len(names)
done = sum(1 for n in names if results[n]["pass"])
with open(os.path.join(HERE, "results_tasks.json"), "w", encoding="utf-8") as f:
json.dump({"passed": done, "total": total, "tasks": results}, f,
ensure_ascii=False, indent=2)
print(f"\nИТОГ ЗАДАЧ: {done}/{total} PASS. Подробности: results_tasks.json")
if do_submit:
os.makedirs(SUB, exist_ok=True)
for fn in ("results_quiz.json", "results_tasks.json"):
p = os.path.join(HERE, fn)
if os.path.exists(p):
shutil.copy2(p, os.path.join(SUB, fn))
for n in SPEC:
d = os.path.join(TASKS, n)
for fn in ("solution.cpp", "solution.sh", "crash.c", "answer.txt"):
p = os.path.join(d, fn)
if os.path.exists(p):
shutil.copy2(p, os.path.join(SUB, f"{n}__{fn}"))
for fn in os.listdir(SUB):
os.chmod(os.path.join(SUB, fn), 0o644)
print(f"Ответы скопированы в {SUB}")
sys.exit(0 if done == total else 1)
if __name__ == "__main__":
main()
+193
View File
@@ -0,0 +1,193 @@
1. [C/C++ и системная память] Чему равен sizeof(struct S { char a; int b; char c; }) на 64-битной платформе без pack-атрибутов?
1) 6
2) 9
3) 12
4) 16
2. [C/C++ и системная память] Что означает undefined behavior (UB)?
1) Программа гарантированно завершится с ошибкой
2) Компилятор вправе сгенерировать любой код, программа не обязана иметь смысл
3) Поведение определено стандартом, но зависит от платформы
4) Это предупреждение компилятора, которое можно игнорировать
3. [C/C++ и системная память] std::move(x) — что он делает сам по себе?
1) Перемещает объект и обнуляет исходный
2) Вызывает конструктор перемещения
3) Приводит x к rvalue-ссылке, перемещение делает уже вызванный конструктор/присваивание
4) Освобождает память объекта
4. [C/C++ и системная память] Чем new отличается от malloc?
1) Только синтаксисом
2) malloc работает только со структурами
3) new не может вернуть nullptr
4) new вызывает конструктор, возвращает типизированный указатель и бросает исключение при неудаче
5. [C/C++ и системная память] Класс владеет ресурсом и имеет деструктор, освобождающий его. Копирующий конструктор/присваивание не объявлены. Что будет?
1) Всё нормально, компилятор сам сделает глубокую копию
2) Неявные копии дадут два владельца одного ресурса → double free/утечка
3) Программа не скомпилируется
4) Компилятор сгенерирует перемещение вместо копирования
6. [C/C++ и системная память] Для чего нужен виртуальный деструктор у базового класса?
1) Чтобы ускорить разрушение
2) Он не нужен, деструкторы всегда виртуальны
3) Чтобы поля базы не копировались
4) Чтобы при delete по указателю на базу вызвался деструктор производного класса
7. [C/C++ и системная память] Что делает ключевое слово const после сигнатуры метода (int size() const)?
1) Делает метод виртуальным
2) Запрещает копирование возвращаемого значения
3) Обязывает не менять поля объекта и позволяет вызывать метод на const-объекте
4) Ничего, это оформление
8. [C/C++ и системная память] Определён ли стандартом порядок вычисления аргументов функции (f(a(), b()))?
1) Слева направо
2) Справа налево
3) Не определён (порядок не специфицирован, но вычисления не пересекаются)
4) Зависит только от оптимизаций
9. [Алгоритмы и структуры данных] Средняя сложность поиска в хеш-таблице (при разумной нагрузке)?
1) O(n log n)
2) O(log n)
3) O(n)
4) O(1)
10. [Алгоритмы и структуры данных] Какая структура лучше подходит для задачи «поиск слов по префиксу»?
1) Префиксное дерево (trie)
2) Хеш-таблица
3) Двоичная куча
4) Связный список
11. [Алгоритмы и структуры данных] Сколько сравнений нужно бинарному поиску в худшем случае в отсортированном массиве из 1 000 000 элементов?
1) 20
2) 10
3) 100
4) 1 000
12. [Алгоритмы и структуры данных] При каком условии применим алгоритм Дейкстры?
1) Граф обязательно связный
2) Веса рёбер неотрицательны
3) Граф неориентированный
4) Граф разреженный
13. [Алгоритмы и структуры данных] Какова сложность построения кучи (heapify) из произвольного массива?
1) O(n)
2) O(log n)
3) O(1)
4) O(n log n)
14. [Алгоритмы и структуры данных] Какая из сортировок устойчивая (stable)?
1) Быстрая (quicksort)
2) Пирамидальная (heapsort)
3) Слиянием (merge sort)
4) Выбором (selection sort)
15. [Алгоритмы и структуры данных] Для какого графа возможна топологическая сортировка?
1) Для ориентированного ациклического (DAG)
2) Для любого связного
3) Для двудольного
4) Для полного
16. [Алгоритмы и структуры данных] Как найти цикл в связном списке за O(1) дополнительной памяти?
1) Алгоритм Флойда: медленный и быстрый указатели
2) Хранить посещённые узлы в хеш-таблице
3) Отсортировать список
4) Рекурсивно пройти до nullptr
17. [Linux / ОС] Что делает fork()?
1) Заменяет образ процесса
2) Создаёт поток в том же адресном пространстве
3) Создаёт копию процесса; в родителе возвращает PID ребёнка, в ребёнке — 0
4) Загружает библиотеку в процесс
18. [Linux / ОС] Чем поток отличается от процесса в Linux?
1) Ничем, это синонимы
2) Потоки делят адресное пространство и файловые дескрипторы, но имеют свои стеки
3) Поток всегда быстрее
4) Процессы не могут работать параллельно
19. [Linux / ОС] Что такое процесс-зомби?
1) Процесс, съедающий CPU
2) Завершившийся процесс, чей статус ещё не собран родителем через wait()
3) Процесс без терминала
4) Процесс, потерявший родителя
20. [Linux / ОС] Какой механизм в Linux штатно обслуживает десятки тысяч сетевых соединений в одном потоке?
1) select()
2) Неблокирующие сокеты в цикле опроса
3) epoll
4) fork на каждое соединение
21. [Linux / ОС] Что происходит со страницами памяти при fork()?
1) Сразу полностью копируются
2) Память обнуляется
3) Копируются при первой записи (copy-on-write)
4) Ребёнок получает те же физические страницы без защиты
22. [Linux / ОС] Процесс завершился с кодом 137. Что это значит?
1) Ошибка сегментации
2) Процесс убит сигналом 9 (SIGKILL) — типично OOM-killer или docker kill
3) Не найден файл
4) Процесс вышел штатно
23. [Linux / ОС] Что даёт вызов mmap() на файл?
1) Полное чтение файла в кучу
2) Отображение файла в адресное пространство: работа как с массивом в памяти
3) Блокировку файла
4) Копирование файла
24. [Linux / ОС] Зачем нужен waitpid() родителю?
1) Чтобы приостановить ребёнка
2) Чтобы сменить приоритет ребёнка
3) Чтобы получить доступ к памяти ребёнка
4) Чтобы собрать статус завершения и убрать зомби
25. [Сети L2/L3, TCP/IP] Сколько байт в Ethernet-заголовке кадра без VLAN-тега?
1) 8
2) 14
3) 18
4) 20
26. [Сети L2/L3, TCP/IP] На каком уровне OSI работает обычный коммутатор (switch)?
1) L2
2) L4
3) L3
4) L1
27. [Сети L2/L3, TCP/IP] Что делает протокол ARP?
1) Раздаёт IP-адреса
2) Проверяет целостность пакетов
3) Разрешает доменные имена
4) Сопоставляет IP-адрес и MAC-адрес в локальном сегменте
28. [Сети L2/L3, TCP/IP] Какое поле IPv4-заголовка уменьшается на единицу на каждом маршрутизаторе?
1) Total Length
2) IHL
3) Checksum
4) TTL
29. [Сети L2/L3, TCP/IP] Правильная последовательность установки TCP-соединения?
1) SYN → SYN-ACK → ACK
2) SYN → ACK → SYN-ACK
3) SYN-ACK → SYN → ACK
4) ACK → SYN → SYN-ACK
30. [Сети L2/L3, TCP/IP] Ключевое отличие TCP от UDP?
1) TCP быстрее
2) TCP работает только на L2
3) UDP не поддерживает порты
4) TCP даёт надёжный поток байт с подтверждениями, порядком и повторной передачей
31. [Сети L2/L3, TCP/IP] Что делает VLAN-тег 802.1Q?
1) Добавляет в кадр 4 байта с 12-битным идентификатором VLAN
2) Шифрует кадр
3) Увеличивает MTU
4) Превращает кадр в IP-пакет
32. [Сети L2/L3, TCP/IP] Сколько адресов узлов доступно в подсети /26?
1) 32
2) 126
3) 64
4) 62
Формат ответа: "1-2 2-4 3-1 ..." (номер вопроса - номер варианта)
+398
View File
@@ -0,0 +1,398 @@
{
"meta": {
"title": "Диагностика уровня: C/C++, алгоритмы, Linux, сети",
"pass_hint": "24+ из 32 верных — уверенный вход; 16–23 — база есть, нужен добор; <16 — модуль в план с нуля",
"domains": {
"c_cpp": "C/C++ и системная память",
"algo": "Алгоритмы и структуры данных",
"linux": "Linux / ОС",
"net": "Сети L2/L3, TCP/IP"
}
},
"questions": [
{
"d": "c_cpp",
"q": "Чему равен sizeof(struct S { char a; int b; char c; }) на 64-битной платформе без pack-атрибутов?",
"o": [
"6",
"9",
"12",
"16"
],
"a": 2,
"e": "a = 1 байт + 3 паддинга, b = 4, c = 1 + 3 хвостового паддинга = 12 (выравнивание структуры по 4)."
},
{
"d": "c_cpp",
"q": "Что означает undefined behavior (UB)?",
"o": [
"Программа гарантированно завершится с ошибкой",
"Компилятор вправе сгенерировать любой код, программа не обязана иметь смысл",
"Поведение определено стандартом, но зависит от платформы",
"Это предупреждение компилятора, которое можно игнорировать"
],
"a": 1,
"e": "UB — стандарт не накладывает требований: компилятор может что угодно, вплоть до выкидывания проверок."
},
{
"d": "c_cpp",
"q": "std::move(x) — что он делает сам по себе?",
"o": [
"Перемещает объект и обнуляет исходный",
"Вызывает конструктор перемещения",
"Приводит x к rvalue-ссылке, перемещение делает уже вызванный конструктор/присваивание",
"Освобождает память объекта"
],
"a": 2,
"e": "move — это static_cast к rvalue; фактическое перемещение выполняет конструктор/оператор присваивания."
},
{
"d": "c_cpp",
"q": "Чем new отличается от malloc?",
"o": [
"Только синтаксисом",
"malloc работает только со структурами",
"new не может вернуть nullptr",
"new вызывает конструктор, возвращает типизированный указатель и бросает исключение при неудаче"
],
"a": 3,
"e": "new = выделение + конструктор + типизированный указатель + исключение; malloc возвращает void* и nullptr при неудаче."
},
{
"d": "c_cpp",
"q": "Класс владеет ресурсом и имеет деструктор, освобождающий его. Копирующий конструктор/присваивание не объявлены. Что будет?",
"o": [
"Всё нормально, компилятор сам сделает глубокую копию",
"Неявные копии дадут два владельца одного ресурса → double free/утечка",
"Программа не скомпилируется",
"Компилятор сгенерирует перемещение вместо копирования"
],
"a": 1,
"e": "Правило трёх/пяти: владеющий ресурсом класс должен определить копирование/перемещение или запретить их."
},
{
"d": "c_cpp",
"q": "Для чего нужен виртуальный деструктор у базового класса?",
"o": [
"Чтобы ускорить разрушение",
"Он не нужен, деструкторы всегда виртуальны",
"Чтобы поля базы не копировались",
"Чтобы при delete по указателю на базу вызвался деструктор производного класса"
],
"a": 3,
"e": "Без virtual-деструктора delete через Base* — UB, деструктор производного не вызовется."
},
{
"d": "c_cpp",
"q": "Что делает ключевое слово const после сигнатуры метода (int size() const)?",
"o": [
"Делает метод виртуальным",
"Запрещает копирование возвращаемого значения",
"Обязывает не менять поля объекта и позволяет вызывать метод на const-объекте",
"Ничего, это оформление"
],
"a": 2,
"e": "const-метод получает this как const-указатель; на изменении поля — ошибка компиляции."
},
{
"d": "c_cpp",
"q": "Определён ли стандартом порядок вычисления аргументов функции (f(a(), b()))?",
"o": [
"Слева направо",
"Справа налево",
"Не определён (порядок не специфицирован, но вычисления не пересекаются)",
"Зависит только от оптимизаций"
],
"a": 2,
"e": "Порядок аргументов не специфицирован; с C++17 вычисления не чередуются (indeterminately sequenced), но порядок всё равно не задан."
},
{
"d": "algo",
"q": "Средняя сложность поиска в хеш-таблице (при разумной нагрузке)?",
"o": [
"O(n log n)",
"O(log n)",
"O(n)",
"O(1)"
],
"a": 3,
"e": "Амортизированно O(1) в среднем; худший случай при всех коллизиях — O(n)."
},
{
"d": "algo",
"q": "Какая структура лучше подходит для задачи «поиск слов по префиксу»?",
"o": [
"Префиксное дерево (trie)",
"Хеш-таблица",
"Двоичная куча",
"Связный список"
],
"a": 0,
"e": "Trie даёт проход по префиксу за O(len) и общий префикс хранит один раз."
},
{
"d": "algo",
"q": "Сколько сравнений нужно бинарному поиску в худшем случае в отсортированном массиве из 1 000 000 элементов?",
"o": [
"20",
"10",
"100",
"1 000"
],
"a": 0,
"e": "log2(1e6) ≈ 19.9 → 20 итераций."
},
{
"d": "algo",
"q": "При каком условии применим алгоритм Дейкстры?",
"o": [
"Граф обязательно связный",
"Веса рёбер неотрицательны",
"Граф неориентированный",
"Граф разреженный"
],
"a": 1,
"e": "Отрицательные рёбра ломают инвариант «кратчайший путь найден»; тогда Беллман–Форд."
},
{
"d": "algo",
"q": "Какова сложность построения кучи (heapify) из произвольного массива?",
"o": [
"O(n)",
"O(log n)",
"O(1)",
"O(n log n)"
],
"a": 0,
"e": "Просеивание снизу вверх даёт сумму геометрической прогрессии — O(n), хотя каждый push по отдельности O(log n)."
},
{
"d": "algo",
"q": "Какая из сортировок устойчивая (stable)?",
"o": [
"Быстрая (quicksort)",
"Пирамидальная (heapsort)",
"Слиянием (merge sort)",
"Выбором (selection sort)"
],
"a": 2,
"e": "Merge sort сохраняет порядок равных элементов; quicksort/heapsort/selection — нет."
},
{
"d": "algo",
"q": "Для какого графа возможна топологическая сортировка?",
"o": [
"Для ориентированного ациклического (DAG)",
"Для любого связного",
"Для двудольного",
"Для полного"
],
"a": 0,
"e": "Цикл делает линейный порядок невозможным; топосорт — признак DAG."
},
{
"d": "algo",
"q": "Как найти цикл в связном списке за O(1) дополнительной памяти?",
"o": [
"Алгоритм Флойда: медленный и быстрый указатели",
"Хранить посещённые узлы в хеш-таблице",
"Отсортировать список",
"Рекурсивно пройти до nullptr"
],
"a": 0,
"e": "«Черепаха и заяц»: если цикл есть, указатели встретятся."
},
{
"d": "linux",
"q": "Что делает fork()?",
"o": [
"Заменяет образ процесса",
"Создаёт поток в том же адресном пространстве",
"Создаёт копию процесса; в родителе возвращает PID ребёнка, в ребёнке — 0",
"Загружает библиотеку в процесс"
],
"a": 2,
"e": "fork = новый процесс (свой PID, COW-копия памяти); execve меняет образ, clone — поток."
},
{
"d": "linux",
"q": "Чем поток отличается от процесса в Linux?",
"o": [
"Ничем, это синонимы",
"Потоки делят адресное пространство и файловые дескрипторы, но имеют свои стеки",
"Поток всегда быстрее",
"Процессы не могут работать параллельно"
],
"a": 1,
"e": "clone с CLONE_VM/CLONE_FILES — потоки; у каждого свой стек и регистры."
},
{
"d": "linux",
"q": "Что такое процесс-зомби?",
"o": [
"Процесс, съедающий CPU",
"Завершившийся процесс, чей статус ещё не собран родителем через wait()",
"Процесс без терминала",
"Процесс, потерявший родителя"
],
"a": 1,
"e": "Запись остаётся в таблице процессов до wait/waitpid; «потерявший родителя» — это сирота (переходит к init)."
},
{
"d": "linux",
"q": "Какой механизм в Linux штатно обслуживает десятки тысяч сетевых соединений в одном потоке?",
"o": [
"select()",
"Неблокирующие сокеты в цикле опроса",
"epoll",
"fork на каждое соединение"
],
"a": 2,
"e": "epoll — O(1) на готовое событие; select линейно по всем дескрипторам."
},
{
"d": "linux",
"q": "Что происходит со страницами памяти при fork()?",
"o": [
"Сразу полностью копируются",
"Память обнуляется",
"Копируются при первой записи (copy-on-write)",
"Ребёнок получает те же физические страницы без защиты"
],
"a": 2,
"e": "COW: страницы помечаются read-only и копируются по факту записи."
},
{
"d": "linux",
"q": "Процесс завершился с кодом 137. Что это значит?",
"o": [
"Ошибка сегментации",
"Процесс убит сигналом 9 (SIGKILL) — типично OOM-killer или docker kill",
"Не найден файл",
"Процесс вышел штатно"
],
"a": 1,
"e": "137 = 128 + 9: завершение по SIGKILL; код 139 = 128 + 11 (SIGSEGV)."
},
{
"d": "linux",
"q": "Что даёт вызов mmap() на файл?",
"o": [
"Полное чтение файла в кучу",
"Отображение файла в адресное пространство: работа как с массивом в памяти",
"Блокировку файла",
"Копирование файла"
],
"a": 1,
"e": "Страницы подгружаются по обращению; запись уходит в файл (или в MAP_SHARED-память)."
},
{
"d": "linux",
"q": "Зачем нужен waitpid() родителю?",
"o": [
"Чтобы приостановить ребёнка",
"Чтобы сменить приоритет ребёнка",
"Чтобы получить доступ к памяти ребёнка",
"Чтобы собрать статус завершения и убрать зомби"
],
"a": 3,
"e": "Без wait завершившиеся дети остаются зомби и держат запись в таблице процессов."
},
{
"d": "net",
"q": "Сколько байт в Ethernet-заголовке кадра без VLAN-тега?",
"o": [
"8",
"14",
"18",
"20"
],
"a": 1,
"e": "dst MAC (6) + src MAC (6) + EtherType (2) = 14; VLAN 802.1Q добавляет 4 байта."
},
{
"d": "net",
"q": "На каком уровне OSI работает обычный коммутатор (switch)?",
"o": [
"L2",
"L4",
"L3",
"L1"
],
"a": 0,
"e": "Коммутация по MAC-адресам и таблице обучения — канальный уровень (L2); маршрутизатор — L3."
},
{
"d": "net",
"q": "Что делает протокол ARP?",
"o": [
"Раздаёт IP-адреса",
"Проверяет целостность пакетов",
"Разрешает доменные имена",
"Сопоставляет IP-адрес и MAC-адрес в локальном сегменте"
],
"a": 3,
"e": "ARP-запрос broadcast, ответ unicast; DHCP раздаёт адреса, DNS — имена."
},
{
"d": "net",
"q": "Какое поле IPv4-заголовка уменьшается на единицу на каждом маршрутизаторе?",
"o": [
"Total Length",
"IHL",
"Checksum",
"TTL"
],
"a": 3,
"e": "TTL защищает от петель; при 0 пакет отбрасывается и уходит ICMP Time Exceeded. Контрольная сумма при этом пересчитывается, но не «уменьшается на 1»."
},
{
"d": "net",
"q": "Правильная последовательность установки TCP-соединения?",
"o": [
"SYN → SYN-ACK → ACK",
"SYN → ACK → SYN-ACK",
"SYN-ACK → SYN → ACK",
"ACK → SYN → SYN-ACK"
],
"a": 0,
"e": "Клиент SYN, сервер SYN-ACK, клиент ACK; номера подтверждения = seq+1."
},
{
"d": "net",
"q": "Ключевое отличие TCP от UDP?",
"o": [
"TCP быстрее",
"TCP работает только на L2",
"UDP не поддерживает порты",
"TCP даёт надёжный поток байт с подтверждениями, порядком и повторной передачей"
],
"a": 3,
"e": "UDP — дейтаграммы «как есть», без гарантий и порядка."
},
{
"d": "net",
"q": "Что делает VLAN-тег 802.1Q?",
"o": [
"Добавляет в кадр 4 байта с 12-битным идентификатором VLAN",
"Шифрует кадр",
"Увеличивает MTU",
"Превращает кадр в IP-пакет"
],
"a": 0,
"e": "Тег EtherType 0x8100 + PCP/DEI + 12 бит VID; логически разделяет L2-сегменты."
},
{
"d": "net",
"q": "Сколько адресов узлов доступно в подсети /26?",
"o": [
"32",
"126",
"64",
"62"
],
"a": 3,
"e": "/26 = 64 адреса, минус адрес сети и broadcast → 62 хоста."
}
]
}
+125
View File
@@ -0,0 +1,125 @@
#!/usr/bin/env python3
"""Диагностический квиз: C/C++, алгоритмы, Linux, сети.
Режимы:
python3 quiz.py — пройти интерактивно (по одной строке на ответ: номер варианта 1-4)
python3 quiz.py --export — вывести вопросы текстом (можно отвечать в чате в формате "1-2 2-4 ...")
python3 quiz.py --answers "1-2 2-4 3-1" — проверить ответы, переданные строкой
Результат пишется в results_quiz.json (там же разбор по доменам).
"""
import json
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
QUIZ = os.path.join(HERE, "quiz.json")
def load():
with open(QUIZ, encoding="utf-8") as f:
return json.load(f)
def export(data):
for i, q in enumerate(data["questions"], 1):
print(f"{i}. [{data['meta']['domains'][q['d']]}] {q['q']}")
for j, o in enumerate(q["o"], 1):
print(f" {j}) {o}")
print()
print('Формат ответа: "1-2 2-4 3-1 ..." (номер вопроса - номер варианта)')
def parse_answers(s):
res = {}
for tok in s.replace(",", " ").split():
if "-" not in tok:
continue
k, v = tok.split("-", 1)
try:
res[int(k)] = int(v)
except ValueError:
continue
return res
def grade(data, answers):
per_domain = {}
details = []
for i, q in enumerate(data["questions"], 1):
d = q["d"]
per_domain.setdefault(d, {"total": 0, "correct": 0})
per_domain[d]["total"] += 1
got = answers.get(i)
ok = got == q["a"] + 1
if ok:
per_domain[d]["correct"] += 1
details.append({
"n": i, "domain": d, "question": q["q"],
"your_answer": got, "correct_answer": q["a"] + 1,
"ok": ok, "explain": q["e"],
})
return per_domain, details
def report(data, per_domain, details):
total = len(details)
correct = sum(1 for x in details if x["ok"])
print(f"\n=== ИТОГ КВИЗА: {correct}/{total} ===")
for d, st in per_domain.items():
pct = round(100 * st["correct"] / st["total"])
if pct >= 80:
verdict = "крепко"
elif pct >= 60:
verdict = "база есть, нужен добор"
else:
verdict = "дыра, идёт в план с нуля"
print(f" {data['meta']['domains'][d]:38s} {st['correct']}/{st['total']} ({pct}%) — {verdict}")
wrong = [x for x in details if not x["ok"]]
if wrong:
print("\nРазбор промахов:")
for x in wrong:
print(f" {x['n']}. {x['question']}")
print(f" ты: {x['your_answer'] or '—'} | верно: {x['correct_answer']}. {x['explain']}")
return correct, total
def main():
data = load()
args = sys.argv[1:]
if "--export" in args:
export(data)
return
if "--answers" in args:
s = args[args.index("--answers") + 1]
answers = parse_answers(s)
else:
answers = {}
for i, q in enumerate(data["questions"], 1):
print(f"\n{i}. [{data['meta']['domains'][q['d']]}] {q['q']}")
for j, o in enumerate(q["o"], 1):
print(f" {j}) {o}")
while True:
raw = input("ответ (1-4, пусто = пропустить, q = выход): ").strip().lower()
if raw == "q":
break
if raw == "":
break
if raw in {"1", "2", "3", "4"}:
answers[i] = int(raw)
break
if raw == "q":
break
per_domain, details = grade(data, answers)
correct, total = report(data, per_domain, details)
out = {
"correct": correct, "total": total,
"per_domain": per_domain,
"details": details,
}
with open(os.path.join(HERE, "results_quiz.json"), "w", encoding="utf-8") as f:
json.dump(out, f, ensure_ascii=False, indent=2)
print(f"\nРезультат сохранён: {os.path.join(HERE, 'results_quiz.json')}")
if __name__ == "__main__":
main()
+27
View File
@@ -0,0 +1,27 @@
// Задача 01 — битовые операции. Заполни реализации.
#include <cstdint>
#include <cstddef>
// число единичных бит в 32-битном слове
int popcount32(uint32_t x) {
(void)x;
return 0; // TODO
}
// развернуть порядок следования всех 32 бит
uint32_t reverse_bits32(uint32_t x) {
(void)x;
return 0; // TODO
}
// true, если x — степень двойки; 0 — не степень двойки
bool is_power_of_two(uint32_t x) {
(void)x;
return false; // TODO
}
// поменять местами байты: 0x11223344 -> 0x44332211
uint32_t bswap32(uint32_t x) {
(void)x;
return 0; // TODO
}
+18
View File
@@ -0,0 +1,18 @@
# Задача 01 — битовые операции (C)
Реализуй в `solution.cpp` четыре функции. Запрещено пользоваться встроенными
`__builtin_popcount`, `std::popcount`, `std::byteswap` — считается любая корректная
ручная реализация.
```c
int popcount32(uint32_t x); // число единичных бит
uint32_t reverse_bits32(uint32_t x); // развернуть порядок всех 32 бит
bool is_power_of_two(uint32_t x); // x — степень двойки (0 не считается)
uint32_t bswap32(uint32_t x); // поменять порядок байт местами
```
Проверка: `python3 grade.py 01` из каталога `diag/`.
Критерий: все проверки `ok`, сборка без предупреждений, ASAN/UBSAN чистые.
Подсказки по разбору после сдачи: `x & (x - 1)`; реверс — классический цикл на 32 итерации
или пара «маска+сдвиг»; `bswap` — четыре маски.
+41
View File
@@ -0,0 +1,41 @@
#include <cstdio>
#include <cstdint>
#include "solution.cpp"
static int failures = 0;
#define CHECK(cond, name) do { if (cond) std::printf("ok %s\n", name); \
else { std::printf("FAIL %s (line %d)\n", name, __LINE__); ++failures; } } while (0)
int main() {
// popcount
CHECK(popcount32(0u) == 0, "popcount(0) == 0");
CHECK(popcount32(1u) == 1, "popcount(1) == 1");
CHECK(popcount32(0xFFFFFFFFu) == 32, "popcount(0xFFFFFFFF) == 32");
CHECK(popcount32(0x80000000u) == 1, "popcount(1<<31) == 1");
CHECK(popcount32(0x0000000Bu) == 3, "popcount(0b1011) == 3");
CHECK(popcount32(0xA5A5A5A5u) == 16, "popcount(0xA5A5A5A5) == 16");
// reverse
CHECK(reverse_bits32(1u) == 0x80000000u, "reverse(1) == 0x80000000");
CHECK(reverse_bits32(0x80000000u) == 1u, "reverse(0x80000000) == 1");
CHECK(reverse_bits32(0x12345678u) == 0x1E6A2C48u, "reverse(0x12345678) == 0x1E6A2C48");
CHECK(reverse_bits32(0xFFFFFFFFu) == 0xFFFFFFFFu, "reverse(0xFFFFFFFF) == 0xFFFFFFFF");
CHECK(reverse_bits32(0u) == 0u, "reverse(0) == 0");
// power of two
CHECK(!is_power_of_two(0u), "pow2(0) == false");
CHECK(is_power_of_two(1u), "pow2(1) == true");
CHECK(is_power_of_two(2u), "pow2(2) == true");
CHECK(!is_power_of_two(3u), "pow2(3) == false");
CHECK(is_power_of_two(1024u), "pow2(1024) == true");
CHECK(is_power_of_two(0x80000000u), "pow2(1<<31) == true");
CHECK(!is_power_of_two(0x80000001u), "pow2(0x80000001) == false");
// bswap
CHECK(bswap32(0x11223344u) == 0x44332211u, "bswap(0x11223344) == 0x44332211");
CHECK(bswap32(0x000000FFu) == 0xFF000000u, "bswap(0x000000FF) == 0xFF000000");
CHECK(bswap32(0xDEADBEEFu) == 0xEFBEADDEu, "bswap(0xDEADBEEF) == 0xEFBEADDE");
std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures);
return failures ? 1 : 0;
}
+25
View File
@@ -0,0 +1,25 @@
// Задача 02 — связный список. Заполни реализации.
#include <cstddef>
struct Node {
int value;
Node* next;
};
// Развернуть список на месте. Вернуть новую голову.
Node* reverse_list(Node* head) {
(void)head;
return nullptr; // TODO
}
// Середина списка: для чётного количества — второй из двух средних узлов.
Node* find_middle(Node* head) {
(void)head;
return nullptr; // TODO
}
// Есть ли цикл в списке.
bool has_cycle(Node* head) {
(void)head;
return false; // TODO
}
+17
View File
@@ -0,0 +1,17 @@
# Задача 02 — связный список (C++)
Даны функции над односвязным списком `Node{int value; Node* next;}`:
```c
Node* reverse_list(Node* head); // развернуть список на месте, вернуть новую голову
Node* find_middle(Node* head); // середина; для чётной длины — второй из двух средних
bool has_cycle(Node* head); // есть ли цикл
```
Требования:
- никаких аллокаций, O(1) дополнительной памяти, один проход там, где это возможно;
- `find_middle` и `has_cycle` — через два указателя (медленный/быстрый);
- корректная работа с пустым списком и списком из одного узла.
Проверка: `python3 grade.py 02`. Критерий: все `ok`, ASAN/UBSAN чистые (утечки в тесте
считаются ошибкой — тест сам освобождает память).
+94
View File
@@ -0,0 +1,94 @@
#include <cstdio>
#include <vector>
#include "solution.cpp"
static int failures = 0;
#define CHECK(cond, name) do { if (cond) std::printf("ok %s\n", name); \
else { std::printf("FAIL %s (line %d)\n", name, __LINE__); ++failures; } } while (0)
static Node* build(const std::vector<int>& v) {
Node* head = nullptr; Node** tail = &head;
for (int x : v) { *tail = new Node{x, nullptr}; tail = &(*tail)->next; }
return head;
}
static std::vector<int> to_vec(Node* h, size_t limit = 1000) {
std::vector<int> r; while (h && r.size() < limit) { r.push_back(h->value); h = h->next; } return r;
}
static void destroy(Node* h) { while (h) { Node* n = h->next; delete h; h = n; } }
int main() {
// reverse
{
Node* h = build({1, 2, 3, 4, 5});
Node* r = reverse_list(h);
CHECK(to_vec(r) == std::vector<int>({5, 4, 3, 2, 1}), "reverse [1..5] -> [5..1]");
destroy(r);
}
{
Node* h = build({42});
Node* r = reverse_list(h);
CHECK(r && r->value == 42 && r->next == nullptr, "reverse одного узла");
destroy(r);
}
CHECK(reverse_list(nullptr) == nullptr, "reverse пустого списка == nullptr");
{
Node* h = build({7, 8});
Node* r = reverse_list(h);
CHECK(to_vec(r) == std::vector<int>({8, 7}), "reverse двух узлов");
destroy(r);
}
// middle
{
Node* h = build({1, 2, 3, 4, 5, 6});
Node* m = find_middle(h);
CHECK(m && m->value == 4, "middle чётного [1..6] == 4 (второй из двух средних)");
destroy(h);
}
{
Node* h = build({1, 2, 3, 4, 5});
Node* m = find_middle(h);
CHECK(m && m->value == 3, "middle нечётного [1..5] == 3");
destroy(h);
}
{
Node* h = build({9});
CHECK(find_middle(h) && find_middle(h)->value == 9, "middle одного узла == 9");
destroy(h);
}
CHECK(find_middle(nullptr) == nullptr, "middle пустого списка == nullptr");
// cycle
{
Node* h = build({1, 2, 3, 4});
CHECK(!has_cycle(h), "has_cycle(без цикла) == false");
destroy(h);
}
{
Node* h = build({1, 2, 3, 4, 5});
Node* third = h->next->next;
Node* last = h; while (last->next) last = last->next;
last->next = third; // цикл 3->4->5->3
CHECK(has_cycle(h), "has_cycle(цикл внутри) == true");
last->next = nullptr;
destroy(h);
}
{
Node* h = build({1, 2});
Node* last = h->next; last->next = h; // цикл на всю длину
CHECK(has_cycle(h), "has_cycle(цикл на всю длину) == true");
last->next = nullptr;
destroy(h);
}
{
Node* one = new Node{5, nullptr};
one->next = one;
CHECK(has_cycle(one), "has_cycle(сам на себя) == true");
one->next = nullptr;
delete one;
}
CHECK(!has_cycle(nullptr), "has_cycle(nullptr) == false");
std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures);
return failures ? 1 : 0;
}
+29
View File
@@ -0,0 +1,29 @@
// Задача 03 — кольцевой буфер. Заполни реализации.
#include <cstddef>
class RingBuffer {
public:
explicit RingBuffer(size_t capacity) : cap_(capacity), data_(new int[capacity ? capacity : 1]), size_(0) {}
~RingBuffer() { delete[] data_; }
bool push(int v) {
(void)v;
return false; // TODO
}
bool pop(int& out) {
(void)out;
return false; // TODO
}
size_t size() const { return size_; }
size_t capacity() const { return cap_; }
bool empty() const { return size_ == 0; }
bool full() const { return size_ == cap_; }
private:
size_t cap_;
int* data_;
size_t size_;
// добавь head_/tail_ по необходимости
};
+25
View File
@@ -0,0 +1,25 @@
# Задача 03 — кольцевой буфер (C++)
Кольцевой буфер фиксированной ёмкости — базовая структура для embedded и сетевого кода.
```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, никаких сдвигов элементов;
- память выделяется один раз в конструкторе, освобождается в деструкторе;
- поведение после заворачивания индексов (wrap-around) корректное;
- копирование запрещать не обязательно, но двойного освобождения быть не должно.
Проверка: `python3 grade.py 03`. Критерий: все `ok`, ASAN/UBSAN чистые.
+46
View File
@@ -0,0 +1,46 @@
#include <cstdio>
#include <vector>
#include "solution.cpp"
static int failures = 0;
#define CHECK(cond, name) do { if (cond) std::printf("ok %s\n", name); \
else { std::printf("FAIL %s (line %d)\n", name, __LINE__); ++failures; } } while (0)
int main() {
{
RingBuffer rb(4);
CHECK(rb.capacity() == 4 && rb.size() == 0 && rb.empty() && !rb.full(), "старт: пуст, ёмкость 4");
CHECK(rb.push(1) && rb.push(2) && rb.push(3) && rb.push(4), "4 push прошли");
CHECK(rb.full() && rb.size() == 4, "после 4 push буфер полон");
CHECK(!rb.push(5), "push в полный буфер -> false");
int v = -1;
CHECK(rb.pop(v) && v == 1, "FIFO: первый pop == 1");
CHECK(rb.pop(v) && v == 2, "FIFO: второй pop == 2");
CHECK(rb.size() == 2 && !rb.full(), "после двух pop размер 2");
CHECK(rb.push(5) && rb.push(6), "push после pop (заворачивание)");
int expect[] = {3, 4, 5, 6};
bool ok = true;
for (int i = 0; i < 4; ++i) { int x = -1; ok = ok && rb.pop(x) && x == expect[i]; }
CHECK(ok, "wrap-around: порядок 3,4,5,6");
CHECK(rb.empty() && !rb.pop(v), "пустой буфер: pop -> false");
}
{
RingBuffer rb(1);
int v = 0;
CHECK(rb.push(11) && !rb.push(12), "ёмкость 1: второй push отклонён");
CHECK(rb.pop(v) && v == 11, "ёмкость 1: pop вернул 11");
CHECK(rb.pop(v) == false, "ёмкость 1: второй pop -> false");
}
{
RingBuffer rb(8);
int cnt = 0;
for (int i = 0; i < 1000; ++i) {
if (rb.push(i)) cnt++;
if (i % 3 == 0) { int x = -1; if (rb.pop(x)) cnt--; }
}
CHECK(cnt == (int)rb.size(), "длинный прогон: счётчик push/pop сходится с size()");
CHECK(rb.size() <= 8, "длинный прогон: размер не превышает ёмкость");
}
std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures);
return failures ? 1 : 0;
}
+28
View File
@@ -0,0 +1,28 @@
// Задача 04 — разбор IPv4-пакета. Заполни реализации.
#include <cstdint>
#include <cstddef>
struct Ipv4Header {
uint8_t version;
uint8_t ihl;
uint8_t protocol;
uint16_t total_length;
uint32_t src_ip;
uint32_t dst_ip;
uint16_t header_checksum;
};
bool parse_ipv4(const uint8_t* buf, size_t len, Ipv4Header* out) {
(void)buf; (void)len; (void)out;
return false; // TODO
}
uint16_t compute_checksum(const uint8_t* buf, size_t len) {
(void)buf; (void)len;
return 0; // TODO
}
bool checksum_valid(const uint8_t* buf, size_t len) {
(void)buf; (void)len;
return false; // TODO
}
+32
View File
@@ -0,0 +1,32 @@
# Задача 04 — разбор IPv4-пакета и контрольная сумма (C++)
Это то, что реально делают сетевые железки: взять буфер из сокета и корректно разобрать
заголовок, не выйдя за границы и не поверив «на слово» полям пакета.
```c++
struct Ipv4Header {
uint8_t version; // должно быть 4
uint8_t ihl; // длина заголовка в 32-битных словах
uint8_t protocol; // 6 = TCP, 17 = UDP, 1 = ICMP
uint16_t total_length; // из заголовка (хост-порядок)
uint32_t src_ip; // байты как на проводе: 10.0.0.1 -> 0x0A000001
uint32_t dst_ip;
uint16_t header_checksum; // поле из заголовка (хост-порядок)
};
// false: буфер короче 20 байт, version != 4, ihl < 5, ihl*4 > len,
// total_length < ihl*4 или total_length > len
bool parse_ipv4(const uint8_t* buf, size_t len, Ipv4Header* out);
// сумма в дополнительном коде по len байтам, затем инверсия — значение для записи в поле
// (вызывается на буфере, где поле контрольной суммы уже обнулено)
uint16_t compute_checksum(const uint8_t* buf, size_t len);
// true, если контрольная сумма заголовка верна: сумма в дополнительном коде по ihl*4 байтам
// заголовка (вместе с полем контрольной суммы) равна 0xFFFF; нагрузка не участвует
bool checksum_valid(const uint8_t* buf, size_t len);
```
Проверка: `python3 grade.py 04`. Критерий: все `ok`, ASAN/UBSAN чистые.
Запрещено приводить буфер к структуре через `reinterpret_cast` и читать поля как есть —
на невыровненных адресах и в другом порядке байт это UB.
+100
View File
@@ -0,0 +1,100 @@
#include <cstdio>
#include <cstring>
#include <vector>
#include "solution.cpp"
static int failures = 0;
#define CHECK(cond, name) do { if (cond) std::printf("ok %s\n", name); \
else { std::printf("FAIL %s (line %d)\n", name, __LINE__); ++failures; } } while (0)
// Независимая реализация суммы в дополнительном коде для проверки тестов.
static uint16_t ref_sum(const uint8_t* b, size_t n, uint32_t init) {
uint32_t acc = init;
for (size_t i = 0; i + 1 < n; i += 2) acc += (uint32_t(b[i]) << 8) | b[i + 1];
if (n % 2) acc += uint32_t(b[n - 1]) << 8;
while (acc >> 16) acc = (acc & 0xFFFF) + (acc >> 16);
return uint16_t(acc);
}
// Пакет: заголовок (ihl=5) + payload_len байт нагрузки.
static std::vector<uint8_t> make_packet(size_t payload_len, uint8_t proto, bool with_options = false,
bool corrupt_checksum = false, uint16_t force_total = 0) {
size_t hlen = with_options ? 24 : 20;
std::vector<uint8_t> p(hlen + payload_len, 0);
p[0] = 0x40 | uint8_t(hlen / 4); // version 4, ihl
p[1] = 0x00; // DSCP/ECN
uint16_t total = force_total ? force_total : uint16_t(hlen + payload_len);
p[2] = uint8_t(total >> 8); p[3] = uint8_t(total & 0xFF);
p[4] = 0x12; p[5] = 0x34; // id
p[6] = 0x40; p[7] = 0x00; // DF
p[8] = 64; // TTL
p[9] = proto;
p[10] = 0; p[11] = 0; // checksum = 0 для расчёта
p[12] = 0x0A; p[13] = 0x00; p[14] = 0x00; p[15] = 0x01; // 10.0.0.1
p[16] = 0x0A; p[17] = 0x00; p[18] = 0x00; p[19] = 0x02; // 10.0.0.2
if (with_options) { p[20] = 0x01; p[21] = 0x01; p[22] = 0x00; p[23] = 0x00; }
for (size_t i = 0; i < payload_len; ++i) p[hlen + i] = uint8_t('A' + (i % 26));
uint16_t cs = uint16_t(~ref_sum(p.data(), hlen, 0));
p[10] = uint8_t(cs >> 8); p[11] = uint8_t(cs & 0xFF);
if (corrupt_checksum) p[10] ^= 0xFF;
return p;
}
int main() {
// parse: обычный пакет
{
auto p = make_packet(10, 6);
Ipv4Header h{};
bool ok = parse_ipv4(p.data(), p.size(), &h);
CHECK(ok, "parse() обычного пакета -> true");
CHECK(h.version == 4, "parse: version == 4");
CHECK(h.ihl == 5, "parse: ihl == 5");
CHECK(h.protocol == 6, "parse: protocol == 6 (TCP)");
CHECK(h.total_length == 30, "parse: total_length == 30");
CHECK(h.src_ip == 0x0A000001u, "parse: src_ip == 10.0.0.1 (порядок байт как на проводе)");
CHECK(h.dst_ip == 0x0A000002u, "parse: dst_ip == 10.0.0.2");
}
// parse: с опциями (ihl=6)
{
auto p = make_packet(0, 17, true);
Ipv4Header h{};
bool ok = parse_ipv4(p.data(), p.size(), &h);
CHECK(ok && h.ihl == 6 && h.total_length == 24, "parse: ihl=6 и total_length=24 (с опциями)");
}
// parse: отрицательные случаи
{
auto p = make_packet(10, 6);
Ipv4Header h{};
CHECK(!parse_ipv4(p.data(), 10, &h), "parse: обрезанный буфер (10 из 30) -> false");
CHECK(!parse_ipv4(nullptr, 0, &h), "parse: пустой буфер -> false");
auto v6 = p; v6[0] = 0x60;
CHECK(!parse_ipv4(v6.data(), v6.size(), &h), "parse: version 6 -> false");
auto bad_ihl = p; bad_ihl[0] = 0x43; // ihl=3
CHECK(!parse_ipv4(bad_ihl.data(), bad_ihl.size(), &h), "parse: ihl < 5 -> false");
auto too_big = make_packet(10, 6, false, false, 100); // total_length больше буфера
CHECK(!parse_ipv4(too_big.data(), too_big.size(), &h), "parse: total_length > len -> false");
auto small_total = make_packet(10, 6, false, false, 12); // меньше самого заголовка
CHECK(!parse_ipv4(small_total.data(), small_total.size(), &h), "parse: total_length < ihl*4 -> false");
}
// checksum
{
auto p = make_packet(10, 6);
auto pz = p; pz[10] = 0; pz[11] = 0; // поле обнулено — так считают контрольную сумму
uint16_t cs = compute_checksum(pz.data(), 20);
CHECK(cs == uint16_t(~ref_sum(pz.data(), 20, 0)), "compute_checksum: совпадает с эталонным расчётом");
CHECK(cs == uint16_t((p[10] << 8) | p[11]), "compute_checksum: значение равно полю в корректном пакете");
CHECK(checksum_valid(p.data(), p.size()), "checksum_valid(корректный пакет) -> true");
auto bad = make_packet(10, 6, false, true);
CHECK(!checksum_valid(bad.data(), bad.size()), "checksum_valid(испорченная сумма) -> false");
auto bad_ttl = p; bad_ttl[8] ^= 0x01;
CHECK(!checksum_valid(bad_ttl.data(), bad_ttl.size()), "checksum_valid(испорчен TTL) -> false");
auto bad_payload = p; bad_payload[24] ^= 0xFF;
CHECK(checksum_valid(bad_payload.data(), bad_payload.size()),
"checksum_valid: нагрузка в контрольную сумму заголовка не входит -> true");
// нечётная длина
uint8_t odd[3] = {0x01, 0x02, 0x03};
CHECK(compute_checksum(odd, 3) == uint16_t(~ref_sum(odd, 3, 0)), "compute_checksum: нечётная длина");
}
std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures);
return failures ? 1 : 0;
}
+7
View File
@@ -0,0 +1,7 @@
// Задача 05 — длина НВП за O(n log n). Заполни реализацию.
#include <vector>
int lis_length(const std::vector<int>& a) {
(void)a;
return 0; // TODO
}
+14
View File
@@ -0,0 +1,14 @@
# Задача 05 — длиннейшая возрастающая подпоследовательность (C++)
```c++
int lis_length(const std::vector<int>& a); // длина НВП (строго возрастающей)
```
Требования:
- пустая последовательность → 0;
- работа за O(n log n); наивное O(n²) не пройдёт проверку на 100 000 элементах
(внутренний тест меряет время и роняет прогон при > 2 с);
- подпоследовательность не обязана быть непрерывной.
Проверка: `python3 grade.py 05`. Критерий: все `ok`, сборка без предупреждений.
Разбор после сдачи: «хвосты» — массив минимальных последних элементов + lower_bound.
+47
View File
@@ -0,0 +1,47 @@
#include <cstdio>
#include <vector>
#include <random>
#include <chrono>
#include <algorithm>
#include "solution.cpp"
static int failures = 0;
#define CHECK(cond, name) do { if (cond) std::printf("ok %s\n", name); \
else { std::printf("FAIL %s (line %d)\n", name, __LINE__); ++failures; } } while (0)
int main() {
CHECK(lis_length({}) == 0, "пустая -> 0");
CHECK(lis_length({5}) == 1, "один элемент -> 1");
CHECK(lis_length({1, 2, 3}) == 3, "возрастающая -> вся длина");
CHECK(lis_length({3, 2, 1}) == 1, "убывающая -> 1");
CHECK(lis_length({10, 9, 2, 5, 3, 7, 101, 18}) == 4, "классика -> 4");
CHECK(lis_length({7, 7, 7}) == 1, "равные элементы -> строго возрастающая длина 1");
CHECK(lis_length({0, 1, 0, 3, 2, 3}) == 4, "лекод-пример -> 4");
CHECK(lis_length({2, 2, 2, 3}) == 2, "дубли + рост -> 2");
CHECK(lis_length({-5, -4, -3, -2}) == 4, "отрицательные -> 4");
// производительность: худший случай для наивного DP
{
std::vector<int> big(100000);
for (int i = 0; i < 100000; ++i) big[i] = i;
auto t0 = std::chrono::steady_clock::now();
int r = lis_length(big);
double sec = std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
std::printf(" (100k возрастающих: result=%d, time=%.3fs)\n", r, sec);
CHECK(r == 100000, "100k возрастающих -> 100000");
CHECK(sec < 2.0, "100k за < 2 c (требуется O(n log n))");
}
{
std::mt19937 rng(12345);
std::vector<int> big(100000);
for (auto& x : big) x = int(rng() % 100000);
auto t0 = std::chrono::steady_clock::now();
int r = lis_length(big);
double sec = std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
std::printf(" (100k случайных: result=%d, time=%.3fs)\n", r, sec);
CHECK(r > 300 && r < 800, "100k случайных: результат в разумном диапазоне");
CHECK(sec < 2.0, "100k случайных за < 2 c");
}
std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures);
return failures ? 1 : 0;
}
+29
View File
@@ -0,0 +1,29 @@
// Задача 06 — потокобезопасная очередь. Заполни реализацию.
#include <cstddef>
#include <stdexcept>
class BlockingQueue {
public:
explicit BlockingQueue(size_t capacity) : cap_(capacity) {}
~BlockingQueue() = default;
void push(int v) {
(void)v;
// TODO: должен блокироваться на полной очереди и бросать std::runtime_error после close()
throw std::runtime_error("push: not implemented");
}
bool pop(int& out) {
(void)out;
return false; // TODO
}
void close() {
// TODO
}
size_t size() const { return 0; }
private:
size_t cap_;
};
+27
View File
@@ -0,0 +1,27 @@
# Задача 06 — потокобезопасная ограниченная очередь (C++)
Классический вопрос на собеседовании в embedded/сетевую разработку: не «знаешь ли ты
std::thread», а «умеешь ли ты не сломать счётчик под нагрузкой».
```c++
class BlockingQueue {
public:
explicit BlockingQueue(size_t capacity);
~BlockingQueue();
void push(int v); // блокируется, пока очередь полна
bool pop(int& out); // блокируется, пока пуста; false — если закрыта и пуста
void close(); // после close: pop() опустошает остаток и отдаёт false
size_t size() const;
};
```
Требования:
- push после close() бросает `std::runtime_error`;
- закрытие разблокирует все ждущие потоки (никакого вечного ожидания и busy-wait);
- размер очереди никогда не превышает capacity;
- ни одной гонки: тест собирается и гоняется с ThreadSanitizer (в том числе `size()`
вызывается из другого потока — он тоже должен быть потокобезопасным).
Проверка: `python3 grade.py 06`. Критерий: все `ok`, TSan чистый, нет зависания.
Разбор после сдачи: почему нужен predicate-цикл вокруг wait, и как один condition_variable
на оба события даёт ложные пробуждения.
+73
View File
@@ -0,0 +1,73 @@
#include <cstdio>
#include <thread>
#include <vector>
#include <atomic>
#include <mutex>
#include <stdexcept>
#include "solution.cpp"
static int failures = 0;
#define CHECK(cond, name) do { if (cond) std::printf("ok %s\n", name); \
else { std::printf("FAIL %s (line %d)\n", name, __LINE__); ++failures; } } while (0)
int main() {
// Базовая дисциплина
{
BlockingQueue q(2);
q.push(1);
q.push(2);
CHECK(q.size() == 2, "size() == 2 после двух push");
int v = 0;
CHECK(q.pop(v) && v == 1, "pop -> 1 (FIFO)");
CHECK(q.pop(v) && v == 2, "pop -> 2 (FIFO)");
q.close();
CHECK(!q.pop(v), "после close и опустошения pop -> false");
bool threw = false;
try { q.push(3); } catch (const std::runtime_error&) { threw = true; }
CHECK(threw, "push после close -> std::runtime_error");
}
// Продюсеры/потребители под нагрузкой
{
const int P = 4, C = 4, PER = 20000, C3 = 64;
BlockingQueue q(C3);
std::atomic<int> pushed{0};
std::atomic<long long> sum{0};
std::atomic<int> popped{0};
std::atomic<int> over{0};
auto producer = [&](int pid) {
for (int i = 0; i < PER; ++i) {
q.push(pid * 1000000 + i);
pushed.fetch_add(1, std::memory_order_relaxed);
if (q.size() > C3) over.fetch_add(1, std::memory_order_relaxed);
}
};
auto consumer = [&](int) {
int v = 0;
while (q.pop(v)) {
sum.fetch_add(v, std::memory_order_relaxed);
popped.fetch_add(1, std::memory_order_relaxed);
}
};
std::vector<std::thread> ts;
for (int i = 0; i < C; ++i) ts.emplace_back(consumer, i);
std::this_thread::sleep_for(std::chrono::milliseconds(20));
for (int i = 0; i < P; ++i) ts.emplace_back(producer, i);
std::this_thread::sleep_for(std::chrono::milliseconds(50));
while (pushed.load() < P * PER) std::this_thread::sleep_for(std::chrono::milliseconds(10));
q.close();
for (auto& t : ts) t.join();
long long expect = 0;
for (int p = 0; p < P; ++p) for (int i = 0; i < PER; ++i) expect += (long long)p * 1000000 + i;
std::printf(" (вставлено=%d, извлечено=%d, переполнений=%d)\n",
pushed.load(), popped.load(), over.load());
CHECK(pushed.load() == P * PER, "все вставки выполнены (нет потерь)");
CHECK(popped.load() == P * PER, "извлечено ровно столько же (нет дублей/потерь)");
CHECK(sum.load() == expect, "сумма значений совпадает с эталоном");
CHECK(over.load() == 0, "size() не превышал capacity");
}
std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures);
return failures ? 1 : 0;
}
+14
View File
@@ -0,0 +1,14 @@
// Задача 07 — TCP-эхо-сервер на epoll. Реализуй сервер.
// Запуск: ./solution <порт>
#include <cstdio>
#include <cstdlib>
int main(int argc, char** argv) {
if (argc < 2) {
std::fprintf(stderr, "usage: %s <port>\n", argv[0]);
return 2;
}
long port = std::strtol(argv[1], nullptr, 10);
std::fprintf(stderr, "TODO: сервер на порту %ld не реализован\n", port);
return 1; // TODO: реализовать сервер на epoll
}
+17
View File
@@ -0,0 +1,17 @@
# Задача 07 — TCP-эхо-сервер на epoll (Linux, C++)
Написать сервер: принимает TCP-соединения на `127.0.0.1:<порт из argv[1]>`, эхо всех
принятых байт обратно в то же соединение, обслуживает несколько клиентов одновременно,
завершается по SIGTERM/SIGINT аккуратно (exit 0).
Требования:
- мультиплексирование через `epoll` (не блокирующий `accept` в цикле, не поток на клиента);
- сокеты неблокирующие, обработка `EAGAIN`/`EINTR`, частичные чтения и записи;
- `SO_REUSEADDR`, bind на 127.0.0.1, прослушивание на нужном порту;
- до 64 одновременных соединений, закрытие по EOF со стороны клиента;
- никаких утечек дескрипторов (тест проверяет закрытие).
Запускается как самостоятельный бинарник: `./solution <порт>`.
Проверка: `python3 grade.py 07` — тест сам поднимает сервер, открывает 3 соединения,
льёт данные, проверяет эхо и корректное завершение по SIGTERM.
Критерий: все `ok` + ревью кода (использование epoll, обработка ошибок).
+118
View File
@@ -0,0 +1,118 @@
#!/usr/bin/env python3
"""Тест задачи 07: поднимает сервер, проверяет эхо на нескольких соединениях и SIGTERM."""
import os
import signal
import socket
import subprocess
import sys
import time
HERE = os.path.dirname(os.path.abspath(__file__))
BIN = os.path.join(HERE, "solution")
failures = 0
def check(cond, name):
global failures
if cond:
print(f"ok {name}")
else:
print(f"FAIL {name}")
failures += 1
def free_port():
s = socket.socket()
s.bind(("127.0.0.1", 0))
p = s.getsockname()[1]
s.close()
return p
def main():
port = free_port()
proc = subprocess.Popen([BIN, str(port)], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
up = False
for _ in range(100): # до 5 секунд на старт
if proc.poll() is not None:
break
try:
s = socket.create_connection(("127.0.0.1", port), timeout=0.3)
s.close()
up = True
break
except OSError:
time.sleep(0.05)
check(up, "сервер принял соединение и слушает порт")
if up:
# три одновременных соединения, большие и разорванные сообщения
socks = []
for i in range(3):
s = socket.create_connection(("127.0.0.1", port), timeout=2)
s.settimeout(5)
socks.append(s)
payloads = [b"hello\n", b"x" * 70000, b"chunk-" + bytes(range(32, 127))]
for s, p in zip(socks, payloads):
s.sendall(p)
ok = True
for i, (s, p) in enumerate(zip(socks, payloads)):
got = b""
while len(got) < len(p):
chunk = s.recv(65536)
if not chunk:
break
got += chunk
if got != p:
ok = False
print(f" соединение {i}: получено {len(got)} из {len(p)} байт")
check(ok, "эхо на трёх одновременных соединениях (включая 70 КБ)")
# эхо в двух шагах по одному соединению
s = socks[0]
s.sendall(b"part1")
first = s.recv(5)
s.sendall(b"part2")
second = b""
while len(second) < 5:
chunk = s.recv(5 - len(second))
if not chunk:
break
second += chunk
check(first == b"part1" and second == b"part2", "эхо по частям (несколько циклов read/write)")
# клиент закрывается — сервер не падает
for s in socks:
s.close()
time.sleep(0.3)
check(proc.poll() is None, "сервер жив после закрытия всех клиентов")
# новое соединение после закрытия старых
try:
s = socket.create_connection(("127.0.0.1", port), timeout=2)
s.settimeout(3)
s.sendall(b"again")
check(s.recv(5) == b"again", "новое соединение после отключения старых")
s.close()
except OSError as e:
check(False, f"новое соединение после отключения старых ({e})")
proc.send_signal(signal.SIGTERM)
try:
rc = proc.wait(timeout=5)
check(rc == 0, f"аккуратное завершение по SIGTERM (exit={rc})")
except subprocess.TimeoutExpired:
proc.kill()
check(False, "аккуратное завершение по SIGTERM (не завершился за 5 с)")
if failures:
err = proc.stderr.read().decode(errors="replace")[:2000]
if err:
print("--- stderr сервера ---")
print(err)
print(f"\n{'FAILURES: %d' % failures if failures else 'ALL PASS'}")
sys.exit(1 if failures else 0)
if __name__ == "__main__":
main()
+12
View File
@@ -0,0 +1,12 @@
10.0.0.1 - - [23/Sep/2026:10:00:01 +0700] "GET /index.html HTTP/1.1" 200 512
10.0.0.2 - - [23/Sep/2026:10:00:02 +0700] "GET /a HTTP/1.1" 404 128
10.0.0.1 - - [23/Sep/2026:10:00:03 +0700] "POST /api HTTP/1.1" 500 42
10.0.0.3 - - [23/Sep/2026:10:00:04 +0700] "GET /b HTTP/1.1" 200 1024
10.0.0.1 - - [23/Sep/2026:10:00:05 +0700] "GET /c HTTP/1.1" 503 0
10.0.0.2 - - [23/Sep/2026:10:00:06 +0700] "GET /d HTTP/1.1" 200 33
10.0.0.4 - - [23/Sep/2026:10:00:07 +0700] "GET /e HTTP/1.1" 200 10
10.0.0.3 - - [23/Sep/2026:10:00:08 +0700] "GET /f HTTP/1.1" 200 11
10.0.0.2 - - [23/Sep/2026:10:00:09 +0700] "GET /g HTTP/1.1" 200 12
10.0.0.5 - - [23/Sep/2026:10:00:10 +0700] "GET /h HTTP/1.1" 500 13
10.0.0.1 - - [23/Sep/2026:10:00:11 +0700] "GET /i HTTP/1.1" 200 14
10.0.0.6 - - [23/Sep/2026:10:00:12 +0700] "GET /j HTTP/1.1" 200 15
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
# Задача 08 — разбор лога доступа. Реализуй.
set -euo pipefail
log="${1:-}"
if [[ -z "$log" ]]; then
echo "usage: $0 <access.log>" >&2
exit 2
fi
echo "TOTAL 0"
# TODO
exit 0
+25
View File
@@ -0,0 +1,25 @@
# Задача 08 — bash: разбор лога доступа
Написать `solution.sh`, который принимает путь к файлу лога (формат nginx/apache access.log)
и печатает ровно четыре строки:
```
TOTAL <число строк в файле>
TOP <ip> <число запросов>
TOP <ip> <число запросов>
TOP <ip> <число запросов>
5XX <число ответов со статусом 500-599>
```
Правила:
- IP — первое поле строки;
- TOP — три самых частых IP по убыванию числа запросов; при равенстве — по возрастанию
строки IP (лексикографически);
- если уникальных IP меньше трёх — печатать столько строк TOP, сколько есть;
- файл может быть большим (миллион строк): построчный bash-цикл по строкам не пройдёт
по времени — нужен awk/sort или один-два прохода;
- пустой файл: `TOTAL 0`, `5XX 0`, строк TOP нет.
Запуск: `bash solution.sh access.log`
Проверка: `python3 grade.py 08` (тест гоняет скрипт на фикстуре и на большом логе).
Критерий: точное совпадение вывода, код возврата 0.
+88
View File
@@ -0,0 +1,88 @@
#!/usr/bin/env python3
"""Тест задачи 08: сравнение вывода solution.sh с эталоном на фикстуре и большом логе."""
import os
import random
import subprocess
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
SCRIPT = os.path.join(HERE, "solution.sh")
failures = 0
def check(cond, name, extra=""):
global failures
if cond:
print(f"ok {name}")
else:
print(f"FAIL {name} {extra}")
failures += 1
def run(path):
r = subprocess.run(["bash", SCRIPT, path], capture_output=True, text=True, timeout=120)
return r.returncode, r.stdout, r.stderr
def expected(lines):
from collections import Counter
ips = [l.split()[0] for l in lines if l.strip()]
total = len(lines)
cnt = Counter(ips)
top = sorted(cnt.items(), key=lambda kv: (-kv[1], kv[0]))[:3]
five = 0
for l in lines:
parts = l.split('"')
if len(parts) >= 3:
codes = parts[2].split()
if codes and codes[0].isdigit() and 500 <= int(codes[0]) <= 599:
five += 1
out = [f"TOTAL {total}"]
for ip, c in top:
out.append(f"TOP {ip} {c}")
out.append(f"5XX {five}")
return "\n".join(out)
def compare(path, lines, label):
rc, out, err = run(path)
exp = expected(lines)
got = out.strip("\n")
if got != exp:
print(f"--- ожидалось ({label}) ---\n{exp}\n--- получено ---\n{got}")
check(rc == 0, f"{label}: код возврата 0 (получен {rc})", err[:300])
check(got == exp, f"{label}: вывод совпадает с эталоном")
def main():
fixture = os.path.join(HERE, "access.log")
with open(fixture, encoding="utf-8") as f:
lines = f.read().splitlines()
compare(fixture, lines, "фикстура")
# пустой файл
empty = os.path.join(HERE, "empty.log")
open(empty, "w").close()
compare(empty, [], "пустой файл")
# большой лог: 200k строк, часть 5xx, один лидер, ничья между двумя IP
rng = random.Random(7)
big = os.path.join(HERE, "big.log")
ips = ["10.0.0.1"] * 50000 + ["10.0.0.2"] * 30000 + ["10.0.0.3"] * 30000
ips += [f"192.168.1.{i}" for i in range(1, 61)] * 10
ips += [f"172.16.0.{i}" for i in range(1, 21)] * 5
rng.shuffle(ips)
big_lines = []
for ip in ips:
code = rng.choice([200, 200, 200, 301, 404, 500, 502, 503])
big_lines.append(f'{ip} - - [23/Sep/2026:11:00:00 +0700] "GET /p HTTP/1.1" {code} 100')
with open(big, "w", encoding="utf-8") as f:
f.write("\n".join(big_lines) + "\n")
compare(big, big_lines, "большой лог 110k строк")
print(f"\n{'FAILURES: %d' % failures if failures else 'ALL PASS'}")
sys.exit(1 if failures else 0)
if __name__ == "__main__":
main()
+20
View File
@@ -0,0 +1,20 @@
Задача 09 — разбор падения.
1. Дефекты (сколько и какие):
- ...
- ...
2. Сборка и первые шаги:
$ g++ -g -O0 -fsanitize=address,undefined crash.c -o crash
$ ./crash world
<что вывел ASAN — своими словами>
3. Команды gdb по шагам и что они показали:
(gdb) ...
...
4. Почему программа падала именно так (механизм):
...
5. Что изменено в crash.c (минимальные правки):
...
+21
View File
@@ -0,0 +1,21 @@
/* Задача 09: программа с дефектами. Починить минимально. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv) {
const char *word = argc > 1 ? argv[1] : "hello";
size_t n = strlen(word);
char *buf = malloc(n);
if (!buf) {
return 2;
}
strcpy(buf, word);
printf("len=%zu\n", strlen(buf));
free(buf);
free(buf);
return 0;
}
+23
View File
@@ -0,0 +1,23 @@
# Задача 09 — отладка: найти и исправить дефекты (gdb)
В `crash.c` лежит программа, которая падает на части входов и портит память.
Задача — не переписать её с нуля, а **найти дефекты отладчиком и починить минимально**.
Ожидаемое поведение после починки:
- `./crash` (без аргумента) печатает `len=5` и завершается с кодом 0;
- `./crash <слово>` печатает `len=<длина слова>` и завершается с кодом 0;
- `./crash ""` печатает `len=0` и завершается с кодом 0;
- сборка с `-fsanitize=address,undefined` не даёт ни одного сообщения об ошибке.
Что сделать:
1. Собрать с отладочной информацией и санитайзерами:
`gcc -g -O0 -fsanitize=address,undefined crash.c -o crash`
2. Разобраться, что именно портит память, а что приводит к падению при выходе.
Полезно: `gdb ./crash`, `run`, `bt`, `frame`, `info locals`, `watch`.
3. Исправить `crash.c` (минимальные правки, стиль сохранить).
4. Заполнить `answer.txt`: сколько дефектов нашёл, какие именно, какими командами
отладчика это подтвердил (по шагам), почему падало именно так.
Проверка: `python3 grade.py 09` — компилирует `crash.c` (компилятор C, gcc) с ASAN/UBSAN
и прогоняет 5 входов.
`answer.txt` проверяется ревью (это часть оценки: важен ход разбора, а не только результат).
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env python3
"""Тест задачи 09: сборка crash.c с ASAN/UBSAN и прогон входов."""
import os
import subprocess
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
SRC = os.path.join(HERE, "crash.c")
BIN = os.path.join(HERE, "crash_checked")
failures = 0
def check(cond, name, extra=""):
global failures
if cond:
print(f"ok {name}")
else:
print(f"FAIL {name} {extra}")
failures += 1
def main():
r = subprocess.run(["gcc", "-g", "-O1", "-Wall", "-Wextra",
"-fsanitize=address,undefined", "-fno-omit-frame-pointer",
SRC, "-o", BIN], capture_output=True, text=True, timeout=180)
check(r.returncode == 0, "сборка с ASAN/UBSAN прошла", r.stderr[:800])
if r.returncode != 0:
print("\nFAILURES: 1")
sys.exit(1)
cases = [
([], "len=5"),
(["world"], "len=5"),
(["a"], "len=1"),
([""], "len=0"),
(["x" * 300], "len=300"),
]
for args, expect in cases:
try:
run = subprocess.run([BIN] + args, capture_output=True, text=True, timeout=60,
env={**os.environ, "ASAN_OPTIONS": "detect_leaks=1"})
except subprocess.TimeoutExpired:
check(False, f"вход {args or '<нет>'}: завершился за 60 c")
continue
out = run.stdout.strip()
err = run.stderr
label = f"вход {args[0][:12]!r}" if args else "вход без аргумента"
check(run.returncode == 0, f"{label}: код возврата 0 (получен {run.returncode})")
check(out == expect, f"{label}: вывод {expect!r} (получено {out!r})")
check("AddressSanitizer" not in err and "runtime error" not in err,
f"{label}: санитайзеры молчат", err[:400].replace("\n", " | "))
# answer.txt заполнен (проверка на «не оставил заготовку»)
apath = os.path.join(HERE, "answer.txt")
if os.path.exists(apath):
txt = open(apath, encoding="utf-8").read()
check("..." not in txt and len(txt) > 400, "answer.txt заполнен разбором (ревью отдельно)")
else:
check(False, "answer.txt: файл с разбором не найден")
print(f"\n{'FAILURES: %d' % failures if failures else 'ALL PASS'}")
sys.exit(1 if failures else 0)
if __name__ == "__main__":
main()