From 4cd63b3d4da8d87c91598f3081c6550cb34b11cd Mon Sep 17 00:00:00 2001 From: Kodlo-chan Date: Wed, 23 Sep 2026 16:05:09 +0700 Subject: [PATCH] =?UTF-8?q?=D0=98=D0=A3=D0=9F:=20=D0=B8=D0=BD=D1=82=D0=B5?= =?UTF-8?q?=D0=BD=D1=81=D0=B8=D0=B2=207=20=D0=B4=D0=BD=D0=B5=D0=B9=20+=20?= =?UTF-8?q?=D0=B4=D0=B8=D0=B0=D0=B3=D0=BD=D0=BE=D1=81=D1=82=D0=B8=D1=87?= =?UTF-8?q?=D0=B5=D1=81=D0=BA=D0=B8=D0=B9=20=D0=BF=D0=B0=D0=BA=D0=B5=D1=82?= =?UTF-8?q?=20(32=20=D0=B2=D0=BE=D0=BF=D1=80=D0=BE=D1=81=D0=B0,=209=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B4=D0=B0=D1=87=20=D1=81=20=D0=B0=D0=B2=D1=82?= =?UTF-8?q?=D0=BE=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=BE=D0=B9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 10 + CONTEXT.md | 62 ++++ PLAN.md | 160 ++++++++++ README.md | 40 +++ TRACKER.md | 23 ++ WEEK7.md | 145 +++++++++ diag/README.md | 54 ++++ diag/grade.py | 158 ++++++++++ diag/questions_export.txt | 193 ++++++++++++ diag/quiz.json | 398 +++++++++++++++++++++++++ diag/quiz.py | 125 ++++++++ diag/tasks/01_bits/solution.cpp | 27 ++ diag/tasks/01_bits/task.md | 18 ++ diag/tasks/01_bits/test_bits.cpp | 41 +++ diag/tasks/02_list/solution.cpp | 25 ++ diag/tasks/02_list/task.md | 17 ++ diag/tasks/02_list/test_list.cpp | 94 ++++++ diag/tasks/03_ring/solution.cpp | 29 ++ diag/tasks/03_ring/task.md | 25 ++ diag/tasks/03_ring/test_ring.cpp | 46 +++ diag/tasks/04_ipv4/solution.cpp | 28 ++ diag/tasks/04_ipv4/task.md | 32 ++ diag/tasks/04_ipv4/test_ipv4.cpp | 100 +++++++ diag/tasks/05_lis/solution.cpp | 7 + diag/tasks/05_lis/task.md | 14 + diag/tasks/05_lis/test_lis.cpp | 47 +++ diag/tasks/06_threads/solution.cpp | 29 ++ diag/tasks/06_threads/task.md | 27 ++ diag/tasks/06_threads/test_threads.cpp | 73 +++++ diag/tasks/07_epoll/solution.cpp | 14 + diag/tasks/07_epoll/task.md | 17 ++ diag/tasks/07_epoll/test_epoll.py | 118 ++++++++ diag/tasks/08_bash/access.log | 12 + diag/tasks/08_bash/solution.sh | 13 + diag/tasks/08_bash/task.md | 25 ++ diag/tasks/08_bash/test_bash.py | 88 ++++++ diag/tasks/09_gdb/answer.txt | 20 ++ diag/tasks/09_gdb/crash.c | 21 ++ diag/tasks/09_gdb/task.md | 23 ++ diag/tasks/09_gdb/test_gdb.py | 66 ++++ 40 files changed, 2464 insertions(+) create mode 100644 .gitignore create mode 100644 CONTEXT.md create mode 100644 PLAN.md create mode 100644 README.md create mode 100644 TRACKER.md create mode 100644 WEEK7.md create mode 100644 diag/README.md create mode 100644 diag/grade.py create mode 100644 diag/questions_export.txt create mode 100644 diag/quiz.json create mode 100644 diag/quiz.py create mode 100644 diag/tasks/01_bits/solution.cpp create mode 100644 diag/tasks/01_bits/task.md create mode 100644 diag/tasks/01_bits/test_bits.cpp create mode 100644 diag/tasks/02_list/solution.cpp create mode 100644 diag/tasks/02_list/task.md create mode 100644 diag/tasks/02_list/test_list.cpp create mode 100644 diag/tasks/03_ring/solution.cpp create mode 100644 diag/tasks/03_ring/task.md create mode 100644 diag/tasks/03_ring/test_ring.cpp create mode 100644 diag/tasks/04_ipv4/solution.cpp create mode 100644 diag/tasks/04_ipv4/task.md create mode 100644 diag/tasks/04_ipv4/test_ipv4.cpp create mode 100644 diag/tasks/05_lis/solution.cpp create mode 100644 diag/tasks/05_lis/task.md create mode 100644 diag/tasks/05_lis/test_lis.cpp create mode 100644 diag/tasks/06_threads/solution.cpp create mode 100644 diag/tasks/06_threads/task.md create mode 100644 diag/tasks/06_threads/test_threads.cpp create mode 100644 diag/tasks/07_epoll/solution.cpp create mode 100644 diag/tasks/07_epoll/task.md create mode 100644 diag/tasks/07_epoll/test_epoll.py create mode 100644 diag/tasks/08_bash/access.log create mode 100755 diag/tasks/08_bash/solution.sh create mode 100644 diag/tasks/08_bash/task.md create mode 100644 diag/tasks/08_bash/test_bash.py create mode 100644 diag/tasks/09_gdb/answer.txt create mode 100644 diag/tasks/09_gdb/crash.c create mode 100644 diag/tasks/09_gdb/task.md create mode 100644 diag/tasks/09_gdb/test_gdb.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..92b58cc --- /dev/null +++ b/.gitignore @@ -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 diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..4232f61 --- /dev/null +++ b/CONTEXT.md @@ -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). diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..b927bd4 --- /dev/null +++ b/PLAN.md @@ -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, +остальное после оффера. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a6d58b3 --- /dev/null +++ b/README.md @@ -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`); +- либо файлами в чат / в топик лейна. diff --git a/TRACKER.md b/TRACKER.md new file mode 100644 index 0000000..11d09ac --- /dev/null +++ b/TRACKER.md @@ -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 | | | | | + +## Слабые темы (по диагностике и по ходу) + +- ... + +## Вопросы, которые хочу разобрать со студией + +- ... diff --git a/WEEK7.md b/WEEK7.md new file mode 100644 index 0000000..2fe0e72 --- /dev/null +++ b/WEEK7.md @@ -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 электроника. diff --git a/diag/README.md b/diag/README.md new file mode 100644 index 0000000..319966d --- /dev/null +++ b/diag/README.md @@ -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`. diff --git a/diag/grade.py b/diag/grade.py new file mode 100644 index 0000000..3eb97bc --- /dev/null +++ b/diag/grade.py @@ -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//. +Результат: 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() diff --git a/diag/questions_export.txt b/diag/questions_export.txt new file mode 100644 index 0000000..0ef1f6b --- /dev/null +++ b/diag/questions_export.txt @@ -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 ..." (номер вопроса - номер варианта) diff --git a/diag/quiz.json b/diag/quiz.json new file mode 100644 index 0000000..1579ee6 --- /dev/null +++ b/diag/quiz.json @@ -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 хоста." + } + ] +} \ No newline at end of file diff --git a/diag/quiz.py b/diag/quiz.py new file mode 100644 index 0000000..8411086 --- /dev/null +++ b/diag/quiz.py @@ -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() diff --git a/diag/tasks/01_bits/solution.cpp b/diag/tasks/01_bits/solution.cpp new file mode 100644 index 0000000..91fd9a3 --- /dev/null +++ b/diag/tasks/01_bits/solution.cpp @@ -0,0 +1,27 @@ +// Задача 01 — битовые операции. Заполни реализации. +#include +#include + +// число единичных бит в 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 +} diff --git a/diag/tasks/01_bits/task.md b/diag/tasks/01_bits/task.md new file mode 100644 index 0000000..f930ef7 --- /dev/null +++ b/diag/tasks/01_bits/task.md @@ -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` — четыре маски. diff --git a/diag/tasks/01_bits/test_bits.cpp b/diag/tasks/01_bits/test_bits.cpp new file mode 100644 index 0000000..da27f24 --- /dev/null +++ b/diag/tasks/01_bits/test_bits.cpp @@ -0,0 +1,41 @@ +#include +#include +#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; +} diff --git a/diag/tasks/02_list/solution.cpp b/diag/tasks/02_list/solution.cpp new file mode 100644 index 0000000..ceafc49 --- /dev/null +++ b/diag/tasks/02_list/solution.cpp @@ -0,0 +1,25 @@ +// Задача 02 — связный список. Заполни реализации. +#include + +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 +} diff --git a/diag/tasks/02_list/task.md b/diag/tasks/02_list/task.md new file mode 100644 index 0000000..ffbdef1 --- /dev/null +++ b/diag/tasks/02_list/task.md @@ -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 чистые (утечки в тесте +считаются ошибкой — тест сам освобождает память). diff --git a/diag/tasks/02_list/test_list.cpp b/diag/tasks/02_list/test_list.cpp new file mode 100644 index 0000000..f23b8b9 --- /dev/null +++ b/diag/tasks/02_list/test_list.cpp @@ -0,0 +1,94 @@ +#include +#include +#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& v) { + Node* head = nullptr; Node** tail = &head; + for (int x : v) { *tail = new Node{x, nullptr}; tail = &(*tail)->next; } + return head; +} +static std::vector to_vec(Node* h, size_t limit = 1000) { + std::vector 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({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({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; +} diff --git a/diag/tasks/03_ring/solution.cpp b/diag/tasks/03_ring/solution.cpp new file mode 100644 index 0000000..5cbf777 --- /dev/null +++ b/diag/tasks/03_ring/solution.cpp @@ -0,0 +1,29 @@ +// Задача 03 — кольцевой буфер. Заполни реализации. +#include + +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_ по необходимости +}; diff --git a/diag/tasks/03_ring/task.md b/diag/tasks/03_ring/task.md new file mode 100644 index 0000000..5fd1f2d --- /dev/null +++ b/diag/tasks/03_ring/task.md @@ -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 чистые. diff --git a/diag/tasks/03_ring/test_ring.cpp b/diag/tasks/03_ring/test_ring.cpp new file mode 100644 index 0000000..6f34263 --- /dev/null +++ b/diag/tasks/03_ring/test_ring.cpp @@ -0,0 +1,46 @@ +#include +#include +#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; +} diff --git a/diag/tasks/04_ipv4/solution.cpp b/diag/tasks/04_ipv4/solution.cpp new file mode 100644 index 0000000..540a736 --- /dev/null +++ b/diag/tasks/04_ipv4/solution.cpp @@ -0,0 +1,28 @@ +// Задача 04 — разбор IPv4-пакета. Заполни реализации. +#include +#include + +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 +} diff --git a/diag/tasks/04_ipv4/task.md b/diag/tasks/04_ipv4/task.md new file mode 100644 index 0000000..914e235 --- /dev/null +++ b/diag/tasks/04_ipv4/task.md @@ -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. diff --git a/diag/tasks/04_ipv4/test_ipv4.cpp b/diag/tasks/04_ipv4/test_ipv4.cpp new file mode 100644 index 0000000..da1af80 --- /dev/null +++ b/diag/tasks/04_ipv4/test_ipv4.cpp @@ -0,0 +1,100 @@ +#include +#include +#include +#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 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 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; +} diff --git a/diag/tasks/05_lis/solution.cpp b/diag/tasks/05_lis/solution.cpp new file mode 100644 index 0000000..ca5ebde --- /dev/null +++ b/diag/tasks/05_lis/solution.cpp @@ -0,0 +1,7 @@ +// Задача 05 — длина НВП за O(n log n). Заполни реализацию. +#include + +int lis_length(const std::vector& a) { + (void)a; + return 0; // TODO +} diff --git a/diag/tasks/05_lis/task.md b/diag/tasks/05_lis/task.md new file mode 100644 index 0000000..8ae0dfa --- /dev/null +++ b/diag/tasks/05_lis/task.md @@ -0,0 +1,14 @@ +# Задача 05 — длиннейшая возрастающая подпоследовательность (C++) + +```c++ +int lis_length(const std::vector& a); // длина НВП (строго возрастающей) +``` + +Требования: +- пустая последовательность → 0; +- работа за O(n log n); наивное O(n²) не пройдёт проверку на 100 000 элементах + (внутренний тест меряет время и роняет прогон при > 2 с); +- подпоследовательность не обязана быть непрерывной. + +Проверка: `python3 grade.py 05`. Критерий: все `ok`, сборка без предупреждений. +Разбор после сдачи: «хвосты» — массив минимальных последних элементов + lower_bound. diff --git a/diag/tasks/05_lis/test_lis.cpp b/diag/tasks/05_lis/test_lis.cpp new file mode 100644 index 0000000..de0d95d --- /dev/null +++ b/diag/tasks/05_lis/test_lis.cpp @@ -0,0 +1,47 @@ +#include +#include +#include +#include +#include +#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 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(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 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(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; +} diff --git a/diag/tasks/06_threads/solution.cpp b/diag/tasks/06_threads/solution.cpp new file mode 100644 index 0000000..b3f0423 --- /dev/null +++ b/diag/tasks/06_threads/solution.cpp @@ -0,0 +1,29 @@ +// Задача 06 — потокобезопасная очередь. Заполни реализацию. +#include +#include + +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_; +}; diff --git a/diag/tasks/06_threads/task.md b/diag/tasks/06_threads/task.md new file mode 100644 index 0000000..694bce6 --- /dev/null +++ b/diag/tasks/06_threads/task.md @@ -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 +на оба события даёт ложные пробуждения. diff --git a/diag/tasks/06_threads/test_threads.cpp b/diag/tasks/06_threads/test_threads.cpp new file mode 100644 index 0000000..cc25b6b --- /dev/null +++ b/diag/tasks/06_threads/test_threads.cpp @@ -0,0 +1,73 @@ +#include +#include +#include +#include +#include +#include +#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 pushed{0}; + std::atomic sum{0}; + std::atomic popped{0}; + std::atomic 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 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; +} diff --git a/diag/tasks/07_epoll/solution.cpp b/diag/tasks/07_epoll/solution.cpp new file mode 100644 index 0000000..9b95d47 --- /dev/null +++ b/diag/tasks/07_epoll/solution.cpp @@ -0,0 +1,14 @@ +// Задача 07 — TCP-эхо-сервер на epoll. Реализуй сервер. +// Запуск: ./solution <порт> +#include +#include + +int main(int argc, char** argv) { + if (argc < 2) { + std::fprintf(stderr, "usage: %s \n", argv[0]); + return 2; + } + long port = std::strtol(argv[1], nullptr, 10); + std::fprintf(stderr, "TODO: сервер на порту %ld не реализован\n", port); + return 1; // TODO: реализовать сервер на epoll +} diff --git a/diag/tasks/07_epoll/task.md b/diag/tasks/07_epoll/task.md new file mode 100644 index 0000000..75d39e8 --- /dev/null +++ b/diag/tasks/07_epoll/task.md @@ -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, обработка ошибок). diff --git a/diag/tasks/07_epoll/test_epoll.py b/diag/tasks/07_epoll/test_epoll.py new file mode 100644 index 0000000..6bb28f3 --- /dev/null +++ b/diag/tasks/07_epoll/test_epoll.py @@ -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() diff --git a/diag/tasks/08_bash/access.log b/diag/tasks/08_bash/access.log new file mode 100644 index 0000000..8d6ae8a --- /dev/null +++ b/diag/tasks/08_bash/access.log @@ -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 diff --git a/diag/tasks/08_bash/solution.sh b/diag/tasks/08_bash/solution.sh new file mode 100755 index 0000000..8d128e2 --- /dev/null +++ b/diag/tasks/08_bash/solution.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# Задача 08 — разбор лога доступа. Реализуй. +set -euo pipefail + +log="${1:-}" +if [[ -z "$log" ]]; then + echo "usage: $0 " >&2 + exit 2 +fi + +echo "TOTAL 0" +# TODO +exit 0 diff --git a/diag/tasks/08_bash/task.md b/diag/tasks/08_bash/task.md new file mode 100644 index 0000000..03e2cbb --- /dev/null +++ b/diag/tasks/08_bash/task.md @@ -0,0 +1,25 @@ +# Задача 08 — bash: разбор лога доступа + +Написать `solution.sh`, который принимает путь к файлу лога (формат nginx/apache access.log) +и печатает ровно четыре строки: + +``` +TOTAL <число строк в файле> +TOP <число запросов> +TOP <число запросов> +TOP <число запросов> +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. diff --git a/diag/tasks/08_bash/test_bash.py b/diag/tasks/08_bash/test_bash.py new file mode 100644 index 0000000..7af016c --- /dev/null +++ b/diag/tasks/08_bash/test_bash.py @@ -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() diff --git a/diag/tasks/09_gdb/answer.txt b/diag/tasks/09_gdb/answer.txt new file mode 100644 index 0000000..67dba67 --- /dev/null +++ b/diag/tasks/09_gdb/answer.txt @@ -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 (минимальные правки): + ... diff --git a/diag/tasks/09_gdb/crash.c b/diag/tasks/09_gdb/crash.c new file mode 100644 index 0000000..1299d03 --- /dev/null +++ b/diag/tasks/09_gdb/crash.c @@ -0,0 +1,21 @@ +/* Задача 09: программа с дефектами. Починить минимально. */ +#include +#include +#include + +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; +} diff --git a/diag/tasks/09_gdb/task.md b/diag/tasks/09_gdb/task.md new file mode 100644 index 0000000..0b129b3 --- /dev/null +++ b/diag/tasks/09_gdb/task.md @@ -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` проверяется ревью (это часть оценки: важен ход разбора, а не только результат). diff --git a/diag/tasks/09_gdb/test_gdb.py b/diag/tasks/09_gdb/test_gdb.py new file mode 100644 index 0000000..860eced --- /dev/null +++ b/diag/tasks/09_gdb/test_gdb.py @@ -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()