Подтвердить что ты не робот

Макросы DEBUG в С++

Я просто столкнулся с макросом DEBUG в C, который мне очень нравится

#ifdef DEBUG_BUILD
#  define DEBUG(x) fprintf(stderr, x)
#else
#  define DEBUG(x) do {} while (0)
#endif

Я предполагаю, что аналогом С++ будет: -

#ifdef DEBUG_BUILD
#  define DEBUG(x) cerr << x
#else
#  define DEBUG(x) do {} while (0)
#endif
  • Второй фрагмент кода аналогичен фрагменту в C?
  • Есть ли у вас любимые макросы отладки С++?

ИЗМЕНИТЬ: Под "Debug Macros" я имею в виду "макросы, которые могут пригодиться при запуске программы в режиме отладки".

4b9b3361

Ответ 1

Является ли второй фрагмент кода аналогичным фрагменту в C?

Более или менее. Он более мощный, так как вы можете включать в аргумент << -раздельные значения, поэтому с помощью одного аргумента вы получаете то, что потребует переменное количество аргументов макроса в C. С другой стороны, есть небольшой шанс, что люди будут злоупотреблять им, включив точку с запятой в аргумент. Или даже получить ошибки из-за забытой точки с запятой после вызова. Поэтому я бы включил это в блок do:

#define DEBUG(x) do { std::cerr << x; } while (0)

Есть ли у вас любимые макросы отладки С++?

Мне нравится выше, и использую его довольно часто. Мой no-op обычно просто читает

#define DEBUG(x)

который имеет тот же эффект для оптимизации компиляторов. Хотя комментарий @Tony D ниже - это corret: это может оставить некоторые ошибки синтаксиса необнаруженными.

Иногда я также использую проверку времени выполнения, предоставляя некоторую форму флага отладки. Поскольку @Tony D напомнил мне, что использование endl также часто полезно.

#define DEBUG(x) do { \
  if (debugging_enabled) { std::cerr << x << std::endl; } \
} while (0)

Иногда я также хочу напечатать выражение:

#define DEBUG2(x) do { std::cerr << #x << ": " << x << std::endl; } while (0)

В некоторых макросах мне нравится включать __FILE__, __LINE__ или __func__, но это чаще всего утверждения, а не простые макросы отладки.

Ответ 2

Вот мой любимый

#ifdef DEBUG 
#define D(x) x
#else 
#define D(x)
#endif

Это супер удобно и делает для чистого (и, что особенно важно, быстрого в режиме выпуска!) кода.

Множество блоков #ifdef DEBUG_BUILD по всему месту (чтобы отфильтровывать отладочные блоки кода) довольно уродливое, но не так плохо, когда вы обертываете несколько строк с помощью D().

Как использовать:

D(cerr << "oopsie";)

Если это еще слишком уродливо/странно/долго для вас,

#ifdef DEBUG
#define DEBUG_STDERR(x) (std::cerr << (x))
#define DEBUG_STDOUT(x) (std::cout << (x))
//... etc
#else 
#define DEBUG_STDERR(x)
#define DEBUG_STDOUT(x)
//... etc
#endif

(I предложить не использовать using namespace std;, хотя, возможно, using std::cout; using std::cerr; может быть хорошей идеей)

Обратите внимание, что вы можете сделать больше вещей, чем просто печатать на stderr, когда вы думаете об "отладке". Получите креатив, и вы можете создавать конструкции, которые предлагают понимание самых сложных взаимодействий в вашей программе, позволяя вам очень быстро переключиться на создание суперэффективной версии, не обремененной отладочной аппаратурой.

Например, в одном из моих последних проектов у меня был огромный блок только для отладки, который начинался с FILE* file = fopen("debug_graph.dot"); и продолжал выгружать graphviz совместимый граф в формате точки, чтобы визуализировать большие деревья в моих данных. Что еще хуже, так это клиент OS X graphviz будет автоматически читать файл с диска при его изменении, поэтому график обновляется всякий раз, когда запускается программа!

Мне также особенно нравится "расширять" классы/структуры с помощью элементов и функций только для отладки. Это открывает возможность реализации функциональных возможностей и состояний, которые помогут вам отслеживать ошибки, и так же, как и все остальное, которое завернуто в макросы отладки, удаляется путем переключения параметра сборки. Гигантская рутина, которая кропотливо проверяет каждый угловой случай при каждом обновлении состояния? Не проблема. Повесьте a D() вокруг него. После того как вы увидите, что это работает, удалите -DDEBUG из сборки script, т.е. Создайте для выпуска, и он ушел, готовый к повторной активации в момент уведомления для вашего тестирования устройства или того, что у вас есть.

Большой, несколько полный пример, иллюстрирующий (возможно, несколько чрезмерное) использование этого понятия:

#ifdef DEBUG
#  define D(x) x
#else
#  define D(x)
#endif // DEBUG

#ifdef UNITTEST
#  include <UnitTest++/UnitTest++.h>
#  define U(x) x // same concept as D(x) macro.
#  define N(x)
#else
#  define U(x)
#  define N(x) x // N(x) macro performs the opposite of U(x)
#endif

struct Component; // fwd decls
typedef std::list<Component> compList;

// represents a node in the graph. Components group GNs
// into manageable chunks (which turn into matrices which is why we want
// graph component partitioning: to minimize matrix size)
struct GraphNode {
    U(Component* comp;) // this guy only exists in unit test build
    std::vector<int> adj; // neighbor list: These are indices
    // into the node_list buffer (used to be GN*)
    uint64_t h_i; // heap index value
    U(int helper;) // dangling variable for search algo to use (comp node idx)
    // todo: use a more space-efficient neighbor container?
    U(GraphNode(uint64_t i, Component* c, int first_edge):)
    N(GraphNode(uint64_t i, int first_edge):)
        h_i(i) {
        U(comp = c;)
        U(helper = -1;)
        adj.push_back(first_edge);
    }
    U(GraphNode(uint64_t i, Component* c):)
    N(GraphNode(uint64_t i):)
        h_i(i)
    {
        U(comp=c;)
        U(helper=-1;)
    }
    inline void add(int n) {
        adj.push_back(n);
    }
};

// A component is a ugraph component which represents a set of rows that
// can potentially be assembled into one wall.
struct Component {
#ifdef UNITTEST // is an actual real struct only when testing
    int one_node; // any node! idx in node_list (used to be GN*)
    Component* actual_component;
    compList::iterator graph_components_iterator_for_myself; // must be init'd
    // actual component refers to how merging causes a tree of comps to be
    // made. This allows the determination of which component a particular
    // given node belongs to a log-time operation rather than a linear one.

    D(int count;) // how many nodes I (should) have

    Component(): one_node(-1), actual_component(NULL) {
        D(count = 0;)
    }
#endif
};

#ifdef DEBUG
// a global pointer to the node list that makes it a little
// easier to reference it
std::vector<GraphNode> *node_list_ptr;

#  ifdef UNITTEST
std::ostream& operator<<(std::ostream& os, const Component& c) {
    os << "<s=" << c.count << ": 1_n=" << node_list_ptr->at(c.one_node).h_i;
    if (c.actual_component) {
        os << " ref=[" << *c.actual_component << "]";
    }
    os << ">";
    return os;
}
#  endif
#endif

Обратите внимание, что для больших блоков кода я просто использую регулярные условные блоки #ifdef, потому что это несколько улучшает читаемость, так как для больших блоков использование чрезвычайно коротких макросов является скорее препятствием!

Причина, по которой должен существовать макрос N(x), заключается в том, чтобы указать, что добавить, когда модульное тестирование отключено.

В этой части:

U(GraphNode(uint64_t i, Component* c, int first_edge):)
N(GraphNode(uint64_t i, int first_edge):)

Было бы хорошо, если бы мы могли сказать что-то вроде

GraphNode(uint64_t i, U(Component* c,) int first_edge):

Но мы не можем, потому что запятая является частью синтаксиса препроцессора. Опускание запятой создает недопустимый синтаксис С++.

Если у вас есть дополнительный код, когда вы не компилируете для отладки, вы можете использовать этот тип соответствующего макроса обратного отладки.

Теперь этот код может не быть примером "действительно хорошего кода", но он иллюстрирует некоторые из вещей, которые вы можете выполнить с помощью умного применения макросов, которые, если вы останетесь дисциплинированными, не обязательно злы.

Я наткнулся на этот драгоценный камень только после того, как задался вопросом о вещах do{} while(0), и вам действительно нужно все это причудливость в этих макросах!

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

Ответ 3

Мне нравится использовать макросы с __LINE__, __FILE__ в качестве аргументов, чтобы показать, где в коде находится распечатка, - это не редкость печатать одно и то же имя переменной в нескольких местах, поэтому fprintf(stderr, "x=%d", x); не будет означать если вы затем добавите еще одну и те же десять строк дальше.

Я также использовал макросы, которые переопределяют определенные функции и сохраняют, откуда они были вызваны (например, выделения памяти), поэтому позже я могу выяснить, какой из них был утечка. Для распределения памяти, что немного сложнее на С++, поскольку вы склонны использовать новое/удалить, и их нелегко заменить, но другие ресурсы, такие как операции блокировки/разблокировки, могут быть очень полезны для отслеживания этого пути [ если у вас есть блокирующая оболочка, которая использует конструкцию/разрушение, как хороший программист на С++, вы должны добавить ее в конструктор, чтобы добавить файл/строку во внутреннюю структуру после того, как вы приобрели блокировку, и вы можете увидеть, где она хранится где-либо еще, вы не можете его приобрести где-то].

Ответ 4

Для вопроса 1] Ответ - да. Он просто распечатает сообщение в стандартный поток ошибок.

Для вопроса 2] Есть много. Мой Fav

#define LOG_ERR(...) fprintf(stderr, __VA_ARGS__)

который позволит включить произвольное количество переменных для включения в сообщение отладки.

Ответ 5

Это макрос журнала, который я использую в настоящее время:

#ifndef DEBUG 
#define DEBUG 1 // set debug mode
#endif

#if DEBUG
#define log(...) {\
    char str[100];\
    sprintf(str, __VA_ARGS__);\
    std::cout << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << str << std::endl;\
    }
#else
#define log(...)
#endif

Использование:

log(">>> test...");

Вывод:

xxxx/proj.ios_mac/Classes/IntroScene.cpp][gotoNextScene][Line 58] >>> test...

Ответ 6

... и как добавление ко всем ответам:

Лично я никогда не использую макросы типа DEBUG для отличной отладки из кода выпуска, вместо этого я использую NDEBUG, который должен быть определен для релизов, чтобы исключить вызовы assert() (да, я используйте assert() экстенсивно). И если последнее не определено, то это отладочная сборка. Легко! Итак, на самом деле нет причин вводить еще один макрос отладки! (и обрабатывать возможные случаи, когда DEBUG и NDEBUG оба не определены).

Ответ 7

Я использую приведенный ниже код для ведения журнала. Есть несколько преимуществ:

  • Я могу включить/выключить их во время выполнения.
  • Я могу скомпилировать инструкции на определенном уровне журнала. Например, на данный момент я безоговорочно скомпилирован в макросе KIMI_PRIVATE, потому что я отлаживаю что-то в сборке релизов, но поскольку существует много потенциально секретного материала для соуса, регистрируемого (lol), я скомпилирую его из релиз.

Эта модель послужила мне очень хорошо на протяжении многих лет. Примечание: хотя существует глобальная функция logMessage, код обычно ставит очередь журнала в поток ведения журнала.

#define KIMI_LOG_INTERNAL(level,EXPR)           \
  if(kimi::Logger::loggingEnabled(level))       \
  {                                             \
    std::ostringstream os;                      \
    os << EXPR;                                 \
    kimi::Logger::logMessage(level ,os.str());  \
  }                                             \
  else (void) 0

#define KIMI_LOG(THELEVEL,EXPR)                 \
  KIMI_LOG_INTERNAL(kimi::Logger::LEVEL_ ## THELEVEL,EXPR)

#define KIMI_ERROR(EXPR)   KIMI_LOG(ERROR,EXPR)
#define KIMI_VERBOSE(EXPR) KIMI_LOG(VERBOSE,EXPR)
#define KIMI_TRACE(EXPR)   KIMI_LOG(TRACE,EXPR)
#define KIMI_INFO(EXPR)    KIMI_LOG(INFO,EXPR)
#define KIMI_PROFILE(EXPR) KIMI_LOG(TRACE,EXPR)

// Use KIMI_PRIVATE for sensitive tracing
//#if defined(_DEBUG)
#  define KIMI_PRIVATE(EXPR) KIMI_LOG(PRIVATE,EXPR)
// #else
// #  define KIMI_PRIVATE(EXPR) (void)0
// #endif

Ответ 8

Это моя версия, используя функцию вариационного шаблона print:

template<typename... ArgTypes>
inline void print(ArgTypes... args)
{
  // trick to expand variadic argument pack without recursion
  using expand_variadic_pack = int[];
  // first zero is to prevent empty braced-init-list
  // void() is to prevent overloaded operator, messing things up
  // trick is to use the side effect of list-initializer to call a function
  // on every argument.
  // (void) is to suppress "statement has no effect" warnings
  (void)expand_variadic_pack{0, ((cout << args), void(), 0)... };
}

#ifndef MYDEBUG
#define debug_print(...)
#else
#define debug_print(...) print(__VA_ARGS__)
#endif

Версия я делает debug_print функцию вариационного шаблона, которая принимает уровень отладки, который позволяет мне выбрать, какой выход я хочу вывести во время выполнения:

template<typename... ArgTypes>
inline void debug_print(debug::debug level, ArgTypes... args)
{
  if(0 != (debug::level & level))
    print(args...);
}

Обратите внимание, что функция print отключает предварительный просмотр Visual Studio 2013 (я не тестировал RC). Я заметил, что это быстрее (в Windows, где консольный вывод медленный), чем мое предыдущее решение, в котором использовался дочерний класс ostream, который перегружал operator<<.

Вы также можете использовать временный stringstream внутри print, если вы хотите только один раз вызвать реальную функцию вывода (или написать свой собственный typeafe printf; -))

Ответ 9

Я использую следующий микро,

#if DEBUG
#define LOGE2(x,y) std::cout << "ERRO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x <<":"<< y <<std::endl;
#define LOGI2(x,y) std::cout << "INFO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x <<":"<< y << std::endl;
#define LOGD2(x,y) std::cout << "DEBG : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x <<":"<< y << std::endl;
#define LOGE(x) std::cout << "ERRO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x << std::endl;
#define LOGI(x) std::cout << "INFO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x << std::endl;
#define LOGD(x) std::cout << "DEBG : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x << std::endl;
#else
#define LOGE2(x,y) NULL
#define LOGI2(x,y) NULL
#define LOGD2(x,y) NULL
#define LOGE(x) NULL
#define LOGI(x) NULL
#define LOGD(x) NULL
#endif

ИСПОЛЬЗОВАНИЕ:

LOGE("ERROR.");
LOGE2("ERROR1","ERROR2");