#define CPP WTF

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Уже давным-давно я работал в одной крупной компании в должности C++-разработчика и столкнулся с одной очень странной ошибкой. Я написал примерно такой класс:

class Foo {
    static void* operator new() {
        return ...;
    };
};

И увидел огромный stack-trace ошибок о недопустимом вызове оператора в этом контекста (на тот момент я использовал MS Visual Studio 2013 и встроенный в него MSVC-компилятор). Я искал проблему часа два, и помогло мне только просматривание готовой единицы трансляции. Как вы могли догадаться, проблема была связана с препроцессором, но обо всём по порядку.

Препроцессор в C++ — это такая, на первый взгляд, очень простая штука, основное назначение которой — добавлять в ваш исходный файл куски кода до того, как им займётся компилятор (для знающих: препроцессор формирует единицы трансляции). Он подключает include-файлы, обрабатывает всякие там #pragma once, но самая главная директива препроцессора, безусловно, это директива #define. Она позволяет заменять один кусок текста на другой. Например, вы пишете #define foo goo, и после этого на этапе работы препроцессора все упоминания отдельного токена (слова) foo в вашем коде заменяются на goo.

Это очень мощный инструмент, он поддерживает аргументы и даже раскрывает последовательности (правда, без рекурсии):

#define foo goo
#define goo doo

foo(); // тут вызовется doo

Единственная проблема — препроцессор очень тупой. Он совершенно не следит за тем, что именно вы define'ите. Например, можно сделать так: #define float double, и это действительно заменит все float на double; или так: #define std qwerty, и это заменит все упоминания пространства имён std на qwerty (std::cout -> qwerty::cout).

Препроцессор действует на уровне единицы трансляции, то есть #define, объявленный в файле a.cpp, не будет действовать в файле b.cpp. Но там, где он действует, он будет действовать беспощадно :-) #define, которого вы не ожидаете может очень сильно изменить логику работы программы и неприятно удивить. Ровно так и получилось в моём случае: в одном из include-файлов было такое объявление:

#define new DEBUG_NEW

а в другом include-файле, такое:

#define DEBUG_NEW new(__LINE__, __FILE__)

Что сделал наш волшебный препроцессор? Правильно, честно всё заменил, и в результате получилось вот так:

class Foo {
    static void* operator new(__LINE__, __FILE__)() {
        return ...;
    };
};

Для чего нужен DEBUG_NEW — вопрос отдельный. Коротко говоря, это отладочный оператор выделения памяти, ведущий учёт всех запрошенных блоков. Тем не менее, теперь ошибка синтаксиса языка уже становится очевидной. Однако это не отменяет того факта, что искать её было очень тяжело.

Успешной вам отладки :-)

Источник: https://habr.com/ru/company/domclick/blog/593553/


Интересные статьи

Интересные статьи

Часто при разговорах с клиентами мы спрашиваем, как они ведут учет различных данных и используют ли они CRM-систему? Популярный ответ — мы работаем с Excel-файлами, а пот...
В этой статье мы расскажем, как оптимизировать крупный проект в «Битрикс24» и увеличить его производительность в 3 раза, изменяя настройки MySQL и режим питания CPU. Дано Корпоративн...
Есть несколько способов добавить водяной знак в Битрикс. Рассмотрим два способа.
Принято считать, что персонализация в интернете это магия, которая создается сотнями серверов на основе БигДата и сложного семантического анализа контента.
Существует традиция, долго и дорого разрабатывать интернет-магазин. :-) Лакировать все детали, придумывать, внедрять и полировать «фишечки» и делать это все до открытия магазина.