ИУП: интенсив 7 дней + диагностический пакет (32 вопроса, 9 задач с автопроверкой)
This commit is contained in:
@@ -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
@@ -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()
|
||||
@@ -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
@@ -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
@@ -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()
|
||||
@@ -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
|
||||
}
|
||||
@@ -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` — четыре маски.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 чистые (утечки в тесте
|
||||
считаются ошибкой — тест сам освобождает память).
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_ по необходимости
|
||||
};
|
||||
@@ -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 чистые.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Задача 05 — длина НВП за O(n log n). Заполни реализацию.
|
||||
#include <vector>
|
||||
|
||||
int lis_length(const std::vector<int>& a) {
|
||||
(void)a;
|
||||
return 0; // TODO
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_;
|
||||
};
|
||||
@@ -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
|
||||
на оба события даёт ложные пробуждения.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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, обработка ошибок).
|
||||
@@ -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()
|
||||
@@ -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
|
||||
Executable
+13
@@ -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
|
||||
@@ -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.
|
||||
@@ -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()
|
||||
@@ -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 (минимальные правки):
|
||||
...
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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` проверяется ревью (это часть оценки: важен ход разбора, а не только результат).
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user