this->notes.
前往频道在 Telegram
О разработке, архитектуре и C++. Tags: #common, #cpp, #highload и другие можно найти поиском. Задачки: #poll. Мои публикации: #pub. Автор и предложка: @vanyakhodor. GitHub: dasfex.
显示更多4 492
订阅者
+124 小时
-67 天
-2030 天
帖子存档
4 490
#highload
Я не очень понял, как это работает. Видосы на самом канале так и не появились, хотя прошло уже больше года с конференции. Плейлист со всеми докладами доступен, и видео выложили год назад. Доступ к ним явно есть. Но просмотров нет. То есть как-то никто и не в курсе, что их можно смотреть.
Доклады с Saint HighLoad++ 2025.
0. Эволюция векторного поиска в YDB. Александр Зевайкин.
Есть ещё в тексте (правда не уверен, что контент 1 в 1).
1. Технологии распределенной карты для автономного транспорта. Гоша Лутидзе.
Прикольное введение в кусок вот этих самоездящих штук.
2. 20 лет на граблях: ошибки, отказы и выводы. Александр Стерлигов.
Несколько кайфовых историй про разные инциденты. Я тоже такое сделаю когда-нибудь.
3. Взгляд за кулисы Яндекс Карт: от сырых спутниковых данных до четких изображений. Игорь Орпанен.
4. Четыре девятки хватит всем? Разбираемся с доступностью дата-центров. Алексей Учакин.
Доклад про датацентры, как они устроены с точки зрения пользователя, что вам обещают и (не)гарантирует.
5. Почему в космосе (пока) нет дата-центров. Александр Токарев.
Ещё один доклад про датацентры, но в космосе. Приятный научпоп.
6. Про избыточность WAL в Postgres. Андрей Бородин.
Интересное для понимания устройства WAL в pg. Не то, чтобы я запомнил что-то, но удовольствие получил.
Факт, который понравился: в pg есть лагающая реплика. Вы можете сделать реплику, в которой всё применяется с задержкой в час, и при факапе остановить её обновление + переключиться на неё.
У меня коллеги когда-то делали похожую штуку, но на уровне приложения. Фиксировали набор табличек, которым нужно делать снапшоты (раз в час и сутки, например). И если источник данных накатил что-то неправильное, из-за чего не получается клиентам отдать данные, мы (как промежуточное звено) могли руками включить чтение из подходяшего снапшота.
Пару раз помогло восстановиться на инцидентах за минуты.
4 490
#cpp и не только.
НАПОМИНАЮ, что 1ого августа будет Back to Back: в Москве back to back трек + C++ трек, в Ереване только back to back, а в Белграде только C++ one.
Программу на плюсовый трек мы собрали сочную. Старались, честное слово. Коллеги по цеху на Back to Back тоже интересного выбрали.
Меня, дай бог всё будет хорошо, сможете найти в Белграде. Можем с вами за здоровье Страуструпа выпить ракии (хотя я ещё её не пробовал, может и не моё).
Буду стараться за неделю после конфы выдать трип репорт.
У вас ещё есть время зарегистрироваться!
4 490
#cpp
Привет.
Сегодня последний day 52 марафона.
Я знаю, что кому-то он поднадоел. Не зря от меня отписалось больше полусотни человек.
Мне тоже надоело, если честно.
Если вдруг вы не узнали/не знали, марафон был основан на C++ Preprocessor Iceberg. Я в целом шёл от начала почти по каждому пункту, скипая что-то, что выглядит совсем мутным и невнятным, что я не смог заставить работать или не смог осознать. Попутно старался дополнять своим ресёрчем. Постов фактически вышло на пару десятков меньше, чем пунктов в оригинальном источнике. Мы примерно на половину задели Deep water и совсем не задели The abyss. К концу я уже высох от темы и решил, что душить себя не очень полезно. Лучше чем-то в радость позаниматься. Так что заинтересованные могут доисследовать самый низ айсберга.
Расскажите в комментах, как вам. Надо такое делать вообще ещё когда-нибудь? На какие темы? Или нахер и лучше ограничиться обычными форматами?
Не факт конечно, что я ваше мнение учту, но мне хочется его знать.
Если не хотите писать открытым текстом, то вот варианты quite feedback:
🔥 — круто, можно повторять
🥴 — круто, можно повторять, но слишком долго
🤯 — круто, лучше не повторять
🌚 — не круто, лучше не повторять.
Ниже собрал ссылочки для удобства. Пересылайте друзьям, мамам и папам. Пользуйтесь в будущем сами.
Above the iceberg:
• #include
• include guards
• #pragma once
• #if
• заворачивание в скобки
• do-while
• ARRAY_SIZE и sizeof
On the iceberg:
• stringification operator
• token concatenation operator
• __VA_ARGS__
• #undef
• #error
• comments concatenation
• X-macro
Below the water:
• FILE and LINE
• DATE and TIME
• #line
• триграфы и иже с ними
• _Pragma
• -E
Middle of the iceberg:
• COUNTER
• CONCAT
• #elifdef #elifndef
• #embed
• SCAN
• лимит на макросы
• mcpp
• перегрузка макросов
• default args в макросах
Bottom of the iceberg:
• blue paint
• BOOL, IF
• INC/DEC
• LOOP
• Boost.Preprocessor
Below the iceberg:
• LIST
• ORDER_PP
• #assert
• #include_next
• ppstep
• how macro expansion works
• sequence
Deep water:
• итерация по sequence
• chaos-pp
• Metalang99
• не знаю точно что это
• push/pop макросов
• ещё несколько ссылочек
4 490
#cpp
Day 51.
Есть ещё несколько интересных репозиториев, связанных с препроцессором:
• datatype99 (фактически синтаксический сахар для генерации типов, но с дополнительными плюшками вроде паттерн матчинга)
• interface99 (Golang/Rust inspired интерфейсы для C99)
• boline (integer arithmetics with base 16).
У последнего код с вайбом ASCII-art.
И есть ещё подгон от подписчика про расширение в clang, позволяющее делать макросы рекурсивными. Названо оно
__THIS_MACRO__:
#define fold_left(op, head, ...)(__VA_OPT__(__THIS_MACRO__(op,__VA_ARGS__)op)head)
fold_left(+, 1, 2, 3, 4) // раскрывается в ((((4)+3)+2)+1)
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.4 490
#cpp
Day 50.
Макросы можно класть на и доставать из (со) стека:
#define PRAMGA(...) _Pragma(#__VA_ARGS__)
#define POP(m) PRAMGA(pop_macro(#m))
#define X 1 POP(X)
#pragma push_macro("X")
#define X 2 POP(X)
#pragma push_macro("X")
#define X 3 POP(X)
#pragma push_macro("X")
#define X 4 POP(X)
X // 4
X // 3
X // 2
X // 1
Не уверен, что понимаю, зачем это нужно.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.4 490
#cpp
Day 49.
Одна из моделей, которую можно реализовать на макросах (хотя мы выяснили, что можно любую видимо) — continuation machine. Фактически это штука, в которой вы можете построить пайплайн работы с данными (но такими словами про неё не говорят, слишком современно). На картинке ниже страшная реализация для простого кейса работы с регистрами (разбирать мы ёе не будем, я хочу просто подышать.....).
Кстати репозиторий это что-то вроде оптимизированного интерпретатора Brainfuck. Есть и другие альтернативы: bfi, CPP_COMPLETE.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
4 490
#cpp
Day 48.
Если вам нужна сравнительно мощная, но более современная альтернатива chaos-pp, стоит взглянуть на Metalang99.
Это хорошая попытка сделать препроцессор похожим на функциональный язык программирования. Но что более важно, проект хорошо документирован. chaos-pp в этом плане проигрывает (и служит скорее исследовательским артефактом).
Так что Metalang99 — ваш второй выбор (после Boost.Preprocessor).
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
4 490
#cpp
Day 47.
Я уже упоминал Paul Mensodines, который написал огромное письмо про механизм раскрытия макросов.
И упоминал про Boost.Preprocessor.
Вот Paul когда-то написал chaos-pp — настоящая глыба в мире препроцессорных библиотек. Там вообще всё что хотите. Рекурсия, высокоуровневые алгоритмы (
MAP, FOLD, FILTER, ZIP и др.), лямбды и частичное применение макросов, разные контейнеры (sequence, tuple, list).
Правда её не так часто используют. Просто потому что она очень сложная (== высокий порог входа), без документации, а большинство задач решаются чем-то попроще.
Но как артефакт кайфово.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.4 490
#cpp
Day 46.
Шагаем на ступеньку номер 7.
Мы бы могли с вами реализовать итерацию по sequence, но иногда лучше вовремя остановиться и заиспользовать что-то готовое:
#define PRINT(r, data, elem) elem
BOOST_PP_SEQ_FOR_EACH(
PRINT,
_,
(a)(b)(c)(d)
)
Получим
a b c d
Как это реализовано?
Ну вот примерно как вчера откусываете по одному токену из начала и применяете к нему «функтор». Чего непонятно-то???
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.4 490
#cpp
Day 45.
Мы уже обсуждали, как сделать
LIST, но есть и более свойственная препроцессору структура данных: sequence.
Sequence это просто набор элементов в скобках:
(1)(2)(3)(4)(5)Тут 5 элементов. Но конечно мы не ограничиваемся числами:
(a)(b)(c) (int)(double)(bool) (1)(2)()((),w,())(awoo())(())Основные операции иногда реализуются просто:
#define SEQ_PUSH_FRONT(x, seq) (x) seq
SEQ_PUSH_FRONT(z, (a)(b)(c)) // (z)(a)(b)(c)
#define FRONT(seq) FRONT_I seq
#define FRONT_I(x) x
FRONT((a)(b)(c))
// FRONT_I(a)(b)(c)
// a
В конце на самом деле надо ещё как-то убрать (b)(c), но сейчас это не так важно.
Другие операции могут быть чуть сложнее, но концептуально там ничего такого. В любом случае самим реализовывать вам не нужно. Берите Boost Preprocessor и крутите
BOOST_PP_SEQ_SIZE((a)(b)(c)) // 3
BOOST_PP_SEQ_ELEM(1, (a)(b)(c)) // b
BOOST_PP_SEQ_PUSH_BACK(seq, x)
Там ещё и итерироваться можно, но про это завтра.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.4 490
#cpp
Day 44.
Сегодня ссылка, где Paul Mensodines скрупулёзно поясняет механизм раскрытия макросов: How macro expansion works.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
4 490
#cpp
Day 43.
Так как писать код на препроцессоре бывает неимоверно неприятно, есть тулы в помощь. Например, ppstep, который позволяет интерактивно (пошагово) разбираться в раскрытии ваших макросов и всего вокруг.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
4 490
#cpp
Day 42.
Представьте такую ситуацию: Вы мегаумный программист, разрабатывающий инфру в какой-нибудь компании. И Вы хорошо понимаете, какие ошибки совершают ваши коллеги чаще других. Вы бы хотели оградить коллег от частых ошибок. Например, запретить пользоваться какой-то функцией из стандартной библиотеки и сообщить в ошибке альтернативу.
Или может вы пишете какой-то тулинг для сбора статистики и хотите подменить стандартную функцию своей, которая будет сначала собирать какую-то информацию, а потом как обычно вызывать стандартное решение.
Для решения таких задач можно подменить стандартный хедер на свой.
Для начала его надо реализовать:
// my_headers/string.h
#pragma once
#include <string.h> // подключаем системный хедер
#define strcpy(...) static_assert(false, "Use strncpy instead")
И начать компилировать вашу программу как-нибудь так:
clang++ -I my_headers ...
(тут мы указываем путь к хедерам, так как наш my_headers расположен первым, то и проверяться он будет первым)
Или через cmake как-то так:
target_include_directories(
my_target
BEFORE PRIVATE my_headers
)
Теперь пользователь будет писать
#include <string.h>
и вместо системного хедера подключится ваш. Круто?
А видите ли вы какую-нибудь проблему в реализации my_headers/string.h?
Например, что внутри мы тоже подключаем <string.h>, который (согласно нашей сборке проекта) будет искаться в my_headers/string.h.
Какая-то рекурсия!
Для решения этой проблемы некоторые компиляторы реализовали нестандартное расширение #include_next. Достаточно в нашем хедере использовать именно его:
#include_next <string.h>
#include_next будет инклудить указанный файл, но поиск файла будет осуществляться после того каталога, в котором находится текущий файл. То есть в нашем примере мы сразу пропустим директорию my_headers и начнём искать где-то ещё. Если у вас нет других string.h до системных хедеров, мы получим нужный файл из стандартной библиотеки.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.4 490
#cpp
Day 41.
Кроме хорошо знакомого вам
assert был и ещё никак не связанный с первым #assert.
Подразумевалось, что #assert будет помогать генерировать какие-то утверждения для препроцессора. Например
#assert cpu(x86)
#assert os(linux)
#assert this(notes)
после чего вы могли писать
#if #cpu(x86) // true
...
#if #os(linux) // true
...
#if #this(notes) // true
...
#if #os(win) // false
...
То есть с помощью #assert препроцессор записывал в отдельную табличку верные утверждения, а потом умел сообщать, есть ли такой факт. Причём проверка проводилась для predicate(argument).
Ещё утверждения можно было отменять с помощью
#unassert cpu(x86)
И больше это неправда.
Фактически это примитивная логическая база знаний.
Выше я писал «был», потому что в C11 #assert и #unassert не стало. Потому что фактически почти никто не реализовал. Все писали примерно:
#define CPU_X86 1
#if CPU_X86
#ifdef CPU_X86
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.4 490
#cpp
Day 40.
Представьте, что вы хотите на препроцессоре писать не макросами, а на каком-нибудь Lisp-like языке программирования (ну просто попытайтесь представить, что вы этого хотите).
Есть такая библиотека
ORDER_PP:
ORDER_PP(8to_lit(8add(8, 5))) // 15
или
ORDER_PP(
8to_lit(
8mul(
8add(2,3),
8add(4,5)
)
)
) // 45
Язык на самом деле мощнее. Можно объявлять переменные, писать функции, рекурсию, списками пользоваться, ленивые вычисления мутить (вроде как).
Можем вот факториал вычислить (это всё будет вычислено препроцессором!):
ORDER_PP
(
(8define FACT
(8fn (8N)
(8if (8zero 8N)
1
(8mul 8N
(FACT (8sub 8N 1)))))
))
ORDER_PP(8to_lit (FACT 6))
(возможно оно не скомпилируется, но суть такая)
Как это работает? И что это за 8 перед командами?
Я не буду рассказывать про устройство библиотеки, но расскажу основной механизм.
Во время лексического анализа препроцессор не знает ни про какие числа. Он знает про токены языка. А они бывают разные:
identifier pp-number string-literal punctuator ... and othersНас интересует pp-number. Ниже примеры того, что является pp-number:
123 1e10 0xFF 3.14 1E+10 0x123ABC 1abc 123hello 8X 8let 8mul 0foobarИ пусть вторую пачку компилятор не сможет разобрать как литералы, это всё равно pp-number (данные, а не identifier). А макросы ищутся только среди идентификаторов. Поэтому библиотека пользуется такими pp-number сколько нужно и не боится, что препроцессор раскроет конструкции раньше времени (8 для префикса автор выбрал по личной симпатии видимо).
ORDER_PP пользуется этой возможностью хранить программу как данные и где-то внутри библиотеки в нужный момент происходит склейка pp-numbers в полноценные макросы:
ORDER_PP_8mul // for example
После этого перехода в мир стандартных макросов уже начинается жоское вычисление всего вокруг.
Чего только не напридумывают.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.4 490
#cpp
Day 39.
Начинаем уровень 6.
С помощью макросов можно сделать и какие-то структуры данных (перенося подход всё то же самое можно и в некоторых функциональных языках, и на шаблонах).
Давайте реализуем list:
#define LIST_HEAD(a,b) a
#define LIST_TAIL(a,b) b
LIST_HEAD(1,(2,(3,))) // 1
LIST_TAIL(1,(2,(3,))) // (2,(3,))
И на самом деле этого достаточно.
Можно научиться проверять на конец:
#define TUPLE_AT_1(x,y,...) y
#define CHECK(...) TUPLE_AT_1(__VA_ARGS__,)
#define LIST_END(...) ,0
#define LIST_IS_END(x) CHECK(LIST_END x,1)
LIST_IS_END((9,)) // 0
LIST_IS_END(LIST_TAIL(9,)) // 1
Теперь вспомним пост про реализацию BOOL и IF и поймём, что это можно использовать как условие в тех конструкциях.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.4 490
#cpp
Day 38.
Есть целая отдельная библиотека Boost.Preprocessor, в которой используется довольно много подходов, на которые мы с вами смотрели.
Код там забористый.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
4 490
#cpp
Day 37.
Иногда можно хулиганить.
Как????
Сейчас расскажу.
Давайте возьмём такой странный пример:
#define SCAN(...) __VA_ARGS__
#define EMPTY()
#define LOOP_INDIRECTION() LOOP
#define LOOP(x) x LOOP_INDIRECTION EMPTY()() (x)
SCAN вы знаете. EMPTY() раскрывается в пустоту. А два последних понятно во что раскрываются. Там прям справа написано. Но как это работает, мы сейчас рассмотрим.
Давайте сначала поймём, во что раскроется LOOP(1):
LOOP(1)
// 1 LOOP_INDIRECTION EMPTY()() (1)
// 1 LOOP_INDIRECTION EMPTY()() (1) // раскрываем EMPTY()
// 1 LOOP_INDIRECTION () (1)
Вот тут важный момент: между LOOP_INDIRECTION и () есть пробел. А значит препроцессор воспринимает это как два отдельных токена. из-за чего раскрывать LOOP_INDIRECTION() он не станет. Так что мы на этом останавливаемся.
Теперь давайте просканируем:
SCAN(LOOP(1))
// SCAN(1 LOOP_INDIRECTION () (1))
// 1 LOOP_INDIRECTION()(1) // токены рассматриваются как один поток
// 1 1 LOOP_INDIRECTION () (1)
То есть получили уже 2 единицы в начале.
Обобщая идею можно реализовать EVAL:
#define EVAL(...) EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
#define EVAL1(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
#define EVAL2(...) __VA_ARGS__
И для
EVAL(LOOP(1));
мы получим единицы на всю глубину EVAL:
1 1 1 1 1 1 1 1 1 1 1 1 1 1 LOOP_INDIRECTION ()(1)
Можно в итоге написать макрос, которые из
E3(LOOP(f,1,2,3,4,5,6,7,8,9,END))
раскроется в
// f(1) f(2) f(3) f(4) f(5) f(6) f(7) f(8) f(9)
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.4 490
#cpp
Day 36.
Иногда используют похожую структуру для реализации
INC/DEC:
#define AT_0(a,b,c,d,e,f,g,h) a
#define AT_1(a,b,c,d,e,f,g,h) b
#define AT_2(a,b,c,d,e,f,g,h) c
#define AT_3(a,b,c,d,e,f,g,h) d
#define AT_4(a,b,c,d,e,f,g,h) e
#define AT_5(a,b,c,d,e,f,g,h) f
#define AT_6(a,b,c,d,e,f,g,h) g
#define AT_7(a,b,c,d,e,f,g,h) h
#define CAT(a,b) CAT_I(a,b)
#define CAT_I(a,b) a##b
#define INC_(n) CAT(AT_, n)(1,2,3,4,5,6,7,0)
#define DEC_(n) CAT(AT_, n)(7,0,1,2,3,4,5,6)
#define INC(n) INC_(n)
#define DEC(n) DEC_(n)
INC(6) // 7
INC(7) // 0
DEC(0) // 7
INC(DEC(INC(INC(1)))) // 3
AT_x равносильно взять элемент по индексу x, а INC/DEC просто передают сдвинутое на 1 в нужную сторону количество аргументов в нужном порядке.
Тут фактически мы сделали конечную группу \Z_8.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.4 490
#cpp
Day 35.
Как мы уже поняли, препроцессор умеет только несколько базовых операций:
• раскрывать макросы
• склеивать токены (
##)
• работать с запятыми в списках аргументов (то бишь разделять по запятым аргументы).
Но иногда хочется делать что-то совсем человеческое. Например, написать if:
IF(1)(a, b)
IF(0)(a, b)
Так нельзя. Потому приходится крутиться.
Сегодня придётся напрячь думалку.
Зайдём издалека.
Давайте определим вот такой странный макрос:
#define PROBE ,found
Обратите внимание, что он раскрывается в 2 аргумента для другого макроса: "" и "found".
И сделаем макрос CHECK:
#define TUPLE_AT_1(b,a,...) a // just return 2nd argument
#define CHECK(...) TUPLE_AT_1(__VA_ARGS__,)
Теперь мы можем сделать конструкцию, которая позволит нам узнать, определён ли макрос:
#define FOO PROBE
#define IS_FOO_IMPL(x) IS_FOO_##x
#define IS_FOO(x) CHECK(IS_FOO_IMPL(x), 0)
#define IS_FOO_FOO PROBE
Теперь можем проверять:
IS_FOO(FOO)
// CHECK(IS_FOO_IMPL(FOO), 0)
// TUPLE_AT_1(IS_FOO_IMPL(FOO), 0, )
// TUPLE_AT_1(IS_FOO_FOO, 0, )
// TUPLE_AT_1(PROBE, 0, )
// TUPLE_AT_1(, found, 0, )
// found
или иначе:
IS_FOO(BAR)
// ...
// 0
Вроде схема работы понятна, но зачем это нужно, не совсем. Давайте вернёмся к изначальному примеру.
IF(1)(A, B ) // A
IF(0)(A, B ) // B
Сделаем базу:
#define PROBE ~, 1
#define SECOND(a, b, ...) b
#define CHECK(...) SECOND(__VA_ARGS__, 0)
Тогда
CHECK(PROBE) // 1
CHECK(x) // 0
Теперь построим макрос NOT:
#define NOT_0 PROBE
#define NOT(x) CHECK(NOT_##x)
И когда используем:
NOT(0) // CHECK(NOT_0) -> CHECK(PROBE) -> 1
NOT(1) // CHECK(NOT_1) -> NOT_1 doesn't exist -> 0
Вот мы уже научились инвертировать значение.
Осталось сделать BOOL:
BOOL(0) = 0
BOOL(1) = 1
Реализация тривиальная:
#define BOOL(x) NOT(NOT(x))
Последнее, что нам нужно, это сделать IF:
#define IF_0(t, f) f
#define IF_1(t, f) t
IF_0(A, B) // B
IF_1(A, B) // A
Его мы можем собрать через знакомый нам CAT:
#define CAT(a, b) CAT_I(a, b)
#define CAT_I(a, b) a##b
#define IF(c) CAT(IF_, BOOL(c))
Конечно, в коде не обязательно писать IF(0)/IF(1). Вместо значения может быть какой-то другой макрос, который раскроется в конкретное число. Сам макрос может прийти откуда-то снаружи.
Жоска мне кажется. Чего только не придумают.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.