Как создать функцию только для отладки, которая принимает список переменных аргументов? Мне нравится printf()

Я хотел бы сделать функцию ведения журнала отладки с теми же параметрами, что и printf, Но тот, который может быть удален препроцессором во время оптимизированных сборок.

Например:

Debug_Print("Warning: value %d > 3!\n", value);

Я смотрел макросы variadic, но они доступны не на всех платформах. gcc поддерживает их, msvc не.

13 ответов

Решение

Я все еще делаю это по-старому, определяя макрос (XTRACE, ниже), который соотносится либо с неоперативным вызовом, либо с вызовом функции со списком переменных аргументов. Внутренне вызовите vsnprintf, чтобы сохранить синтаксис printf:

#include <stdio.h>

void XTrace0(LPCTSTR lpszText)
{
   ::OutputDebugString(lpszText);
}

void XTrace(LPCTSTR lpszFormat, ...)
{
    va_list args;
    va_start(args, lpszFormat);
    int nBuf;
    TCHAR szBuffer[512]; // get rid of this hard-coded buffer
    nBuf = _vsnprintf(szBuffer, 511, lpszFormat, args);
    ::OutputDebugString(szBuffer);
    va_end(args);
}

Затем типичный переключатель #ifdef:

#ifdef _DEBUG
#define XTRACE XTrace
#else
#define XTRACE
#endif

Ну, это можно немного убрать, но это основная идея.

Вот как я отлаживаю распечатки в C++. Определите 'dout' (отладку) следующим образом:

#ifdef DEBUG
#define dout cout
#else
#define dout 0 && cout
#endif

В коде я использую "dout" так же, как "cout".

dout << "in foobar with x= " << x << " and y= " << y << '\n';

Если препроцессор заменяет 'dout' на '0 && cout', обратите внимание, что << имеет более высокий приоритет, чем &&, и при оценке короткого замыкания && вся строка оценивается как 0. Поскольку 0 не используется, компилятор вообще не генерирует код для этой линии.

Вот что я делаю в C/C++. Во-первых, вы пишете функцию, которая использует материал varargs (см. Ссылку в публикации Стю). Затем сделайте что-то вроде этого:


 int debug_printf( const char *fmt, ... );
 #if defined( DEBUG )
  #define DEBUG_PRINTF(x) debug_printf x
 #else
   #define DEBUG_PRINTF(x)
 #endif

 DEBUG_PRINTF(( "Format string that takes %s %s\n", "any number", "of args" ));

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

Ах, vsprintf() было то, чего мне не хватало. Я могу использовать это для прямой передачи списка аргументов в printf():

#include <stdarg.h>
#include <stdio.h>

void DBG_PrintImpl(char * format, ...)
{
    char buffer[256];
    va_list args;
    va_start(args, format);
    vsprintf(buffer, format, args);
    printf("%s", buffer);
    va_end(args);
}

Затем заверните все это в макрос.

Еще один интересный способ заглушить функции с переменными параметрами:

#define function sizeof

@CodingTheWheel:

Есть одна небольшая проблема с вашим подходом. Рассмотрим вызов, такой как

XTRACE("x=%d", x);

Это прекрасно работает в отладочной сборке, но в выпускной сборке она будет расширена до:

("x=%d", x);

Это вполне допустимый C, который компилируется и обычно выполняется без побочных эффектов, но генерирует ненужный код. Подход, который я обычно использую для устранения этой проблемы:

  1. Заставьте функцию XTrace возвращать int (просто верните 0, возвращаемое значение не имеет значения)

  2. Измените #define в предложении #else на:

    0 && XTrace
    

Теперь версия выпуска будет расширена до:

0 && XTrace("x=%d", x);

и любой приличный оптимизатор выбросит все это, так как оценка короткого замыкания предотвратила бы что-либо после выполнения &&.

Конечно, так же, как я написал это последнее предложение, я понял, что, возможно, исходная форма тоже может быть оптимизирована, и в случае побочных эффектов, таких как вызовы функций, передаваемых в качестве параметров в XTrace, это может быть лучшим решением, поскольку оно будет убедитесь, что версии отладки и выпуска будут вести себя одинаково.

В C++ вы можете использовать потоковый оператор для упрощения вещей:

#if defined _DEBUG

class Trace
{
public:
   static Trace &GetTrace () { static Trace trace; return trace; }
   Trace &operator << (int value) { /* output int */ return *this; }
   Trace &operator << (short value) { /* output short */ return *this; }
   Trace &operator << (Trace &(*function)(Trace &trace)) { return function (*this); }
   static Trace &Endl (Trace &trace) { /* write newline and flush output */ return trace; }
   // and so on
};

#define TRACE(message) Trace::GetTrace () << message << Trace::Endl

#else
#define TRACE(message)
#endif

и используйте это как:

void Function (int param1, short param2)
{
   TRACE ("param1 = " << param1 << ", param2 = " << param2);
}

Затем вы можете реализовать настраиваемый вывод трассировки для классов почти так же, как и для вывода в std::cout,

Часть проблемы с этим типом функциональности состоит в том, что часто для этого требуются переменные макросы. Они были стандартизированы сравнительно недавно (C99), и многие старые компиляторы C не поддерживают стандарт или имеют свои собственные специальные решения.

Ниже приведен заголовок отладки, который имеет несколько интересных функций:

  • Поддерживает синтаксис C99 и C89 для макросов отладки
  • Включить / отключить вывод на основе аргумента функции
  • Вывод в файловый дескриптор (файл io)

Примечание. По некоторым причинам у меня возникли небольшие проблемы с форматированием кода.

#ifndef _DEBUG_H_
#define _DEBUG_H_
#if HAVE_CONFIG_H
#include "config.h"
#endif

#include "stdarg.h"
#include "stdio.h"

#define ENABLE 1
#define DISABLE 0

extern FILE* debug_fd;

int debug_file_init(char *file);
int debug_file_close(void);

#if HAVE_C99
#define PRINT(x, format, ...) \
if ( x ) { \
if ( debug_fd != NULL ) { \
fprintf(debug_fd, format, ##__VA_ARGS__); \
} \
else { \
fprintf(stdout, format, ##__VA_ARGS__); \
} \
}
#else
void PRINT(int enable, char *fmt, ...);
#endif

#if _DEBUG
#if HAVE_C99
#define DEBUG(x, format, ...) \
if ( x ) { \
if ( debug_fd != NULL ) { \
fprintf(debug_fd, "%s : %d " format, __FILE__, __LINE__, ##__VA_ARGS__); \
} \
else { \
fprintf(stderr, "%s : %d " format, __FILE__, __LINE__, ##__VA_ARGS__); \
} \
}

#define DEBUGPRINT(x, format, ...) \
if ( x ) { \
if ( debug_fd != NULL ) { \
fprintf(debug_fd, format, ##__VA_ARGS__); \
} \
else { \
fprintf(stderr, format, ##__VA_ARGS__); \
} \
}
#else /* HAVE_C99 */

void DEBUG(int enable, char *fmt, ...);
void DEBUGPRINT(int enable, char *fmt, ...);

#endif /* HAVE_C99 */
#else /* _DEBUG */
#define DEBUG(x, format, ...)
#define DEBUGPRINT(x, format, ...)
#endif /* _DEBUG */

#endif /* _DEBUG_H_ */

На каких платформах они недоступны? stdarg является частью стандартной библиотеки:

http://www.opengroup.org/onlinepubs/009695399/basedefs/stdarg.h.html

Любая платформа, не предоставляющая ее, не является стандартной реализацией C (или очень, очень старой). Для них вам придется использовать varargs:

http://opengroup.org/onlinepubs/007908775/xsh/varargs.h.html

Посмотрите на эту тему:

Это должно ответить на ваш вопрос.

Это версия ответа пользователя TCHAR, поэтому он будет работать в режиме ASCII (обычный) или в режиме Unicode (более или менее).

#define DEBUG_OUT( fmt, ...) DEBUG_OUT_TCHAR(       \
            TEXT(##fmt), ##__VA_ARGS__ )
#define DEBUG_OUT_TCHAR( fmt, ...)                  \
            Trace( TEXT("[DEBUG]") #fmt,            \
            ##__VA_ARGS__ )
void Trace(LPCTSTR format, ...)
{
    LPTSTR OutputBuf;
    OutputBuf = (LPTSTR)LocalAlloc(LMEM_ZEROINIT,   \
            (size_t)(4096 * sizeof(TCHAR)));
    va_list args;
    va_start(args, format);
    int nBuf;
    _vstprintf_s(OutputBuf, 4095, format, args);
    ::OutputDebugString(OutputBuf);
    va_end(args);
    LocalFree(OutputBuf); // tyvm @sam shaw
}

Я говорю "более или менее", потому что он не будет автоматически преобразовывать строковые аргументы ASCII в WCHAR, но он должен вытащить вас из большинства фрагментов Unicode, не беспокоясь о переносе строки формата в TEXT() или предшествующем ей с помощью L,

В основном получено из MSDN: получение кода последней ошибки

Столкнувшись с проблемой сегодня, я решил следующий макрос:

    static TCHAR __DEBUG_BUF[1024]
    #define DLog(fmt, ...)  swprintf(__DEBUG_BUF, fmt, ##__VA_ARGS__); OutputDebugString(__DEBUG_BUF) 

Затем вы можете вызвать функцию следующим образом:

    int value = 42;
    DLog(L"The answer is: %d\n", value);

Это то, что я использую:

inline void DPRINTF(int level, char *format, ...)
{
#    ifdef _DEBUG_LOG
        va_list args;
        va_start(args, format);
        if(debugPrint & level) {
                vfprintf(stdout, format, args);
        }
        va_end(args);
#    endif /* _DEBUG_LOG */
}

который абсолютно ничего не стоит во время выполнения, когда флаг _DEBUG_LOG выключен.

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

#define show(args...) describe(#args,args);
template<typename T>
void describe(string var_name,T value)
{
    clog<<var_name<<" = "<<value<<" ";
}

template<typename T,typename... Args>
void describe(string var_names,T value,Args... args)
{
    string::size_type pos = var_names.find(',');
    string name = var_names.substr(0,pos);
    var_names = var_names.substr(pos+1);
    clog<<name<<" = "<<value<<" | ";
    describe(var_names,args...);
}

Образец использования:

int main()
{
    string a;
    int b;
    double c;
    a="string here";
    b = 7;
    c= 3.14;
    show(a,b,c);
}

Выход:

a = string here | b = 7 | c = 3.14 
Другие вопросы по тегам