RAII — твой лучший друг (и почему не стоит бояться умных указателей)
Старый добрый
Решение — RAII (Resource Acquisition Is Initialization): ресурсы живут ровно столько, сколько объект, который ими владеет. Ушёл объект из области видимости — ресурс освободился.
Пример с умными указателями:
Что важно знать:
*
*
* Никогда не делай
RAII работает не только для памяти: файлы, мьютексы, сокеты — всё. Достаточно обернуть ресурс в класс с деструктором.
Профит: меньше багов, меньше утечек, чище код.
➡️ @cpp_geek
Старый добрый
new
/
delete
— это классика, но и источник утечек, крашей и боли. В современном C++ ручное управление памятью почти всегда антипаттерн.Решение — RAII (Resource Acquisition Is Initialization): ресурсы живут ровно столько, сколько объект, который ими владеет. Ушёл объект из области видимости — ресурс освободился.
Пример с умными указателями:
#include <memory>
#include <iostream>
struct Foo {
Foo() { std::cout << "Init\n"; }
~Foo() { std::cout << "Destroy\n"; }
};
void bar() {
std::unique_ptr<Foo> p = std::make_unique<Foo>(); // RAII
// делаем что-то
} // тут автоматически вызовется ~Foo()
Что важно знать:
*
std::unique_ptr
— владение в единственном числе, идеально для большинства случаев.*
std::shared_ptr
— разделённое владение (но дороже по производительности).* Никогда не делай
new
без обёртки — почти всегда лучше std::make_unique
или std::make_shared
.RAII работает не только для памяти: файлы, мьютексы, сокеты — всё. Достаточно обернуть ресурс в класс с деструктором.
Профит: меньше багов, меньше утечек, чище код.
➡️ @cpp_geek
❤3👍3👌1👾1
Почему
Если вы думаете, что
Чтобы экономить память, стандартная библиотека делает его специализацией, хранящей биты упакованно. Цена — нетипичное поведение:
Элементы
- нельзя получить указатель на элемент;
- работа с ними медленнее, чем с
- поведение иногда ломает шаблонный код, который ожидает нормальные ссылки и указатели.
Что делать?
Если вам нужна коллекция булей — берите
➡️ @cpp_geek
std::vector<bool>
— это ловушкаЕсли вы думаете, что
std::vector<bool>
— обычный вектор, только для bool
, то… сюрприз! Это не совсем так.Чтобы экономить память, стандартная библиотека делает его специализацией, хранящей биты упакованно. Цена — нетипичное поведение:
std::vector<bool> v{true, false, true};
bool* p = &v[0]; // Ошибка! Тут нет обычного bool*
Элементы
vector<bool>
— это не настоящие bool
, а прокси-объекты. Они ведут себя как bool
, но фактически это обёртки над битами. Итог:- нельзя получить указатель на элемент;
- работа с ними медленнее, чем с
bool
;- поведение иногда ломает шаблонный код, который ожидает нормальные ссылки и указатели.
Что делать?
Если вам нужна коллекция булей — берите
std::vector<char>
или std::vector<uint8_t>
. Памяти уйдёт чуть больше, но всё будет предсказуемо и быстро.std::vector<bool>
стоит использовать только если критична экономия памяти, и вы понимаете все его подводные камни.➡️ @cpp_geek
👍12❤2
Невидимый враг:
Кажется, что
Под капотом
- Элемент — это не
- Нельзя получить настоящий указатель на элемент:
- Код работает медленнее из-за лишней возни с битовыми масками.
Пример неожиданности:
Хотите реально быстрый и предсказуемый контейнер?
- Используйте
- Или подключите
Итог:
➡️ @cpp_geek
std::vector<bool>
Кажется, что
std::vector<bool>
— обычный вектор из bool. Но на самом деле это не так.Под капотом
vector<bool>
хранит биты плотно упакованными (1 бит на элемент), а не как bool
(обычно 1 байт). Из-за этого:- Элемент — это не
bool&
, а прокси-объект (std::vector<bool>::reference
).- Нельзя получить настоящий указатель на элемент:
&v[0]
не даст bool*
.- Код работает медленнее из-за лишней возни с битовыми масками.
Пример неожиданности:
std::vector<bool> v{true, false};
auto p = &v[0]; // ❌ Ошибка! Нет bool*
Хотите реально быстрый и предсказуемый контейнер?
- Используйте
std::vector<char>
или std::vector<uint8_t>
для хранения флагов.- Или подключите
boost::dynamic_bitset
, если нужна именно битовая упаковка.Итог:
vector<bool>
— это костыль ради экономии памяти. Если важны скорость и нормальная семантика — обходите стороной.➡️ @cpp_geek
👍8💩3👾1
Зачем
Сколько раз вы видели: функция возвращает значение, а кто-то его тупо игнорирует. А потом баги из ниоткуда. В C++17 завезли
Компилятор предупреждает: "эй, ты вызвал, но не используешь результат".
Где это особенно важно:
- Функции, которые возвращают ошибки (
- Функции, где пропуск результата ломает логику (например, RAII-объекты, токены отмены, хендлы).
Можно навесить
Вместо неочевидных багов — раннее предупреждение компилятора.
⚡ Лайфхак: Если компилятор слишком шумит — можно сделать
Вывод: ставьте
➡️ @cpp_geek
[[nodiscard]]
и когда он реально спасает?Сколько раз вы видели: функция возвращает значение, а кто-то его тупо игнорирует. А потом баги из ниоткуда. В C++17 завезли
[[nodiscard]]
, и это реально полезная штука.
[[nodiscard]] int compute() {
return 42;
}
int main() {
compute(); // warning: ignoring return value
}
Компилятор предупреждает: "эй, ты вызвал, но не используешь результат".
Где это особенно важно:
- Функции, которые возвращают ошибки (
std::error_code
, std::optional
, bool
успеха операции);- Функции, где пропуск результата ломает логику (например, RAII-объекты, токены отмены, хендлы).
Можно навесить
[[nodiscard]]
и на типы (C++20):
struct [[nodiscard]] Result {
bool ok;
};
Result foo();
foo(); // warning!
Вместо неочевидных багов — раннее предупреждение компилятора.
⚡ Лайфхак: Если компилятор слишком шумит — можно сделать
[[nodiscard("объяснение")]]
(C++20), чтобы явно подсказать, зачем это.Вывод: ставьте
[[nodiscard]]
на всё, что нельзя безнаказанно проигнорировать. Это дешёвая страховка от глупых багов.➡️ @cpp_geek
👍11❤4👾1
std::exchange
— простой способ менять значения и возвращать старыеВместо того чтобы писать руками:
auto old = value;
value = new_value;
return old;
В modern C++ есть готовый инструмент -
std::exchange
(C++14+).
#include <utility>
#include <string>
#include <iostream>
int main() {
std::string s = "Hello";
auto old = std::exchange(s, "World");
std::cout << "old = " << old << ", s = " << s << '\n';
}
Вывод:
old = Hello, s = World
Когда полезно:
- Реализация move-конструкторов/операторов:
MyType(MyType&& other)
: data_(std::exchange(other.data_, nullptr)) {}
- Сброс состояния объектов и возвращение старого значения.
- Реализация одноразовых флагов (`once_flag` паттерн).
Плюсы:
- Одна строка вместо трёх.
- Читаемость выше — сразу видно, что мы заменяем значение и берём старое.
Помни: по умолчанию второе значение копируется/перемещается, так что это не нулевой по стоимости вызов.
➡️ @cpp_geek
👍4❤3👾1
Как уменьшить время компиляции C++ проектов
Сегодня я хочу поговорить про боль, которую мы все знаем — долгую компиляцию больших C++ проектов.
Когда проект растёт, время сборки иногда становится просто катастрофическим. Вот несколько приёмов, которые реально помогают:
1. PCH (Precompiled Headers) — вынесите редко меняющийся код (например,
2. Разделяйте код на интерфейсы и реализации — заголовки должны быть минимальными, только объявления. Всё, что можно, уносите в
3. Используйте
4. Минимизируйте include-цепочки — подключайте в заголовках только то, что реально нужно. Остальное — в
5. Инкрементальная сборка — убедитесь, что сборочная система (CMake, Ninja, Make) не пересобирает лишнее.
Когда я внедрял эти подходы в одном проекте, время компиляции сократилось с 18 минут до 6. Это реально чувствуется.
➡️ @cpp_geek
Сегодня я хочу поговорить про боль, которую мы все знаем — долгую компиляцию больших C++ проектов.
Когда проект растёт, время сборки иногда становится просто катастрофическим. Вот несколько приёмов, которые реально помогают:
1. PCH (Precompiled Headers) — вынесите редко меняющийся код (например,
<iostream>
, <vector>
, <string>
) в precompiled header. Это может срезать время компиляции в разы.2. Разделяйте код на интерфейсы и реализации — заголовки должны быть минимальными, только объявления. Всё, что можно, уносите в
.cpp
.3. Используйте
#pragma once
вместо include guard — чуть быстрее и проще.4. Минимизируйте include-цепочки — подключайте в заголовках только то, что реально нужно. Остальное — в
.cpp
.5. Инкрементальная сборка — убедитесь, что сборочная система (CMake, Ninja, Make) не пересобирает лишнее.
Когда я внедрял эти подходы в одном проекте, время компиляции сократилось с 18 минут до 6. Это реально чувствуется.
➡️ @cpp_geek
🔥7👍2
std::move
vs std::forward
: когда и что использоватьСегодня покажу вам коротко, чем отличаются std::move и std::forward.
- std::move(obj) — безусловно превращает объект в rvalue. После этого объект считается "пустым" для повторного использования (в рамках контракта move). Используем, когда мы точно хотим "забрать" ресурсы.
- std::forward<T>(obj) — условно делает rvalue, если изначально пришёл rvalue. То есть это "perfect forwarding" для шаблонных функций.
Пример:
#include <utility>
#include <string>
#include <iostream>
template <typename T>
void wrapper(T&& arg) {
process(std::forward<T>(arg)); // сохраняет rvalue/lvalue-семантику
}
void process(const std::string& s) { std::cout << "Lvalue: " << s << '\n'; }
void process(std::string&& s) { std::cout << "Rvalue: " << s << '\n'; }
int main() {
std::string str = "Hello";
wrapper(str); // Lvalue
wrapper(std::move(str)); // Rvalue
}
Запомните:
-
std::move
- "забрать".-
std::forward
- "передать как есть".➡️ @cpp_geek
🔥5🥴4
🧵 Сегодня покажу вам, как удобно логгировать значения в отладке с помощью макроса, который реально упрощает жизнь.
Когда мы отлаживаем код, часто хочется быстро вывести значения переменных. Писать
Фишка в том, что
🔥 Такой макрос отлично заходит при написании алгоритмов, отладки функций, проверки значений — и при этом делает код аккуратнее.
Хочешь улучшить - можно сделать вывод в файл или добавить таймстемпы.
➡️ @cpp_geek
Когда мы отлаживаем код, часто хочется быстро вывести значения переменных. Писать
std::cout << "x: " << x << std::endl;
каждый раз — боль. Давайте упростим:
#include <iostream>
#define LOG(var) std::cout << #var << " = " << (var) << std::endl;
int main() {
int x = 42;
double pi = 3.1415;
LOG(x); // x = 42
LOG(pi); // pi = 3.1415
}
Фишка в том, что
#var
превращает имя переменной в строку. А (var)
- значение.🔥 Такой макрос отлично заходит при написании алгоритмов, отладки функций, проверки значений — и при этом делает код аккуратнее.
Хочешь улучшить - можно сделать вывод в файл или добавить таймстемпы.
➡️ @cpp_geek
🔥13👍5🗿2👾1
Media is too big
VIEW IN TELEGRAM
🚀 YADRO приглашает C++ разработчиков в команду OpenBMC и встроенных систем!
Если вы хотите создавать сложное программное обеспечение для серверов и систем хранения данных, работать с передовыми технологиями Linux и участвовать в проектах open source, то эта возможность для вас.
📌 Кого мы ищем:
• Ведущего разработчика C++ (Linux/OpenBMC)
• Ведущего разработчика интерфейсов встроенных систем
• TeamLead разработки OpenBMC
🧰 Технологический стек и задачи:
• C++ (стандарты 17, 20, 23), STL, Boost
• Linux-среда, systemd, D-Bus, Yocto, bash, Python
• Работа с ядром прошивки OpenBMC, взаимодействие с UEFI/BIOS
• Разработка и поддержка сложных интерфейсов встроенных систем
💼 Условия работы:
• Гибкий формат: удалённо или в офисах в Москве, Санкт-Петербурге, Екатеринбурге, Нижнем Новгороде и Минске
• Работа с масштабными проектами в уникальной команде инженеров
• Возможность горизонтального и вертикального карьерного роста
💙 Узнайте больше и откликайтесь на вакансии прямо на сайте!
Если вы хотите создавать сложное программное обеспечение для серверов и систем хранения данных, работать с передовыми технологиями Linux и участвовать в проектах open source, то эта возможность для вас.
📌 Кого мы ищем:
• Ведущего разработчика C++ (Linux/OpenBMC)
• Ведущего разработчика интерфейсов встроенных систем
• TeamLead разработки OpenBMC
🧰 Технологический стек и задачи:
• C++ (стандарты 17, 20, 23), STL, Boost
• Linux-среда, systemd, D-Bus, Yocto, bash, Python
• Работа с ядром прошивки OpenBMC, взаимодействие с UEFI/BIOS
• Разработка и поддержка сложных интерфейсов встроенных систем
💼 Условия работы:
• Гибкий формат: удалённо или в офисах в Москве, Санкт-Петербурге, Екатеринбурге, Нижнем Новгороде и Минске
• Работа с масштабными проектами в уникальной команде инженеров
• Возможность горизонтального и вертикального карьерного роста
Please open Telegram to view this post
VIEW IN TELEGRAM
🖕4❤3😁1
Что такое виртуальный деструктор и зачем он используется в C++?
В C++ виртуальный деструктор используется для правильного освобождения памяти при удалении объекта через указатель на базовый класс. Если базовый класс имеет виртуальный деструктор, то при удалении объекта через указатель на базовый класс будет вызван деструктор не только базового класса, но и всех его производных классов. Это позволяет избежать утечек памяти и неопределенного поведения при работе с полиморфными объектами.
Если виртуального деструктора не объявлено в базовом классе, то при удалении производного объекта через указатель на базовый класс будут вызваны только деструкторы базового класса, что может привести к утечкам памяти и неопределенному поведению.
➡️ @cpp_geek
В C++ виртуальный деструктор используется для правильного освобождения памяти при удалении объекта через указатель на базовый класс. Если базовый класс имеет виртуальный деструктор, то при удалении объекта через указатель на базовый класс будет вызван деструктор не только базового класса, но и всех его производных классов. Это позволяет избежать утечек памяти и неопределенного поведения при работе с полиморфными объектами.
Если виртуального деструктора не объявлено в базовом классе, то при удалении производного объекта через указатель на базовый класс будут вызваны только деструкторы базового класса, что может привести к утечкам памяти и неопределенному поведению.
➡️ @cpp_geek
👍6❤2🤯2👾1
Изменяемые лямбда-функции
Ключевое слово mutable используется для сохранения состояния в лямбда-функциях. Обычно оператор вызова функции замыкания является константным. Другими словами — лямбда не может модифицировать переменные, захваченные по значению.
Но ключевое слово
Следует заметить, что в отличии от mutable-переменных в объявлении класса, мутабельные лямбда-функции должны использоваться относительно редко и очень аккуратно. Сохранение состояния между вызовами лямбда-функции может быть опасным и контринтуитивным.
➡️ @cpp_geek
Ключевое слово mutable используется для сохранения состояния в лямбда-функциях. Обычно оператор вызова функции замыкания является константным. Другими словами — лямбда не может модифицировать переменные, захваченные по значению.
Но ключевое слово
mutable
может быть применено ко всей лямбда-функции, что сделает все её переменные изменяемыми.Следует заметить, что в отличии от mutable-переменных в объявлении класса, мутабельные лямбда-функции должны использоваться относительно редко и очень аккуратно. Сохранение состояния между вызовами лямбда-функции может быть опасным и контринтуитивным.
➡️ @cpp_geek
👍2❤1😱1
Сейчас покажу вам простой, но мощный приём для ускорения компиляции - разделение объявления и реализации шаблонов с использованием
Все мы знаем, что шаблоны в C++ реализуются в заголовочных файлах. Это значит, что каждый
🔧 Что можно сделать?
Разделяем интерфейс и реализацию:
Теперь в клиентском коде:
✅ Это даст:
- Сокращение времени компиляции
- Чище зависимости
- Потенциально меньший размер бинарника
Но помните:
➡️ @cpp_geek
explicit instantiation
.Все мы знаем, что шаблоны в C++ реализуются в заголовочных файлах. Это значит, что каждый
.cpp
файл, который включает такой заголовок, заново инстанцирует шаблон. Результат - долгое время компиляции и раздутый бинарник.🔧 Что можно сделать?
Разделяем интерфейс и реализацию:
// MyTemplate.hpp
#pragma once
template<typename T>
class MyTemplate {
public:
void doSomething();
};
// MyTemplate.cpp
#include "MyTemplate.hpp"
#include <iostream>
template<typename T>
void MyTemplate<T>::doSomething() {
std::cout << "Doing something\n";
}
// Явная инстанциация
template class MyTemplate<int>;
Теперь в клиентском коде:
#include "MyTemplate.hpp"
int main() {
MyTemplate<int> obj;
obj.doSomething();
}
✅ Это даст:
- Сокращение времени компиляции
- Чище зависимости
- Потенциально меньший размер бинарника
Но помните:
explicit instantiation
работает, только если вы заранее знаете типы, с которыми будете использовать шаблон.➡️ @cpp_geek
👍8😱2
Генерируем пароли с помощью C++
Эта программа создает константы для допустимых символов в пароле и для длины пароля. Затем она инициализирует генератор случайных чисел и генерирует пять случайных паролей.
Каждый пароль формируется путем выбора случайного символа из всего набора символов. Этот процесс повторяется до достижения желаемой длины пароля.
➡️ @cpp_geek
Эта программа создает константы для допустимых символов в пароле и для длины пароля. Затем она инициализирует генератор случайных чисел и генерирует пять случайных паролей.
Каждый пароль формируется путем выбора случайного символа из всего набора символов. Этот процесс повторяется до достижения желаемой длины пароля.
➡️ @cpp_geek
👍10🤣1
memmove
Функция memmove используется для копирования блока памяти из одного места в другое. Она объявлена в заголовочном файле. Она принимает аргументы типа
memmove может обрабатывать перекрывающиеся буферы. В отличие от memcpy, которая просто копирует данные из одного места в другое, memmove может безопасно перемещать данные, даже если исходный и целевой буферы перекрываются.
Функция memmove может быть полезна для удаления элементов из массива. Например, если вы хотите удалить элемент из массива и сдвинуть оставшиеся элементы влево, вы можете использовать memmove для перемещения данных в массиве.
➡️ @cpp_geek
Функция memmove используется для копирования блока памяти из одного места в другое. Она объявлена в заголовочном файле. Она принимает аргументы типа
void *
и const void *
, что позволяет ей работать с любыми типами данных. Она просто копирует указанное количество байтов из исходного буфера в целевой.memmove может обрабатывать перекрывающиеся буферы. В отличие от memcpy, которая просто копирует данные из одного места в другое, memmove может безопасно перемещать данные, даже если исходный и целевой буферы перекрываются.
Функция memmove может быть полезна для удаления элементов из массива. Например, если вы хотите удалить элемент из массива и сдвинуть оставшиеся элементы влево, вы можете использовать memmove для перемещения данных в массиве.
➡️ @cpp_geek
❤🔥1👍1
Move-only объекты и почему
Многие удивляются, когда компилятор ругается: «
Ключевой момент: после
Если нужен shareable ресурс – используйте
Частая ошибка на собеседованиях: «почему нельзя вернуть
👉 Правило: если объект владеет чем-то уникально – делайте его move-only (удалите копирование). Это повышает безопасность кода и явно выражает семантику владения.
➡️ @cpp_geek
std::unique_ptr
нельзя копироватьМногие удивляются, когда компилятор ругается: «
unique_ptr
не имеет конструктора копирования». Это не баг, а фича: он move-only. Логика простая: владелец ресурса должен быть только один.
#include <memory>
#include <iostream>
int main() {
std::unique_ptr<int> p1 = std::make_unique<int>(42);
// std::unique_ptr<int> p2 = p1; // ❌ ошибка копирования
std::unique_ptr<int> p2 = std::move(p1); // ✅ перенос
std::cout << *p2 << "\n"; // 42
std::cout << (p1 ? "not null" : "null") << "\n"; // null
}
Ключевой момент: после
std::move
старый указатель «обнуляется», чтобы избежать двойного освобождения памяти.Если нужен shareable ресурс – используйте
std::shared_ptr
. Но помните: это дороже (счётчик ссылок, атомики).Частая ошибка на собеседованиях: «почему нельзя вернуть
unique_ptr
по значению?» - на самом деле можно! Он спокойно двигается:
std::unique_ptr<int> make_ptr() {
return std::make_unique<int>(99); // move происходит неявно
}
👉 Правило: если объект владеет чем-то уникально – делайте его move-only (удалите копирование). Это повышает безопасность кода и явно выражает семантику владения.
➡️ @cpp_geek
👍6😁3❤1
move constructor
Move-конструктор — это специальный конструктор, который позволяет эффективно перемещать ресурсы из одного объекта в другой, без необходимости копирования данных. Он используется для реализации семантики перемещения (move semantics) и оптимизации работы с временными объектами.
Move-конструктор принимает rvalue ссылку (&&) на объект, который будет перемещен, и выполняет простое копирование указателей на данные, а не их фактическое копирование.
Использование move-конструктора позволяет избежать лишних копирований данных и повысить производительность при работе с большими или ресурсоемкими объектами.
➡️ @cpp_geek
Move-конструктор — это специальный конструктор, который позволяет эффективно перемещать ресурсы из одного объекта в другой, без необходимости копирования данных. Он используется для реализации семантики перемещения (move semantics) и оптимизации работы с временными объектами.
Move-конструктор принимает rvalue ссылку (&&) на объект, который будет перемещен, и выполняет простое копирование указателей на данные, а не их фактическое копирование.
Использование move-конструктора позволяет избежать лишних копирований данных и повысить производительность при работе с большими или ресурсоемкими объектами.
➡️ @cpp_geek
👍6👾1
Давай программировать стек TCP/IP. Part 1: Ethernet & ARP
Написание собственного стека TCP/IP может показаться сложной задачей. Действительно, за более чем тридцать лет существования TCP накопилось множество спецификаций. Однако основная спецификация на удивление компактна — важные части включают разбор заголовков TCP, автомат конечных состояний, контроль перегрузок и вычисление времени ожидания повторной передачи.
Наиболее распространенные протоколы второго и третьего уровней — Ethernet и IP, соответственно, — в сравнении с TCP гораздо проще. В этой серии статей мы реализуем минимальный стек TCP/IP в пространстве пользователя для Linux.
Цель этих публикаций и создаваемого ПО исключительно образовательная — углубленное изучение сетевого и системного программирования.
TUN/TAP устройства
Чтобы перехватывать сетевой трафик низкого уровня из ядра Linux, мы будем использовать TAP-устройство Linux. Если кратко, TUN/TAP устройства часто применяются приложениями в пространстве пользователя для работы с трафиком на уровне L3 и L2 соответственно. Популярным примером является туннелирование, когда пакет инкапсулируется внутри полезной нагрузки другого пакета.
Преимущество TUN/TAP устройств в том, что их легко настроить в программе в пространстве пользователя, и они уже используются во множестве программ, таких как OpenVPN.
Поскольку мы хотим строить стек сетевого взаимодействия, начиная со второго уровня, нам потребуется TAP-устройство. Мы создаем его следующим образом:
https://www.saminiir.com/lets-code-tcp-ip-stack-1-ethernet-arp/
➡️ @cpp_geek
Написание собственного стека TCP/IP может показаться сложной задачей. Действительно, за более чем тридцать лет существования TCP накопилось множество спецификаций. Однако основная спецификация на удивление компактна — важные части включают разбор заголовков TCP, автомат конечных состояний, контроль перегрузок и вычисление времени ожидания повторной передачи.
Наиболее распространенные протоколы второго и третьего уровней — Ethernet и IP, соответственно, — в сравнении с TCP гораздо проще. В этой серии статей мы реализуем минимальный стек TCP/IP в пространстве пользователя для Linux.
Цель этих публикаций и создаваемого ПО исключительно образовательная — углубленное изучение сетевого и системного программирования.
TUN/TAP устройства
Чтобы перехватывать сетевой трафик низкого уровня из ядра Linux, мы будем использовать TAP-устройство Linux. Если кратко, TUN/TAP устройства часто применяются приложениями в пространстве пользователя для работы с трафиком на уровне L3 и L2 соответственно. Популярным примером является туннелирование, когда пакет инкапсулируется внутри полезной нагрузки другого пакета.
Преимущество TUN/TAP устройств в том, что их легко настроить в программе в пространстве пользователя, и они уже используются во множестве программ, таких как OpenVPN.
Поскольку мы хотим строить стек сетевого взаимодействия, начиная со второго уровня, нам потребуется TAP-устройство. Мы создаем его следующим образом:
/*
* Taken from Kernel Documentation/networking/tuntap.txt
*/
int tun_alloc(char *dev)
{
struct ifreq ifr;
int fd, err;
if( (fd = open("/dev/net/tap", O_RDWR)) < 0 ) {
print_error("Cannot open TUN/TAP dev");
exit(1);
}
CLEAR(ifr);
/* Flags: IFF_TUN - TUN device (no Ethernet headers)
* IFF_TAP - TAP device
*
* IFF_NO_PI - Do not provide packet information
*/
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
if( *dev ) {
strncpy(ifr.ifr_name, dev, IFNAMSIZ);
}
if( (err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0 ){
print_error("ERR: Could not ioctl tun: %s\n", strerror(errno));
close(fd);
return err;
}
strcpy(dev, ifr.ifr_name);
return fd;
}
https://www.saminiir.com/lets-code-tcp-ip-stack-1-ethernet-arp/
➡️ @cpp_geek
👍3🤩3❤1🔥1
Давай программировать стек TCP/IP. Part 2: IPv4 & ICMPv4
На этот раз в нашем TCP/IP стеке в пространстве пользователя мы реализуем минимально жизнеспособный IP-уровень и протестируем его с помощью ICMP-запросов эхо (известных также как ping).
Мы рассмотрим форматы IPv4 и ICMPv4 и опишем, как проверять их целостность. Некоторые функции, такие как фрагментация IP, оставлены в качестве упражнения для читателя.
Для нашего сетевого стека был выбран IPv4 вместо IPv6, так как он до сих пор является основным сетевым протоколом Интернета. Однако это быстро меняется, и наш стек можно будет расширить поддержкой IPv6 в будущем.
Internet Checksum
Поле интернет-контрольной суммы используется для проверки целостности IP-дейтаграммы. Вычисление контрольной суммы относительно простое и определено в оригинальной спецификации:
Поле контрольной суммы представляет собой 16-битное дополнение до единицы суммы всех 16-битных слов в заголовке. Для вычисления контрольной суммы значение этого поля принимается равным нулю.
Фактический код для алгоритма выглядит следующим образом:
https://www.saminiir.com/lets-code-tcp-ip-stack-2-ipv4-icmpv4/
➡️ @cpp_geek
На этот раз в нашем TCP/IP стеке в пространстве пользователя мы реализуем минимально жизнеспособный IP-уровень и протестируем его с помощью ICMP-запросов эхо (известных также как ping).
Мы рассмотрим форматы IPv4 и ICMPv4 и опишем, как проверять их целостность. Некоторые функции, такие как фрагментация IP, оставлены в качестве упражнения для читателя.
Для нашего сетевого стека был выбран IPv4 вместо IPv6, так как он до сих пор является основным сетевым протоколом Интернета. Однако это быстро меняется, и наш стек можно будет расширить поддержкой IPv6 в будущем.
Internet Checksum
Поле интернет-контрольной суммы используется для проверки целостности IP-дейтаграммы. Вычисление контрольной суммы относительно простое и определено в оригинальной спецификации:
Поле контрольной суммы представляет собой 16-битное дополнение до единицы суммы всех 16-битных слов в заголовке. Для вычисления контрольной суммы значение этого поля принимается равным нулю.
Фактический код для алгоритма выглядит следующим образом:
uint16_t checksum(void *addr, int count)
{
/* Compute Internet Checksum for "count" bytes
* beginning at location "addr".
* Taken from https://tools.ietf.org/html/rfc1071
*/
register uint32_t sum = 0;
uint16_t * ptr = addr;
while( count > 1 ) {
/* This is the inner loop */
sum += * ptr++;
count -= 2;
}
/* Add left-over byte, if any */
if( count > 0 )
sum += * (uint8_t *) ptr;
/* Fold 32-bit sum to 16 bits */
while (sum>>16)
sum = (sum & 0xffff) + (sum >> 16);
return ~sum;
}
https://www.saminiir.com/lets-code-tcp-ip-stack-2-ipv4-icmpv4/
➡️ @cpp_geek
👍2🔥1
Давай программировать стек TCP/IP. Part 3: TCP Basics & Handshake
Теперь, когда наш стек TCP/IP в пользовательском пространстве имеет минимальные реализации для Ethernet и IPv4, пришло время рассмотреть работу протокола управления передачей (Transmission Control Protocol, TCP).
Работающий на четвертом сетевом уровне OSI1, транспортном, TCP отвечает за восстановление ошибочных соединений и ошибок в доставке пакетов. По сути, TCP - это рабочая лошадка Интернета, обеспечивающая надежную связь практически во всех современных компьютерных сетях.
TCP не совсем новый протокол - первая спецификация вышла в 1974 году2. С тех пор многое изменилось, и TCP обзавелся множеством расширений и исправлений3.
В этой статье мы рассмотрим основные теоретические основы TCP и попытаемся дать мотивацию для его разработки. Кроме того, мы рассмотрим заголовок TCP и обсудим установление соединения (TCP handshaking). В качестве последнего шага мы продемонстрируем первую функциональность TCP в нашем сетевом стеке.
https://www.saminiir.com/lets-code-tcp-ip-stack-3-tcp-handshake/
➡️ @cpp_geek
Теперь, когда наш стек TCP/IP в пользовательском пространстве имеет минимальные реализации для Ethernet и IPv4, пришло время рассмотреть работу протокола управления передачей (Transmission Control Protocol, TCP).
Работающий на четвертом сетевом уровне OSI1, транспортном, TCP отвечает за восстановление ошибочных соединений и ошибок в доставке пакетов. По сути, TCP - это рабочая лошадка Интернета, обеспечивающая надежную связь практически во всех современных компьютерных сетях.
TCP не совсем новый протокол - первая спецификация вышла в 1974 году2. С тех пор многое изменилось, и TCP обзавелся множеством расширений и исправлений3.
В этой статье мы рассмотрим основные теоретические основы TCP и попытаемся дать мотивацию для его разработки. Кроме того, мы рассмотрим заголовок TCP и обсудим установление соединения (TCP handshaking). В качестве последнего шага мы продемонстрируем первую функциональность TCP в нашем сетевом стеке.
https://www.saminiir.com/lets-code-tcp-ip-stack-3-tcp-handshake/
➡️ @cpp_geek
👍3❤1🔥1
std::tie
std::tie — это функция, которая создает кортеж ссылок на lvalue из своих аргументов или экземпляров std::ignore.
Она может использоваться для распаковки кортежей или пары значений в отдельные переменные. Например, если у вас есть функция, которая возвращает std::pair или std::tuple, вы можете использовать std::tie, чтобы присвоить значения этого кортежа отдельным переменным.
В этом примере мы используем std::tie для распаковки результата вызова set_of_s.insert(value) в две переменные: итератор iter и логическую переменную inserted.
Это позволяет нам проверить, было ли значение успешно вставлено в набор.
➡️ @cpp_geek
std::tie — это функция, которая создает кортеж ссылок на lvalue из своих аргументов или экземпляров std::ignore.
Она может использоваться для распаковки кортежей или пары значений в отдельные переменные. Например, если у вас есть функция, которая возвращает std::pair или std::tuple, вы можете использовать std::tie, чтобы присвоить значения этого кортежа отдельным переменным.
В этом примере мы используем std::tie для распаковки результата вызова set_of_s.insert(value) в две переменные: итератор iter и логическую переменную inserted.
Это позволяет нам проверить, было ли значение успешно вставлено в набор.
➡️ @cpp_geek
👍4❤1