FedoraForum.org - Fedora Support Forums and Community
Page 2 of 3 FirstFirst 1 2 3 LastLast
Results 16 to 30 of 33
  1. #16
    Join Date
    Oct 2010
    Location
    Canberra
    Posts
    2,921

    Re: Debugging Techniques

    A common variation on the macro approach is to use some symbols to control the debugging:
    Code:
    #ifdef debugging
    #define debug(M, ...) fprintf(stderr, "DEBUG %s %s %d: " M "\n",__FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__ )
    #else
    #define debug(M, ...)
    #endif
    You can then use the -D compiler option to turn debugging on or off during compilation.

    You could replace the fprintf() with a more complex function that could even do things like only report nominated files and/or ranges of line numbers so as to better target the debugging output - too much output can be as bad as too little.

  2. #17
    Join Date
    Oct 2007
    Location
    Freedonia
    Age
    68
    Posts
    3,065

    Re: Debugging Techniques

    Back in the mid 1980s, I worked at JPL with Dan Alderson on some fairly complex FORTRAN subroutines. Many of them contained nested IF statements, such that the final IF didn't need an ELSE. Dan, however, always inserted the ELSE, having the program print "1 = 2" and stop. This is because if the program ever reached that point, something unanticipated had happened and he didn't want the program to continue. And yes, we did get output like that a few times.
    Registered Linux user #470359 and permanently recovered BOFH.

    Any advice in this post is worth exactly what you paid for it.

  3. #18
    Join Date
    Oct 2010
    Location
    Canberra
    Posts
    2,921

    Re: Debugging Techniques

    Quote Originally Posted by sidebrnz
    Back in the mid 1980s, I worked at JPL with Dan Alderson on some fairly complex FORTRAN subroutines. Many of them contained nested IF statements, such that the final IF didn't need an ELSE. Dan, however, always inserted the ELSE, having the program print "1 = 2" and stop. This is because if the program ever reached that point, something unanticipated had happened and he didn't want the program to continue. And yes, we did get output like that a few times.
    One of the things I always look at when doing code reviews is if statements that don't have an else. Its often quite interesting to ask what that means.

    Another similar defensive programming techniques is putting the immutable or non-lvalue on the left hand side of a boolean tests. For example, use 2 == x rather than x == 2. The idea is that if the == is mistyped as = then you will get an error. I am not entirely convinced of this as I think the mental gymnastics required to think 2 == x rather than the more obvious x == 2 is probably more likely to result in an error.

    What to do when the impossible happens is also an interesting question. Causing the program to stop might or might not be appropriate. I am reminded of the adage "Don't test for things you don't know how to handle." but I don't think its good advice.

  4. #19
    Join Date
    Jun 2005
    Location
    Montreal, Que, Canada
    Posts
    5,107

    Re: Debugging Techniques

    Quote Originally Posted by ocratato
    A common variation on the macro approach is to use some symbols to control the debugging:
    Code:
    #ifdef debugging
    #define debug(M, ...) fprintf(stderr, "DEBUG %s %s %d: " M "\n",__FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__ )
    #else
    #define debug(M, ...)
    #endif
    You can then use the -D compiler option to turn debugging on or off during compilation.

    You could replace the fprintf() with a more complex function that could even do things like only report nominated files and/or ranges of line numbers so as to better target the debugging output - too much output can be as bad as too little.
    Hi Ocratato

    I purposely went away from the global approach. gcc -d NDEBUG . In my code, as I developed each function, I tested or checked results with my debug1(xxxx). When I was satisfied, I renamed the debug1 to debug.
    The global way with #ifdef NDEBUG... would give me reams of multiple debug outputs instead of just the selected portions of code. Yes, for trouble shooting one function debug ==> debug1,
    recompiled. When satisfied, I did a rename of debug1==>debug and a recompile.
    I have a family of debug debuglog, and other variants. In the end, the debug() statements cause zero impact to other areas of the code.
    look at this list
    #ifndef NDEBUG
    #define debug(M, ...)
    #else

    #define debug(M, ...) fprintf(stdout, "DEBUG %s %d: " M "\n", __FUNCTION__, __LINE__, ##__VA_ARGS__ );
    #define clean_errno() (errno == 0 ? "None" : strerror(errno))
    #define log_err(M, ...) fprintf(stderr, "[ERROR] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)
    #define log_warn(M, ...) fprintf(stderr, "[WARN] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)
    #define log_info(M, ...) fprintf(stderr, "[INFO] (%s:%d) " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)
    #define check(A, M, ...) if(!(A)) { log_err(M, ##__VA_ARGS__); errno=0; goto error; }
    #define sentinel(M, ...) { log_err(M, ##__VA_ARGS__); errno=0; goto error; }
    #define check_mem(A) check((A), "Out of memory.")
    #define check_debug(A, M, ...) if(!(A)) { debug(M, ##__VA_ARGS__); errno=0; goto error; }
    #endif

    Nightmares to manage in the source code. I took what I call the KISS approach. Keep it simple, Satenstein
    Leslie in Montreal

    Interesting web sites list
    http://forums.fedoraforum.org/showth...40#post1697840

  5. #20
    Join Date
    Feb 2009
    Posts
    125

    Re: Debugging Techniques

    Valgrind - a great tool that can find memory leaks, invalid memory access, use of uninitialized variables and other problems.

  6. #21
    Join Date
    Oct 2010
    Location
    Canberra
    Posts
    2,921

    Re: Debugging Techniques

    Quote Originally Posted by tankist02
    Valgrind - a great tool that can find memory leaks, invalid memory access, use of uninitialized variables and other problems.
    Very much agree.
    It would be a good idea to run your program under Valgrind as a normal part of your unit testing.

  7. #22
    Join Date
    Jun 2005
    Location
    Montreal, Que, Canada
    Posts
    5,107

    Re: Debugging Techniques

    I use valgrind as part of my tool kit. I also use the atexit( ) function which call a routine to do all the free( ) stuff.
    Leslie in Montreal

    Interesting web sites list
    http://forums.fedoraforum.org/showth...40#post1697840

  8. #23
    Join Date
    Oct 2010
    Location
    Canberra
    Posts
    2,921

    Re: Debugging Techniques

    One of the more tricky bugs to handle is memory corruption, usually caused by calling free() twice.

    In C programs its probably the result of a logic error.
    In C++ there are some more subtle ways of doing a double free.

    For example, if a class has a pointer to something, and the destructor for the class calls delete on that pointer, you are well on the way to a memory problem. If you make a copy of the object, such as passing it by value to a function, the delete will be called when one of the instances of the object goes out of scope leaving the other instance holding an invalid pointer.

    There are several ways of avoiding this issue: Either prevent the object from being copied, or make a copy of the pointed to object when you make a copy of an object (this is a deep copy), or use some sort of reference counter so that delete is only called when the last reference is removed.

    The usual symptom is a program crash with a segmentation fault. Unfortunately finding the location where the crash occurs is not all that helpful, except that there will probably be some memory allocation happening at that point. The problem is that the second call to free (or delete) doesn't fail, but it does leave that bit of memory in an invalid state as far as malloc is concerned. Other calls to malloc might still work if they use other memory that hasn't been corrupted.

    One of the things you might find is that as soon as you start adding debugging code to try and determine where the error is, the location of the crash can change. This is good confirmation that you have a memory corruption bug.

    For simple programs you can probably do an inspection to find the problem. For more complex programs you will probably need Valgrind.

  9. #24
    Join Date
    Jun 2005
    Location
    Montreal, Que, Canada
    Posts
    5,107

    Re: Debugging Techniques

    Hi Ocratato

    Your posting is absolutely to the point where beginners make errors with their use of malloc()/free(). I have some personal experience that I hope you don"t mind my presenting:

    I am using a fairly large array of malloc'd memory. The array may contain up to two thousand entries. Each non zero entry is a pointer to an malloc'd area.
    Near the end of the program, I run through the array, doing a free of some malloc'd areas. One way I got around my double free() was to use a cover function to free(). That function takes the address of the pointer to that malloc'd area.

    Here is a snipit for the C programmer new to malloc/free.//-++

    Code:
    void dofree (void **ptr)
    {
       if ( ptr != NULL && *ptr != NULL)
       {  
         free(*ptr);
         *ptr=NULL; 
       }
    }
    Of course, in earlier posts on this topic, I read that you reminded the reader about valgrind. Have you an example in C++ to share with constructors/destructors?
    .
    .
    Leslie in Montreal

    Interesting web sites list
    http://forums.fedoraforum.org/showth...40#post1697840

  10. #25
    Join Date
    Oct 2010
    Location
    Canberra
    Posts
    2,921

    Re: Debugging Techniques

    Quote Originally Posted by lsatenstein
    ...
    Near the end of the program, I run through the array, doing a free of some malloc'd areas. One way I got around my double free() was to use a cover function to free(). That function takes the address of the pointer to that malloc'd area.

    .
    That is quite a nice approach.
    I usually just do
    Code:
    free( ptr ); ptr = NULL;
    These approaches rely on the fact that free( NULL ) does nothing.

    It is a defensive programming technique.
    A similar one is to always initialise your variables when declaring them:
    Code:
    char * ptr = NULL;
    so that if you accidentally use it before it gets a value it won't just clobber some random part of your data.

    I'll post a C++ example later.

  11. #26
    Join Date
    Oct 2010
    Location
    Canberra
    Posts
    2,921

    Re: Debugging Techniques

    Here is an example of how NOT to write C++ classes:
    Code:
    // program to demonstrate a memory bug
    
    class A
    {
    private:
    	int * ptr;	// owned
    public:
    	A();
    	~A();
    };
    
    A::A()
    {
    	ptr = new int;
    	*ptr = 0;
    }
    
    A::~A()
    {
    	delete ptr;
    }
    
    int main()
    {
    	A a;
    	A b = a;
    
    	return 0;
    }
    This will crash as it ends since the variables a and b are both referring to the same ptr.

    The following shows the three ways to do it. Class A disallows copying, class B makes its own copy of ptr, and class C shares the ptr but uses a reference count to only delete it when all uses end.
    Code:
    // program to demonstrate correct ways of handling classes containing pointers
    
    #include <memory>
    
    class A
    {
    private:
    	int * ptr;	// owned
    public:
    	A();
    	~A();
    	// prevent copying
    	A ( const A & ) = delete;
    	void operator = ( const A & ) = delete;
    };
    
    A::A()
    {
    	ptr = new int;
    	*ptr = 0;
    }
    
    A::~A()
    {
    	delete ptr;
    }
    
    // -----------------------------------------------------
    
    class B
    {
    private:
    	int * ptr;	// owned
    public:
    	B();
    	~B();
    	// deep copy
    	B( const B & );
    	B& operator = ( const B & );
    };
    
    B::B()
    {
    	ptr = new int;
    	*ptr = 0;
    }
    
    B::B( const B &b )
    {
    	ptr = new int;
    	*ptr = *b.ptr;
    }
    
    B& B::operator = ( const B &b )
    {
    	*ptr = *b.ptr;
    	return *this;
    }
    
    B::~B()
    {
    	delete ptr;
    }
    
    // ----------------------------------------------------
    
    using int_ptr = std::shared_ptr< int >;
    
    class C
    {
    private:
    	int_ptr ptr;	// owned
    public:
    	C();
    };
    
    C::C()
    {
    	ptr = int_ptr(new int);
    	*ptr = 0;
    }
    
    int main()
    {
    	A a;
    	// A aa = a;  // this would be an error
    
    	B b;
    	B bb = b;
    
    	C c;
    	C cc = c;
    
    	return 0;
    }

  12. #27
    Join Date
    Jun 2005
    Location
    Montreal, Que, Canada
    Posts
    5,107

    Re: Debugging Techniques

    Thank you!
    Leslie in Montreal

    Interesting web sites list
    http://forums.fedoraforum.org/showth...40#post1697840

  13. #28
    Join Date
    Oct 2010
    Location
    Canberra
    Posts
    2,921

    Re: Debugging Techniques

    The bug I am working on at the moment is perhaps not really a bug, but it does illustrate a debugging technique.

    My program is using an object from the Qt4 library, that apparently is not available in the Qt5 library. I am using Eclipse and I am not confident that I can set it up to reliably switch between using Qt4 and Qt5. This makes building software for both versions of the library a bit awkward - usually it is just a tweak during the final build, but this one seems like it might be a bit more involved and require a bit of experimentation to get right.

    The technique is to build a separate program that manifests the bug but that is much simpler than the main program.

    The first step is to build a version of the program that reliably manifests the bug.
    This demonstrates that you actually have isolated the cause of the bug.

    The second step is to apply your fix, and confirm that the bug has gone away.

    The third step is to transfer your fix back into the main program (and confirm it really has gone away).

    User error. Please replace user and try again

  14. #29
    Join Date
    Feb 2009
    Posts
    125

    Re: Debugging Techniques

    With modern C++ compilers you can wrap memory allocations in smart pointers like std::unique_ptr, etc and have memory managed for you.

  15. #30
    Join Date
    Oct 2010
    Location
    Canberra
    Posts
    2,921

    Re: Debugging Techniques

    Quote Originally Posted by tankist02
    With modern C++ compilers you can wrap memory allocations in smart pointers like std::unique_ptr, etc and have memory managed for you.
    Most definitely. The third of my examples in post #26 does exactly that.

    There are some that ascribe to the idea "no naked new" which is that all memory allocations should be wrapped in a smart pointer.

    While this mostly a good idea, it does put some limitations on what you can do. For example, referencing the "this" pointer in the constructor, such as when building a tree structure, becomes more trouble than its worth.

    User error. Please replace user and try again

Page 2 of 3 FirstFirst 1 2 3 LastLast

Similar Threads

  1. Header Compression techniques Ethernet links
    By rpdumps in forum Using Fedora
    Replies: 0
    Last Post: 30th June 2008, 06:58 PM

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •