C/C++ Ready | Программирование
15.2K subscribers
1.47K photos
94 videos
727 links
Авторский канал по разработке на C и C++.
Ресурсы, гайды, задачи, шпаргалки.
Информация ежедневно пополняется!

Cотрудничество: @energy_c

РКН: https://clck.ru/3QREHc
Download Telegram
Сегодня мы посвятим время важной теме — использованию макросов в C++ с помощью директивы препроцессора #define.

Макросы в C++ представляют собой механизм текстовой подстановки, реализуемый посредством директивы #define. Они позволяют задавать именованные константы и создавать макрофункции — фрагменты кода, которые заменяются непосредственно в исходном коде на этапе препроцессинга.

Например, часто встречается макрос для задания константы:
#define PI 3.14159 // Значение числа π


После его определения PI можно использовать как числовую константу:
double r = 10;
double S = PI * r * r; // Вычисляем площадь круга с радиусом 10


Еще одно полезное применение макросов — вычисление количества элементов в статическом массиве:
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))


Этот макрос позволяет получить размер массива без необходимости вручную пересчитывать элементы:
int numbers[] = {1, 2, 3, 4, 5};
int size = ARRAY_SIZE(numbers); // size будет равен 5


Но следует помнить, ARRAY_SIZE() корректно работает только со статическими массивами. Если передать в него указатель (например, динамический массив), результат будет некорректным.

🔥 Если вы хотите углубить свои знания по этой теме, не стесняйтесь писать в комментариях!

📣 C++ Ready | #практика
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥20👍10👎2
Constexpr-хеширование FNV-1a: делаем switch по строкам!

Нужен быстрый switch без std::string_view сравнений?

Возьмём FNV-1a-хеш, который считается на этапе компиляции:
// 32-битный constexpr-хеш FNV-1a
constexpr uint32_t fnv1a(char const* s, uint32_t h = 0x811c9dc5) {
return *s ? fnv1a(s + 1, (h ^ uint8_t(*s)) * 0x01000193) : h;
}
#define H(str) fnv1a(str)


Теперь можно «переключаться» по строкам так же быстро, как по int:
void handle(std::string_view cmd) {
switch (fnv1a(cmd.data())) {
case H("start"): /* … */ break;
case H("stop"): /* … */ break;
case H("status"): /* … */ break;
default: /* ? */ break;
}}


🔥 Любые литеральные строки превращаются в константы, а сам switch — чистый O(1) без сравнений символов.

📣 C++ Ready | #практика
Please open Telegram to view this post
VIEW IN TELEGRAM
❤14👍10🔥4
Пишем свой assert с логированием и stacktrace!

Сейчас научимся создавать удобный макрос ASSERT, который при провале проверки выводит не только выражение и место в коде, но и подробный stacktrace. Это пригодится для быстрой отладки и локализации ошибок в сложных проектах.

Начнём с подключения библиотек из стандарта C++23 — ничего дополнительно не ставить не нужно:
#include <iostream>
#include <stdexcept>
#include <source_location>
#include <stacktrace>


Теперь объявим функцию-обёртку, которая будет бросать исключение с нужными данными:
void assert_fail(const char* expr,
const std::source_location& loc = std::source_location::current()) {
std::cerr << "ASSERTION FAILED: (" << expr << ")\n"
<< " at " << loc.file_name() << ":" << loc.line()
<< " in " << loc.function_name() << "\n"
<< "Stacktrace:\n" << std::stacktrace::current() << "\n";
std::terminate();
}


И сам макрос, использующий эту функцию:
#define ASSERT(expr) \
((expr) ? void(0) : assert_fail(#expr))


Давайте проверим на примере:
int factorial(int n) {
ASSERT(n >= 0);
return n <= 1 ? 1 : n * factorial(n - 1);
}

int main() {
std::cout << "5! = " << factorial(5) << "\n";
std::cout << "(-1)! = " << factorial(-1) << "\n"; // вызовет ASSERT
return 0;
}


При запуске получим:
5! = 120
ASSERTION FAILED: (n >= 0)
at example.cpp:8 in factorial()
Stacktrace:
#0 factorial() at example.cpp:8
#1 main() at example.cpp:14
#2 __libc_start_main
...


🔥 Таким образом ты получаешь собственный assert с детальным логом и стек-трейсом — никакой потери контекста при отладке и быстрый поиск причины крашей.

📣 C++ Ready | #практика
Please open Telegram to view this post
VIEW IN TELEGRAM
❤18👍11🔥6🤝1
DBG-макрос на std::source_location: удобно дебажим выражения с контекстом

Иногда хочется быстро подсмотреть значение переменной, а не протаскивать логгер, не ставить точку останова и не писать std::cout << ... руками.

Давайте сделаем простой DBG(expr), который в debug-сборке печатает:
• само выражение;
• его значение;
• файл, строку и функцию, где это произошло.

В релизе же макрос превратится в пустышку и не повлияет на производительность.

Подключаем заголовки:
#include <iostream>
#include <string_view>
#include <source_location>


Сделаем вспомогательную функцию debug_print, которая принимает значение, текст выражения и информацию о месте вызова:
template <typename T>
void debug_print(const T& value,
std::string_view expr,
const std::source_location& loc = std::source_location::current())
{
std::cerr << "[DBG] " << expr << " = " << value << "\n"
<< " at " << loc.file_name() << ":" << loc.line()
<< " in " << loc.function_name() << "\n";
}


Теперь объявим макрос DBG, который работает только в debug-сборке (пока не определён NDEBUG):
#ifndef NDEBUG
#define DBG(expr) debug_print((expr), #expr)
#else
#define DBG(expr) ((void)0)
#endif


Проверим на простом примере:
#include <vector>

int main() {
int x = 42;
std::vector<int> v{1, 2, 3};

DBG(x);
DBG(v.size());
DBG(v[1] + x);

return 0;
}


При запуске debug-сборки увидим что-то вроде:
[DBG] x = 42
at example.cpp:8 in main
[DBG] v.size() = 3
at example.cpp:9 in main
[DBG] v[1] + x = 44
at example.cpp:10 in main


А в релизной сборке (-DNDEBUG) все вызовы DBG(...) тихо исчезнут на этапе компиляции.

🔥 В итоге у тебя есть удобный DBG-макрос на чистом стандарте C++20: в отладке он показывает выражения с контекстом (файл, строка, функция), а в релизе не оставляет ни следа в бинарнике.

📣 C++ Ready | #практика
Please open Telegram to view this post
VIEW IN TELEGRAM
👍21🔥13❤8
Антипаттерн: # define DEBUG 1 vs NDEBUG

Часто для отладочного кода делают что-то вроде #define DEBUG 1 и оборачивают всё в #ifdef DEBUG.

Работает? Да.
Надёжно? Не всегда.

Проблема в том, что такой DEBUG — не стандартный, и он легко расходится с реальным типом сборки. В итоге можно случайно оставить отладочный код в релизе.

В стандарте C++ уже есть специальный макрос для этого — NDEBUG. Если NDEBUG не определён, значит это debug-сборка. Если NDEBUG определён, значит релиз.

Именно на него завязаны assert и многие стандартные проверки.

Плохой вариант:
#define DEBUG 1

void foo(int x) {
#ifdef DEBUG
std::cout << "x = " << x << "\n";
#endif

work(x);
}


Если забыть убрать DEBUG или определить его не там — отладочный код спокойно уедет в прод.

Правильный вариант — ориентироваться на NDEBUG:
void foo(int x) {
#ifndef NDEBUG
std::cerr << "x = " << x << "\n";
#endif

work(x);
}


Теперь всё согласовано:
• debug-сборка → логирование и проверки есть
• release (-DNDEBUG) → код полностью вырезается компилятором

Точно так же стоит писать и свои отладочные макросы:
#ifndef NDEBUG
#define DBG_LOG(msg) std::cerr << msg << "\n"
#else
#define DBG_LOG(msg) ((void)0)
#endif


🔥 Использование NDEBUG — это не мелочь, а дисциплина сборки: отладочный код никогда не попадёт в релиз случайно, и всё поведение будет согласовано со стандартными механизмами C++.

📣 C++ Ready | #практика
Please open Telegram to view this post
VIEW IN TELEGRAM
❤22👍13🔥7
Как min/max из windows.h ломают std::min/std::max?

На Windows легко словить странную ошибку компиляции в невинном месте:
#include <windows.h>
#include <algorithm>

auto m = std::min(a, b);


Причина: windows.h (и некоторые связанные хедеры) могут определить макросы min/max. Тогда std::min(a, b) превращается в макро-подстановку, и начинает “ехать” всё, что выглядит как вызов функции.

Правильный вариант — отключить эти макросы до подключения Windows-заголовков:
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <algorithm>

auto m = std::min(a, b);


Если windows.h уже подключили (или его подтянула зависимость), можно убрать макросы точечно:
#include <windows.h>
#undef min
#undef max
#include <algorithm>


И есть аварийный трюк, если нельзя менять include-порядок: обернуть имя в скобки — тогда макрос не сработает:
auto m = (std::min)(a, b);


🔥 Итог: на Windows держи правило — `NOMINMAX` включён всегда, иначе стандартная библиотека будет ломаться в самых неожиданных местах.

📣 C++ Ready | #практика
Please open Telegram to view this post
VIEW IN TELEGRAM
👍22❤9🤝9