Training example to show that sometimes printf as debugging can hide the error

I remember when I was in the course of programming in C, the teacher once suggested using printf to view the execution of a program that I was trying to debug. This program had a segmentation error with a reason that I cannot remember at the moment. I followed his advice, and the segmentation error disappeared. Fortunately, smart TA asked me to debug instead of using printf s. In this case, it was helpful.

So, today I wanted to show someone that using printf could potentially hide the error, but I cannot find this old code with this bizarre error (function? Hmmm).

Question: Have any of you encountered this behavior? How can I reproduce something like this?

Edit:

I see that my part of the question is orienting my opinion on "using printf is wrong." I don’t quite say that, and I don’t like expressions of extreme opinions, so I edit the question. I agree that printf is a good tool, but I just wanted to recreate the case where printf fixes a segmentation error and therefore proves that you need to be careful.

+7
c debugging segmentation-fault printf-debugging
source share
11 answers

There are times when adding printf calls changes the behavior of the code, but there are also times when debugging does the same. The most striking example is debugging multithreaded code, where stopping the execution of a thread can change the behavior of the program, so the error you are looking for may not occur.

Therefore, the use of printf operators has good reason. Debugging or printf should be decided on a case-by-case basis. Please note that these two are not exclusive - you can debug code even if it contains printf calls printf

+18
source share

You will have a very difficult time convincing me not to use logging (and printf in this situation is a form of logging) to debug. Obviously, to debug a failure, you first need to get a backtrace and use a cleanup or similar tool, but if the reason is not obvious, then the log is by far one of the best tools you can use. The debugger allows you to focus on the details, registration gives you a big picture. Both are helpful.

+7
source share

Looks like you're dealing with heisenbug .

I don’t think that there is something inherently “wrong” using printf as a debugging tool. But yes, like any other tool, it has its drawbacks, and yes, there was more than one case where adding printf statements created a heisenbug. However, I also had heisenbugs releases as a result of changes to the memory layout introduced by the debugger, in which case printf was invaluable in tracking down the steps that lead to the crash.

+4
source share

IMHO Each developer still relies on printouts here and there. We just learned to call them "detailed magazines."

Moreover, the main problem that I saw is that people see printfs as invincible. For example, in Java it’s not uncommon to see something like

 System.out.println("The value of z is " + z + " while " + obj.someMethod().someOtherMethod()); 

This is great, except that z was actually involved in the method, but there was no other object, and to make sure that you did not get an exception from the expression on obj.

Another thing is that printouts do that they introduce delays. I saw that the code with the conditions of the race is sometimes "fixed" when entering printouts. I would not be surprised if some code uses this.

+2
source share

I remember once trying to debug a Macintosh program (circa 1991), where the compiler created the cleanup code for a stack frame between 32 KB and 64 KB, it was erroneous because it used 16-bit address addition rather than 32-bit (16 -digit quantity added to the address register will be expanded by 68000). The sequence was something like this:

   copy stack pointer to some register
   push some other registers on stack
   subtract about 40960 from stack pointer
   do some stuff which leaves saved stack-pointer register alone
   add -8192 (signed interpretation of 0xA000) to stack pointer
   pop registers
   reload stack pointer from that other register

The net effect was that everything was fine except that the saved registers were corrupted, and one of them contained a constant (global array address). If the compiler optimizes the variable in the register during the code section, it reports this in the debugging information file so that the debugger can correctly output it. When the constant is so optimized, the compiler does not seem to include such information, as it should not be necessary. I traced everything by doing "printf" the address of the array and setting breakpoints so that I could look at the address before and after printf. The debugger correctly reported the address before and after printf, but printf returned the wrong value, so I parsed the code and saw that printf was pushing register A3 onto the stack; having looked at the register A3 before printf showed that it has a value significantly different from the address of the array (printf showed that the value A3 is actually satisfied).

I don't know how I would ever trace this if I could not use both debuggers and printf together (or, for that matter, if I did not understand assembly code 68000).

+2
source share

I managed to do this. I read data from a flat file. My error algorithm was as follows:

  • get input file length in bytes
  • allocates an array of variable-length characters as a buffer
    • the files are small, so I'm not worried about stack overflow, but what about input files of zero length? oops!
  • returns an error code if the length of the input file is 0

I found that my function reliably throws a seg error - if there was no printf in the body of the function, in which case it will work exactly as I expected. The fix for the seg error was to highlight the file length plus one in step 2.

+1
source share

I had a similar experience. Here is my specific problem and reason:

 // Makes the first character of a word capital, and the rest small // (Must be compiled with -std=c99) void FixCap( char *word ) { *word = toupper( *word ); for( int i=1 ; *(word+i) != '\n' ; ++i ) *(word+i) = tolower( *(word+i) ); } 

The problem with the loop condition is that I used '\ n' instead of the null character, '\ 0'. Now I don’t know exactly how printf works, but from this experience, I assume that it uses some memory after my variables as a temporary / workspace. If the expression printf causes the character "\ n" to be written in some place after saving my word, the FixCap function will be able to stop at some point. If I remove printf, it will continue the loop, looking for "\ n" but never finding it until it disappears.

So, in the end, the main reason for my problem is that sometimes I type "\ n" when I mean "\ 0". This is a mistake I made earlier, and probably I will do it again. But now I know what to look for.

+1
source share

Well, maybe you could teach him how to use gdb or other debugging programs? Tell him that if the error disappears due to "printf", then it really will not disappear and may appear again last. The error should be fixed, not ignored.

0
source share

This will divide by 0 when deleting the printf line:

 int a=10; int b=0; float c = 0.0; int CalculateB() { b=2; return b; } float CalculateC() { return a*1.0/b; } void Process() { printf("%d", CalculateB()); // without this, b remains 0 c = CalculateC(); } 
0
source share

What will be the case of debugging? Printing the char *[] array before calling exec() just to see how it was marked. I think this is a pretty legitimate use for printf() .

However, if the format loaded in printf() has sufficient cost and complexity that it can actually change program execution (mainly speed), a debugger may be the best way. Again, debuggers and profilers are also expensive. Or one may expose races that may not appear in their absence.

It all depends on what you write and the mistake you pursue. Available tools are debuggers, printf() (grouping registrars in printf, as well) statements and profilers.

Is a screwdriver a better blade than others? Depends on what you need. Notice, I'm not saying that statements are good or bad. This is another tool.

0
source share

One way to solve this problem is to configure a macro system that makes it easy to disable printfs without having to remove them in the code. I am using something like this:

 #define LOGMESSAGE(LEVEL, ...) logging_messagef(LEVEL, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__); /* Generally speaking, user code should only use these macros. They * are pithy. You can use them like a printf: * * DBGMESSAGE("%f%% chance of fnords for the next %d days.", fnordProb, days); * * You don't need to put newlines in them; the logging functions will * do that when appropriate. */ #define FATALMESSAGE(...) LOGMESSAGE(LOG_FATAL, __VA_ARGS__); #define EMERGMESSAGE(...) LOGMESSAGE(LOG_EMERG, __VA_ARGS__); #define ALERTMESSAGE(...) LOGMESSAGE(LOG_ALERT, __VA_ARGS__); #define CRITMESSAGE(...) LOGMESSAGE(LOG_CRIT, __VA_ARGS__); #define ERRMESSAGE(...) LOGMESSAGE(LOG_ERR, __VA_ARGS__); #define WARNMESSAGE(...) LOGMESSAGE(LOG_WARNING, __VA_ARGS__); #define NOTICEMESSAGE(...) LOGMESSAGE(LOG_NOTICE, __VA_ARGS__); #define INFOMESSAGE(...) LOGMESSAGE(LOG_INFO, __VA_ARGS__); #define DBGMESSAGE(...) LOGMESSAGE(LOG_DEBUG, __VA_ARGS__); #if defined(PAINFULLY_VERBOSE) # define PV_DBGMESSAGE(...) LOGMESSAGE(LOG_DEBUG, __VA_ARGS__); #else # define PV_DBGMESSAGE(...) ((void)0); #endif 

logging_messagef() is a function defined in a separate .c file. Use the XMESSAGE (...) macros in the code depending on the purpose of the message. The best thing about this setting is that it works for debugging and logging at the same time, and the logging_messagef() function can be changed to perform several different actions (printf to stderr, to a log file, using syslog or another system log object, etc.), and messages below a certain level can be ignored in logging_messagef() when you do not need them. PV_DBGMESSAGE() intended for those complex debugging messages that you will definitely want to disable during production.

0
source share

All Articles