Using __FILE__ and __LINE__ to Report Errors
by Curtis Krauskopf
Q: How can I create a string that
contains the C++ filename and line number that a runtime
error occurs on?
A: The __FILE__
C++ preprocessor directive contains the name of the
file being compiled. Similarly, the __LINE__
preprocessor directive contains the line number of the
original source file that is being compiled. Both the
__FILE__ and __LINE__
preprocessor directives have two underscores
both before and after the word. Separating
each character of the __FILE__
preprocessor directive looks like:
_ _ F I L E _ _
Listing 1 is a console-mode application that shows
an easy way to associate runtime errors with the original
source line.
#include <stdio.h>
int main(int , char**)
{
printf("This fake error is in %s on line %d\n", __FILE__, __LINE__);
return 0;
}
|
|
Listing 1: Show the Location of
a Runtime Error
|
Figure 1 shows the result of running the program in
Listing 1:
|
|
|
|
This fake
error is in c:\temp\test.cpp on line 5 |
|
|
|
|
|
Figure 1: The Output of Listing
1
|
Taking It One Step Further
I want every error report to go through a common error()
function so that I can set breakpoints when certain
errors occur and so I can segregate how errors are handled.
For example, I might want some errors to appear on the
screen and other errors to be logged to a file. And
some errors might need to appear on the screen and be
logged to a file.
A prototype for such an error logging function is:
void error(const char *file, const unsigned long line,
const char *msg);
and it would be called like this:
error(__FILE__, __LINE__, "my error message");
Preprocessor Magic
There are three awkward parts to the above solution:
- The __FILE__ and __LINE__
preprocessor directives need to be added to every
error() function call.
- It's easy to forget to put both underscores on both
parts of the __FILE__
and __LINE__ directives.
Getting it wrong will lead to a compile-time error.
- __LINE__ is an integer.
Doing string manipulation on an integer just adds
another level of complexity to any error()
function I create. I will never need to use __LINE__
as an integer -- I always want to use it as a string
so that it can be output to the screen or a log file.
It would be nicer if __FILE__
and __LINE__ could somehow
be handled automatically so I couldn't get them wrong
every time I write an error() call.
What I would like to be able to do is write something
like:
error(AT, "my error message");
In the above example, the AT
macro would expand to be "c:\temp\test.cpp:5".
The prototype for my new error()
function becomes:
void error(const char *location, const char *msg);
Because the Borland C++ Builder compiler automatically
merges adjacent strings, I can create a #define
for AT that looks
like this:
#define AT __FILE__ ":" __LINE__
That doesn't work, though, because __LINE__
expands to an integer. The above #define
expands to this at compile-time:
"c:\temp\test.cpp" ":"
5
That is an invalid string because strings can't have
an unquoted integer at the end of the string.
A special preprocessor directive that turns a symbol
into a string is the #
token. Changing the above #define
to
#define AT __FILE__ ":" #__LINE__
seems like it should work but it doesn't because the
compiler complains that #
is an illegal character. The problem is that the #
preprocessor symbol is only recognized when it's used
like this:
#define symbol(X) #X
So, not being one to fight the problem, I'll change
my AT macro to look like
this:
#define STRINGIFY(x) #x
#define AT __FILE__ ":" STRINGIFY(__LINE__)
That compiles, but at runtime it yields the bizarre
message in Figure 2:
|
|
|
|
c:\temp\test.cpp:__LINE__:
fake error |
|
|
|
|
|
Figure 2: The preprocessor directive
appears in the output.
|
As shown in Figure 2, the __LINE__
preprocessor directive itself has become a part of the
output!
The solution is to take the STRINGIFY()
solution one step further -- to wrap the STRINGIFY()
macro in yet another macro:
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define AT __FILE__ ":" TOSTRING(__LINE__)
Listing 2 shows the final sample program and Figure
3 shows the output of Listing 2.
#include <stdio.h>
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define AT __FILE__ ":" TOSTRING(__LINE__)
void error(const char *location, const char *msg)
{
printf("Error at %s: %s\n", location, msg);
}
int main(int , char**)
{
error(AT, "fake error");
return 0;
}
|
|
Listing 2: The final solution that
turns __LINE__ into a string
|
|
|
|
|
Error at
c:\temp\test\test.cpp:11: fake error |
|
|
|
|
|
Figure 3: The Output of Listing
2
|
Visual Studio Support
Tim Johnston tried this solution in Microsoft Video
Studio but he found that the __LINE__ preprocessor symbol
did not have the correct value in debug mode. His solution
was to change the "Debug Information Format"
setting on the C/C++ project
setting tab. The setting that does not work is "Program
Database for Edit and Continue"; the setting that
works is "Program Database".
Conclusion:
The preprocessor directives __FILE__
and __LINE__ can provide
some useful debugging information. This information
can be made available at runtime by print()ing
those values to the screen or to a log file.
Transforming the __LINE__
preprocessor directive into a string turned out to be
much more difficult than originally imagined. Through
the use of some #define
preprocessor magic, though, the __LINE__
preprocessor directive was tamed and forced to compile
as a string.
This has the advantage that the string is automatically
merged with the __FILE__
preprocessor value which creates one string for error
processing.
This also has the advantage of removing the need for
integer to string conversion in the error()
function.
This
article was written by Curtis Krauskopf (email at ).
Curtis Krauskopf is a software
engineer and the president of The Database Managers (www.decompile.com).
He has been writing code professionally for over 25 years. His prior projects
include multiple web e-commerce applications, decompilers
for the DataFlex language, aircraft simulators, an automated Y2K conversion
program for over 3,000,000 compiled DataFlex programs, and inventory control projects.
Curtis has spoken at many domestic and international DataFlex developer conferences
and has been published in FlexLines Online, JavaPro
Magazine, C/C++
Users Journal and C++ Builder Developer's Journal.
Popular C++ topics at The Database Managers:
The Database Managers
helps companies to:- become more profitable
- grow their
business
- fix programs
that are behaving badly
- write new programs
to solve business problems
- do more with
fewer resources
Email them at
to find out how to make your company more successful.
|
|