this->notes.
前往频道在 Telegram
О разработке, архитектуре и C++. Tags: #common, #cpp, #highload и другие можно найти поиском. Задачки: #poll. Мои публикации: #pub. Автор и предложка: @vanyakhodor. GitHub: dasfex.
显示更多4 503
订阅者
-524 小时
-217 天
-5530 天
数据加载中...
相似频道
标签云
进出提及
---
---
---
---
---
---
吸引订阅者
六月 '26
六月 '26
+28
在1个频道中
五月 '26
+106
在0个频道中
Get PRO
四月 '26
+70
在1个频道中
Get PRO
三月 '26
+57
在2个频道中
Get PRO
二月 '26
+30
在0个频道中
Get PRO
一月 '26
+69
在0个频道中
Get PRO
十二月 '25
+75
在0个频道中
Get PRO
十一月 '25
+75
在1个频道中
Get PRO
十月 '25
+141
在3个频道中
Get PRO
九月 '25
+1 111
在1个频道中
Get PRO
八月 '25
+774
在1个频道中
Get PRO
七月 '25
+342
在3个频道中
Get PRO
六月 '25
+94
在2个频道中
Get PRO
五月 '25
+100
在2个频道中
Get PRO
四月 '25
+71
在1个频道中
Get PRO
三月 '25
+63
在2个频道中
Get PRO
二月 '25
+51
在1个频道中
Get PRO
一月 '25
+34
在0个频道中
Get PRO
十二月 '24
+56
在0个频道中
Get PRO
十一月 '24
+100
在1个频道中
Get PRO
十月 '24
+93
在1个频道中
Get PRO
九月 '24
+220
在2个频道中
Get PRO
八月 '24
+76
在0个频道中
Get PRO
七月 '24
+155
在1个频道中
Get PRO
六月 '24
+44
在1个频道中
Get PRO
五月 '24
+86
在0个频道中
Get PRO
四月 '24
+144
在5个频道中
Get PRO
三月 '24
+100
在1个频道中
Get PRO
二月 '24
+64
在0个频道中
Get PRO
一月 '24
+132
在0个频道中
Get PRO
十二月 '23
+26
在0个频道中
Get PRO
十一月 '23
+16
在0个频道中
Get PRO
十月 '23
+26
在0个频道中
Get PRO
九月 '23
+29
在0个频道中
Get PRO
八月 '23
+29
在0个频道中
Get PRO
七月 '23
+63
在0个频道中
Get PRO
六月 '23
+50
在0个频道中
Get PRO
五月 '23
+53
在0个频道中
Get PRO
四月 '23
+145
在0个频道中
Get PRO
三月 '23
+48
在0个频道中
Get PRO
二月 '23
+46
在0个频道中
Get PRO
一月 '23
+38
在0个频道中
Get PRO
十二月 '22
+33
在0个频道中
Get PRO
十一月 '22
+60
在0个频道中
Get PRO
十月 '22
+37
在0个频道中
Get PRO
九月 '22
+90
在0个频道中
Get PRO
八月 '22
+49
在0个频道中
Get PRO
七月 '22
+352
在0个频道中
| 日期 | 订阅者增长 | 提及 | 频道 | |
| 24 六月 | +1 | |||
| 23 六月 | +1 | |||
| 22 六月 | 0 | |||
| 21 六月 | +1 | |||
| 20 六月 | +1 | |||
| 19 六月 | +1 | |||
| 18 六月 | +1 | |||
| 17 六月 | +3 | |||
| 16 六月 | +1 | |||
| 15 六月 | 0 | |||
| 14 六月 | +1 | |||
| 13 六月 | +1 | |||
| 12 六月 | 0 | |||
| 11 六月 | +5 | |||
| 10 六月 | +1 | |||
| 09 六月 | 0 | |||
| 08 六月 | +1 | |||
| 07 六月 | 0 | |||
| 06 六月 | +1 | |||
| 05 六月 | 0 | |||
| 04 六月 | +3 | |||
| 03 六月 | +2 | |||
| 02 六月 | +2 | |||
| 01 六月 | +1 |
频道帖子
#cpp
Day 23.
Мы уже пользовались, но не оговаривали.
У многих компиляторов есть нестандартные флаги компиляции.
-E указывает компилятору, что нужно только выполнить препроцессинг. Не далее. Можно пользоваться, если хотите посмотреть на код после или подебагать макросы.
Если помните, когда мы смотрели на #line, в результате компиляции файла с -E мы видели маркеры строк. Это которые меняли значения __LINE__ и __FILE__. Вот их можно убрать, если к -E добавить -P.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn.| 2 | #cpp
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. | 969 |
| 3 | #cpp
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. | 1 007 |
| 4 | #cpp
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;
??>
Мы так лабы сдавали в универе.
Последние deprecated, так как несут проблемы и могут менять ваше поведение. Из-за того, что они processed early, вместо
"What's going on??!"
вы можете получить
"What's going on|"
Проблемес.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn. | 1 165 |
| 5 | #cpp
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. | 1 201 |
| 6 | #cpp
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. | 1 352 |
| 7 | #cpp
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. | 1 583 |
| 8 | Меня звали, а я ходил!
Поболтали с ПСИвИТ про всякое. Не C++ just in case: https://t.me/psyvit/1142 | 1 764 |
| 9 | Доброе что у вас там. Это пост-заглушка.
Я не пропустил день марафона. Пост написан. Но сегодня должно быть что-то другое, что не хочется постить когда-нибудь.
В худшем случае марафонский пост будет запощен чуть позже.
Это сообщение чуть позже удалю.
А я сегодня и до конца недели тусуюсь на ACCU on sea. Трипрепорт тоже попробую сообразить поскорее. | 1 001 |
| 10 | #cpp
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. | 1 817 |
| 11 | #cpp
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. | 1 786 |
| 12 | #cpp
Day 14.
Бывает, что вы наусловнокомпилировали чего-то. Ифдефами обмазались. И не можете покрыть все-все случаи, которые у вас возникают.
Или хотите быть прозрачными с пользователем, что в его ситуации нет пока готового решения.
Или может хотите юзеру сообщить, что он делает что-то странное (например, пытается использовать стандарт X на платформе Y).
Что делать?
Выдать понятное сообщение!
В препроцессорном мире вам поможет #error:
#error "You did something wrong. Drink beer."
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn. | 1 684 |
| 13 | #cpp
Day 13.
Если вам #define нужен «на время», вы можете потом раздефайнить:
#define X
X
X
...
X
#undef X
X // ⛔️CE
Фактически это замена области видимости для макросов.
Один из примеров (я не говорю, что хороших, мы тут вообще про макросы говорим, что с ними хорошего?) — получить доступ к членам объектов для тестирования:
// in test.c
#define private public
#include "logic_with_A_class.h"
// in some test
A a = getA();
assert(a.x == 1);
#undef private
После #define в вашем инклуде все приватные поля в A станут публичными, что даёт нам возможность к ним обратиться (к полю x в примере).
Аналогично вы можете подменить все вызовы функции в подключённом хедере на вашу функцию.
#define malloc my_malloc
#include "code.h"
#undef malloc
Конечно, можно сломать что-нибудь:
#undef assert
#undef errno
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn. | 1 734 |
| 14 | #cpp
Day 12.
Иногда вы хотите написать какой-то общий макрос, который будет работать для произвольного числа аргументов:
#define MACRO(...) __VA_ARGS__
__VA_ARGS__ — способ сослаться на все аргументы, обозначенные как ... в макросе. Компилятор просто перечислит их через запятую:
#define DEBUG(...) printString(__VA_ARGS__)
DEBUG("1", "2", "3"); // printString("1", "2", "3")
Частая проблема с __VA_ARGS__ — пустой пак аргументов.
#define LOG(fmt, ...) print(fmt, __VA_ARGS__)
LOG("str"); // print("fmt", )
Мы получаем пустой пак аргументов, из-за чего получаем лишнюю запятую, после которой ничего нет. Это большая боль.
Как жить с этим, расскажу попозже.
У макросов часто возникают проблемы с шаблонами, так как параметры макроса сплитятся по запятой:
#define A(a, b) f(a, b)
A(std::pair<int, int>{1, 1}, 1);
И вы получите ошибку компиляции, т.к. в примере выше A получил 4 аргумента:
• std::pair<int
• int>{1,
• 1}
• 1
А может принять только 2 (и никак не f(std::pair<int, int>{1) ).
Я постоянно стукаюсь об это, когда пишу тесты с gtest.
С __VA_ARGS__ это иногда может заработать, т.к. вы теперь все аргументы всегда передаёте пачкой:
#define A(...) f(__VA_ARGS__)
A(std::pair<int, int>{1, 1}, 1); // f(std::pair<int, int>{1, 1}, 1)
__VA_ARGS__ можно использовать для подсчёта кол-ва аргументов в макросе (пример для до 5 аргументов):
#define NARGS_IMPL(_1,_2,_3,_4,_5,N,...) N
#define NARGS(...) NARGS_IMPL(__VA_ARGS__, 5,4,3,2,1)
NARGS(a, b, c) // 3
Раскрывается примерно так:
NARGS(a, b, c)
NARGS_IMPL(
_1 = a
_2 = b
_3 = c
_4 = 5
_5 = 4
N = 3
)
N = 3
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn. | 1 891 |
| 15 | #cpp
Day 11.
## — token pasting operator или token concatenation operator.
Используется в макросах для склеивания двух токенов (на этапе препроцессинга конечно же).
#define MAKE_VAR(x) var_##x
int MAKE_VAR(test) = 42; // int var_test = 42;
Склеиваются именно токены. После препроцессинга должен получиться валидный токен:
#define BAD(a, b) a ## b
BAD(+, +) // ++ ✅
BAD(x, *) // x* ⛔️
Используется для генерации чего угодно. Часто думают только про имена функций/переменных/классов, но можно и для операторов, ключевых слов, литералы.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn. | 1 962 |
| 16 | #cpp
Day 10.
Когда вы работаете с макросами, вам может пригодиться stringification operator.
#define STR(x) #x
Он позволяет превратить аргумент макроса в строку:
STR(123 foo real) // "123 foo real"
Причём аргумент в таком случае не expandится:
#define FOO 123
#define STR(x) #x
STR(FOO) // "FOO"
Если хотите раскрыть, нужен ещё один уровень:
#define STR2(x) #x
#define STR(x) STR2(x)
STR(FOO) // "123"
# почти сохраняет исходный текст (может поудалять лишние пробелы):
STR( a + b ) // "a + b"
Так что некоторые разные входные данные могут давать одинаковый результат. Надо быть осторожными.
Вы можете использовать # для дебажных утилиток:
#define PRINT(expr) \
std::cout << #expr << " = " << (expr) << '\n';
PRINT(x + y) // x + y = 12
Эта же фича используется в assert (вы видите упавшее условие в ошибке).
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn. | 2 111 |
| 17 | #cpp
Day 9.
До сегодняшнего дня мы были где-то на уровне 1. Сегодня делаем шаг на следующую ступеньку (вниз или вверх, это как посмотреть).
Подстановка макросов (expansion) не являются рекурсивной.
#define A(x) A(x x)
A(x) // A(x x)
#define B(x) C(x x)
#define C(x) B(x x)
B(x) // B(x x x x)
Выстрелить себе в ногу становиться чуть сложнее. Или проще. Это опять как посмотреть.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn. | 2 238 |
| 18 | #cpp
Day 8.
В C тоже есть массивы. И работягам тоже хочется знать, сколько в этих массивах элементов. Стандартного решения у ребят там нет, но есть общий подход, перетекающий из кодовой базы в кодовую базу. Зовётся ARRAY_LENGTH/ARRAY_LEN/ARRAY_SIZE/COUNTOF/...
Выглядит так:
#define ARRAY_LENGTH(x) (sizeof(x) / sizeof((x)[0]))
Тут мы полагаемся на sizeof, который, согласно стандарту C99 (моя вольная интерпретация):
sizeof возвращает размер операнда (в байтах). Размер зависит от типа операнда. Результат — int. Обычно результат не evaluated и является integer constant.
Another use of the sizeof operator is to compute the number of elements in an array:
sizeof array / sizeof array[0]
Так что sizeof(x) вернёт кол-во байт типа массива (если x — массив int[3], то можем получить (в зависимости от системы) 12). sizeof((x)[0]) вернёт размер типа одного элемента (в нашем случае 4). Вот и получаем 3.
На собесах могут спрашивать вопросы с подвохом вида:
int x = 10;
sizeof(x++);
std::cout << x; // result?
Конечно, вы на такое не попадётесь и скажете 10, ведь sizeof интересует тип. Он не evaluatит свой аргумент. Но всегда ли это так?
Конечно нет!
Если ваш аргумент — Variable Length Array, то sizeof придётся вычислить аргумент. Мы можем запруфать это через наличие сайдэффекта:
int f() {
printf("called\n");
return 10;
}
int main() {
sizeof(int[f()]);
}
Увидим called в output. https://godbolt.org/z/8G1soa8T1
Теперь срочно требуйте оффер х3 от вашего текущего дохода, ведь собеседующий почти наверняка этого не знает.
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn. | 2 400 |
| 19 | #cpp
Day 7.
Вчера мы писали макросы, которые заменяли собой один statement. А что, если я хочу что-то более сложное?
Обопрусь на пример от Паши (https://t.me/cpp_durka/23): напишем макрос для инкремента двух переменных.
#define INCREMENT_BOTH(x, y) (x)++; (y)++
Тут мы умные. Сразу взяли выражения в скобки. Не поставили в конце ; , чтобы обязать пользователя её поставить самому (для консистентности кода).
Но если мы чуть-чуть отступим от глупого использования:
if (condition)
INCREMENT_BOTH(a, b);
мы получим
if (condition)
(a)++; (b)++;
b инкрементится вне зависимости от условия.
Или ещё пример:
#define MACRO(condition, x) if (condition) std::cout << (x)
if (flag)
MACRO(flag2, 5);
else
std::cout << 10;
Тут else вдруг начинает относиться к if из макроса, а не изначальному, что очевидно баг.
Канонический способ такое исправить:
#define INCREMENT_BOTH(x, y) \
do { \
(x)++; \
(y)++; \
} while (0)
#define MACRO(condition, x) \
do { \
if (condition) { \
std::cout << (x);\
} \
} while (0)
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn. | 2 196 |
| 20 | #cpp
Day 6.
Предположим, мы хотим написать макрос для добавления одной переменной к другой:
#define SQR(x) x * x
В зависимости от способа использования, вы можете не получить или получить проблемы. В таком коде:
int a = SQR(1 + 2);
мы на самом деле получим
int a = 1 + 2 * 1 + 2;
Что не совсем то, что вы ожидали.
Для надёжности лучше завернуть аргументы в скобки:
#define SQR(x) (x) * (x)
Но этого тоже может иногда не хватать:
#define INC(x) (x) + 1
int a = 10 / INC(1 + 1);
Получим:
int a = 10 / (1 + 1) + 1;
Что тоже не то, что мы ожидали. Так что адекватный макрос должен как минимум завернуть в скобки каждый отдельный аргумент + завернуть всё выражение:
#define SQR(x) ((x) * (x))
Как максимум, ваш x может быть вообще-то функцией с сайд-эффектом:
int x = SQR(GetValueFromDbAndPostToKafka());
Так что прям совсем идеально было бы сохранить результат внутри макроса и переиспользовать его. Но я не знаю, как это написать, чтобы было полностью эквивалентно функции.
Мб пора начать сворачивать с макродорожки на что-то более современное......
@thisnotes. Patreon, Boosty.
Спасибо Artyom Garkavy и niki4smirn. | 2 151 |
现已上线!2025 年 Telegram 研究 — 年度关键洞察 
