ИУП: интенсив 7 дней + диагностический пакет (32 вопроса, 9 задач с автопроверкой)
This commit is contained in:
@@ -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