C++ Academy
По всем вопросам- @workakkk РКН: clck.ru/3FmxJF #VRHSZ
Mostrar más📈 Análisis del canal de Telegram C++ Academy
El canal C++ Academy (@cpluspluc) en el segmento lingüístico de Ruso es un actor destacado. Actualmente la comunidad reúne a 15 520 suscriptores, ocupando la posición 8 176 en la categoría Tecnologías y Aplicaciones y el puesto 42 412 en la región Rusia.
📊 Métricas de audiencia y dinámica
Desde su creación el невідомо, el proyecto ha mostrado un crecimiento acelerado, reuniendo a 15 520 suscriptores.
Según los últimos datos del 25 agosto, 2026, el canal mantiene una actividad estable. En los últimos 30 días la variación de miembros fue de -45, y en las últimas 24 horas de 0, conservando un alto alcance.
- Estado de verificación: No verificado
- Tasa de interacción (ER): El promedio de interacción de la audiencia es 15.76%. Durante las primeras 24 horas tras publicar, el contenido suele obtener 6.98% de reacciones respecto al total de suscriptores.
- Alcance de las publicaciones: Cada publicación recibe en promedio 2 446 visualizaciones. En el primer día suele acumular 1 083 visualizaciones.
- Reacciones e interacción: La audiencia responde de forma activa: el promedio de reacciones por publicación es 23.
- Intereses temáticos: El contenido se centra en temas clave como c++, github, linux, api, архитектура.
📝 Descripción y política de contenido
El autor describe el recurso como un espacio para expresar opiniones subjetivas:
“По всем вопросам- @workakkk
РКН: clck.ru/3FmxJF
#VRHSZ”
Gracias a la alta frecuencia de actualizaciones (últimos datos recibidos el 26 agosto, 2026), el canal mantiene la vigencia y un amplio alcance. La analítica demuestra que la audiencia interactúa activamente con el contenido, lo que lo convierte en un punto de referencia dentro de la categoría Tecnologías y Aplicaciones.
for (int i = 0; i < n; i++) {
int j = rand() % n;
swap(a[i], a[j]);
}
На вид всё нормально: каждый элемент случайно меняется местами с другим.
Но проблема в вероятностях.
Для массива из n элементов существует n! перестановок.
Хороший shuffle должен давать каждой перестановке одинаковый шанс.
Наивный вариант делает n шагов, и на каждом шаге выбирает индекс из полного диапазона 0..n-1.
В итоге некоторые перестановки появляются чаще других.
Правильный подход - Fisher-Yates shuffle:
for (int i = n - 1; i > 0; i--) {
int j = random(0, i);
swap(a[i], a[j]);
}
Идея простая:
на каждом шаге мы выбираем элемент только из ещё не зафиксированной части массива.
Сначала выбираем последний элемент из всего массива.
Потом предпоследний - из оставшихся.
Потом следующий - из ещё меньшего диапазона.
Так каждая перестановка получает одинаковую вероятность.
В C++ лучше не писать через rand() % n, потому что там может быть ещё и modulo bias.
Нормальный вариант:
std::mt19937 rng(std::random_device{}());
for (int i = n - 1; i > 0; --i) {
std::uniform_int_distribution<int> dist(0, i);
int j = dist(rng);
std::swap(a[i], a[j]);
}
Shuffle - хороший пример, где код может выглядеть “рандомным”, но математически быть неправильным.
case '0' ... '9':
Кажется, что такой switch вообще не должен компилироваться.
Но в GCC это работает. Это расширение называется case ranges — можно задавать сразу диапазон значений внутри case.
Например:
case '0' ... '9':
return DIGIT;
case 'a' ... 'z':
case 'A' ... 'Z':
return LETTER;
Вместо десяти отдельных case для цифр и ещё десятков для букв — одна строка на диапазон.
Но есть нюанс: это не стандартный C, а расширение GCC. Если код должен быть переносимым между компиляторами, на такой синтаксис лучше не рассчитывать.
Одна из тех возможностей C, которые выглядят неправильно, пока не узнаешь, что компилятор действительно это поддерживает.
foo(i++, i++);
Из-за этого один и тот же код может дать разный результат:
* gcc: foo(1, 0)
* clang: foo(0, 1)
Причина простая: компиляторы по-разному вычисляют аргументы.
Такие вещи годами становились источником очень неприятных багов.
Хорошая новость: сейчас -Wall обычно умеет это подсветить.
Вывод банальный, но важный: не пишите код, который зависит от порядка вычисления аргументов.
uint32_t nonce = 0;
while (1) {
header.nonce = nonce;
hash = sha256(sha256(header));
if (hash < target)
break;
nonce++;
}
То есть майнинг Bitcoin в основе своей это гигантский перебор чисел с постоянным пересчётом SHA-256.
Простой цикл, который в итоге породил ASIC-фермы, энергопотребление в масштабах стран и индустрию на миллиарды долларов.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 не означает «удали адреса». Это скорее сигнал ядру: «эти страницы мне сейчас не нужны, можешь забрать физическую память».
Адреса остаются. Страницы уходят. Следующее чтение приносит нули.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 раз.
Мелочь, которую легко пропустить: проблема не в копировании строки, а в постоянном повторном поиске её конца.
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 такой компактный:
никаких явных рёбер, указателей и рекурсии. Только массив и битовая арифметика.
Красота структуры в том, что дерево как бы спрятано внутри двоичного представления индекса.
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 #Programmingstatic меняет поведение в зависимости от того, где именно оно написано.
### 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 #Cppxorshift32:
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
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 году. Небольшое усложнение цикла может заметно уменьшить ошибку без перехода на более тяжёлый числовой тип.# в препроцессоре C превращает токены в строковые литералы ещё на этапе компиляции.
Никакого преобразования во время выполнения не происходит.
Ядро Linux использует этот трюк в макросах вроде WARN_ON(), чтобы вывести точное условие, которое не прошло проверку.
Вы пишете выражение один раз, а препроцессор автоматически генерирует соответствующую строку.
Один оператор - и ваши debug-сообщения остаются идеально синхронизированы с кодом.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, а маленький трюк архитектуры.