program is just so easy that no one ever
bothers to write a decent debugger for
the language. Compiled languages,
on the other hand, usually have debuggers because the time needed to
add a print statement and rebuild a
large program is longer than it takes
to fire up the debugger. An example of
this problem is present in my scripting language of choice, Python. I love
writing in Python, but I do not love the
Python debugger. It has improved over
the past few years, likely because bigger and bigger systems are being built
in Python, so having a debugger makes
finding the bugs easier. As debuggers
go, however, the ones for Python are
nothing compared with those available for compiled languages.
The second instance where print
beats a debugger is one that perhaps
most readers of this column have not
had to experience: bringing up a new
piece of hardware. In the not-too-dis-
tant past it was uncommon for anyone
except a device-driver writer to worry
about bringing up new hardware. With
more people using open source oper-
ating systems, however, it has become
more common to have to do some level
of work with new hardware. I recently
experienced this when I bought a new
laptop. Of all the things that did not
work when I installed my operating
system of choice, it happened to be
the built-in keyboard that did not work
with the operating system’s keyboard
driver. It turned out I could plug in a
USB keyboard and boot with the inter-
nal keyboard disabled, but that was not
quite how I envisioned using my new,
light, slick, laptop—with a USB key-
board attached.
I normally don’t work on keyboard
drivers, but I know the people who did,
and I know there is nothing more frustrating than having a whiny user send
you an email message saying, “The
keyboard doesn’t work.” The driver
itself was not long, and I knew about
where the hang would happen in the
code, so I just backtracked from where
I thought the hang point was and used
an Emacs macro I had written for just
such an occasion, as shown in Figure 1.
Attaching the code shown in Figure 1 to a key sequence, I could insert a print statement anywhere in
my code, and when it was reached, it
would print out the function, filename,
and line that had been reached. Using
this primitive method, I was able to
track down what was causing the system to hang and thus could avoid it,
as well as send a much more detailed
bug report to the driver maintainer.
Certainly more could be done with this
macro; Figure 2 shows an example that
builds on the previous code to enclose
the print statement in a debug block
that can be turned on and off from the
makefile or command line.
Yes, there are times when you need
or want printf, or print statements, but I still say that those times
are, hopefully, few and far between.
kV
Dear kV,
You have written in previous columns
about not using printf to debug
programs, and you recommended using a debugger, but you must admit
that there are times when a print
statement is just an easier way of debugging a program and that using a
debugger is overkill.
still Pounding on Printf
figure 1. emacs macro example.
the people on your system. If cleaning
up after yourself takes 30 seconds, you
are pretty likely to do it; if it requires 30
minutes, you are going to put it off as
long as you can, usually long enough
for the file system to fill up again.
kV
Dear Pounding,
True, I have written in previous columns about the reasons for not using
print statements for debugging, and
I have recommended that people use
finer tools such as debuggers to find
problems in their programs. There are
two instances in which I agree that a
print statement is a better solution.
The first instance where print
beats a debugger is when either you
have no debugger or the debugger itself is incredibly painful to use. I find
this happens often with interpreted
languages, probably because adding a
print statement and rerunning your
(defun dbgprintf ()
“Insert a debug printf for kernel debugging.”
(interactive “*”)
(indent-for-tab-command)
(insert “printf(\”reached function %s file %s line %d\\n\”,\n”)
(indent-for-tab-command)
(insert “__func__, __FILE__, __LINE__);\n”))
figure 2. emacs macro example with print statement enclosed in a debug block.
(defun debug-block ()
“Insert a debug printf inside a C ifdef debug block.”
(interactive “*”)
(insert “#if defined(DEBUG)\n”)
(indent-for-tab-command)
(dbgprintf)
(insert “#endif /* DEBUG */\n”)
(indent-for-tab-command)
)
Related articles
on queue.acm.org
A Conversation with Bruce Lindsay
http://queue.acm.org/detail.cfm?id=1036486
Photoshop Scalability: Keeping It Simple
Clem Cole, Russell Williams
http://queue.acm.org/detail.cfm?id=1858330
A Call to Arms
Jim Gray, Mark Compton
http://queue.acm.org/detail.cfm?id=1059805
George V. neville-neil ( kv@acm.org) is the proprietor of
Neville-Neil consulting and a member of the ACM Queue
editorial board. he works on networking and operating
systems code for fun and profit, teaches courses on
various programming-related subjects, and encourages
your comments, quips, and code snips pertaining to his
Communications column.