Попытка создать макрос, который можно использовать для отладочных сообщений печати, когда определен DEBUG, например, следующий псевдокод:
#define DEBUG 1
#define debug_print(args ...) if (DEBUG) fprintf(stderr, args)
Как это достигается с помощью макроса?
c
c-preprocessor
jfarrell
источник
источник
Ответы:
Если вы используете компилятор C99 или новее
Предполагается, что вы используете C99 (нотация списка переменных аргумента не поддерживается в более ранних версиях). В
do { ... } while (0)
идиома гарантирует , что код действует как (вызов функции) заявление. Безусловное использование кода гарантирует, что компилятор всегда проверяет правильность вашего кода отладки - но оптимизатор удалит код, когда DEBUG равен 0.Если вы хотите работать с #ifdef DEBUG, измените условие теста:
А затем используйте DEBUG_TEST, где я использовал DEBUG.
Если вы настаиваете на строковый литерал для строки формата (вероятно, хорошая идея в любом случае), вы можете также ввести такие вещи , как
__FILE__
,__LINE__
и__func__
в продукции, которая может улучшить диагностику:Это основано на конкатенации строк для создания строки большего формата, чем пишет программист.
Если вы используете компилятор C89
Если вы застряли с C89 и не имеете полезного расширения компилятора, то нет особо чистого способа справиться с этим. Техника, которую я использовал, была:
А затем в коде напишите:
Двойные скобки имеют решающее значение - и именно поэтому у вас есть забавные обозначения в расширении макроса. Как и прежде, компилятор всегда проверяет код на синтаксическую достоверность (что хорошо), но оптимизатор вызывает функцию печати только в том случае, если макрос DEBUG оценивает ненулевое значение.
Для этого требуется поддержка функции - dbg_printf () в примере - для обработки таких вещей, как 'stderr'. Требуется, чтобы вы знали, как писать функции varargs, но это не сложно:
Конечно, вы также можете использовать эту технику в C99, но эта
__VA_ARGS__
техника более изящна, потому что она использует обычную функцию обозначения, а не хак с двойными скобками.Почему так важно, чтобы компилятор всегда видел код отладки?
[ Перефразирование комментариев к другому ответу. ]
Одна центральная идея, лежащая в основе описанных выше реализаций C99 и C89, заключается в том, что сам компилятор всегда видит отладочные операторы типа printf. Это важно для долгосрочного кода - кода, который будет длиться десять или два года.
Предположим, что часть кода была в основном неактивной (стабильной) в течение ряда лет, но теперь ее необходимо изменить. Вы снова активируете трассировку отладки, но разочаровывает необходимость отладки кода отладки (трассировки), поскольку он ссылается на переменные, которые были переименованы или перепечатаны в годы стабильного обслуживания. Если компилятор (постпроцессор) всегда видит оператор печати, он гарантирует, что любые окружающие изменения не сделали диагностику недействительной. Если компилятор не видит оператор печати, он не может защитить вас от вашей собственной небрежности (или небрежности ваших коллег или сотрудников). См. « Практику программирования » Кернигана и Пайка, особенно главу 8 (см. Также Википедию по TPOP ).
Это был опыт «сделано, сделано так» - я использовал в основном технику, описанную в других ответах, где не отладочная сборка не видит подобные printf операторы в течение ряда лет (более десяти лет). Но я натолкнулся на совет в TPOP (см. Мой предыдущий комментарий), а затем через несколько лет включил некоторый код отладки и столкнулся с проблемами изменения контекста, нарушающими отладку. Несколько раз проверка печати всегда спасала меня от дальнейших проблем.
Я использую NDEBUG только для контроля утверждений и отдельный макрос (обычно DEBUG) для контроля встроенной трассировки отладки в программу. Даже когда встроена трассировка отладки, я часто не хочу, чтобы выходные данные отладки появлялись безоговорочно, поэтому у меня есть механизм для контроля появления выходных данных (уровни отладки, и вместо
fprintf()
прямого вызова я вызываю функцию печати отладки, которая печатает только условно поэтому одна и та же сборка кода может печатать или не печатать в зависимости от параметров программы). У меня также есть версия кода с несколькими подсистемами для больших программ, так что у меня могут быть разные разделы программы, производящие различное количество трассировки - под контролем времени выполнения.Я защищаю то, что для всех сборок компилятор должен видеть диагностические утверждения; однако компилятор не будет генерировать код для операторов трассировки отладки, если не включена отладка. По сути, это означает, что весь ваш код проверяется компилятором каждый раз, когда вы компилируете - для выпуска или отладки. Это хорошая вещь!
debug.h - версия 1.2 (1990-05-01)
debug.h - версия 3.6 (2008-02-11)
Вариант с одним аргументом для C99 или позже
Кайл Брандт спросил:
Есть один простой старомодный хак:
Решение только для GCC, показанное ниже, также поддерживает это.
Тем не менее, вы можете сделать это с прямой системой C99, используя:
По сравнению с первой версией, вы теряете ограниченную проверку, которая требует аргумента 'fmt', что означает, что кто-то может попытаться вызвать 'debug_print ()' без аргументов (но завершающая запятая в списке аргументов
fprintf()
не будет компилироваться) , Если потери проверки является проблемой вообще спорно.GCC-специфическая техника для одного аргумента
Некоторые компиляторы могут предлагать расширения для других способов обработки списков аргументов переменной длины в макросах. В частности, как впервые отмечено в комментариях Хьюго Иделера , GCC позволяет вам пропустить запятую, которая обычно появляется после последнего «фиксированного» аргумента макроса. Это также позволяет вам использовать
##__VA_ARGS__
в тексте замены макроса, который удаляет запятую, предшествующую нотации, если, но только если предыдущий токен является запятой:Это решение сохраняет преимущество требования аргумента формата при принятии необязательных аргументов после формата.
Этот метод также поддерживается Clang для совместимости с GCC.
Почему цикл do-while?
Вы хотите иметь возможность использовать макрос так, чтобы он выглядел как вызов функции, что означает, что за ним следует точка с запятой. Поэтому вы должны упаковать тело макроса в соответствии с вашими требованиями. Если вы используете
if
утверждение без окруженияdo { ... } while (0)
, у вас будет:Теперь предположим, что вы пишете:
К сожалению, этот отступ не отражает фактического управления потоком, потому что препроцессор производит код, эквивалентный этому (отступ и скобки добавлены, чтобы подчеркнуть фактическое значение):
Следующая попытка макроса может быть:
И тот же фрагмент кода теперь производит:
И
else
теперь это синтаксическая ошибка. Вdo { ... } while(0)
избегает цикла обе эти проблемы.Есть еще один способ написания макроса, который может работать:
Это оставляет фрагмент программы, показанный как действительный. Приведение
(void)
запрещает его использование в контекстах, где требуется значение, но его можно использовать в качестве левого операнда оператора запятой, гдеdo { ... } while (0)
версия не может. Если вы считаете, что сможете встраивать отладочный код в такие выражения, вы можете предпочесть это. Если вы предпочитаете, чтобы отладочная печать действовала как полный оператор, тоdo { ... } while (0)
версия лучше. Обратите внимание, что если в теле макроса используются точки с запятой (грубо говоря), то вы можете использовать толькоdo { ... } while(0)
обозначения. Это всегда работает; механизм выражения выражения может быть более сложным для применения. Вы также можете получить предупреждения от компилятора с формой выражения, которую вы предпочитаете избегать; это будет зависеть от компилятора и используемых вами флагов.Ранее TPOP был на http://plan9.bell-labs.com/cm/cs/tpop и http://cm.bell-labs.com/cm/cs/tpop, но оба сейчас (2015-08-10) сломана.
Код в GitHub
Если вам интересно, вы можете посмотреть на этот код в GitHub в моих SOQ (Stack Overflow) Вопросы хранилища в виде файлов
debug.c
,debug.h
иmddebug.c
в SRC / libsoq подкаталоге.источник
#define debug(...) \ do { if (DEBUG) \ printk("DRIVER_NAME:"); \ printk(__VA_ARGS__); \ printk("\n"); \ } while (0)
__FILE__, __LINE__, __func__, __VA_ARGS__
он не скомпилируется, если у вас нет параметров printf, т.е. если вы просто вызовете.debug_print("Some msg\n");
Это можно исправить с помощьюfprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__);
## __ VA_ARGS__, позволяющего не передавать параметры в функцию.#define debug_print(fmt, ...)
и#define debug_print(...)
. Первый из них требует как минимум один аргумент, формат строки (fmt
) и ноль или более других аргументов; для второго требуется ноль или более аргументов. Если вы используетеdebug_print()
с первым, вы получите ошибку от препроцессора о неправильном использовании макроса, в то время как второе - нет. Тем не менее, вы по-прежнему получаете ошибки компиляции, потому что текст замены не является допустимым C. Таким образом, на самом деле это не большая разница - отсюда и использование термина «ограниченная проверка».-D input=4,macros=9,rules=2
для установки уровня отладки системы ввода на 4, системы макросов на 9 (подвергается тщательному анализу). ) и система правил до 2. Существуют бесконечные вариации на тему; используйте то, что вам подходит.Я использую что-то вроде этого:
Чем я просто использую D в качестве префикса:
Компилятор видит код отладки, проблем с запятыми нет, и он работает везде. Кроме того, он работает, когда
printf
этого недостаточно, например, когда необходимо вывести массив или вычислить некоторое диагностическое значение, которое является избыточным для самой программы.РЕДАКТИРОВАТЬ: Хорошо, это может вызвать проблему, когда есть
else
где-то рядом, которые могут быть перехвачены этим введеноif
. Это версия, которая проходит через это:источник
for(;0;)
, это может вызвать проблемы, когда вы пишете что-то вродеD continue;
илиD break;
.Для переносимой (ISO C90) реализации вы можете использовать двойные скобки, например так;
или (хак, не рекомендую)
источник
Вот версия, которую я использую:
источник
Я бы сделал что-то вроде
Я думаю, что это чище.
источник
assert()
из stdlib работает так же, и я обычно просто повторно используюNDEBUG
макрос для моего собственного кода отладки ...Согласно http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html , должно быть
##
до__VA_ARGS__
.В противном случае, макрос
#define dbg_print(format, ...) printf(format, __VA_ARGS__)
не будет скомпилировать следующий пример:dbg_print("hello world");
.источник
источник
Это то, что я использую:
Это имеет хорошее преимущество для правильной обработки printf даже без дополнительных аргументов. В случае DBG == 0 даже самому глупому компилятору нечего жевать, поэтому код не генерируется.
источник
Мой любимый из приведенных ниже
var_dump
, который, когда называется как:var_dump("%d", count);
производит вывод как:
patch.c:150:main(): count = 0
Кредит @ "Джонатан Леффлер". Все счастливы C89:
Код
источник
Итак, при использовании gcc мне нравится:
Потому что это может быть вставлено в код.
Предположим, вы пытаетесь отладить
Затем вы можете изменить его на:
И вы можете получить анализ того, какое выражение было оценено для чего.
Он защищен от проблемы двойной оценки, но отсутствие гензимов делает его открытым для коллизий имен.
Однако это гнездо:
Поэтому я думаю, что пока вы не будете использовать g2rE3 в качестве имени переменной, все будет в порядке.
Конечно, я нашел его (и смежные версии для строк, и версии для уровней отладки и т. Д.) Бесценными.
источник
Я много лет думал о том, как это сделать, и наконец нашел решение. Однако я не знал, что здесь уже были другие решения. Во-первых, в отличие от ответа Леффлера , я не вижу его аргумента в пользу того, что отладочные отпечатки всегда должны компилироваться. Я бы предпочел не иметь тонны ненужного кода, выполняемого в моем проекте, когда он не нужен, в тех случаях, когда мне нужно тестировать, и они могут не оптимизироваться.
Не компиляция каждый раз может звучать хуже, чем на практике. Вы получаете отладочные отпечатки, которые иногда не компилируются, но их не так сложно скомпилировать и протестировать перед завершением проекта. В этой системе, если вы используете три уровня отладок, просто поместите их на уровень отладочных сообщений три, исправьте ошибки компиляции и проверьте все остальные, прежде чем завершить работу с кодом. (Поскольку, конечно, компиляция отладочных операторов не является гарантией того, что они все еще работают так, как задумано.)
Мое решение также предусматривает уровни детализации отладки; и если вы установите его на самый высокий уровень, они все скомпилируются. Если вы недавно использовали высокий уровень детализации отладки, все они могли компилироваться в то время. Окончательные обновления должны быть довольно легкими. Мне никогда не требовалось больше трех уровней, но Джонатан говорит, что он использовал девять. Этот метод (как и метод Леффлера) можно распространить на любое количество уровней. Использование моего метода может быть проще; требуется только два утверждения при использовании в вашем коде. Однако я тоже кодирую макрос CLOSE - хотя он ничего не делает. Возможно, если бы я отправлял в файл.
Против стоимости дополнительный шаг тестирования их, чтобы видеть, что они будут компилироваться перед поставкой, состоит в том, что
Ветви на самом деле довольно дорогие в современных процессорах предварительной выборки. Возможно, нет ничего страшного, если ваше приложение не является критичным по времени; но если производительность - это проблема, то да, достаточно крупная сделка, которую я предпочел бы выбрать для более быстрого выполнения отладочного кода (и, возможно, более быстрого выпуска, в редких случаях, как отмечалось).
Итак, я хотел отладочный макрос печати, который не компилируется, если его не нужно печатать, но делает, если он есть. Я также хотел уровни отладки, чтобы, например, если бы я хотел, чтобы важные части кода не печатались в некоторых случаях, а печатались в других, я мог бы установить уровень отладки и получить дополнительные отладочные отпечатки. I наткнулся на способ реализации уровней отладки, которые определяли, был ли отпечаток даже скомпилирован или нет. Я достиг этого так:
DebugLog.h:
DebugLog.cpp:
Использование макросов
Чтобы использовать это, просто сделайте:
Для записи в файл журнала просто выполните:
Чтобы закрыть его, вы делаете:
хотя в настоящее время это даже не нужно, технически говоря, поскольку это ничего не делает. Я все еще использую ЗАКРЫТЬ прямо сейчас, однако, на случай, если я передумаю о том, как это работает, и хочу оставить файл открытым между инструкциями регистрации.
Затем, когда вы хотите включить отладочную печать, просто отредактируйте первый #define в заголовочном файле, чтобы сказать, например,
Чтобы операторы регистрации ничего не компилировали, выполните
Если вам нужна информация из часто выполняемого фрагмента кода (т.е. с высоким уровнем детализации), вы можете написать:
Если вы определили, что DEBUG равен 3, уровни логирования 1, 2 и 3 компилируются. Если вы установите его на 2, вы получите уровни ведения журнала 1 и 2. Если вы установите его на 1, вы получите только операторы уровня регистрации 1.
Что касается цикла do-while, поскольку он оценивает либо одну функцию, либо ничего, вместо оператора if цикл не нужен. Хорошо, обвини меня в том, что я использую C вместо C ++ IO (и Qtring :: arg () в Qt - более безопасный способ форматирования переменных, когда в Qt тоже - это довольно удобно, но требует больше кода, а документация по форматированию не так организована как это может быть - но все же я нашел случаи, когда это предпочтительнее), но вы можете поместить любой код в файл .cpp, который вы хотите. Это также может быть класс, но тогда вам нужно будет создать его экземпляр и не отставать от него, или выполнить new () и сохранить его. Таким образом, вы просто добавляете операторы #include, init и, возможно, закрываете их в свой источник, и вы готовы начать их использовать. Однако, если бы вы были так склонны, это было бы хорошим уроком.
Ранее я видел много решений, но ни одно из них не соответствовало моим критериям так же, как это.
Не очень важно, но в дополнение:
DEBUGLOG_LOG(3, "got here!");
); что позволяет вам использовать, например, более безопасное форматирование .arg () в Qt. Это работает на MSVC, и, таким образом, вероятно, GCC. Он использует##
в#define
s, что является нестандартным, как указывает Леффлер, но широко поддерживается. (Вы можете перекодировать его, чтобы не использовать##
при необходимости, но вам придется использовать взлом, такой как он.)Предупреждение. Если вы забудете предоставить аргумент уровня ведения журнала, MSVC бесполезно заявляет, что идентификатор не определен.
Возможно, вы захотите использовать имя символа препроцессора, отличное от DEBUG, так как некоторые источники также определяют этот символ (например, программы используют
./configure
команды для подготовки к сборке). Мне казалось естественным, когда я его разработал. Я разработал его в приложении, где DLL используется чем-то другим, и более удобно отправлять распечатки журнала в файл; но изменение его на vprintf () тоже подойдет.Я надеюсь, что это спасет многих из вас от горя при поиске лучшего способа ведения журнала отладки; или показывает вам тот, который вы можете предпочесть. Я нерешительно пытался понять это в течение десятилетий. Работает в MSVC 2012 и 2015, и, вероятно, на GCC; а также, вероятно, работает над многими другими, но я не проверял это на них.
Я хочу сделать потоковую версию этого дня тоже.
Примечание: спасибо Леффлеру, который помог мне лучше отформатировать мое сообщение для StackOverflow.
источник
if (DEBUG)
операторов во время выполнения, которые не оптимизируются», что является отклонением на ветряных мельницах . Весь смысл этой системы я описал, что код проверяется компилятором (важно, и автоматический - не требуется специальная сборка) , но код отладки не генерируются на всех , потому что это оптимизированное (так что нулевое воздействии выполнения на размер кода или производительность, потому что код не присутствует во время выполнения).((void)0)
- это легко.Я считаю, что этот вариант темы дает отладочные категории без необходимости иметь отдельное имя макроса для каждой категории.
Я использовал этот вариант в проекте Arduino, где пространство программы ограничено 32 КБ, а динамическая память ограничена 2 КБ. Добавление операторов отладки и строк отладки трассировки быстро занимает место. Поэтому важно иметь возможность ограничить трассировку отладки, включенную во время компиляции, до минимума, необходимого при каждой сборке кода.
debug.h
вызов файла .cpp
источник