admin管理员组

文章数量:1434908

I'm developing an API looking like GLib logging.

My API can also disable these functions when the flag -DDEBUG isn't passed to GCC.

That means, there are no residues in the binary, all these debug functions including their parameters aren't present in the executable if this flag isn't passed.

It works perfectly in C, because it's macro functions I can easily replace ;

Example in C

debug.h

#include <stdio.h>
#include <stdlib.h>

#include "term.h"

/**
 * @brief Print something in debug mode
 * @details No loglevel/context
 * @param format
 * @param ... Parameters for format
 */
#define printf_debug(fmt, ...)

#define LOG_NONE 0
#define LOG_FATAL 1
#define LOG_ERROR 2
#define LOG_WARNING 3
#define LOG_INFO 4
#define LOG_DEBUG 5
#define LOG_TRACE 6

#if (defined(DEBUG))
#undef printf_debug

#define printf_debug(format, ...) \
    fprintf(stderr, "        " format "\n", __FILE__, __func__, __LINE__, ##__VA_ARGS__)
#endif // (defined(DEBUG))

main.c

#include <debug/debug.h>

int main()
{
    printf_debug("Hello world !");      // Show in any debug context

    return 0;
}

If I compile without the flag -DDEBUG, neither my macro function printf_debug() nor the string "Hello" remains.


I would have like to do the same in C++ with the << operator (such as std::cout works), but I can't manage << operator like I manage parameters in C.

Here's what I have tried ;

debug.hpp

#include <iostream>

#define LOG_UNDEFINED (-1)
#define LOG_NONE 0
#define LOG_FATAL 1
#define LOG_ERROR 2
#define LOG_WARNING 3
#define LOG_INFO 4
#define LOG_DEBUG 5
#define LOG_TRACE 6

namespace debug
{
#ifdef DEBUG
    class debug_cout
        : public std::ostream
    {
    public:
        debug_cout(std::streambuf *sbuf)
            : std::ios(sbuf), std::ostream(sbuf)
        {}
        ~debug_cout() {}
    };

    debug_cout cout(int l) {
        static int level = l;
        return debug_cout(std::cout.rdbuf());
    }
#else
    class debug_cout
        : public std::ostream
    {
    public:
        debug_cout() {}
        ~debug_cout() {}
    };

    debug_cout cout(int l) {
        return debug_cout();
    }
#endif
}

main.cpp

#include <debug/debug.hpp>


int main() {
    debug::cout(LOG_ERROR) << "Hello " << 3 << std::endl;

    return 0;
}

It compiles correctly this way ; Using -DDEBUG flag, it outputs ;

Hello 3

Without -DDEBUG, it outputs nothing, but there are residues in the executable. I see using objdump my functions debug::cout() remains and is called.

I have also tried using GCC optimization flag -O3.


So my question ; Is it possible to ignore these instructions in C++ ? The ones having << operator.

Note: It's not an issue if I can't use namespace

Note 2: When I talk about residues I mean my function debug::cout() remains in the executable in C++ and is executed (and it just does nothing).

I'm developing an API looking like GLib logging.

My API can also disable these functions when the flag -DDEBUG isn't passed to GCC.

That means, there are no residues in the binary, all these debug functions including their parameters aren't present in the executable if this flag isn't passed.

It works perfectly in C, because it's macro functions I can easily replace ;

Example in C

debug.h

#include <stdio.h>
#include <stdlib.h>

#include "term.h"

/**
 * @brief Print something in debug mode
 * @details No loglevel/context
 * @param format
 * @param ... Parameters for format
 */
#define printf_debug(fmt, ...)

#define LOG_NONE 0
#define LOG_FATAL 1
#define LOG_ERROR 2
#define LOG_WARNING 3
#define LOG_INFO 4
#define LOG_DEBUG 5
#define LOG_TRACE 6

#if (defined(DEBUG))
#undef printf_debug

#define printf_debug(format, ...) \
    fprintf(stderr, "        " format "\n", __FILE__, __func__, __LINE__, ##__VA_ARGS__)
#endif // (defined(DEBUG))

main.c

#include <debug/debug.h>

int main()
{
    printf_debug("Hello world !");      // Show in any debug context

    return 0;
}

If I compile without the flag -DDEBUG, neither my macro function printf_debug() nor the string "Hello" remains.


I would have like to do the same in C++ with the << operator (such as std::cout works), but I can't manage << operator like I manage parameters in C.

Here's what I have tried ;

debug.hpp

#include <iostream>

#define LOG_UNDEFINED (-1)
#define LOG_NONE 0
#define LOG_FATAL 1
#define LOG_ERROR 2
#define LOG_WARNING 3
#define LOG_INFO 4
#define LOG_DEBUG 5
#define LOG_TRACE 6

namespace debug
{
#ifdef DEBUG
    class debug_cout
        : public std::ostream
    {
    public:
        debug_cout(std::streambuf *sbuf)
            : std::ios(sbuf), std::ostream(sbuf)
        {}
        ~debug_cout() {}
    };

    debug_cout cout(int l) {
        static int level = l;
        return debug_cout(std::cout.rdbuf());
    }
#else
    class debug_cout
        : public std::ostream
    {
    public:
        debug_cout() {}
        ~debug_cout() {}
    };

    debug_cout cout(int l) {
        return debug_cout();
    }
#endif
}

main.cpp

#include <debug/debug.hpp>


int main() {
    debug::cout(LOG_ERROR) << "Hello " << 3 << std::endl;

    return 0;
}

It compiles correctly this way ; Using -DDEBUG flag, it outputs ;

Hello 3

Without -DDEBUG, it outputs nothing, but there are residues in the executable. I see using objdump my functions debug::cout() remains and is called.

I have also tried using GCC optimization flag -O3.


So my question ; Is it possible to ignore these instructions in C++ ? The ones having << operator.

Note: It's not an issue if I can't use namespace

Note 2: When I talk about residues I mean my function debug::cout() remains in the executable in C++ and is executed (and it just does nothing).

Share Improve this question edited Nov 17, 2024 at 17:31 Eternal Dreamer asked Nov 17, 2024 at 17:07 Eternal DreamerEternal Dreamer 5271 gold badge5 silver badges15 bronze badges 0
Add a comment  | 

3 Answers 3

Reset to default 2

Your non-logging debug_cout shouldn't inherit from std::ostream. Instead implement empty functions for streaming that does nothing. That should be easy for the compiler to optimize away:

class debug_cout {
public:
    template <class T>
        requires requires(T t) { std::cout << t; }
    debug_cout& operator<<(T&&) {
        return *this;
    }

    debug_cout& operator<<(std::ios_base& (*)(std::ios_base&)) {
        return *this;
    }

    debug_cout& operator<<(std::ios& (*)(std::ios&)) { return *this; }

    debug_cout& operator<<(std::ostream& (*)(std::ostream&)) {
        return *this;
    }
};

Demo

Is it possible to ignore these instructions in C++

Yes. if constexpr can eliminate entire branches of code:

#ifdef DEBUG
#  define DEBUG_COUT(level)   if constexpr (0) {} else real_stream{level}
#else
#  define DEBUG_COUT(level)   if constexpr (1) {} else null_stream{level}
#endif

And use it like this:

DEBUG_COUT(5) << "Hello, " << 5 << " Worlds\n";

Live example: https://godbolt./z/7j6drn9ec

P.S. Different definitions of the same class depending on some macro? That's a recipe for wonderful bugs in case of linking wrong library/object. Don't do that.

#else
    class debug_cout
        : public std::ostream

Your NDEBUG dummy object is an ostream. That's not nothing!

Even if the streambuf isn't connected to anything, that can change (and must therefore be checked) at runtime. You already know this, because you're setting the same streambuf up at runtime in the DEBUG branch.

You want a type that does nothing, so just write that:

#else
    class debug_cout {};

and you want it to work with stream insertion syntax, so write that:

    template <class T>
    debug_cout& operator<<(debug_cout& s, T&&)
    {
        return s;
    }

本文标签: Ignore the use of a C function during compilation having operatorltltStack Overflow