Препроцессорное исключение вызовов функций в пространстве имен - PullRequest
2 голосов
/ 11 августа 2010

В настоящее время я работаю над библиотекой отчетов в рамках большого проекта.Он содержит набор функций регистрации и системных сообщений.Я пытаюсь использовать макросы препроцессора, чтобы вырезать подмножество вызовов функций, предназначенных исключительно для отладки, а также сами определения функций и реализации, используя условную компиляцию и функции, подобные макросам, которые не определены (подобно тому, как assert() вызовы удаляются, если определено DEBUG).Я столкнулся с проблемой.Я предпочитаю полностью определять пространства имен, я считаю, что это улучшает читабельность;и мои функции отчетности обернуты в пространство имен.Поскольку символ двоеточия не может быть частью макротокена, я не могу включить пространство имен в разбор вызовов функций.Если я определил функции в одиночку, я получу Namespace::.Я рассмотрел только использование условной компиляции для блокировки кода функции для этих функций, но я обеспокоен тем, что компилятор может не компетентно оптимизировать пустые функции.

namespace Reporting
{
    const extern std::string logFileName;

    void Report(std::string msg);
    void Report(std::string msg, std::string msgLogAdd);
    void Log(std::string msg);
    void Message(std::string msg);

    #ifdef DEBUG
        void Debug_Log(std::string message);
        void Debug_Message(std::string message);
        void Debug_Report(std::string message);
        void Debug_Assert(bool test, std::string message);
    #else
        #define Debug_Log(x);
        #define Debug_Message(x);
        #define Debug_Report(x);
        #define Debug_Assert(x);
    #endif

};

Есть идеи о том, как обращаться с квалификаторами пространства имен с препроцессором?
Мысли, проблемы с простым удалением кода функции?
Есть ли другие способы достичь моей цели?

Ответы [ 4 ]

2 голосов
/ 11 августа 2010

Вот как я это сделал, когда несколько месяцев назад написал похожую библиотеку.И да, ваш оптимизатор удалит пустые вызовы встроенных функций.Если вы объявляете их вне строки (не в заголовочном файле), ваш компилятор НЕ встроит их, если вы не используете LTO.

namespace Reporting
{
    const extern std::string logFileName;

    void Report(std::string msg);
    void Report(std::string msg, std::string msgLogAdd);
    void Log(std::string msg);
    void Message(std::string msg);

    #ifdef DEBUG
        inline void Debug_Log(std::string message) { return Log(message); }
        inline void Debug_Message(std::string message) { return Message(message); }
        inline void Debug_Report(std::string message) { return Report(message); }
        inline void Debug_Assert(bool test, std::string message) { /* Not sure what to do here */ }
    #else
        inline void Debug_Log(std::string) {}
        inline void Debug_Message(std::string) {}
        inline void Debug_Report(std::string) {}
        inline void Debug_Assert(std::string) {}
    #endif
};

Как примечание, не передавайте строки по значениюесли вам не нужно сделать копию в любом случае.Вместо этого используйте константную ссылку.Это предотвращает дорогостоящее выделение + strcpy в строке для КАЖДОГО вызова функции.

РЕДАКТИРОВАТЬ: На самом деле, теперь, когда я думаю об этом, просто используйте const char *.Глядя на сборку, она намного быстрее, особенно для пустых тел функций.

GCC оптимизирует это при -O1, я не думаю, что есть большая проблема с этим:

clark@clark-laptop /tmp $ cat t.cpp
#include <cstdio>

inline void do_nothing()
{
}

int main()
{
        do_nothing();
        return 0;
}
clark@clark-laptop /tmp $ g++ -O1 -S t.cpp   
clark@clark-laptop /tmp $ cat t.s
        .file   "t.cpp"
        .text
.globl main
        .type   main, @function
main:
.LFB32:
        .cfi_startproc
        movl    $0, %eax
        ret
        .cfi_endproc
.LFE32:
        .size   main, .-main
        .ident  "GCC: (Gentoo 4.5.0 p1.2, pie-0.4.5) 4.5.0"
        .section        .note.GNU-stack,"",@progbits

После небольшой настройки кажется, что это будет полное удаление, только если вы используете const char *, NOT std :: string или const std :: string &.Вот сборка для const char *:

clark@clark-laptop /tmp $ cat t.cpp 
inline void do_nothing(const char*)
{
}

int main()
{
        do_nothing("test");
        return 0;
}
clark@clark-laptop /tmp $ g++ -O1 -S t.cpp 
clark@clark-laptop /tmp $ cat t.s
        .file   "t.cpp"
        .text
.globl main
        .type   main, @function
main:
.LFB1:
        .cfi_startproc
        movl    $0, %eax
        ret
        .cfi_endproc
.LFE1:
        .size   main, .-main
        .ident  "GCC: (Gentoo 4.5.0 p1.2, pie-0.4.5) 4.5.0"
        .section        .note.GNU-stack,"",@progbits

А вот с const std :: string & ...

        .file   "t.cpp"
        .section        .rodata.str1.1,"aMS",@progbits,1
.LC0:
        .string "test"
        .text
.globl main
        .type   main, @function
main:
.LFB591:
        .cfi_startproc
        subq    $24, %rsp
        .cfi_def_cfa_offset 32
        leaq    14(%rsp), %rdx
        movq    %rsp, %rdi
        movl    $.LC0, %esi
        call    _ZNSsC1EPKcRKSaIcE
        movq    (%rsp), %rdi
        subq    $24, %rdi
        cmpq    $_ZNSs4_Rep20_S_empty_rep_storageE, %rdi
        je      .L11
        movl    $_ZL22__gthrw_pthread_cancelm, %eax
        testq   %rax, %rax
        je      .L3
        movl    $-1, %eax
        lock xaddl      %eax, 16(%rdi)
        jmp     .L4
.L3:
        movl    16(%rdi), %eax
        leal    -1(%rax), %edx
        movl    %edx, 16(%rdi)
.L4:
        testl   %eax, %eax
        jg      .L11
        leaq    15(%rsp), %rsi
        call    _ZNSs4_Rep10_M_destroyERKSaIcE
.L11:
        movl    $0, %eax
        addq    $24, %rsp
        .cfi_def_cfa_offset 8
        ret
        .cfi_endproc
.LFE591:
        .size   main, .-main
        [Useless stuff removed...]
        .ident  "GCC: (Gentoo 4.5.0 p1.2, pie-0.4.5) 4.5.0"
        .section        .note.GNU-stack,"",@progbits

Огромная разница, а?

0 голосов
/ 26 сентября 2014

Я знаю, что на эти вопросы давным-давно ответили, но я столкнулся с этой проблемой, когда помещал макрос журнала в пространство имен.Вы предлагали пустые функции и уровни оптимизации.Кларк Геблс заставил меня задуматься из-за разных результатов с использованием const char* или const std::string&.Следующий код не дает мне никаких разумных изменений в сборке без включенных уровней оптимизации:

#include <iostream>
#undef _DEBUG // undefine to use __NOJOB

namespace Debug
{
    typedef void __NOJOB;

    class Logger
    {
    public:
        static void Log( const char* msg, const char* file, int line )
        {
            std::cout << "Log: " << msg << " in " <<
                file << ":" << line << std::endl;
        }
    };
}

#ifdef _DEBUG
#define Log( msg ) Logger::Log( msg, __FILE__, __LINE__ );
#else
#define Log( msg )__NOJOB(0);
#endif

int main()
{
   Debug::Log( "please skip me" );

   return 0;
}

созданная сборка http://assembly.ynh.io/:

main:
                .LFB972:
                    .cfi_startproc
0000 55             pushq   %rbp
                    .cfi_def_cfa_offset 16
                    .cfi_offset 6, -16
0001 4889E5         movq    %rsp, %rbp
                    .cfi_def_cfa_register 6 // <- stack main
// no code for void( 0 )  here
0004 B8000000       movl    $0, %eax // return
     00
0009 5D             popq    %rbp // -> end stack main
                    .cfi_def_cfa 7, 8
000a C3             ret

Может быть, я ошибся или понял что-то не так?Было бы приятно услышать от вас.

0 голосов
/ 11 августа 2010

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

0 голосов
/ 11 августа 2010

Я не уверен, полностью ли я понимаю вашу проблему. Поможет ли следующее?

namespace X
{
    namespace{int dummy;}
    void debug_check(int);
}

#ifdef DEBUG
#define DEBUG_CHECK(ARG) debug_check(ARG)
#else 
#define DEBUG_CHECK(ARG) dummy // just ignore args
#endif

int main()
{
 X::DEBUG_CHECK(1);
}

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

// debug_check and "#ifdef DEBUG" part omitted
namespace X
{
    typedef void dummy_type; 
}
namespace Y
{
    typedef void dummy_type; 
}
typedef void dummy_type;

#define DEBUG(X) dummy_type dummy_fn();

int main()
{
    X::DEBUG(1);
    Y::DEBUG(2);
    X::DEBUG(3);
    Y::DEBUG(4);    
    DEBUG(5);   
    DEBUG(6);   
};

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

...