ИУП: интенсив 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
+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()