Уроки D2-D7 (14 файлов): деревья/куча, epoll, потоки, gdb, графы, L2-L3, ДП, TCP, bash, ядро, docker, мок-интервью + cards_src 613
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
# D2, часть 1. Деревья, BST, куча (урок)
|
||||
|
||||
Дерево — рекурсивная структура: узел + набор поддеревьев. На собеседовании не спрашивают
|
||||
определение, спрашивают следствия: как способ хранения (указатели vs массив) и выбранный
|
||||
инвариант (порядок BST vs экстремум кучи) напрямую определяют сложность операций. Порядок
|
||||
разбора: как дерево лежит в памяти → как его обходят → что даёт BST-инвариант → что даёт
|
||||
heap-инвариант → куда это применяется в top-K, LCA, валидации.
|
||||
|
||||
## 1. Представление: узлы с указателями vs массив
|
||||
|
||||
**Узлы с указателями.** Типичный `TreeNode` — значение плюс два указателя на детей:
|
||||
|
||||
```c++
|
||||
struct TreeNode {
|
||||
int val;
|
||||
TreeNode *left, *right;
|
||||
};
|
||||
```
|
||||
|
||||
На x86-64 `sizeof(TreeNode) == 24`: `val` (`int`, 4 байта) лежит по смещению 0, дальше 4
|
||||
байта паддинга — указатель должен быть выровнен на 8 байт, — `left` по смещению 8 (8 байт),
|
||||
`right` по смещению 16 (8 байт). Итого 24, а не 20 — та же механика паддинга, что и в
|
||||
структурах вообще (`char+int+char` → 12 байт по тем же правилам выравнивания). Каждый узел —
|
||||
отдельная аллокация в куче, значит отдельный вызов аллокатора и произвольный адрес — узлы
|
||||
дерева обычно не лежат рядом в памяти, отсюда промахи кэша при обходе.
|
||||
|
||||
**Массив.** Для узла с индексом `i` дети — `2i+1` и `2i+2`, родитель — `(i-1)/2` (целочисленное
|
||||
деление) — индексная арифметика заменяет указатели. Представление компактно только для
|
||||
**полного или близкого к полному** дерева (все уровни заполнены слева направо без пропусков,
|
||||
как в куче): тогда индексы плотно покрывают массив без дыр. Для разреженного дерева такое
|
||||
представление может потребовать до `2^h` ячеек массива, из которых заполнена малая часть —
|
||||
это и есть причина, по которой куча всегда хранится в массиве, а произвольное дерево (BST,
|
||||
дерево поиска) — почти всегда через указатели.
|
||||
|
||||
**Факты для карточек**
|
||||
- base | Из чего состоит узел бинарного дерева при представлении указателями? — значение + два указателя (left, right)
|
||||
- core | Размер `struct TreeNode { int val; TreeNode *left, *right; }` на x86-64? — 24 байта: 4 байта `val` + 4 байта паддинга (выравнивание указателя на 8) + 8 + 8 байт под указатели
|
||||
- core | Когда массивное представление дерева компактно, а когда нет? — компактно только для полного/близкого к полному дерева (куча); для разреженного дерева индексы `2i+1/2i+2` требуют до `2^h` ячеек, большинство из которых пустует
|
||||
- deep | Почему куча хранится в массиве, а не через указатели? — куча всегда почти полное дерево (заполнена по уровням слева направо без пропусков), индексная арифметика не тратит память впустую и не требует отдельной аллокации на узел
|
||||
|
||||
Почему дальше: раз дерево можно линейно уместить в массив только при полном заполнении по уровням, как вообще перебирают узлы произвольного дерева — обходы.
|
||||
|
||||
## 2. Обходы: DFS (preorder/inorder/postorder) и BFS
|
||||
|
||||
Три порядка DFS отличаются местом, где посещается сам узел относительно детей:
|
||||
|
||||
- **preorder** (node, left, right) — узел раньше детей; используется, когда нужно
|
||||
восстановить структуру дерева по порядку посещения (сериализация, копирование).
|
||||
- **inorder** (left, node, right) — для BST даёт значения **по возрастанию** (см. раздел 3).
|
||||
- **postorder** (left, right, node) — оба ребёнка раньше узла; используется, когда узел
|
||||
нельзя обработать/освободить до того, как обработаны/освобождены его дети (удаление дерева
|
||||
снизу вверх, вычисление арифметического дерева выражений).
|
||||
|
||||
**DFS рекурсией** использует неявный стек вызовов, глубина = высота дерева `h` → память
|
||||
`O(h)`. **DFS итеративно** — явный `std::stack<TreeNode*>`, та же асимптотика `O(h)`, но без
|
||||
риска переполнения стека потока: кадр функции тяжелее записи в explicit-стеке (адрес возврата
|
||||
+ сохранённые регистры против одного указателя, 8 байт), а стек потока на Linux ограничен
|
||||
(обычно порядка нескольких мегабайт) — на сильно вырожденном дереве (`h` порядка `n`)
|
||||
рекурсия может упереться в этот лимит раньше, чем закончится память под явный стек.
|
||||
|
||||
**BFS** обходит уровень за уровнем через очередь; память — `O(w)`, где `w` — максимальная
|
||||
ширина уровня, а не высота. Для полного сбалансированного дерева из `n` узлов высота
|
||||
`h ≈ log₂ n`, но ширина последнего уровня — около `n/2`: при `n = 1 000 000` это `h ≈ 20`
|
||||
(`log₂10⁶ ≈ 20`) против ширины последнего уровня ≈ 500 000. То есть BFS-очередь на пике может
|
||||
требовать памяти на порядки больше, чем DFS-стек, для одного и того же дерева.
|
||||
|
||||
**Ловушки**
|
||||
- Не проверить `node == nullptr` перед обращением к `node->val` в рекурсии → разыменование нулевого указателя → SIGSEGV, UBSAN отдельно ловит это с `-fsanitize=null`.
|
||||
- В итеративном DFS перепутать порядок `push` детей (класть left раньше right вместо наоборот при эмуляции preorder через стек — стек разворачивает порядок) → обход посещает поддеревья не в том порядке, тихий баг, виден только сравнением с рекурсивным эталоном.
|
||||
- Использовать рекурсивный DFS на сильно несбалансированном дереве (`h ≈ n`, например после вставки отсортированной последовательности) → глубина рекурсии `n` → переполнение стека потока, а не просто медленная работа.
|
||||
|
||||
**Факты для карточек**
|
||||
- base | Порядок посещения в inorder-обходе? — left, node, right
|
||||
- base | Какой обход даёт отсортированный порядок для BST? — inorder
|
||||
- core | Память DFS (рекурсия или явный стек) по глубине дерева? — O(h) — пропорционально высоте
|
||||
- core | Память BFS (очередь)? — O(w) — пропорционально максимальной ширине уровня
|
||||
- core | Для полного сбалансированного дерева из 10⁶ узлов: высота и ширина последнего уровня? — h ≈ 20 (log₂10⁶≈20), ширина последнего уровня ≈ 500 000 — BFS может требовать памяти на порядки больше, чем DFS
|
||||
- deep | Какой обход используют, чтобы освободить дерево снизу вверх? — postorder (сначала оба ребёнка, потом сам узел)
|
||||
|
||||
Почему дальше: обходы работают одинаково независимо от порядка значений в узлах; BST добавляет конкретный порядковый инвариант — разберём, что именно он даёт.
|
||||
|
||||
## 3. BST-инвариант и что он даёт
|
||||
|
||||
Инвариант: для каждого узла все значения в левом поддереве меньше значения узла, все значения
|
||||
в правом — больше. Отсюда напрямую:
|
||||
|
||||
- **Поиск/вставка/удаление — O(h).** На каждом узле решение однозначно (значение меньше —
|
||||
влево, больше — вправо), путь до цели или до `nullptr` не длиннее высоты дерева.
|
||||
- **Inorder-обход = отсортированный порядок.** Прямое следствие инварианта: в любом
|
||||
поддереве все значения левой части меньше корня, все значения правой — больше, рекурсивно
|
||||
это верно на каждом уровне, поэтому обход left→node→right посещает значения строго по
|
||||
возрастанию.
|
||||
|
||||
Высота `h` — не константа, а зависит от формы дерева: у сбалансированного BST (например,
|
||||
построенного случайными вставками или явно балансирующегося, как красно-чёрное дерево)
|
||||
`h ≈ log₂ n`; у **вырожденного** — `h = n`. Конкретный пример вырождения: вставка значений
|
||||
`1, 2, 3, ..., n` по порядку в обычный (не самобалансирующийся) BST — каждый новый узел
|
||||
становится правым ребёнком предыдущего максимума, дерево превращается в цепочку, и поиск,
|
||||
ожидаемо `O(log n)`, на деле деградирует до `O(n)` — то есть до линейного перебора связного
|
||||
списка.
|
||||
|
||||
**Факты для карточек**
|
||||
- base | Инвариант BST? — для каждого узла все значения в левом поддереве меньше значения узла, все значения в правом — больше
|
||||
- base | Сложность поиска в BST? — O(h), где h — высота дерева
|
||||
- core | Что даёт inorder-обход BST? — значения по возрастанию — прямое следствие инварианта
|
||||
- core | Высота BST в лучшем и худшем случае для n узлов? — сбалансированное: h ≈ log₂n; вырожденное: h = n
|
||||
- deep | Что произойдёт при вставке 1,2,...,n по порядку в обычный (небалансирующийся) BST? — дерево вырождается в цепочку (каждый новый узел — правый ребёнок предыдущего), поиск деградирует до O(n)
|
||||
|
||||
Почему дальше: раз наивный BST может выродиться в список, встают два практических вопроса — как правильно проверить дерево на BST-инвариант и как искать общего предка, используя (или не используя) этот инвариант.
|
||||
|
||||
## 4. Валидация BST и LCA
|
||||
|
||||
**Частая ошибка валидации** — сравнивать узел только с непосредственным родителем
|
||||
(`node->left->val < node->val` и `node->right->val > node->val` на каждом шаге). Контрпример:
|
||||
корень `5`, его левый ребёнок `3`, а правый ребёнок узла `3` — `6`. Локальная проверка
|
||||
`6 > 3` проходит (это правый ребёнок `3`, и `6` больше `3`), но `6` находится в левом
|
||||
поддереве корня `5`, а `6 > 5` — инвариант BST для всего дерева нарушен. Локальное сравнение
|
||||
с родителем этого не ловит, потому что оно ничего не знает про предков выше.
|
||||
|
||||
**Правильная валидация** — рекурсия с передачей вниз границ `(min, max)`: каждый узел должен
|
||||
строго лежать между ними; при спуске влево верхняя граница ужесточается до значения узла
|
||||
(`max = node->val`), при спуске вправо ужесточается нижняя (`min = node->val`). Альтернатива —
|
||||
inorder-обход с проверкой строгого возрастания относительно последнего посещённого значения
|
||||
(`O(n)` время, `O(1)` дополнительной памяти, если не копить весь список, а сравнивать на лету).
|
||||
|
||||
**LCA в BST** использует порядок значений: начиная с корня, если оба искомых значения меньше
|
||||
текущего узла — идти влево, если оба больше — вправо, иначе текущий узел и есть точка, где
|
||||
пути к двум значениям расходятся, то есть LCA. Сложность `O(h)` — та же логика, что у поиска.
|
||||
|
||||
**LCA в произвольном бинарном дереве** (без порядка) не может отбросить половину дерева на
|
||||
каждом шаге — порядка, который это позволяет, нет. Стандартное решение — рекурсия postorder:
|
||||
если узел `nullptr` или совпадает с одним из искомых — вернуть его; рекурсивно получить
|
||||
результат из левого и правого поддерева; если оба непустые — текущий узел и есть LCA;
|
||||
иначе вернуть тот результат, который непустой (продвинуть найденный узел вверх). Сложность
|
||||
`O(n)` — в худшем случае нужно посетить каждый узел один раз; память `O(h)` на стек рекурсии.
|
||||
|
||||
**Ловушки**
|
||||
- Валидировать BST сравнением только с прямым родителем → пропускает нарушения через поколение (контрпример: корень 5, левый 3, правый потомок 3 равен 6) → тест с таким деревом должен упасть, а наивная проверка его пропустит.
|
||||
- Использовать нестрогое сравнение (`<=` вместо `<`) без явного решения, допустимы ли дубликаты и в какое поддерево они кладутся → BST с равными значениями валидируется непредсказуемо.
|
||||
- В LCA по BST не проверить, что оба узла реально принадлежат дереву → функция вернёт правдоподобный, но неверный узел вместо явной ошибки.
|
||||
|
||||
**Факты для карточек**
|
||||
- base | Почему нельзя валидировать BST, сравнивая узел только с непосредственным родителем? — нарушение инварианта может возникнуть через поколение (правый потомок левого поддерева больше корня, но меньше своего прямого родителя) — локальная проверка это не ловит
|
||||
- core | Как правильно валидировать BST рекурсивно? — передавать вниз границы (min, max): узел должен строго лежать между ними, для левого поддерева ужесточается верхняя граница, для правого — нижняя
|
||||
- core | Сложность LCA в BST и почему? — O(h): если оба искомых значения меньше текущего узла — влево, оба больше — вправо, иначе текущий узел и есть LCA
|
||||
- core | Сложность LCA в обычном бинарном дереве без порядка? — O(n): нет инварианта, отсекающего часть дерева, нужна рекурсия postorder по потенциально всем узлам
|
||||
- deep | Альтернативный способ валидации BST без явных границ (min, max)? — inorder-обход с проверкой строгого возрастания относительно предыдущего посещённого значения
|
||||
|
||||
Почему дальше: и валидация, и LCA используют дерево как структуру поиска по значению; для задач top-K важнее не порядок всех элементов, а быстрый доступ к экстремуму — для этого нужен другой инвариант, куча.
|
||||
|
||||
## 5. Куча: инвариант и индексы
|
||||
|
||||
Инвариант max-heap: значение в родителе не меньше значений в обоих детях. Хранится в массиве
|
||||
(см. раздел 1): для индекса `i` дети — `2i+1`, `2i+2`, родитель — `(i-1)/2`.
|
||||
|
||||
- **`top()` — O(1).** По инварианту максимум всегда лежит в корне, то есть в начале массива —
|
||||
прямое обращение по индексу 0, без поиска.
|
||||
- **`sift-up`/`sift-down` — O(log n).** Оба переставляют элемент вдоль одного пути от узла до
|
||||
корня или от корня до листа; длина этого пути ограничена высотой дерева.
|
||||
|
||||
**Bottom-up heapify — O(n), а не O(n log n).** Через `n` последовательных `push` (каждая —
|
||||
`sift-up` до `O(log i)` на i-й вставке) суммарная стоимость — `Σ log₂(i)` для `i = 1..n`, это
|
||||
порядка `n log₂ n`. Bottom-up heapify стартует с последнего нелистового узла (индекс
|
||||
`n/2 - 1`) и идёт к корню (индекс 0), вызывая `sift-down` на каждом узле; работа `sift-down`
|
||||
в узле пропорциональна **высоте именно этого узла `h`**, а не высоте всего дерева — узлов
|
||||
большой высоты экспоненциально мало (в корне — один узел высоты `log n`, листьев высоты 0 —
|
||||
около `n/2`, они вообще пропускаются). Сумма `Σ h/2^h` по всем высотам сходится к константе
|
||||
(≈2) при росте `n`, поэтому суммарная работа — `O(n)`, а не `O(n log n)`.
|
||||
|
||||
**Факты для карточек**
|
||||
- base | Индексы детей и родителя в куче на массиве для узла i? — дети 2i+1, 2i+2; родитель (i-1)/2 (целочисленное деление)
|
||||
- base | Сложность доступа к максимуму (top) в max-heap? — O(1) — максимум всегда в корне по инварианту
|
||||
- core | Сложность sift-up/sift-down? — O(log n) — путь ограничен высотой дерева
|
||||
- core | За какое время строится куча через bottom-up heapify против n последовательных push? — O(n) против O(n log n): работа sift-down в узле пропорциональна его высоте h, а сумма Σ h/2^h по всем узлам сходится к константе
|
||||
- deep | С какого индекса стартует bottom-up heapify и куда идёт? — с последнего нелистового узла, индекс n/2 - 1, к корню (индекс 0)
|
||||
|
||||
Почему дальше: куча даёт O(1) доступ к экстремуму и O(log n) перестройку — на этом строится вся группа задач top-K, и у кучи есть конкурент по сложности — quickselect.
|
||||
|
||||
## 6. priority_queue, top-K и nth_element
|
||||
|
||||
`std::priority_queue` по умолчанию — max-heap поверх `std::vector` (сравнение `std::less`,
|
||||
для min-heap передают `std::greater<>` третьим параметром шаблона).
|
||||
|
||||
Три способа получить top-K:
|
||||
|
||||
1. **Полная куча.** `heapify` за `O(n)`, затем `k` раз `pop` (`sift-down` корня) по `O(log n)`
|
||||
каждый — итого `O(n + k log n)`.
|
||||
2. **Потоковый (min-heap размера k).** Когда данные не помещаются в память целиком: держат
|
||||
min-heap ровно из `k` элементов, каждый новый элемент сравнивают с минимумом кучи (`top`,
|
||||
O(1)), и если новый больше — минимум вытесняется, новый добавляется. Сложность
|
||||
`O(n log k)`, память `O(k)` вместо `O(n)`.
|
||||
3. **`nth_element` (quickselect).** Партиционирует диапазон вокруг опорного элемента и
|
||||
рекурсивно спускается только в ту половину, где лежит нужный ранг: в среднем `T(n) =
|
||||
T(n/2) + O(n) → O(n)`, в худшем случае (устойчиво плохой выбор опорного, та же причина,
|
||||
что и у quicksort) — `O(n²)`. Результат — частичный порядок (всё слева не больше опорного,
|
||||
всё справа не меньше, но без порядка внутри половин), не отсортированный список: для
|
||||
готового top-K по убыванию `nth_element` придётся досортировать `k`-элементный префикс,
|
||||
тогда как `pop` из кучи уже отдаёт элементы по убыванию сам по себе.
|
||||
|
||||
**Top K Frequent** — отдельный паттерн: подсчёт частот хеш-таблицей за `O(n)`, затем либо
|
||||
heap размера `k` (`O(n log k)`), либо **bucket sort по частоте**: частота любого элемента не
|
||||
превышает `n`, поэтому заводят массив корзин размера `n + 1`, кладут элемент в
|
||||
`bucket[частота]` и сканируют от максимальной частоты к минимальной — `O(n)` без
|
||||
логарифмического множителя вообще.
|
||||
|
||||
**Ловушки**
|
||||
- Строить кучу через `push` в цикле вместо bottom-up `heapify` → результат корректен (инвариант тот же), но `O(n log n)` вместо `O(n)` — на миллионах элементов заметно медленнее, и это отдельный вопрос на собеседовании.
|
||||
- Использовать `nth_element` там, где нужен полностью отсортированный top-K, и забыть досортировать k-элементный префикс → порядок в выводе неверный, хотя набор элементов правильный.
|
||||
- В потоковом top-K сравнивать новый элемент с максимумом кучи вместо минимума (перепутать min-heap и max-heap для этой задачи) → кандидаты вытесняются в обратном порядке, итоговый top-K — неверный набор элементов.
|
||||
|
||||
**Факты для карточек**
|
||||
- base | Что такое `std::priority_queue` по умолчанию? — max-heap поверх vector; для min-heap передают компаратор std::greater<>
|
||||
- core | Сложность top-K через полную кучу? — O(n + k log n): O(n) heapify + k раз pop по O(log n)
|
||||
- core | Когда используют min-heap размера k вместо полной кучи? — на потоке данных, не помещающемся в память целиком: min-heap размера k хранит только k текущих кандидатов, O(n log k), память O(k)
|
||||
- core | Средняя и худшая сложность `nth_element` (quickselect)? — в среднем O(n), в худшем O(n²) — та же причина, что у quicksort: устойчиво плохой выбор опорного
|
||||
- deep | Почему top-K через nth_element не даёт готовый отсортированный список? — nth_element только частично упорядочивает вокруг k-го элемента без порядка внутри половин; нужна досортировка k-элементного префикса
|
||||
- deep | Как получить top-K частых элементов за O(n) без log-фактора? — подсчёт хеш-таблицей O(n), затем bucket sort по частоте: частота не превышает n, массив корзин размера n+1 индексируется частотой напрямую
|
||||
|
||||
Почему дальше: дерево, BST-инвариант и куча — это и есть механизмы за стандартными задачами интервью; дальше — какая задача проверяет какой именно из них.
|
||||
|
||||
## 7. Задачи-триггеры
|
||||
|
||||
- **Invert Binary Tree** — рекурсивно поменять местами `left`/`right` у каждого узла: `O(n)`
|
||||
время (каждый узел посещается один раз), `O(h)` память на стек рекурсии.
|
||||
- **Validate BST** — рекурсия с границами `(min, max)`, передаваемыми вниз (раздел 4).
|
||||
- **Kth Largest** — min-heap размера `k` (`O(n log k)`) или `nth_element`/quickselect
|
||||
(в среднем `O(n)`) (раздел 6).
|
||||
- **Top K Frequent** — хеш-таблица + bucket sort по частоте (`O(n)`) или heap размера `k`
|
||||
(`O(n log k)`) (раздел 6).
|
||||
|
||||
Ссылка на задачу дня: `tasks/11_heap` — там же ключевое требование «`heapify` только
|
||||
bottom-up, не `push` в цикле», прямая проверка раздела 5.
|
||||
|
||||
## Проверь себя
|
||||
|
||||
<details>
|
||||
<summary>1. Почему нельзя проверить BST, сравнивая каждый узел только с прямым родителем?</summary>
|
||||
Потому что нарушение инварианта может произойти через поколение: узел может быть больше
|
||||
своего прямого родителя, но при этом лежать в поддереве более раннего предка, для которого
|
||||
он слишком велик (например, правый потомок левого ребёнка корня оказывается больше самого
|
||||
корня). Правильная проверка передаёт вниз границы (min, max), актуальные для всего пути от
|
||||
корня, а не только для одного родителя.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>2. Почему bottom-up heapify — O(n), хотя каждый sift-down в отдельности — O(log n)?</summary>
|
||||
Потому что работа sift-down в конкретном узле пропорциональна высоте именно этого узла, а не
|
||||
высоте всего дерева, а узлов с большой высотой экспоненциально мало (в корне — один узел
|
||||
высоты log n, листьев высоты 0 — около n/2, для них sift-down почти ничего не делает). Сумма
|
||||
Σ h/2^h по всем высотам сходится к константе, поэтому суммарная работа растёт линейно с n.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>3. Чем LCA в BST отличается по сложности от LCA в обычном бинарном дереве и почему?</summary>
|
||||
В BST — O(h): порядок значений позволяет на каждом шаге однозначно решить, идти влево, вправо
|
||||
или остановиться. В обычном дереве такого порядка нет, поэтому приходится рекурсивно
|
||||
обходить дерево (postorder) и в худшем случае посетить все n узлов — O(n).
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>4. Почему nth_element в среднем O(n), но не гарантирует худший случай?</summary>
|
||||
Потому что рекурсия спускается только в ту часть, где лежит искомый ранг, отбрасывая
|
||||
остальное после каждого партиционирования — в среднем это даёт T(n) = T(n/2) + O(n) → O(n).
|
||||
Но при устойчиво плохом выборе опорного элемента (та же причина, что и у худшего случая
|
||||
quicksort) партиционирование каждый раз отбрасывает лишь один элемент, и сложность
|
||||
деградирует до O(n²).
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>5. Почему массив — плохое представление для сильно несбалансированного (не полного) дерева?</summary>
|
||||
Потому что индексы 2i+1/2i+2 предполагают позицию узла в полном дереве соответствующей
|
||||
глубины; если дерево заполнено не полностью (например, вырожденная цепочка), индексы,
|
||||
которые физически используются, разбросаны на диапазон до 2^h, и большая часть массива
|
||||
такого размера останется пустой.
|
||||
</details>
|
||||
|
||||
## Задачи дня
|
||||
|
||||
- `tasks/11_heap` — куча: bottom-up `heapify` за O(n), `kth_largest`/`top_k`; ключевая
|
||||
проверка — запрет строить кучу через `push` в цикле.
|
||||
|
||||
## Материалы
|
||||
|
||||
- Документация `std::priority_queue` (куча в top-K) — https://en.cppreference.com/w/cpp/container/priority_queue
|
||||
- Документация `std::nth_element` (quickselect для k-го элемента) — https://en.cppreference.com/w/cpp/algorithm/nth_element
|
||||
- cppreference: undefined behavior — https://en.cppreference.com/w/cpp/language/ub
|
||||
- GCC: флаги инструментации (ASAN/UBSAN) — https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html
|
||||
Reference in New Issue
Block a user