C++ Academy
15.5K subscribers
844 photos
143 videos
1 file
760 links
По всем вопросам- @workakkk

РКН: clck.ru/3FmxJF

#VRHSZ
Download Telegram
🔥 Как найти младший установленный бит без цикла

Этот трюк возвращает позицию самого правого бита 1 в 32-битном числе:


static const int table[32] = {
0, 1, 28, 2, 29, 14, 24, 3,
30, 22, 20, 15, 25, 17, 4, 8,
31, 27, 13, 23, 21, 19, 16, 7,
26, 12, 18, 6, 11, 5, 10, 9
};

int lowest_set_bit(uint32_t v)
{
return table[((v & -v) * 0x077CB531U) >> 27];
}


Выражение v & -v изолирует младший установленный бит.

Затем результат умножается на константу 0x077CB531 из последовательности де Брёйна. Для каждой из 32 возможных позиций старшие 5 бит произведения образуют уникальный индекс.

Остаётся одно обращение к таблице - и позиция найдена без перебора всех битов.

Важно: функция рассчитана на v != 0.

В современном коде также стоит проверить std::countr_zero() или __builtin_ctz() - компилятор часто превращает их в одну инструкцию процессора.
👍10🔥7❤‍🔥21
Doom в 1993 работал на 486 и 4 МБ RAM. А современные игры почему едят 16 ГБ?

Секрет был в BSP-дереве: уровень заранее делился на области, а движок во время рендера просто обходил дерево в нужном порядке. Без z-buffer, без лишней магии, без пересчёта видимости с нуля каждый кадр.

Кармак буквально заставил геометрию работать вместо железа.

Подписывайся, тут ещё много таких историй про код, который унижает современные «оптимизации».
🔥217🎉6👏1🤣1
C++ enum class: безопасно, но местами раздражает

enum class даёт строгую типизацию и не позволяет случайно смешивать значения с обычными числами.

Но есть нюанс: даже если enum используется как набор флагов,


Flags::Read | Flags::Write


не скомпилируется.

Для |, &, ^, ~ придётся вручную определить операторы и приводить значения к базовому типу.

Это правильное поведение с точки зрения type safety, но бойлерплейта становится заметно больше.

Поэтому в некоторых C++-проектах для битовых флагов до сих пор используют обычный enum внутри namespace: меньше защиты, зато код значительно проще.
11👍7🔥4
LEA в x86 выглядит как инструкция для адресов, но компиляторы часто используют её как скрытый калькулятор.

Формально LEA считает адрес без обращения к памяти:


lea eax, [rdi + 3]


Но по факту это обычная арифметика:


return x + 3;


Ещё хитрее:


lea eax, [rdi + rdi*4]


Это уже:


return x * 5;


Почему так делают?

Потому что x86-адресация умеет base + index * scale + offset, а LEA позволяет использовать эту механику без чтения памяти.

Бонус: LEA не трогает флаги процессора, в отличие от add.

Красота C и asm в том, что за простой строкой x * 5 может стоять не mul, а маленький трюк архитектуры.
21🔥12❤‍🔥2
Оператор # в препроцессоре C превращает токены в строковые литералы ещё на этапе компиляции.

Никакого преобразования во время выполнения не происходит.

Ядро Linux использует этот трюк в макросах вроде WARN_ON(), чтобы вывести точное условие, которое не прошло проверку.

Вы пишете выражение один раз, а препроцессор автоматически генерирует соответствующую строку.

Один оператор - и ваши debug-сообщения остаются идеально синхронизированы с кодом.
❤‍🔥11🤷‍♂3🔥21
Четыре строки делают сложение `float` заметно точнее

При последовательном сложении чисел с плавающей точкой часть младших битов теряется из-за округления. На больших массивах эта ошибка постепенно накапливается.

Алгоритм Кэхэна хранит потерянную часть в отдельной переменной и компенсирует её на следующем шаге:


float kahanSum(const float *nums, int count)
{
float sum = 0.0f;
float correction = 0.0f;

for (int i = 0; i < count; ++i)
{
float adjusted = nums[i] - correction;
float next = sum + adjusted;

correction = (next - sum) - adjusted;
sum = next;
}

return sum;
}


Здесь correction запоминает ошибку округления, которая потерялась при предыдущем сложении.

Обычная сумма быстрее, но Kahan Summation полезен там, где важна численная точность:

- научные расчёты;
- статистика и аналитика;
- графика и симуляции;
- обработка больших массивов;
- накопление очень маленьких значений рядом с большими.

Метод предложил Уильям Кэхэн в 1965 году. Небольшое усложнение цикла может заметно уменьшить ошибку без перехода на более тяжёлый числовой тип.
17🔥9❤‍🔥2
Лето, ИТ-Пикник и музыка известных артистов уже через несколько дней!

8 августа в Коломенском пройдет ИТ-Пикник.

В программе — выступления проекта LAB Антона Беляева, IOWA, Cream Soda, Pompeya, мартина и Совы.

А днем — научпоп-лекции, дискуссии об ИИ и больших языковых моделях, мастер-классы и интерактивы. Полезные знакомства и развлечения тоже будут.

Зарегистрироваться и узнать подробности можно на сайте мероприятия.

В билет входит +1 — можно позвать близких и друзей.

До встречи в месте притяжения ИТ.
16
Keylogger в несколько строк C? Почти.

На самом деле это не «магия взлома», а raw mode в терминале.

Обычно терминал ждёт Enter и сам обрабатывает ввод.
А здесь программа отключает echo и canonical mode, поэтому видит каждое нажатие сразу.

Именно так работают редакторы, TUI-приложения и терминальные игры.

Важно: использовать только на своём вводе и в учебных целях. Перехват чужих клавиш - уже не программирование, а нарушение безопасности.
👍13❤‍🔥5🔥42🥰1
⚡️ Генератор случайных чисел всего из трёх XOR и сдвигов

В 2003 году Джордж Марсалья представил xorshift - один из самых простых и быстрых генераторов псевдослучайных чисел.

Классический xorshift32:


uint32_t xorshift32(void)
{
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
return state;
}


Фактически весь алгоритм:


shift → XOR
shift → XOR
shift → XOR


При ненулевом начальном state период может достигать:


2³² - 1


Никаких умножений, делений или тяжёлой математики, поэтому подобные RNG отлично подходят для игр, симуляций и procedural generation, где важна скорость.

Но есть нюанс: xorshift нельзя использовать для криптографии. Его внутреннее состояние можно предсказать, поэтому для ключей, паролей и токенов нужны криптографически стойкие генераторы.

Иногда действительно полезный алгоритм помещается буквально в три строки.

#Programming #Algorithms #C #Random
Please open Telegram to view this post
VIEW IN TELEGRAM
17🔥8👍2🥰1
⚡️ Один `static` - три разных смысла. Добро пожаловать в C.

В C ключевое слово static меняет поведение в зависимости от того, где именно оно написано.

### 1. static у глобальной переменной


static int global;


Переменная имеет internal linkage - она доступна только внутри текущего .c файла.

Это удобный способ спрятать детали реализации модуля.

### 2. static внутри функции


void foo(void) {
static int count;
count++;
}


count не создаётся заново при каждом вызове.

Он существует всё время работы программы и сохраняет значение между вызовами функции.


foo(); // count = 1
foo(); // count = 2
foo(); // count = 3


### 3. static у функции


static void bar(void) {
}


Функция становится видна только внутри текущего translation unit.

Другой .c файл вызвать bar() напрямую уже не сможет.

Итого:


static global variable -> скрыть символ внутри файла
static local variable -> сохранить состояние между вызовами
static function -> скрыть функцию внутри файла


🔥 Поэтому static в C полезнее воспринимать не как одно конкретное поведение, а как подсказку проверить две вещи:

lifetime и linkage.

#C #Programming #SystemsProgramming #LowLevel #Cpp
👍15🔥96❤‍🔥1
🎶 Оптимизированный аудиовизуализатор для Windows

Spectrum — это легковесный аудиовизуализатор на C++, который обрабатывает звук в реальном времени и выводит его в консоль без разрывов экрана. Он использует FFTW3 для анализа аудиосигналов и обеспечивает точное визуальное представление частот.

🚀 Основные моменты:
- Реализация на C++17 для Windows.
- Низкое использование CPU и памяти.
- Поддержка WASAPI для захвата системного звука.
- Отображение в виде ASCII-графиков.
- Легкий в использовании и установке.

📌 GitHub: https://github.com/majockbim/spectrum

#cpp
👍63🔥2
💡 Clang умеет показывать AST, и это один из лучших способов реально понять, что компилятор видит в вашем C/C++ коде.

AST — это Abstract Syntax Tree, внутреннее представление программы после парсинга.

Например, простой код:


int x = a + b * 2;


для компилятора — не просто строка текста, а дерево примерно такого смысла:


VarDecl
└── BinaryOperator +
├── a
└── BinaryOperator *
├── b
└── 2


Именно через такое представление компилятор понимает структуру выражений, типы, области видимости и то, какие преобразования можно выполнить дальше.

У Clang AST можно получить напрямую:


clang++ -Xclang -ast-dump -fsyntax-only main.cpp


А в Compiler Explorer / Godbolt есть отдельный режим просмотра AST, поэтому можно менять код и сразу видеть, как перестраивается дерево.

Особенно полезно разбирать так:

* шаблоны;
* перегрузку функций;
* implicit conversions;
* auto;
* лямбды;
* range-based for;
* временные объекты;
* разные формы инициализации.

Если регулярно смотреть AST, C++ постепенно перестаёт выглядеть как набор «магических правил».

Начинаешь видеть код примерно так, как его видит компилятор.

🔗 https://godbolt.org/z/cfc7h41bT

#Cpp #Clang #Compiler #Programming
👍18🔥8🥰42💯1
📚 Библиотека для работы с SQLite в C++26 с использованием рефлексии

Reflite — это библиотека на C++26, которая упрощает взаимодействие с SQLite, позволяя использовать обычные структуры как основу для выполнения запросов. Она поддерживает основные операции: вставка, удаление, выборка и обновление, избавляя от лишнего шаблона кода.

🚀 Основные моменты:
- Легковесная библиотека в одном файле
- Поддержка операций INSERT, DELETE, SELECT, UPDATE
- Использует рефлексию для работы с типами структур
- Не требует полной реализации SQL, фокус на простоте
- Совместима с современными компиляторами C++26

📌 GitHub: https://github.com/KaruroChori/reflite

#cpp
🔥9👍42🥰1👌1
⚡️ Fenwick Tree держится на одном битовом трюке

Fenwick Tree, или Binary Indexed Tree, считает prefix sums за O(log n).

Вся магия в операции:


i & -i


Она находит младший установленный бит числа.

Почему это работает?

В two’s complement число -i получается как инверсия битов i плюс 1.
Когда мы делаем i & -i, остаётся только самый правый бит, равный 1.

Например:


i = 12 // 1100
-i // 0100 в нужной маске
i & -i = 4


Именно это значение говорит Fenwick Tree, на сколько нужно прыгнуть по индексам.

Для обновления:


for (; i < MAXN; i += i & -i)
tree[i] += v;


Мы идём вверх по структуре и обновляем все узлы, которые покрывают этот индекс.

Для запроса суммы:


for (; i > 0; i -= i & -i)
s += tree[i];


Мы идём вниз и собираем нужные блоки суммы.

Одна и та же операция управляет двумя направлениями:

* i += i & -i — перейти к следующему ответственному узлу
* i -= i & -i — убрать последний блок из prefix sum

Поэтому Fenwick Tree такой компактный:
никаких явных рёбер, указателей и рекурсии. Только массив и битовая арифметика.

Красота структуры в том, что дерево как бы спрятано внутри двоичного представления индекса.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍134🔥2
⚙️ Обычный strcat() в цикле может незаметно превратить простую склейку строк в O(n²).

Причина в том, что strcat() при каждом вызове сначала ищет конец уже собранной строки.

Чем длиннее буфер, тем больше данных приходится повторно проходить.

Например:


for (int i = 0; i < 100000; i++)
strcat(buf, "chunk");


В бенчмарке сборка строки примерно на 1 МБ заняла около 4,1 секунды.

Если же заранее выделить буфер и просто хранить текущую позицию записи:


char *p = buf;

for (int i = 0; i < 100000; i++) {
memcpy(p, "chunk", 5);
p += 5;
}


тот же объём собирается примерно за 0,4 мс.

Разница больше чем в 10 000 раз.

Мелочь, которую легко пропустить: проблема не в копировании строки, а в постоянном повторном поиске её конца.
13
⚡️ Linux может освободить RAM, не уничтожая сам диапазон виртуальной памяти процесса.

Это как раз то, что делает madvise(MADV_DONTNEED) для anonymous mappings.

Сценарий такой:


char *region = mmap(NULL, GB,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);

// потрогали часть страниц

madvise(region, GB, MADV_DONTNEED);


После madvise виртуальные адреса остаются валидными. Процесс всё ещё «видит» тот же диапазон памяти.

Но физические страницы, которые стояли за этим диапазоном, ядро может забрать обратно. То есть адресное пространство осталось, а реальная RAM освободилась.

При следующем обращении к этому участку процесс получит свежие zero-filled страницы. Старых данных там уже не будет.

Почему это полезно:

* можно держать большой виртуальный регион без постоянного удержания RAM
* аллокаторы могут возвращать неиспользуемые страницы ядру
* long-running процессы меньше раздувают RSS
* память можно переиспользовать без полного munmap и нового mmap

Важная деталь: MADV_DONTNEED не означает «удали адреса». Это скорее сигнал ядру: «эти страницы мне сейчас не нужны, можешь забрать физическую память».

Адреса остаются. Страницы уходят. Следующее чтение приносит нули.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤‍🔥2🥰2
🖥 C++26 закрывает одну из самых больных тем lock-free кода - безопасное удаление памяти.

Проблема не в atomics.

Проблема в том, что один поток уже убрал узел из структуры, а другой всё ещё может его читать.

Удалишь слишком рано, получишь use-after-free.

Поэтому в C++26 стандартизируют Hazard Pointers.

Идея простая:

поток заранее помечает объект как “я сейчас его читаю”.

Пока хотя бы один reader держит такой hazard pointer, объект нельзя удалять.

Удаление откладывается до момента, когда все читатели закончат работу.

Это делает lock-free структуры вроде стеков, очередей и списков намного безопаснее.

Lock-free программирование становится не магией, а чуть более нормальным инженерным инструментом.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥125👍3
Кто-то под именем Сатоши Накамото в 2008 году опубликовал идею, из которой выросла целая индустрия майнинга с огромным энергопотреблением.

И до сих пор никто достоверно не знает, кто скрывается за этим именем.

При этом сама базовая идея Proof of Work выглядит почти примитивно: берём число nonce, меняем его снова и снова, каждый раз считаем хэш и проверяем, попал ли результат ниже нужного target.

Условно это выглядит так:


uint32_t nonce = 0;

while (1) {
header.nonce = nonce;
hash = sha256(sha256(header));

if (hash < target)
break;

nonce++;
}


То есть майнинг Bitcoin в основе своей это гигантский перебор чисел с постоянным пересчётом SHA-256.

Простой цикл, который в итоге породил ASIC-фермы, энергопотребление в масштабах стран и индустрию на миллиарды долларов.
11👍2🔥2
🔥 Хочешь быстрее расти в IT? Хватит учиться в одиночку

Окружение решает больше, чем кажется.

Собрал папки и каналы, где можно быстрее влиться в нужное направление, следить за трендами и не вариться в своём пузыре.

AI: t.iss.one/ai_machinelearning_big_data
Python: t.iss.one/pythonl
Linux: t.iss.one/linuxacademiya
Хакинг: t.iss.one/linuxkalii
DevOps: t.iss.one/DevOPSitsec
Docker: https://t.iss.one/+90Z5TAyfuNU5YmRi
Golang: t.iss.one/Golang_google
Rust: t.iss.one/rust_code
C++: t.iss.one/cpluspluc
C#: t.iss.one/csharp_1001_notes
Java: t.iss.one/javatg
JavaScript: t.iss.one/javascriptv
React: t.iss.one/react_tg
Frontend: t.iss.one/front
PHP: t.iss.one/phpshka
Android: t.iss.one/android_its
Мобильная разработка: t.iss.one/mobdevelop
Базы данных: t.iss.one/sqlhub
Data Science: t.iss.one/data_analysis_ml
Big Data: t.iss.one/bigdatai
Математика: t.iss.one/data_math
Физика: t.iss.one/fizmat
Kubernetes: t.iss.one/kubernetc
GameDev: https://t.iss.one/gamedev
Haskell: t.iss.one/haskell_tg

Собеседования и карьера:

DS собеседования: t.iss.one/machinelearning_interview
Python собеседования: t.iss.one/python_job_interview

Папка с вакансиями: t.iss.one/addlist/_zyy_jQ_QUsyM2Vi
Папка Go разработчика: t.iss.one/addlist/MUtJEeJSxeY2YTFi
Папка Python разработчика: t.iss.one/addlist/eEPya-HF6mkxMGIy
Папка ML: https://t.iss.one/addlist/2Ls-snqEeytkMDgy
Папка Frontend: https://t.iss.one/addlist/mzMMG3RPZhY2M2Iy

Полезное сверху:

ИТ-мемы: t.iss.one/memes_prog
Английский для программистов: t.iss.one/english_forprogrammers
ИИ и технологии: t.iss.one/vistehno
954 ГБ open-source курсов: https://t.iss.one/+rKBQEMccAA01MTcy
ИТ-книги бесплатно: https://t.iss.one/addlist/BkskQciUW_FhNjEy

Max Ai: https://max.ru/ai_machinelearning_big_data
Max python: https://max.ru/pythonl
ТЕХНО: https://max.ru/vistehno
Max Go: https://max.ru/Golang_google
Max Linux: https://max.ru/linuxkalii
Devops: https://max.ru/DevOPSitsec
C#: https://max.ru/csharp_ci
C++: https://max.ru/cpluspluc
SQL: https://max.ru/sqlhub
Java: https://max.ru/javatg

Подписывайся на нужные направления и собирай себе ленту, которая реально двигает вперёд.
5👍2🔥2🤔1
Одна строка C, которая может сломать вам логику

В C порядок вычисления аргументов функции не определён.


foo(i++, i++);


Из-за этого один и тот же код может дать разный результат:

* gcc: foo(1, 0)
* clang: foo(0, 1)

Причина простая: компиляторы по-разному вычисляют аргументы.

Такие вещи годами становились источником очень неприятных багов.
Хорошая новость: сейчас -Wall обычно умеет это подсветить.

Вывод банальный, но важный: не пишите код, который зависит от порядка вычисления аргументов.
9👍4🥰1