2010-08-11 3 views
2

В настоящее время я работаю над библиотекой отчетов как частью большого проекта. Он содержит набор функций ведения журнала и системных сообщений. Я пытаюсь использовать макросы препроцессора, чтобы вырезать подмножество вызовов функций, которые предназначены исключительно для отладки, а также определения и реализации функций сами по себе, используя условную компиляцию и функцию, такие как макросы, определенные ничем (подобно тому, как звонки 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 

}; 

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

+0

Вы можете использовать условную компиляцию, чтобы полностью удалить функции. – 2010-08-11 08:48:43

+0

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

ответ

2

Вот как я это сделал, когда написал несколько месяцев назад подобную библиотеку. И да, ваш оптимизатор удалит пустые, встроенные вызовы функций. Если вы объявляете их вне очереди (не в файле заголовка), ваш компилятор НЕ будет встраивать их, если вы не используете 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 
}; 

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

EDIT: На самом деле, теперь, когда я думаю об этом, просто используйте const char *. Глядя на сборку, она намного быстрее, особенно для пустых тел функций.

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

[email protected] /tmp $ cat t.cpp 
#include <cstdio> 

inline void do_nothing() 
{ 
} 

int main() 
{ 
     do_nothing(); 
     return 0; 
} 
[email protected] /tmp $ g++ -O1 -S t.cpp 
[email protected] /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 

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

[email protected] /tmp $ cat t.cpp 
inline void do_nothing(const char*) 
{ 
} 

int main() 
{ 
     do_nothing("test"); 
     return 0; 
} 
[email protected] /tmp $ g++ -O1 -S t.cpp 
[email protected] /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 

А вот с сопзЬ станд :: строка & ...

 .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

Так что, до тех пор, пока я уверен, что все реализовано в заголовке и определено как встроенное, я должен уметь предположить, что с помощью компилятора на основе GCC (я использую MinGW) все вызовы функций с пустым телом функции будут оптимизированы на -O1 или выше? Когда вы сказали «быстрее, особенно для пустых тел функций». Вы имели в виду быстрее, когда его не оптимизировали? –

+0

И он будет быстрее, когда он будет оптимизирован. Это предотвращает создание строки временным. И да, пустые тела встроенных функций исчезнут в O1 с любым современным компилятором. –

+0

Я все еще немного смущен. Я понимаю улучшение скорости, заданное путем передачи указателя на массив символов, а не передачи строки по значению. Но, в случае функций отладки, если в конечном итоге она будет оптимизирована, как будет работать любая версия? –

0

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

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 дает тот же тип, то это должны правовым, так как определения типов не являются различными типами.

+0

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

+0

Здесь нет вызовов функций. Только функции ** декларации **. Объявления во время выполнения ничего не делают. –

0

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

+0

Я упомянул об этом как опцию, но нет гарантии, что компилятор будет оптимизировать пустую функцию. –

0

Я знаю, что на эти вопросы был дан ответ с возрастов, но я столкнулся с этой проблемой, когда я помещал макрос журнала в пространство имен. Вы предлагали пустые функции и уровни оптимизации. Кларк Геблес заставлял меня думать, из-за разных результатов, используя 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 

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

Смежные вопросы