Good practice of writing debug print statements

This is a discussion on Good practice of writing debug print statements within the C Programming forums, part of the General Programming Boards category; I inherited a software that has this: Code: #ifdef DEBUG #define dbprintf0(v0) printf(v0) #define dbprintf1(v0,v1) printf(v0,v1) #define dbprintf2(v0,v1,v2) printf(v0,v1,v2) #define ...

  1. #1
    Registered User
    Join Date
    Jul 2005
    Posts
    98

    Good practice of writing debug print statements

    I inherited a software that has this:
    Code:
    #ifdef DEBUG
      #define dbprintf0(v0)                         printf(v0)
      #define dbprintf1(v0,v1)                      printf(v0,v1)
      #define dbprintf2(v0,v1,v2)                   printf(v0,v1,v2)
      #define dbprintf3(v0,v1,v2,v3)                printf(v0,v1,v2,v3)
      #define dbprintf4(v0,v1,v2,v3,v4)             printf(v0,v1,v2,v3,v4)
      #define dbprintf5(v0,v1,v2,v3,v4,v5)          printf(v0,v1,v2,v3,v4,v5)
      #define dbprintf6(v0,v1,v2,v3,v4,v5,v6)       printf(v0,v1,v2,v3,v4,v5,v6)
      #define dbprintf7(v0,v1,v2,v3,v4,v5,v6,v7)    printf(v0,v1,v2,v3,v4,v5,v6,v7)
    #else
      #define dbprintf0(v0)
      #define dbprintf1(v0,v1)
      #define dbprintf2(v0,v1,v2)
      #define dbprintf3(v0,v1,v2,v3)
      #define dbprintf4(v0,v1,v2,v3,v4)
      #define dbprintf5(v0,v1,v2,v3,v4,v5)
      #define dbprintf6(v0,v1,v2,v3,v4,v5,v6)
      #define dbprintf7(v0,v1,v2,v3,v4,v5,v6,v7)
    #endif
    Is this good programming practice? Any better solution?
    And usually should debug print output go to stderr instead of stdout?

  2. #2
    cwr
    cwr is offline
    Registered Luser cwr's Avatar
    Join Date
    Jul 2005
    Location
    Sydney, Australia
    Posts
    869
    That's insane. You should make a macro that allows you to specify a variable number of arguments, then pass it to something that then uses vprintf and friends.
    Code:
    #define DBPRINT(fmt) logfunc fmt
    
    void logfunc(const char *, ...);
    
    /* ... */
    
    /* Note that we need the two parentheses pairs to ensure
    the multiple parameters are treated as one in the macro */
    DBPRINT(("Some debugging line: %d %d %s", v1, v2, s1));
    Then logfunc takes the va_list and passes it to vprintf.

    As for stderr/stdout, it really spends on the specifics of the application. Another option is to a debug log file, that keeps it separate from normal stdout (and stderr).

  3. #3
    Registered User
    Join Date
    Jul 2005
    Posts
    98
    Do you mean I have to write a logfunc()? logfunc() does not seem to be a library call. And I am too dumb to understand the double parenthesis thing. Can you elaborate a bit? Anyway, taking cue from your suggestion and looking up on this va_list stuff, I come up with the following, is it OK?
    "debug.h"
    Code:
    #ifndef _DEBUG_H
    #define _DEBUG_H
    
    #ifdef DEBUG
    void dbprintf(const char *, ...);
    #else
    static inline void dbprintf(const char *fmt, ...) {};
    #endif 
    
    #endif
    "debug.c"
    Code:
    #include <stdarg.h> /* va_end(), va_list, va_start(), vprintf() */
    #include <stdio.h> /* vprintf() */
    #include "debug.h"
    
    void dbprintf(const char *fmt, ...)
    {
      va_list ap;
      va_start(ap, fmt);
      vprintf(fmt, ap);
      va_end(ap);
    }
    "testdebug.c"
    Code:
    #include <stdlib.h>
    #include "debug.h"
    
    int main()
    {
      dbprintf("yy\n");
      dbprintf("DEBUG MSG: %s %s %d\n", "xx", "yy", 1);
      dbprintf("DEBUG MSG: %s %d %s %d\n", "xx", 0, "yy", 1);
      return EXIT_SUCCESS;
    }
    Also, whereas in Linux, va_start is in Section 3 (i.e. library call) of the manual, va_start is in Section 9F (Kernel Functions for Drivers) in Solaris. Is it OK to use a "Kernel Function for Drivers" in an application program to be run in Solaris?
    Last edited by hzmonte; 11-08-2005 at 06:51 PM.

  4. #4
    cwr
    cwr is offline
    Registered Luser cwr's Avatar
    Join Date
    Jul 2005
    Location
    Sydney, Australia
    Posts
    869
    I would suggest va_start is in the wrong section in Solaris, then.

    va_start is part of standard C, so it's okay to use it in any environment that claims to implement standard C.

    Yes, the idea was for you to write log_func, or dbprintf, or whatever you want to call it. The advantage of using a macro is that you can apply more fluff around the edge, like pass __LINE__ as one of the parameters, so you know what line it came from.

    The other thing with a macro is that you can more easily remove it completely.

  5. #5
    Registered User
    Join Date
    Jul 2005
    Posts
    98
    In my implementation, if I do
    gcc testdebug.c debug.c
    without defining DEBUG, I would get an error - dbprintf() is defined both in debug.h and debug.c. What can i do? I think I followed the advice in a "Proper Linux Kernel Coding Style" article but apparently I miss something.

  6. #6
    cwr
    cwr is offline
    Registered Luser cwr's Avatar
    Join Date
    Jul 2005
    Location
    Sydney, Australia
    Posts
    869
    The problem is that you're defining static inline void dbprintf in your header, and defining the real dbprintf in your .c file.

    If you were going to use that method, which I wouldn't recommend, you would need a corresponding ifndef wrapper around your definition in debug.c, so it disappeared when debug was undefined:
    Code:
    #ifndef DEBUG
    #endif
    If I were you, I'd use a macro, that either calls the function or doesn't, depending on DEBUG.

  7. #7
    Registered User
    Join Date
    Jul 2005
    Posts
    98
    I don't like that double paren very much. Could you elaborate a bit why it is needed? Thanks.

  8. #8
    cwr
    cwr is offline
    Registered Luser cwr's Avatar
    Join Date
    Jul 2005
    Location
    Sydney, Australia
    Posts
    869
    because you can't make a macro expand a variable number of parameters. Generically:
    Code:
    #define FOO(x) bar(x)
    Here, we can do:
    Code:
    FOO(3)
    And the macro will expand it to:
    Code:
    bar(3)
    However, when we try:
    Code:
    FOO(3,5)
    We get a preprocessing error, because the FOO macro is only expecting one parameter. The hack is to use two parentheses:
    Code:
    #define FOO(x) bar x
    FOO((3,5));
    That now expands to:
    Code:
    bar (3,5);
    The hack looks less ugly if you do something more complicated, like have a debug level:
    Code:
    LOG(LOG_ERR, ("this is a debug statement: %d", status));
    So you can define a macro that accepts your variable length format stuff in the inner parentheses, and other values outside of them, as above.

  9. #9
    Registered User
    Join Date
    Jul 2005
    Posts
    98
    Quote Originally Posted by cwr
    The advantage of using a macro is that you can apply more fluff around the edge, like pass __LINE__ as one of the parameters, so you know what line it came from.

    The other thing with a macro is that you can more easily remove it completely.
    Could you please give example to illustrate these 2 points? What do you mean exactly by saying "remove a macro"? I really appreciate your help.

  10. #10
    cwr
    cwr is offline
    Registered Luser cwr's Avatar
    Join Date
    Jul 2005
    Location
    Sydney, Australia
    Posts
    869
    Well, this is a really dirty example off the top of my head, I guess similar can be done more elegantly:
    Code:
    #include <stdlib.h>
    #include <stdarg.h>
    #include <stdio.h>
    
    #ifdef DEBUG
    #define DEBUGLOG(level, fmt) do { char *TEMP; TEMP = build_log fmt; do_log(__LINE__, __FILE__, level, TEMP); free(TEMP); } while (0)
    #else
    /* define DEBUGLOG out if no DEBUG defined. */
    #define DEBUGLOG(level, fmt)
    #endif
    
    char *build_log(const char *, ...);
    void do_log(int, char *, int, char *);
    
    int main(void)
    {
        int x = 100;
        DEBUGLOG(5, ("This is a log line: %d", x));
        return 0;
    }
    
    char *build_log(const char *fmt, ...)
    {
        char *p;
        va_list ap;
        p = malloc(512); /* check for null in real code */
        va_start(ap, fmt);
        vsnprintf(p, 512, fmt, ap);
        va_end(ap);
        return p;
    }
    
    void do_log(int line, char *file, int level, char *m)
    {
        fprintf(stderr, "LOG LEVEL %d: %s:%d \"%s\"\n", level, file, line, m);
    }
    Output (assuming it is compiled with DEBUG defined, otherwise it's taken out):
    Code:
    LOG LEVEL 5: macro.c:17 "This is a log line: 100"

  11. #11
    Registered User
    Join Date
    Jul 2005
    Posts
    98
    How about
    #ifdef DEBUG
    #define dbprintf(...) printf(__VA_ARGS__)
    #else
    #define dbprintf(...)
    #endif
    ?

  12. #12
    Just Lurking Dave_Sinkula's Avatar
    Join Date
    Oct 2002
    Posts
    5,006
    7. It is easier to write an incorrect program than understand a correct one.
    40. There are two ways to write error-free programs; only the third one works.*

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Lessons in writing good software - part 1
    By Salem in forum A Brief History of Cprogramming.com
    Replies: 17
    Last Post: 10-13-2003, 10:59 PM
  2. Good C programming practice
    By Juganoo in forum C Programming
    Replies: 4
    Last Post: 12-21-2002, 03:20 AM
  3. Replies: 13
    Last Post: 04-30-2002, 12:06 AM
  4. Goto statements, good or bad?
    By Esparno in forum C++ Programming
    Replies: 20
    Last Post: 03-20-2002, 09:16 PM
  5. good programming practice
    By Unregistered in forum C Programming
    Replies: 0
    Last Post: 02-26-2002, 08:09 AM

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21