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

This commit is contained in:
Kodlo-chan
2026-09-23 16:05:09 +07:00
commit 4cd63b3d4d
40 changed files with 2464 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
# Диагностика уровня — пакет (C/C++, алгоритмы, Linux, сети)
Собран под вакансию Eltex «Разработчик C/C++» (Новосибирск, hh #136775547).
Цель — не «проверить себя на ощупь», а получить машинную оценку по доменам и список дыр.
## Что внутри
- `quiz.json` + `quiz.py` — 32 вопроса по 4 доменам (C/C++ и память, алгоритмы и структуры
данных, Linux/ОС, сети L2/L3 и TCP/IP). 24+ верных — уверенный вход.
- `tasks/` — 9 практических задач, у каждой автопроверка:
| # | Задача | Что проверяется |
|---|--------|-----------------|
| 01 | `01_bits` | битовые операции, без встроенных popcount |
| 02 | `02_list` | связный список: разворот, середина, цикл (Флойд) |
| 03 | `03_ring` | кольцевой буфер, wrap-around, ASAN |
| 04 | `04_ipv4` | разбор IPv4-заголовка из буфера + контрольная сумма |
| 05 | `05_lis` | НВП за O(n log n), тест на 100 000 элементов |
| 06 | `06_threads` | ограниченная очередь: mutex/condvar, ThreadSanitizer |
| 07 | `07_epoll` | TCP-эхо-сервер на epoll: несколько соединений, SIGTERM |
| 08 | `08_bash` | разбор лога доступа: TOTAL/TOP-3/5XX, 110k строк |
| 09 | `09_gdb` | отладка ASAN-падения + письменный разбор в `answer.txt` |
- `grade.py` — прогон задач с автооценкой (`PASS/FAIL`, число проверок, время).
- `solutions/` — **эталонные решения, в руки не отдавать**: они лежат здесь, чтобы
автопроверка не «протухла» (эталон → все PASS, заглушка → FAIL).
## Как проходить
```bash
cd diag
python3 quiz.py # вопросы по одному; ответы — номер варианта 1-4
python3 quiz.py --export # вывести вопросы текстом (можно отвечать в чате)
python3 grade.py # все задачи (с ASAN/UBSAN/TSAN)
python3 grade.py 01 04 # только выбранные
python3 grade.py --fast # без санитайзеров, быстрее
python3 grade.py --submit # результаты + свои файлы в submissions/
```
Порядок: сначала квиз, потом задачи с 01 по 09. Зависание = FAIL (есть таймауты).
Правила честности: без встроенных `popcount`/`reverse`-хелперов, без копирования чужих
решений, `answer.txt` в задаче 09 — обязательная часть (оценивается ход разбора).
## Что считается результатом
- `results_quiz.json`: балл по каждому домену + разбор промахов с пояснениями.
- `results_tasks.json`: по задаче — PASS/FAIL, число проверок, время, полный лог сборки.
- Итог и решение по плану (с каких модулей стартовать) собираются по этим двум файлам.
## Публикация и доступ
- Пакет в каталоге: `/home/pi/eduplan/diag` (мир читает; каталог доступен на исполнение).
- Инбокс для ответов: `/home/pi/eduplan/diag/submissions/` (0777).
- Архив для скачивания: `/home/pi/eduplan/diag/dist/eduplan_diag.tar.gz`.
+158
View File
@@ -0,0 +1,158 @@
#!/usr/bin/env python3
"""Автопроверка практических задач диагностики.
Использование (из каталога diag):
python3 grade.py # проверить все задачи (с санитайзерами)
python3 grade.py 01 04 07 # только указанные
python3 grade.py --fast # без ASAN/UBSAN/TSAN (быстрее, слабее)
python3 grade.py --submit # после проверки скопировать ответы в submissions/
Что требуется от решающего: файл solution.cpp / solution.sh / crash.c + answer.txt,
каждый в своём каталоге tasks/<NN_имя>/.
Результат: results_tasks.json (по задаче: pass, сколько проверок, время, лог).
"""
import json
import os
import shutil
import subprocess
import sys
import time
HERE = os.path.dirname(os.path.abspath(__file__))
TASKS = os.path.join(HERE, "tasks")
SUB = os.path.join(HERE, "submissions")
CXX = os.environ.get("CXX", "g++")
SAN = "-fsanitize=address,undefined -fno-omit-frame-pointer"
FAST = False
# имя задачи: (тип, что запускать, таймаут, флаги компиляции)
SPEC = {
"01_bits": ("cpp", "test_bits.cpp", 180),
"02_list": ("cpp", "test_list.cpp", 180),
"03_ring": ("cpp", "test_ring.cpp", 180),
"04_ipv4": ("cpp", "test_ipv4.cpp", 180),
"05_lis": ("cpp", "test_lis.cpp", 300),
"06_threads": ("cpp", "test_threads.cpp", 600), # собирается с TSan, не с ASAN
"07_epoll": ("bin", "test_epoll.py", 300),
"08_bash": ("bash", "test_bash.py", 300),
"09_gdb": ("py", "test_gdb.py", 300),
}
def sh(cmd, cwd, timeout):
t0 = time.time()
try:
r = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True, timeout=timeout)
return r.returncode, r.stdout + r.stderr, time.time() - t0, False
except subprocess.TimeoutExpired as e:
out = (e.stdout or b"").decode(errors="replace") if isinstance(e.stdout, bytes) else (e.stdout or "")
return None, out + "\n[TIMEOUT] превышено время %s c" % timeout, time.time() - t0, True
def run_task(name, root):
d = os.path.join(root, name)
kind, test, timeout = SPEC[name]
log = []
rc = None
timed_out = False
if kind == "cpp":
flags = ["-std=c++17", "-O2", "-Wall", "-Wextra", "-g"]
extra = []
if name == "06_threads":
flags = ["-std=c++17", "-O1", "-Wall", "-Wextra", "-g", "-pthread"]
extra = [] if FAST else ["-fsanitize=thread"]
elif not FAST:
extra = SAN.split()
cmd = [CXX] + flags + (extra or []) + [test, "-o", "t_" + name]
rc, out, secs, timed_out = sh(cmd, d, timeout)
log.append("$ " + " ".join(cmd) + "\n" + out)
if rc != 0:
return False, log, secs, timed_out
rc, out, secs2, timed_out = sh(["./t_" + name], d, timeout)
log.append("$ ./t_" + name + "\n" + out)
secs += secs2
if "unsupported VMA" in out and not FAST:
# ThreadSanitizer не поддерживает этот размер адресного пространства
# (Raspberry Pi / ядро 6.18, 47-бит VMA) — откатываемся на ASAN.
cmd2 = [CXX, "-std=c++17", "-O1", "-Wall", "-Wextra", "-g", "-pthread"] \
+ SAN.split() + [test, "-o", "t_" + name]
rc, out2, s, to = sh(cmd2, d, timeout)
log.append("$ " + " ".join(cmd2) + "\n# TSan недоступен на этой платформе, откат на ASAN\n" + out2)
if rc == 0:
rc, out2, s2, to = sh(["./t_" + name], d, timeout)
log.append("$ ./t_" + name + "\n" + out2)
secs += s2
elif kind == "bin":
# epoll-сервер: solution.cpp -> бинарь solution, затем python-тест
cmd = [CXX, "-std=c++17", "-O2", "-Wall", "-Wextra", "-g",
"solution.cpp", "-o", "solution"]
rc, out, secs, timed_out = sh(cmd, d, timeout)
log.append("$ " + " ".join(cmd) + "\n" + out)
if rc != 0:
return False, log, secs, timed_out
rc, out, secs2, timed_out = sh([sys.executable, test], d, timeout)
log.append("$ python3 " + test + "\n" + out)
secs += secs2
else: # bash / py — тест сам всё делает
if name == "08_bash":
os.chmod(os.path.join(d, "solution.sh"), 0o755)
rc, out, secs, timed_out = sh([sys.executable, test], d, timeout)
log.append("$ python3 " + test + "\n" + out)
body = log[-1]
passed = (rc == 0) and ("FAIL" not in body) and ("ALL PASS" in body)
return passed, log, secs, timed_out
def main():
global FAST
args = [a for a in sys.argv[1:]]
FAST = "--fast" in args
do_submit = "--submit" in args
names = [a for a in args if a.isdigit() or a in SPEC]
if names:
names = [n for n in SPEC if n.split("_")[0] in names or n in names]
else:
names = list(SPEC)
results = {}
print(f"{'задача':12s} {'итог':7s} {'проверок':9s} {'FAIL':5s} {'время':8s}")
for n in names:
ok, log, secs, to = run_task(n, TASKS)
body = log[-1]
n_ok = body.count("ok ")
n_bad = body.count("FAIL ")
results[n] = {
"pass": ok, "checks_ok": n_ok, "checks_fail": n_bad,
"seconds": round(secs, 2), "timeout": to, "log": log,
}
print(f"{n:12s} {'PASS' if ok else 'FAIL':7s} {n_ok:<9d} {n_bad:<5d} {secs:8.2f}")
total = len(names)
done = sum(1 for n in names if results[n]["pass"])
with open(os.path.join(HERE, "results_tasks.json"), "w", encoding="utf-8") as f:
json.dump({"passed": done, "total": total, "tasks": results}, f,
ensure_ascii=False, indent=2)
print(f"\nИТОГ ЗАДАЧ: {done}/{total} PASS. Подробности: results_tasks.json")
if do_submit:
os.makedirs(SUB, exist_ok=True)
for fn in ("results_quiz.json", "results_tasks.json"):
p = os.path.join(HERE, fn)
if os.path.exists(p):
shutil.copy2(p, os.path.join(SUB, fn))
for n in SPEC:
d = os.path.join(TASKS, n)
for fn in ("solution.cpp", "solution.sh", "crash.c", "answer.txt"):
p = os.path.join(d, fn)
if os.path.exists(p):
shutil.copy2(p, os.path.join(SUB, f"{n}__{fn}"))
for fn in os.listdir(SUB):
os.chmod(os.path.join(SUB, fn), 0o644)
print(f"Ответы скопированы в {SUB}")
sys.exit(0 if done == total else 1)
if __name__ == "__main__":
main()
+193
View File
@@ -0,0 +1,193 @@
1. [C/C++ и системная память] Чему равен sizeof(struct S { char a; int b; char c; }) на 64-битной платформе без pack-атрибутов?
1) 6
2) 9
3) 12
4) 16
2. [C/C++ и системная память] Что означает undefined behavior (UB)?
1) Программа гарантированно завершится с ошибкой
2) Компилятор вправе сгенерировать любой код, программа не обязана иметь смысл
3) Поведение определено стандартом, но зависит от платформы
4) Это предупреждение компилятора, которое можно игнорировать
3. [C/C++ и системная память] std::move(x) — что он делает сам по себе?
1) Перемещает объект и обнуляет исходный
2) Вызывает конструктор перемещения
3) Приводит x к rvalue-ссылке, перемещение делает уже вызванный конструктор/присваивание
4) Освобождает память объекта
4. [C/C++ и системная память] Чем new отличается от malloc?
1) Только синтаксисом
2) malloc работает только со структурами
3) new не может вернуть nullptr
4) new вызывает конструктор, возвращает типизированный указатель и бросает исключение при неудаче
5. [C/C++ и системная память] Класс владеет ресурсом и имеет деструктор, освобождающий его. Копирующий конструктор/присваивание не объявлены. Что будет?
1) Всё нормально, компилятор сам сделает глубокую копию
2) Неявные копии дадут два владельца одного ресурса → double free/утечка
3) Программа не скомпилируется
4) Компилятор сгенерирует перемещение вместо копирования
6. [C/C++ и системная память] Для чего нужен виртуальный деструктор у базового класса?
1) Чтобы ускорить разрушение
2) Он не нужен, деструкторы всегда виртуальны
3) Чтобы поля базы не копировались
4) Чтобы при delete по указателю на базу вызвался деструктор производного класса
7. [C/C++ и системная память] Что делает ключевое слово const после сигнатуры метода (int size() const)?
1) Делает метод виртуальным
2) Запрещает копирование возвращаемого значения
3) Обязывает не менять поля объекта и позволяет вызывать метод на const-объекте
4) Ничего, это оформление
8. [C/C++ и системная память] Определён ли стандартом порядок вычисления аргументов функции (f(a(), b()))?
1) Слева направо
2) Справа налево
3) Не определён (порядок не специфицирован, но вычисления не пересекаются)
4) Зависит только от оптимизаций
9. [Алгоритмы и структуры данных] Средняя сложность поиска в хеш-таблице (при разумной нагрузке)?
1) O(n log n)
2) O(log n)
3) O(n)
4) O(1)
10. [Алгоритмы и структуры данных] Какая структура лучше подходит для задачи «поиск слов по префиксу»?
1) Префиксное дерево (trie)
2) Хеш-таблица
3) Двоичная куча
4) Связный список
11. [Алгоритмы и структуры данных] Сколько сравнений нужно бинарному поиску в худшем случае в отсортированном массиве из 1 000 000 элементов?
1) 20
2) 10
3) 100
4) 1 000
12. [Алгоритмы и структуры данных] При каком условии применим алгоритм Дейкстры?
1) Граф обязательно связный
2) Веса рёбер неотрицательны
3) Граф неориентированный
4) Граф разреженный
13. [Алгоритмы и структуры данных] Какова сложность построения кучи (heapify) из произвольного массива?
1) O(n)
2) O(log n)
3) O(1)
4) O(n log n)
14. [Алгоритмы и структуры данных] Какая из сортировок устойчивая (stable)?
1) Быстрая (quicksort)
2) Пирамидальная (heapsort)
3) Слиянием (merge sort)
4) Выбором (selection sort)
15. [Алгоритмы и структуры данных] Для какого графа возможна топологическая сортировка?
1) Для ориентированного ациклического (DAG)
2) Для любого связного
3) Для двудольного
4) Для полного
16. [Алгоритмы и структуры данных] Как найти цикл в связном списке за O(1) дополнительной памяти?
1) Алгоритм Флойда: медленный и быстрый указатели
2) Хранить посещённые узлы в хеш-таблице
3) Отсортировать список
4) Рекурсивно пройти до nullptr
17. [Linux / ОС] Что делает fork()?
1) Заменяет образ процесса
2) Создаёт поток в том же адресном пространстве
3) Создаёт копию процесса; в родителе возвращает PID ребёнка, в ребёнке — 0
4) Загружает библиотеку в процесс
18. [Linux / ОС] Чем поток отличается от процесса в Linux?
1) Ничем, это синонимы
2) Потоки делят адресное пространство и файловые дескрипторы, но имеют свои стеки
3) Поток всегда быстрее
4) Процессы не могут работать параллельно
19. [Linux / ОС] Что такое процесс-зомби?
1) Процесс, съедающий CPU
2) Завершившийся процесс, чей статус ещё не собран родителем через wait()
3) Процесс без терминала
4) Процесс, потерявший родителя
20. [Linux / ОС] Какой механизм в Linux штатно обслуживает десятки тысяч сетевых соединений в одном потоке?
1) select()
2) Неблокирующие сокеты в цикле опроса
3) epoll
4) fork на каждое соединение
21. [Linux / ОС] Что происходит со страницами памяти при fork()?
1) Сразу полностью копируются
2) Память обнуляется
3) Копируются при первой записи (copy-on-write)
4) Ребёнок получает те же физические страницы без защиты
22. [Linux / ОС] Процесс завершился с кодом 137. Что это значит?
1) Ошибка сегментации
2) Процесс убит сигналом 9 (SIGKILL) — типично OOM-killer или docker kill
3) Не найден файл
4) Процесс вышел штатно
23. [Linux / ОС] Что даёт вызов mmap() на файл?
1) Полное чтение файла в кучу
2) Отображение файла в адресное пространство: работа как с массивом в памяти
3) Блокировку файла
4) Копирование файла
24. [Linux / ОС] Зачем нужен waitpid() родителю?
1) Чтобы приостановить ребёнка
2) Чтобы сменить приоритет ребёнка
3) Чтобы получить доступ к памяти ребёнка
4) Чтобы собрать статус завершения и убрать зомби
25. [Сети L2/L3, TCP/IP] Сколько байт в Ethernet-заголовке кадра без VLAN-тега?
1) 8
2) 14
3) 18
4) 20
26. [Сети L2/L3, TCP/IP] На каком уровне OSI работает обычный коммутатор (switch)?
1) L2
2) L4
3) L3
4) L1
27. [Сети L2/L3, TCP/IP] Что делает протокол ARP?
1) Раздаёт IP-адреса
2) Проверяет целостность пакетов
3) Разрешает доменные имена
4) Сопоставляет IP-адрес и MAC-адрес в локальном сегменте
28. [Сети L2/L3, TCP/IP] Какое поле IPv4-заголовка уменьшается на единицу на каждом маршрутизаторе?
1) Total Length
2) IHL
3) Checksum
4) TTL
29. [Сети L2/L3, TCP/IP] Правильная последовательность установки TCP-соединения?
1) SYN → SYN-ACK → ACK
2) SYN → ACK → SYN-ACK
3) SYN-ACK → SYN → ACK
4) ACK → SYN → SYN-ACK
30. [Сети L2/L3, TCP/IP] Ключевое отличие TCP от UDP?
1) TCP быстрее
2) TCP работает только на L2
3) UDP не поддерживает порты
4) TCP даёт надёжный поток байт с подтверждениями, порядком и повторной передачей
31. [Сети L2/L3, TCP/IP] Что делает VLAN-тег 802.1Q?
1) Добавляет в кадр 4 байта с 12-битным идентификатором VLAN
2) Шифрует кадр
3) Увеличивает MTU
4) Превращает кадр в IP-пакет
32. [Сети L2/L3, TCP/IP] Сколько адресов узлов доступно в подсети /26?
1) 32
2) 126
3) 64
4) 62
Формат ответа: "1-2 2-4 3-1 ..." (номер вопроса - номер варианта)
+398
View File
@@ -0,0 +1,398 @@
{
"meta": {
"title": "Диагностика уровня: C/C++, алгоритмы, Linux, сети",
"pass_hint": "24+ из 32 верных — уверенный вход; 16–23 — база есть, нужен добор; <16 — модуль в план с нуля",
"domains": {
"c_cpp": "C/C++ и системная память",
"algo": "Алгоритмы и структуры данных",
"linux": "Linux / ОС",
"net": "Сети L2/L3, TCP/IP"
}
},
"questions": [
{
"d": "c_cpp",
"q": "Чему равен sizeof(struct S { char a; int b; char c; }) на 64-битной платформе без pack-атрибутов?",
"o": [
"6",
"9",
"12",
"16"
],
"a": 2,
"e": "a = 1 байт + 3 паддинга, b = 4, c = 1 + 3 хвостового паддинга = 12 (выравнивание структуры по 4)."
},
{
"d": "c_cpp",
"q": "Что означает undefined behavior (UB)?",
"o": [
"Программа гарантированно завершится с ошибкой",
"Компилятор вправе сгенерировать любой код, программа не обязана иметь смысл",
"Поведение определено стандартом, но зависит от платформы",
"Это предупреждение компилятора, которое можно игнорировать"
],
"a": 1,
"e": "UB — стандарт не накладывает требований: компилятор может что угодно, вплоть до выкидывания проверок."
},
{
"d": "c_cpp",
"q": "std::move(x) — что он делает сам по себе?",
"o": [
"Перемещает объект и обнуляет исходный",
"Вызывает конструктор перемещения",
"Приводит x к rvalue-ссылке, перемещение делает уже вызванный конструктор/присваивание",
"Освобождает память объекта"
],
"a": 2,
"e": "move — это static_cast к rvalue; фактическое перемещение выполняет конструктор/оператор присваивания."
},
{
"d": "c_cpp",
"q": "Чем new отличается от malloc?",
"o": [
"Только синтаксисом",
"malloc работает только со структурами",
"new не может вернуть nullptr",
"new вызывает конструктор, возвращает типизированный указатель и бросает исключение при неудаче"
],
"a": 3,
"e": "new = выделение + конструктор + типизированный указатель + исключение; malloc возвращает void* и nullptr при неудаче."
},
{
"d": "c_cpp",
"q": "Класс владеет ресурсом и имеет деструктор, освобождающий его. Копирующий конструктор/присваивание не объявлены. Что будет?",
"o": [
"Всё нормально, компилятор сам сделает глубокую копию",
"Неявные копии дадут два владельца одного ресурса → double free/утечка",
"Программа не скомпилируется",
"Компилятор сгенерирует перемещение вместо копирования"
],
"a": 1,
"e": "Правило трёх/пяти: владеющий ресурсом класс должен определить копирование/перемещение или запретить их."
},
{
"d": "c_cpp",
"q": "Для чего нужен виртуальный деструктор у базового класса?",
"o": [
"Чтобы ускорить разрушение",
"Он не нужен, деструкторы всегда виртуальны",
"Чтобы поля базы не копировались",
"Чтобы при delete по указателю на базу вызвался деструктор производного класса"
],
"a": 3,
"e": "Без virtual-деструктора delete через Base* — UB, деструктор производного не вызовется."
},
{
"d": "c_cpp",
"q": "Что делает ключевое слово const после сигнатуры метода (int size() const)?",
"o": [
"Делает метод виртуальным",
"Запрещает копирование возвращаемого значения",
"Обязывает не менять поля объекта и позволяет вызывать метод на const-объекте",
"Ничего, это оформление"
],
"a": 2,
"e": "const-метод получает this как const-указатель; на изменении поля — ошибка компиляции."
},
{
"d": "c_cpp",
"q": "Определён ли стандартом порядок вычисления аргументов функции (f(a(), b()))?",
"o": [
"Слева направо",
"Справа налево",
"Не определён (порядок не специфицирован, но вычисления не пересекаются)",
"Зависит только от оптимизаций"
],
"a": 2,
"e": "Порядок аргументов не специфицирован; с C++17 вычисления не чередуются (indeterminately sequenced), но порядок всё равно не задан."
},
{
"d": "algo",
"q": "Средняя сложность поиска в хеш-таблице (при разумной нагрузке)?",
"o": [
"O(n log n)",
"O(log n)",
"O(n)",
"O(1)"
],
"a": 3,
"e": "Амортизированно O(1) в среднем; худший случай при всех коллизиях — O(n)."
},
{
"d": "algo",
"q": "Какая структура лучше подходит для задачи «поиск слов по префиксу»?",
"o": [
"Префиксное дерево (trie)",
"Хеш-таблица",
"Двоичная куча",
"Связный список"
],
"a": 0,
"e": "Trie даёт проход по префиксу за O(len) и общий префикс хранит один раз."
},
{
"d": "algo",
"q": "Сколько сравнений нужно бинарному поиску в худшем случае в отсортированном массиве из 1 000 000 элементов?",
"o": [
"20",
"10",
"100",
"1 000"
],
"a": 0,
"e": "log2(1e6) ≈ 19.9 → 20 итераций."
},
{
"d": "algo",
"q": "При каком условии применим алгоритм Дейкстры?",
"o": [
"Граф обязательно связный",
"Веса рёбер неотрицательны",
"Граф неориентированный",
"Граф разреженный"
],
"a": 1,
"e": "Отрицательные рёбра ломают инвариант «кратчайший путь найден»; тогда Беллман–Форд."
},
{
"d": "algo",
"q": "Какова сложность построения кучи (heapify) из произвольного массива?",
"o": [
"O(n)",
"O(log n)",
"O(1)",
"O(n log n)"
],
"a": 0,
"e": "Просеивание снизу вверх даёт сумму геометрической прогрессии — O(n), хотя каждый push по отдельности O(log n)."
},
{
"d": "algo",
"q": "Какая из сортировок устойчивая (stable)?",
"o": [
"Быстрая (quicksort)",
"Пирамидальная (heapsort)",
"Слиянием (merge sort)",
"Выбором (selection sort)"
],
"a": 2,
"e": "Merge sort сохраняет порядок равных элементов; quicksort/heapsort/selection — нет."
},
{
"d": "algo",
"q": "Для какого графа возможна топологическая сортировка?",
"o": [
"Для ориентированного ациклического (DAG)",
"Для любого связного",
"Для двудольного",
"Для полного"
],
"a": 0,
"e": "Цикл делает линейный порядок невозможным; топосорт — признак DAG."
},
{
"d": "algo",
"q": "Как найти цикл в связном списке за O(1) дополнительной памяти?",
"o": [
"Алгоритм Флойда: медленный и быстрый указатели",
"Хранить посещённые узлы в хеш-таблице",
"Отсортировать список",
"Рекурсивно пройти до nullptr"
],
"a": 0,
"e": "«Черепаха и заяц»: если цикл есть, указатели встретятся."
},
{
"d": "linux",
"q": "Что делает fork()?",
"o": [
"Заменяет образ процесса",
"Создаёт поток в том же адресном пространстве",
"Создаёт копию процесса; в родителе возвращает PID ребёнка, в ребёнке — 0",
"Загружает библиотеку в процесс"
],
"a": 2,
"e": "fork = новый процесс (свой PID, COW-копия памяти); execve меняет образ, clone — поток."
},
{
"d": "linux",
"q": "Чем поток отличается от процесса в Linux?",
"o": [
"Ничем, это синонимы",
"Потоки делят адресное пространство и файловые дескрипторы, но имеют свои стеки",
"Поток всегда быстрее",
"Процессы не могут работать параллельно"
],
"a": 1,
"e": "clone с CLONE_VM/CLONE_FILES — потоки; у каждого свой стек и регистры."
},
{
"d": "linux",
"q": "Что такое процесс-зомби?",
"o": [
"Процесс, съедающий CPU",
"Завершившийся процесс, чей статус ещё не собран родителем через wait()",
"Процесс без терминала",
"Процесс, потерявший родителя"
],
"a": 1,
"e": "Запись остаётся в таблице процессов до wait/waitpid; «потерявший родителя» — это сирота (переходит к init)."
},
{
"d": "linux",
"q": "Какой механизм в Linux штатно обслуживает десятки тысяч сетевых соединений в одном потоке?",
"o": [
"select()",
"Неблокирующие сокеты в цикле опроса",
"epoll",
"fork на каждое соединение"
],
"a": 2,
"e": "epoll — O(1) на готовое событие; select линейно по всем дескрипторам."
},
{
"d": "linux",
"q": "Что происходит со страницами памяти при fork()?",
"o": [
"Сразу полностью копируются",
"Память обнуляется",
"Копируются при первой записи (copy-on-write)",
"Ребёнок получает те же физические страницы без защиты"
],
"a": 2,
"e": "COW: страницы помечаются read-only и копируются по факту записи."
},
{
"d": "linux",
"q": "Процесс завершился с кодом 137. Что это значит?",
"o": [
"Ошибка сегментации",
"Процесс убит сигналом 9 (SIGKILL) — типично OOM-killer или docker kill",
"Не найден файл",
"Процесс вышел штатно"
],
"a": 1,
"e": "137 = 128 + 9: завершение по SIGKILL; код 139 = 128 + 11 (SIGSEGV)."
},
{
"d": "linux",
"q": "Что даёт вызов mmap() на файл?",
"o": [
"Полное чтение файла в кучу",
"Отображение файла в адресное пространство: работа как с массивом в памяти",
"Блокировку файла",
"Копирование файла"
],
"a": 1,
"e": "Страницы подгружаются по обращению; запись уходит в файл (или в MAP_SHARED-память)."
},
{
"d": "linux",
"q": "Зачем нужен waitpid() родителю?",
"o": [
"Чтобы приостановить ребёнка",
"Чтобы сменить приоритет ребёнка",
"Чтобы получить доступ к памяти ребёнка",
"Чтобы собрать статус завершения и убрать зомби"
],
"a": 3,
"e": "Без wait завершившиеся дети остаются зомби и держат запись в таблице процессов."
},
{
"d": "net",
"q": "Сколько байт в Ethernet-заголовке кадра без VLAN-тега?",
"o": [
"8",
"14",
"18",
"20"
],
"a": 1,
"e": "dst MAC (6) + src MAC (6) + EtherType (2) = 14; VLAN 802.1Q добавляет 4 байта."
},
{
"d": "net",
"q": "На каком уровне OSI работает обычный коммутатор (switch)?",
"o": [
"L2",
"L4",
"L3",
"L1"
],
"a": 0,
"e": "Коммутация по MAC-адресам и таблице обучения — канальный уровень (L2); маршрутизатор — L3."
},
{
"d": "net",
"q": "Что делает протокол ARP?",
"o": [
"Раздаёт IP-адреса",
"Проверяет целостность пакетов",
"Разрешает доменные имена",
"Сопоставляет IP-адрес и MAC-адрес в локальном сегменте"
],
"a": 3,
"e": "ARP-запрос broadcast, ответ unicast; DHCP раздаёт адреса, DNS — имена."
},
{
"d": "net",
"q": "Какое поле IPv4-заголовка уменьшается на единицу на каждом маршрутизаторе?",
"o": [
"Total Length",
"IHL",
"Checksum",
"TTL"
],
"a": 3,
"e": "TTL защищает от петель; при 0 пакет отбрасывается и уходит ICMP Time Exceeded. Контрольная сумма при этом пересчитывается, но не «уменьшается на 1»."
},
{
"d": "net",
"q": "Правильная последовательность установки TCP-соединения?",
"o": [
"SYN → SYN-ACK → ACK",
"SYN → ACK → SYN-ACK",
"SYN-ACK → SYN → ACK",
"ACK → SYN → SYN-ACK"
],
"a": 0,
"e": "Клиент SYN, сервер SYN-ACK, клиент ACK; номера подтверждения = seq+1."
},
{
"d": "net",
"q": "Ключевое отличие TCP от UDP?",
"o": [
"TCP быстрее",
"TCP работает только на L2",
"UDP не поддерживает порты",
"TCP даёт надёжный поток байт с подтверждениями, порядком и повторной передачей"
],
"a": 3,
"e": "UDP — дейтаграммы «как есть», без гарантий и порядка."
},
{
"d": "net",
"q": "Что делает VLAN-тег 802.1Q?",
"o": [
"Добавляет в кадр 4 байта с 12-битным идентификатором VLAN",
"Шифрует кадр",
"Увеличивает MTU",
"Превращает кадр в IP-пакет"
],
"a": 0,
"e": "Тег EtherType 0x8100 + PCP/DEI + 12 бит VID; логически разделяет L2-сегменты."
},
{
"d": "net",
"q": "Сколько адресов узлов доступно в подсети /26?",
"o": [
"32",
"126",
"64",
"62"
],
"a": 3,
"e": "/26 = 64 адреса, минус адрес сети и broadcast → 62 хоста."
}
]
}
+125
View File
@@ -0,0 +1,125 @@
#!/usr/bin/env python3
"""Диагностический квиз: C/C++, алгоритмы, Linux, сети.
Режимы:
python3 quiz.py — пройти интерактивно (по одной строке на ответ: номер варианта 1-4)
python3 quiz.py --export — вывести вопросы текстом (можно отвечать в чате в формате "1-2 2-4 ...")
python3 quiz.py --answers "1-2 2-4 3-1" — проверить ответы, переданные строкой
Результат пишется в results_quiz.json (там же разбор по доменам).
"""
import json
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
QUIZ = os.path.join(HERE, "quiz.json")
def load():
with open(QUIZ, encoding="utf-8") as f:
return json.load(f)
def export(data):
for i, q in enumerate(data["questions"], 1):
print(f"{i}. [{data['meta']['domains'][q['d']]}] {q['q']}")
for j, o in enumerate(q["o"], 1):
print(f" {j}) {o}")
print()
print('Формат ответа: "1-2 2-4 3-1 ..." (номер вопроса - номер варианта)')
def parse_answers(s):
res = {}
for tok in s.replace(",", " ").split():
if "-" not in tok:
continue
k, v = tok.split("-", 1)
try:
res[int(k)] = int(v)
except ValueError:
continue
return res
def grade(data, answers):
per_domain = {}
details = []
for i, q in enumerate(data["questions"], 1):
d = q["d"]
per_domain.setdefault(d, {"total": 0, "correct": 0})
per_domain[d]["total"] += 1
got = answers.get(i)
ok = got == q["a"] + 1
if ok:
per_domain[d]["correct"] += 1
details.append({
"n": i, "domain": d, "question": q["q"],
"your_answer": got, "correct_answer": q["a"] + 1,
"ok": ok, "explain": q["e"],
})
return per_domain, details
def report(data, per_domain, details):
total = len(details)
correct = sum(1 for x in details if x["ok"])
print(f"\n=== ИТОГ КВИЗА: {correct}/{total} ===")
for d, st in per_domain.items():
pct = round(100 * st["correct"] / st["total"])
if pct >= 80:
verdict = "крепко"
elif pct >= 60:
verdict = "база есть, нужен добор"
else:
verdict = "дыра, идёт в план с нуля"
print(f" {data['meta']['domains'][d]:38s} {st['correct']}/{st['total']} ({pct}%) — {verdict}")
wrong = [x for x in details if not x["ok"]]
if wrong:
print("\nРазбор промахов:")
for x in wrong:
print(f" {x['n']}. {x['question']}")
print(f" ты: {x['your_answer'] or '—'} | верно: {x['correct_answer']}. {x['explain']}")
return correct, total
def main():
data = load()
args = sys.argv[1:]
if "--export" in args:
export(data)
return
if "--answers" in args:
s = args[args.index("--answers") + 1]
answers = parse_answers(s)
else:
answers = {}
for i, q in enumerate(data["questions"], 1):
print(f"\n{i}. [{data['meta']['domains'][q['d']]}] {q['q']}")
for j, o in enumerate(q["o"], 1):
print(f" {j}) {o}")
while True:
raw = input("ответ (1-4, пусто = пропустить, q = выход): ").strip().lower()
if raw == "q":
break
if raw == "":
break
if raw in {"1", "2", "3", "4"}:
answers[i] = int(raw)
break
if raw == "q":
break
per_domain, details = grade(data, answers)
correct, total = report(data, per_domain, details)
out = {
"correct": correct, "total": total,
"per_domain": per_domain,
"details": details,
}
with open(os.path.join(HERE, "results_quiz.json"), "w", encoding="utf-8") as f:
json.dump(out, f, ensure_ascii=False, indent=2)
print(f"\nРезультат сохранён: {os.path.join(HERE, 'results_quiz.json')}")
if __name__ == "__main__":
main()
+27
View File
@@ -0,0 +1,27 @@
// Задача 01 — битовые операции. Заполни реализации.
#include <cstdint>
#include <cstddef>
// число единичных бит в 32-битном слове
int popcount32(uint32_t x) {
(void)x;
return 0; // TODO
}
// развернуть порядок следования всех 32 бит
uint32_t reverse_bits32(uint32_t x) {
(void)x;
return 0; // TODO
}
// true, если x — степень двойки; 0 — не степень двойки
bool is_power_of_two(uint32_t x) {
(void)x;
return false; // TODO
}
// поменять местами байты: 0x11223344 -> 0x44332211
uint32_t bswap32(uint32_t x) {
(void)x;
return 0; // TODO
}
+18
View File
@@ -0,0 +1,18 @@
# Задача 01 — битовые операции (C)
Реализуй в `solution.cpp` четыре функции. Запрещено пользоваться встроенными
`__builtin_popcount`, `std::popcount`, `std::byteswap` — считается любая корректная
ручная реализация.
```c
int popcount32(uint32_t x); // число единичных бит
uint32_t reverse_bits32(uint32_t x); // развернуть порядок всех 32 бит
bool is_power_of_two(uint32_t x); // x — степень двойки (0 не считается)
uint32_t bswap32(uint32_t x); // поменять порядок байт местами
```
Проверка: `python3 grade.py 01` из каталога `diag/`.
Критерий: все проверки `ok`, сборка без предупреждений, ASAN/UBSAN чистые.
Подсказки по разбору после сдачи: `x & (x - 1)`; реверс — классический цикл на 32 итерации
или пара «маска+сдвиг»; `bswap` — четыре маски.
+41
View File
@@ -0,0 +1,41 @@
#include <cstdio>
#include <cstdint>
#include "solution.cpp"
static int failures = 0;
#define CHECK(cond, name) do { if (cond) std::printf("ok %s\n", name); \
else { std::printf("FAIL %s (line %d)\n", name, __LINE__); ++failures; } } while (0)
int main() {
// popcount
CHECK(popcount32(0u) == 0, "popcount(0) == 0");
CHECK(popcount32(1u) == 1, "popcount(1) == 1");
CHECK(popcount32(0xFFFFFFFFu) == 32, "popcount(0xFFFFFFFF) == 32");
CHECK(popcount32(0x80000000u) == 1, "popcount(1<<31) == 1");
CHECK(popcount32(0x0000000Bu) == 3, "popcount(0b1011) == 3");
CHECK(popcount32(0xA5A5A5A5u) == 16, "popcount(0xA5A5A5A5) == 16");
// reverse
CHECK(reverse_bits32(1u) == 0x80000000u, "reverse(1) == 0x80000000");
CHECK(reverse_bits32(0x80000000u) == 1u, "reverse(0x80000000) == 1");
CHECK(reverse_bits32(0x12345678u) == 0x1E6A2C48u, "reverse(0x12345678) == 0x1E6A2C48");
CHECK(reverse_bits32(0xFFFFFFFFu) == 0xFFFFFFFFu, "reverse(0xFFFFFFFF) == 0xFFFFFFFF");
CHECK(reverse_bits32(0u) == 0u, "reverse(0) == 0");
// power of two
CHECK(!is_power_of_two(0u), "pow2(0) == false");
CHECK(is_power_of_two(1u), "pow2(1) == true");
CHECK(is_power_of_two(2u), "pow2(2) == true");
CHECK(!is_power_of_two(3u), "pow2(3) == false");
CHECK(is_power_of_two(1024u), "pow2(1024) == true");
CHECK(is_power_of_two(0x80000000u), "pow2(1<<31) == true");
CHECK(!is_power_of_two(0x80000001u), "pow2(0x80000001) == false");
// bswap
CHECK(bswap32(0x11223344u) == 0x44332211u, "bswap(0x11223344) == 0x44332211");
CHECK(bswap32(0x000000FFu) == 0xFF000000u, "bswap(0x000000FF) == 0xFF000000");
CHECK(bswap32(0xDEADBEEFu) == 0xEFBEADDEu, "bswap(0xDEADBEEF) == 0xEFBEADDE");
std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures);
return failures ? 1 : 0;
}
+25
View File
@@ -0,0 +1,25 @@
// Задача 02 — связный список. Заполни реализации.
#include <cstddef>
struct Node {
int value;
Node* next;
};
// Развернуть список на месте. Вернуть новую голову.
Node* reverse_list(Node* head) {
(void)head;
return nullptr; // TODO
}
// Середина списка: для чётного количества — второй из двух средних узлов.
Node* find_middle(Node* head) {
(void)head;
return nullptr; // TODO
}
// Есть ли цикл в списке.
bool has_cycle(Node* head) {
(void)head;
return false; // TODO
}
+17
View File
@@ -0,0 +1,17 @@
# Задача 02 — связный список (C++)
Даны функции над односвязным списком `Node{int value; Node* next;}`:
```c
Node* reverse_list(Node* head); // развернуть список на месте, вернуть новую голову
Node* find_middle(Node* head); // середина; для чётной длины — второй из двух средних
bool has_cycle(Node* head); // есть ли цикл
```
Требования:
- никаких аллокаций, O(1) дополнительной памяти, один проход там, где это возможно;
- `find_middle` и `has_cycle` — через два указателя (медленный/быстрый);
- корректная работа с пустым списком и списком из одного узла.
Проверка: `python3 grade.py 02`. Критерий: все `ok`, ASAN/UBSAN чистые (утечки в тесте
считаются ошибкой — тест сам освобождает память).
+94
View File
@@ -0,0 +1,94 @@
#include <cstdio>
#include <vector>
#include "solution.cpp"
static int failures = 0;
#define CHECK(cond, name) do { if (cond) std::printf("ok %s\n", name); \
else { std::printf("FAIL %s (line %d)\n", name, __LINE__); ++failures; } } while (0)
static Node* build(const std::vector<int>& v) {
Node* head = nullptr; Node** tail = &head;
for (int x : v) { *tail = new Node{x, nullptr}; tail = &(*tail)->next; }
return head;
}
static std::vector<int> to_vec(Node* h, size_t limit = 1000) {
std::vector<int> r; while (h && r.size() < limit) { r.push_back(h->value); h = h->next; } return r;
}
static void destroy(Node* h) { while (h) { Node* n = h->next; delete h; h = n; } }
int main() {
// reverse
{
Node* h = build({1, 2, 3, 4, 5});
Node* r = reverse_list(h);
CHECK(to_vec(r) == std::vector<int>({5, 4, 3, 2, 1}), "reverse [1..5] -> [5..1]");
destroy(r);
}
{
Node* h = build({42});
Node* r = reverse_list(h);
CHECK(r && r->value == 42 && r->next == nullptr, "reverse одного узла");
destroy(r);
}
CHECK(reverse_list(nullptr) == nullptr, "reverse пустого списка == nullptr");
{
Node* h = build({7, 8});
Node* r = reverse_list(h);
CHECK(to_vec(r) == std::vector<int>({8, 7}), "reverse двух узлов");
destroy(r);
}
// middle
{
Node* h = build({1, 2, 3, 4, 5, 6});
Node* m = find_middle(h);
CHECK(m && m->value == 4, "middle чётного [1..6] == 4 (второй из двух средних)");
destroy(h);
}
{
Node* h = build({1, 2, 3, 4, 5});
Node* m = find_middle(h);
CHECK(m && m->value == 3, "middle нечётного [1..5] == 3");
destroy(h);
}
{
Node* h = build({9});
CHECK(find_middle(h) && find_middle(h)->value == 9, "middle одного узла == 9");
destroy(h);
}
CHECK(find_middle(nullptr) == nullptr, "middle пустого списка == nullptr");
// cycle
{
Node* h = build({1, 2, 3, 4});
CHECK(!has_cycle(h), "has_cycle(без цикла) == false");
destroy(h);
}
{
Node* h = build({1, 2, 3, 4, 5});
Node* third = h->next->next;
Node* last = h; while (last->next) last = last->next;
last->next = third; // цикл 3->4->5->3
CHECK(has_cycle(h), "has_cycle(цикл внутри) == true");
last->next = nullptr;
destroy(h);
}
{
Node* h = build({1, 2});
Node* last = h->next; last->next = h; // цикл на всю длину
CHECK(has_cycle(h), "has_cycle(цикл на всю длину) == true");
last->next = nullptr;
destroy(h);
}
{
Node* one = new Node{5, nullptr};
one->next = one;
CHECK(has_cycle(one), "has_cycle(сам на себя) == true");
one->next = nullptr;
delete one;
}
CHECK(!has_cycle(nullptr), "has_cycle(nullptr) == false");
std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures);
return failures ? 1 : 0;
}
+29
View File
@@ -0,0 +1,29 @@
// Задача 03 — кольцевой буфер. Заполни реализации.
#include <cstddef>
class RingBuffer {
public:
explicit RingBuffer(size_t capacity) : cap_(capacity), data_(new int[capacity ? capacity : 1]), size_(0) {}
~RingBuffer() { delete[] data_; }
bool push(int v) {
(void)v;
return false; // TODO
}
bool pop(int& out) {
(void)out;
return false; // TODO
}
size_t size() const { return size_; }
size_t capacity() const { return cap_; }
bool empty() const { return size_ == 0; }
bool full() const { return size_ == cap_; }
private:
size_t cap_;
int* data_;
size_t size_;
// добавь head_/tail_ по необходимости
};
+25
View File
@@ -0,0 +1,25 @@
# Задача 03 — кольцевой буфер (C++)
Кольцевой буфер фиксированной ёмкости — базовая структура для embedded и сетевого кода.
```c++
class RingBuffer {
public:
explicit RingBuffer(size_t capacity);
~RingBuffer();
bool push(int v); // false, если буфер полон (перезапись НЕ делать)
bool pop(int& out); // false, если буфер пуст
size_t size() const;
size_t capacity() const;
bool empty() const;
bool full() const;
};
```
Требования:
- O(1) на push/pop, никаких сдвигов элементов;
- память выделяется один раз в конструкторе, освобождается в деструкторе;
- поведение после заворачивания индексов (wrap-around) корректное;
- копирование запрещать не обязательно, но двойного освобождения быть не должно.
Проверка: `python3 grade.py 03`. Критерий: все `ok`, ASAN/UBSAN чистые.
+46
View File
@@ -0,0 +1,46 @@
#include <cstdio>
#include <vector>
#include "solution.cpp"
static int failures = 0;
#define CHECK(cond, name) do { if (cond) std::printf("ok %s\n", name); \
else { std::printf("FAIL %s (line %d)\n", name, __LINE__); ++failures; } } while (0)
int main() {
{
RingBuffer rb(4);
CHECK(rb.capacity() == 4 && rb.size() == 0 && rb.empty() && !rb.full(), "старт: пуст, ёмкость 4");
CHECK(rb.push(1) && rb.push(2) && rb.push(3) && rb.push(4), "4 push прошли");
CHECK(rb.full() && rb.size() == 4, "после 4 push буфер полон");
CHECK(!rb.push(5), "push в полный буфер -> false");
int v = -1;
CHECK(rb.pop(v) && v == 1, "FIFO: первый pop == 1");
CHECK(rb.pop(v) && v == 2, "FIFO: второй pop == 2");
CHECK(rb.size() == 2 && !rb.full(), "после двух pop размер 2");
CHECK(rb.push(5) && rb.push(6), "push после pop (заворачивание)");
int expect[] = {3, 4, 5, 6};
bool ok = true;
for (int i = 0; i < 4; ++i) { int x = -1; ok = ok && rb.pop(x) && x == expect[i]; }
CHECK(ok, "wrap-around: порядок 3,4,5,6");
CHECK(rb.empty() && !rb.pop(v), "пустой буфер: pop -> false");
}
{
RingBuffer rb(1);
int v = 0;
CHECK(rb.push(11) && !rb.push(12), "ёмкость 1: второй push отклонён");
CHECK(rb.pop(v) && v == 11, "ёмкость 1: pop вернул 11");
CHECK(rb.pop(v) == false, "ёмкость 1: второй pop -> false");
}
{
RingBuffer rb(8);
int cnt = 0;
for (int i = 0; i < 1000; ++i) {
if (rb.push(i)) cnt++;
if (i % 3 == 0) { int x = -1; if (rb.pop(x)) cnt--; }
}
CHECK(cnt == (int)rb.size(), "длинный прогон: счётчик push/pop сходится с size()");
CHECK(rb.size() <= 8, "длинный прогон: размер не превышает ёмкость");
}
std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures);
return failures ? 1 : 0;
}
+28
View File
@@ -0,0 +1,28 @@
// Задача 04 — разбор IPv4-пакета. Заполни реализации.
#include <cstdint>
#include <cstddef>
struct Ipv4Header {
uint8_t version;
uint8_t ihl;
uint8_t protocol;
uint16_t total_length;
uint32_t src_ip;
uint32_t dst_ip;
uint16_t header_checksum;
};
bool parse_ipv4(const uint8_t* buf, size_t len, Ipv4Header* out) {
(void)buf; (void)len; (void)out;
return false; // TODO
}
uint16_t compute_checksum(const uint8_t* buf, size_t len) {
(void)buf; (void)len;
return 0; // TODO
}
bool checksum_valid(const uint8_t* buf, size_t len) {
(void)buf; (void)len;
return false; // TODO
}
+32
View File
@@ -0,0 +1,32 @@
# Задача 04 — разбор IPv4-пакета и контрольная сумма (C++)
Это то, что реально делают сетевые железки: взять буфер из сокета и корректно разобрать
заголовок, не выйдя за границы и не поверив «на слово» полям пакета.
```c++
struct Ipv4Header {
uint8_t version; // должно быть 4
uint8_t ihl; // длина заголовка в 32-битных словах
uint8_t protocol; // 6 = TCP, 17 = UDP, 1 = ICMP
uint16_t total_length; // из заголовка (хост-порядок)
uint32_t src_ip; // байты как на проводе: 10.0.0.1 -> 0x0A000001
uint32_t dst_ip;
uint16_t header_checksum; // поле из заголовка (хост-порядок)
};
// false: буфер короче 20 байт, version != 4, ihl < 5, ihl*4 > len,
// total_length < ihl*4 или total_length > len
bool parse_ipv4(const uint8_t* buf, size_t len, Ipv4Header* out);
// сумма в дополнительном коде по len байтам, затем инверсия — значение для записи в поле
// (вызывается на буфере, где поле контрольной суммы уже обнулено)
uint16_t compute_checksum(const uint8_t* buf, size_t len);
// true, если контрольная сумма заголовка верна: сумма в дополнительном коде по ihl*4 байтам
// заголовка (вместе с полем контрольной суммы) равна 0xFFFF; нагрузка не участвует
bool checksum_valid(const uint8_t* buf, size_t len);
```
Проверка: `python3 grade.py 04`. Критерий: все `ok`, ASAN/UBSAN чистые.
Запрещено приводить буфер к структуре через `reinterpret_cast` и читать поля как есть —
на невыровненных адресах и в другом порядке байт это UB.
+100
View File
@@ -0,0 +1,100 @@
#include <cstdio>
#include <cstring>
#include <vector>
#include "solution.cpp"
static int failures = 0;
#define CHECK(cond, name) do { if (cond) std::printf("ok %s\n", name); \
else { std::printf("FAIL %s (line %d)\n", name, __LINE__); ++failures; } } while (0)
// Независимая реализация суммы в дополнительном коде для проверки тестов.
static uint16_t ref_sum(const uint8_t* b, size_t n, uint32_t init) {
uint32_t acc = init;
for (size_t i = 0; i + 1 < n; i += 2) acc += (uint32_t(b[i]) << 8) | b[i + 1];
if (n % 2) acc += uint32_t(b[n - 1]) << 8;
while (acc >> 16) acc = (acc & 0xFFFF) + (acc >> 16);
return uint16_t(acc);
}
// Пакет: заголовок (ihl=5) + payload_len байт нагрузки.
static std::vector<uint8_t> make_packet(size_t payload_len, uint8_t proto, bool with_options = false,
bool corrupt_checksum = false, uint16_t force_total = 0) {
size_t hlen = with_options ? 24 : 20;
std::vector<uint8_t> p(hlen + payload_len, 0);
p[0] = 0x40 | uint8_t(hlen / 4); // version 4, ihl
p[1] = 0x00; // DSCP/ECN
uint16_t total = force_total ? force_total : uint16_t(hlen + payload_len);
p[2] = uint8_t(total >> 8); p[3] = uint8_t(total & 0xFF);
p[4] = 0x12; p[5] = 0x34; // id
p[6] = 0x40; p[7] = 0x00; // DF
p[8] = 64; // TTL
p[9] = proto;
p[10] = 0; p[11] = 0; // checksum = 0 для расчёта
p[12] = 0x0A; p[13] = 0x00; p[14] = 0x00; p[15] = 0x01; // 10.0.0.1
p[16] = 0x0A; p[17] = 0x00; p[18] = 0x00; p[19] = 0x02; // 10.0.0.2
if (with_options) { p[20] = 0x01; p[21] = 0x01; p[22] = 0x00; p[23] = 0x00; }
for (size_t i = 0; i < payload_len; ++i) p[hlen + i] = uint8_t('A' + (i % 26));
uint16_t cs = uint16_t(~ref_sum(p.data(), hlen, 0));
p[10] = uint8_t(cs >> 8); p[11] = uint8_t(cs & 0xFF);
if (corrupt_checksum) p[10] ^= 0xFF;
return p;
}
int main() {
// parse: обычный пакет
{
auto p = make_packet(10, 6);
Ipv4Header h{};
bool ok = parse_ipv4(p.data(), p.size(), &h);
CHECK(ok, "parse() обычного пакета -> true");
CHECK(h.version == 4, "parse: version == 4");
CHECK(h.ihl == 5, "parse: ihl == 5");
CHECK(h.protocol == 6, "parse: protocol == 6 (TCP)");
CHECK(h.total_length == 30, "parse: total_length == 30");
CHECK(h.src_ip == 0x0A000001u, "parse: src_ip == 10.0.0.1 (порядок байт как на проводе)");
CHECK(h.dst_ip == 0x0A000002u, "parse: dst_ip == 10.0.0.2");
}
// parse: с опциями (ihl=6)
{
auto p = make_packet(0, 17, true);
Ipv4Header h{};
bool ok = parse_ipv4(p.data(), p.size(), &h);
CHECK(ok && h.ihl == 6 && h.total_length == 24, "parse: ihl=6 и total_length=24 (с опциями)");
}
// parse: отрицательные случаи
{
auto p = make_packet(10, 6);
Ipv4Header h{};
CHECK(!parse_ipv4(p.data(), 10, &h), "parse: обрезанный буфер (10 из 30) -> false");
CHECK(!parse_ipv4(nullptr, 0, &h), "parse: пустой буфер -> false");
auto v6 = p; v6[0] = 0x60;
CHECK(!parse_ipv4(v6.data(), v6.size(), &h), "parse: version 6 -> false");
auto bad_ihl = p; bad_ihl[0] = 0x43; // ihl=3
CHECK(!parse_ipv4(bad_ihl.data(), bad_ihl.size(), &h), "parse: ihl < 5 -> false");
auto too_big = make_packet(10, 6, false, false, 100); // total_length больше буфера
CHECK(!parse_ipv4(too_big.data(), too_big.size(), &h), "parse: total_length > len -> false");
auto small_total = make_packet(10, 6, false, false, 12); // меньше самого заголовка
CHECK(!parse_ipv4(small_total.data(), small_total.size(), &h), "parse: total_length < ihl*4 -> false");
}
// checksum
{
auto p = make_packet(10, 6);
auto pz = p; pz[10] = 0; pz[11] = 0; // поле обнулено — так считают контрольную сумму
uint16_t cs = compute_checksum(pz.data(), 20);
CHECK(cs == uint16_t(~ref_sum(pz.data(), 20, 0)), "compute_checksum: совпадает с эталонным расчётом");
CHECK(cs == uint16_t((p[10] << 8) | p[11]), "compute_checksum: значение равно полю в корректном пакете");
CHECK(checksum_valid(p.data(), p.size()), "checksum_valid(корректный пакет) -> true");
auto bad = make_packet(10, 6, false, true);
CHECK(!checksum_valid(bad.data(), bad.size()), "checksum_valid(испорченная сумма) -> false");
auto bad_ttl = p; bad_ttl[8] ^= 0x01;
CHECK(!checksum_valid(bad_ttl.data(), bad_ttl.size()), "checksum_valid(испорчен TTL) -> false");
auto bad_payload = p; bad_payload[24] ^= 0xFF;
CHECK(checksum_valid(bad_payload.data(), bad_payload.size()),
"checksum_valid: нагрузка в контрольную сумму заголовка не входит -> true");
// нечётная длина
uint8_t odd[3] = {0x01, 0x02, 0x03};
CHECK(compute_checksum(odd, 3) == uint16_t(~ref_sum(odd, 3, 0)), "compute_checksum: нечётная длина");
}
std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures);
return failures ? 1 : 0;
}
+7
View File
@@ -0,0 +1,7 @@
// Задача 05 — длина НВП за O(n log n). Заполни реализацию.
#include <vector>
int lis_length(const std::vector<int>& a) {
(void)a;
return 0; // TODO
}
+14
View File
@@ -0,0 +1,14 @@
# Задача 05 — длиннейшая возрастающая подпоследовательность (C++)
```c++
int lis_length(const std::vector<int>& a); // длина НВП (строго возрастающей)
```
Требования:
- пустая последовательность → 0;
- работа за O(n log n); наивное O(n²) не пройдёт проверку на 100 000 элементах
(внутренний тест меряет время и роняет прогон при > 2 с);
- подпоследовательность не обязана быть непрерывной.
Проверка: `python3 grade.py 05`. Критерий: все `ok`, сборка без предупреждений.
Разбор после сдачи: «хвосты» — массив минимальных последних элементов + lower_bound.
+47
View File
@@ -0,0 +1,47 @@
#include <cstdio>
#include <vector>
#include <random>
#include <chrono>
#include <algorithm>
#include "solution.cpp"
static int failures = 0;
#define CHECK(cond, name) do { if (cond) std::printf("ok %s\n", name); \
else { std::printf("FAIL %s (line %d)\n", name, __LINE__); ++failures; } } while (0)
int main() {
CHECK(lis_length({}) == 0, "пустая -> 0");
CHECK(lis_length({5}) == 1, "один элемент -> 1");
CHECK(lis_length({1, 2, 3}) == 3, "возрастающая -> вся длина");
CHECK(lis_length({3, 2, 1}) == 1, "убывающая -> 1");
CHECK(lis_length({10, 9, 2, 5, 3, 7, 101, 18}) == 4, "классика -> 4");
CHECK(lis_length({7, 7, 7}) == 1, "равные элементы -> строго возрастающая длина 1");
CHECK(lis_length({0, 1, 0, 3, 2, 3}) == 4, "лекод-пример -> 4");
CHECK(lis_length({2, 2, 2, 3}) == 2, "дубли + рост -> 2");
CHECK(lis_length({-5, -4, -3, -2}) == 4, "отрицательные -> 4");
// производительность: худший случай для наивного DP
{
std::vector<int> big(100000);
for (int i = 0; i < 100000; ++i) big[i] = i;
auto t0 = std::chrono::steady_clock::now();
int r = lis_length(big);
double sec = std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
std::printf(" (100k возрастающих: result=%d, time=%.3fs)\n", r, sec);
CHECK(r == 100000, "100k возрастающих -> 100000");
CHECK(sec < 2.0, "100k за < 2 c (требуется O(n log n))");
}
{
std::mt19937 rng(12345);
std::vector<int> big(100000);
for (auto& x : big) x = int(rng() % 100000);
auto t0 = std::chrono::steady_clock::now();
int r = lis_length(big);
double sec = std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
std::printf(" (100k случайных: result=%d, time=%.3fs)\n", r, sec);
CHECK(r > 300 && r < 800, "100k случайных: результат в разумном диапазоне");
CHECK(sec < 2.0, "100k случайных за < 2 c");
}
std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures);
return failures ? 1 : 0;
}
+29
View File
@@ -0,0 +1,29 @@
// Задача 06 — потокобезопасная очередь. Заполни реализацию.
#include <cstddef>
#include <stdexcept>
class BlockingQueue {
public:
explicit BlockingQueue(size_t capacity) : cap_(capacity) {}
~BlockingQueue() = default;
void push(int v) {
(void)v;
// TODO: должен блокироваться на полной очереди и бросать std::runtime_error после close()
throw std::runtime_error("push: not implemented");
}
bool pop(int& out) {
(void)out;
return false; // TODO
}
void close() {
// TODO
}
size_t size() const { return 0; }
private:
size_t cap_;
};
+27
View File
@@ -0,0 +1,27 @@
# Задача 06 — потокобезопасная ограниченная очередь (C++)
Классический вопрос на собеседовании в embedded/сетевую разработку: не «знаешь ли ты
std::thread», а «умеешь ли ты не сломать счётчик под нагрузкой».
```c++
class BlockingQueue {
public:
explicit BlockingQueue(size_t capacity);
~BlockingQueue();
void push(int v); // блокируется, пока очередь полна
bool pop(int& out); // блокируется, пока пуста; false — если закрыта и пуста
void close(); // после close: pop() опустошает остаток и отдаёт false
size_t size() const;
};
```
Требования:
- push после close() бросает `std::runtime_error`;
- закрытие разблокирует все ждущие потоки (никакого вечного ожидания и busy-wait);
- размер очереди никогда не превышает capacity;
- ни одной гонки: тест собирается и гоняется с ThreadSanitizer (в том числе `size()`
вызывается из другого потока — он тоже должен быть потокобезопасным).
Проверка: `python3 grade.py 06`. Критерий: все `ok`, TSan чистый, нет зависания.
Разбор после сдачи: почему нужен predicate-цикл вокруг wait, и как один condition_variable
на оба события даёт ложные пробуждения.
+73
View File
@@ -0,0 +1,73 @@
#include <cstdio>
#include <thread>
#include <vector>
#include <atomic>
#include <mutex>
#include <stdexcept>
#include "solution.cpp"
static int failures = 0;
#define CHECK(cond, name) do { if (cond) std::printf("ok %s\n", name); \
else { std::printf("FAIL %s (line %d)\n", name, __LINE__); ++failures; } } while (0)
int main() {
// Базовая дисциплина
{
BlockingQueue q(2);
q.push(1);
q.push(2);
CHECK(q.size() == 2, "size() == 2 после двух push");
int v = 0;
CHECK(q.pop(v) && v == 1, "pop -> 1 (FIFO)");
CHECK(q.pop(v) && v == 2, "pop -> 2 (FIFO)");
q.close();
CHECK(!q.pop(v), "после close и опустошения pop -> false");
bool threw = false;
try { q.push(3); } catch (const std::runtime_error&) { threw = true; }
CHECK(threw, "push после close -> std::runtime_error");
}
// Продюсеры/потребители под нагрузкой
{
const int P = 4, C = 4, PER = 20000, C3 = 64;
BlockingQueue q(C3);
std::atomic<int> pushed{0};
std::atomic<long long> sum{0};
std::atomic<int> popped{0};
std::atomic<int> over{0};
auto producer = [&](int pid) {
for (int i = 0; i < PER; ++i) {
q.push(pid * 1000000 + i);
pushed.fetch_add(1, std::memory_order_relaxed);
if (q.size() > C3) over.fetch_add(1, std::memory_order_relaxed);
}
};
auto consumer = [&](int) {
int v = 0;
while (q.pop(v)) {
sum.fetch_add(v, std::memory_order_relaxed);
popped.fetch_add(1, std::memory_order_relaxed);
}
};
std::vector<std::thread> ts;
for (int i = 0; i < C; ++i) ts.emplace_back(consumer, i);
std::this_thread::sleep_for(std::chrono::milliseconds(20));
for (int i = 0; i < P; ++i) ts.emplace_back(producer, i);
std::this_thread::sleep_for(std::chrono::milliseconds(50));
while (pushed.load() < P * PER) std::this_thread::sleep_for(std::chrono::milliseconds(10));
q.close();
for (auto& t : ts) t.join();
long long expect = 0;
for (int p = 0; p < P; ++p) for (int i = 0; i < PER; ++i) expect += (long long)p * 1000000 + i;
std::printf(" (вставлено=%d, извлечено=%d, переполнений=%d)\n",
pushed.load(), popped.load(), over.load());
CHECK(pushed.load() == P * PER, "все вставки выполнены (нет потерь)");
CHECK(popped.load() == P * PER, "извлечено ровно столько же (нет дублей/потерь)");
CHECK(sum.load() == expect, "сумма значений совпадает с эталоном");
CHECK(over.load() == 0, "size() не превышал capacity");
}
std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures);
return failures ? 1 : 0;
}
+14
View File
@@ -0,0 +1,14 @@
// Задача 07 — TCP-эхо-сервер на epoll. Реализуй сервер.
// Запуск: ./solution <порт>
#include <cstdio>
#include <cstdlib>
int main(int argc, char** argv) {
if (argc < 2) {
std::fprintf(stderr, "usage: %s <port>\n", argv[0]);
return 2;
}
long port = std::strtol(argv[1], nullptr, 10);
std::fprintf(stderr, "TODO: сервер на порту %ld не реализован\n", port);
return 1; // TODO: реализовать сервер на epoll
}
+17
View File
@@ -0,0 +1,17 @@
# Задача 07 — TCP-эхо-сервер на epoll (Linux, C++)
Написать сервер: принимает TCP-соединения на `127.0.0.1:<порт из argv[1]>`, эхо всех
принятых байт обратно в то же соединение, обслуживает несколько клиентов одновременно,
завершается по SIGTERM/SIGINT аккуратно (exit 0).
Требования:
- мультиплексирование через `epoll` (не блокирующий `accept` в цикле, не поток на клиента);
- сокеты неблокирующие, обработка `EAGAIN`/`EINTR`, частичные чтения и записи;
- `SO_REUSEADDR`, bind на 127.0.0.1, прослушивание на нужном порту;
- до 64 одновременных соединений, закрытие по EOF со стороны клиента;
- никаких утечек дескрипторов (тест проверяет закрытие).
Запускается как самостоятельный бинарник: `./solution <порт>`.
Проверка: `python3 grade.py 07` — тест сам поднимает сервер, открывает 3 соединения,
льёт данные, проверяет эхо и корректное завершение по SIGTERM.
Критерий: все `ok` + ревью кода (использование epoll, обработка ошибок).
+118
View File
@@ -0,0 +1,118 @@
#!/usr/bin/env python3
"""Тест задачи 07: поднимает сервер, проверяет эхо на нескольких соединениях и SIGTERM."""
import os
import signal
import socket
import subprocess
import sys
import time
HERE = os.path.dirname(os.path.abspath(__file__))
BIN = os.path.join(HERE, "solution")
failures = 0
def check(cond, name):
global failures
if cond:
print(f"ok {name}")
else:
print(f"FAIL {name}")
failures += 1
def free_port():
s = socket.socket()
s.bind(("127.0.0.1", 0))
p = s.getsockname()[1]
s.close()
return p
def main():
port = free_port()
proc = subprocess.Popen([BIN, str(port)], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
up = False
for _ in range(100): # до 5 секунд на старт
if proc.poll() is not None:
break
try:
s = socket.create_connection(("127.0.0.1", port), timeout=0.3)
s.close()
up = True
break
except OSError:
time.sleep(0.05)
check(up, "сервер принял соединение и слушает порт")
if up:
# три одновременных соединения, большие и разорванные сообщения
socks = []
for i in range(3):
s = socket.create_connection(("127.0.0.1", port), timeout=2)
s.settimeout(5)
socks.append(s)
payloads = [b"hello\n", b"x" * 70000, b"chunk-" + bytes(range(32, 127))]
for s, p in zip(socks, payloads):
s.sendall(p)
ok = True
for i, (s, p) in enumerate(zip(socks, payloads)):
got = b""
while len(got) < len(p):
chunk = s.recv(65536)
if not chunk:
break
got += chunk
if got != p:
ok = False
print(f" соединение {i}: получено {len(got)} из {len(p)} байт")
check(ok, "эхо на трёх одновременных соединениях (включая 70 КБ)")
# эхо в двух шагах по одному соединению
s = socks[0]
s.sendall(b"part1")
first = s.recv(5)
s.sendall(b"part2")
second = b""
while len(second) < 5:
chunk = s.recv(5 - len(second))
if not chunk:
break
second += chunk
check(first == b"part1" and second == b"part2", "эхо по частям (несколько циклов read/write)")
# клиент закрывается — сервер не падает
for s in socks:
s.close()
time.sleep(0.3)
check(proc.poll() is None, "сервер жив после закрытия всех клиентов")
# новое соединение после закрытия старых
try:
s = socket.create_connection(("127.0.0.1", port), timeout=2)
s.settimeout(3)
s.sendall(b"again")
check(s.recv(5) == b"again", "новое соединение после отключения старых")
s.close()
except OSError as e:
check(False, f"новое соединение после отключения старых ({e})")
proc.send_signal(signal.SIGTERM)
try:
rc = proc.wait(timeout=5)
check(rc == 0, f"аккуратное завершение по SIGTERM (exit={rc})")
except subprocess.TimeoutExpired:
proc.kill()
check(False, "аккуратное завершение по SIGTERM (не завершился за 5 с)")
if failures:
err = proc.stderr.read().decode(errors="replace")[:2000]
if err:
print("--- stderr сервера ---")
print(err)
print(f"\n{'FAILURES: %d' % failures if failures else 'ALL PASS'}")
sys.exit(1 if failures else 0)
if __name__ == "__main__":
main()
+12
View File
@@ -0,0 +1,12 @@
10.0.0.1 - - [23/Sep/2026:10:00:01 +0700] "GET /index.html HTTP/1.1" 200 512
10.0.0.2 - - [23/Sep/2026:10:00:02 +0700] "GET /a HTTP/1.1" 404 128
10.0.0.1 - - [23/Sep/2026:10:00:03 +0700] "POST /api HTTP/1.1" 500 42
10.0.0.3 - - [23/Sep/2026:10:00:04 +0700] "GET /b HTTP/1.1" 200 1024
10.0.0.1 - - [23/Sep/2026:10:00:05 +0700] "GET /c HTTP/1.1" 503 0
10.0.0.2 - - [23/Sep/2026:10:00:06 +0700] "GET /d HTTP/1.1" 200 33
10.0.0.4 - - [23/Sep/2026:10:00:07 +0700] "GET /e HTTP/1.1" 200 10
10.0.0.3 - - [23/Sep/2026:10:00:08 +0700] "GET /f HTTP/1.1" 200 11
10.0.0.2 - - [23/Sep/2026:10:00:09 +0700] "GET /g HTTP/1.1" 200 12
10.0.0.5 - - [23/Sep/2026:10:00:10 +0700] "GET /h HTTP/1.1" 500 13
10.0.0.1 - - [23/Sep/2026:10:00:11 +0700] "GET /i HTTP/1.1" 200 14
10.0.0.6 - - [23/Sep/2026:10:00:12 +0700] "GET /j HTTP/1.1" 200 15
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
# Задача 08 — разбор лога доступа. Реализуй.
set -euo pipefail
log="${1:-}"
if [[ -z "$log" ]]; then
echo "usage: $0 <access.log>" >&2
exit 2
fi
echo "TOTAL 0"
# TODO
exit 0
+25
View File
@@ -0,0 +1,25 @@
# Задача 08 — bash: разбор лога доступа
Написать `solution.sh`, который принимает путь к файлу лога (формат nginx/apache access.log)
и печатает ровно четыре строки:
```
TOTAL <число строк в файле>
TOP <ip> <число запросов>
TOP <ip> <число запросов>
TOP <ip> <число запросов>
5XX <число ответов со статусом 500-599>
```
Правила:
- IP — первое поле строки;
- TOP — три самых частых IP по убыванию числа запросов; при равенстве — по возрастанию
строки IP (лексикографически);
- если уникальных IP меньше трёх — печатать столько строк TOP, сколько есть;
- файл может быть большим (миллион строк): построчный bash-цикл по строкам не пройдёт
по времени — нужен awk/sort или один-два прохода;
- пустой файл: `TOTAL 0`, `5XX 0`, строк TOP нет.
Запуск: `bash solution.sh access.log`
Проверка: `python3 grade.py 08` (тест гоняет скрипт на фикстуре и на большом логе).
Критерий: точное совпадение вывода, код возврата 0.
+88
View File
@@ -0,0 +1,88 @@
#!/usr/bin/env python3
"""Тест задачи 08: сравнение вывода solution.sh с эталоном на фикстуре и большом логе."""
import os
import random
import subprocess
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
SCRIPT = os.path.join(HERE, "solution.sh")
failures = 0
def check(cond, name, extra=""):
global failures
if cond:
print(f"ok {name}")
else:
print(f"FAIL {name} {extra}")
failures += 1
def run(path):
r = subprocess.run(["bash", SCRIPT, path], capture_output=True, text=True, timeout=120)
return r.returncode, r.stdout, r.stderr
def expected(lines):
from collections import Counter
ips = [l.split()[0] for l in lines if l.strip()]
total = len(lines)
cnt = Counter(ips)
top = sorted(cnt.items(), key=lambda kv: (-kv[1], kv[0]))[:3]
five = 0
for l in lines:
parts = l.split('"')
if len(parts) >= 3:
codes = parts[2].split()
if codes and codes[0].isdigit() and 500 <= int(codes[0]) <= 599:
five += 1
out = [f"TOTAL {total}"]
for ip, c in top:
out.append(f"TOP {ip} {c}")
out.append(f"5XX {five}")
return "\n".join(out)
def compare(path, lines, label):
rc, out, err = run(path)
exp = expected(lines)
got = out.strip("\n")
if got != exp:
print(f"--- ожидалось ({label}) ---\n{exp}\n--- получено ---\n{got}")
check(rc == 0, f"{label}: код возврата 0 (получен {rc})", err[:300])
check(got == exp, f"{label}: вывод совпадает с эталоном")
def main():
fixture = os.path.join(HERE, "access.log")
with open(fixture, encoding="utf-8") as f:
lines = f.read().splitlines()
compare(fixture, lines, "фикстура")
# пустой файл
empty = os.path.join(HERE, "empty.log")
open(empty, "w").close()
compare(empty, [], "пустой файл")
# большой лог: 200k строк, часть 5xx, один лидер, ничья между двумя IP
rng = random.Random(7)
big = os.path.join(HERE, "big.log")
ips = ["10.0.0.1"] * 50000 + ["10.0.0.2"] * 30000 + ["10.0.0.3"] * 30000
ips += [f"192.168.1.{i}" for i in range(1, 61)] * 10
ips += [f"172.16.0.{i}" for i in range(1, 21)] * 5
rng.shuffle(ips)
big_lines = []
for ip in ips:
code = rng.choice([200, 200, 200, 301, 404, 500, 502, 503])
big_lines.append(f'{ip} - - [23/Sep/2026:11:00:00 +0700] "GET /p HTTP/1.1" {code} 100')
with open(big, "w", encoding="utf-8") as f:
f.write("\n".join(big_lines) + "\n")
compare(big, big_lines, "большой лог 110k строк")
print(f"\n{'FAILURES: %d' % failures if failures else 'ALL PASS'}")
sys.exit(1 if failures else 0)
if __name__ == "__main__":
main()
+20
View File
@@ -0,0 +1,20 @@
Задача 09 — разбор падения.
1. Дефекты (сколько и какие):
- ...
- ...
2. Сборка и первые шаги:
$ g++ -g -O0 -fsanitize=address,undefined crash.c -o crash
$ ./crash world
<что вывел ASAN — своими словами>
3. Команды gdb по шагам и что они показали:
(gdb) ...
...
4. Почему программа падала именно так (механизм):
...
5. Что изменено в crash.c (минимальные правки):
...
+21
View File
@@ -0,0 +1,21 @@
/* Задача 09: программа с дефектами. Починить минимально. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv) {
const char *word = argc > 1 ? argv[1] : "hello";
size_t n = strlen(word);
char *buf = malloc(n);
if (!buf) {
return 2;
}
strcpy(buf, word);
printf("len=%zu\n", strlen(buf));
free(buf);
free(buf);
return 0;
}
+23
View File
@@ -0,0 +1,23 @@
# Задача 09 — отладка: найти и исправить дефекты (gdb)
В `crash.c` лежит программа, которая падает на части входов и портит память.
Задача — не переписать её с нуля, а **найти дефекты отладчиком и починить минимально**.
Ожидаемое поведение после починки:
- `./crash` (без аргумента) печатает `len=5` и завершается с кодом 0;
- `./crash <слово>` печатает `len=<длина слова>` и завершается с кодом 0;
- `./crash ""` печатает `len=0` и завершается с кодом 0;
- сборка с `-fsanitize=address,undefined` не даёт ни одного сообщения об ошибке.
Что сделать:
1. Собрать с отладочной информацией и санитайзерами:
`gcc -g -O0 -fsanitize=address,undefined crash.c -o crash`
2. Разобраться, что именно портит память, а что приводит к падению при выходе.
Полезно: `gdb ./crash`, `run`, `bt`, `frame`, `info locals`, `watch`.
3. Исправить `crash.c` (минимальные правки, стиль сохранить).
4. Заполнить `answer.txt`: сколько дефектов нашёл, какие именно, какими командами
отладчика это подтвердил (по шагам), почему падало именно так.
Проверка: `python3 grade.py 09` — компилирует `crash.c` (компилятор C, gcc) с ASAN/UBSAN
и прогоняет 5 входов.
`answer.txt` проверяется ревью (это часть оценки: важен ход разбора, а не только результат).
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env python3
"""Тест задачи 09: сборка crash.c с ASAN/UBSAN и прогон входов."""
import os
import subprocess
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
SRC = os.path.join(HERE, "crash.c")
BIN = os.path.join(HERE, "crash_checked")
failures = 0
def check(cond, name, extra=""):
global failures
if cond:
print(f"ok {name}")
else:
print(f"FAIL {name} {extra}")
failures += 1
def main():
r = subprocess.run(["gcc", "-g", "-O1", "-Wall", "-Wextra",
"-fsanitize=address,undefined", "-fno-omit-frame-pointer",
SRC, "-o", BIN], capture_output=True, text=True, timeout=180)
check(r.returncode == 0, "сборка с ASAN/UBSAN прошла", r.stderr[:800])
if r.returncode != 0:
print("\nFAILURES: 1")
sys.exit(1)
cases = [
([], "len=5"),
(["world"], "len=5"),
(["a"], "len=1"),
([""], "len=0"),
(["x" * 300], "len=300"),
]
for args, expect in cases:
try:
run = subprocess.run([BIN] + args, capture_output=True, text=True, timeout=60,
env={**os.environ, "ASAN_OPTIONS": "detect_leaks=1"})
except subprocess.TimeoutExpired:
check(False, f"вход {args or '<нет>'}: завершился за 60 c")
continue
out = run.stdout.strip()
err = run.stderr
label = f"вход {args[0][:12]!r}" if args else "вход без аргумента"
check(run.returncode == 0, f"{label}: код возврата 0 (получен {run.returncode})")
check(out == expect, f"{label}: вывод {expect!r} (получено {out!r})")
check("AddressSanitizer" not in err and "runtime error" not in err,
f"{label}: санитайзеры молчат", err[:400].replace("\n", " | "))
# answer.txt заполнен (проверка на «не оставил заготовку»)
apath = os.path.join(HERE, "answer.txt")
if os.path.exists(apath):
txt = open(apath, encoding="utf-8").read()
check("..." not in txt and len(txt) > 400, "answer.txt заполнен разбором (ревью отдельно)")
else:
check(False, "answer.txt: файл с разбором не найден")
print(f"\n{'FAILURES: %d' % failures if failures else 'ALL PASS'}")
sys.exit(1 if failures else 0)
if __name__ == "__main__":
main()