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.
|Listing 1: Show the Location of a Runtime Error|
Figure 1 shows the result of running the program in Listing 1:
|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:
and it would be called like this:
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:
In the above example, the AT macro would expand to be "c:\temp\test.cpp:5".
The prototype for my new error() function becomes:
Because the Borland C++ Builder compiler automatically merges adjacent strings, I can create a #define for AT that looks like this:
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
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:
So, not being one to fight the problem, I'll change my AT macro to look like this:
That compiles, but at runtime it yields the bizarre message in Figure 2:
|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:
Listing 2 shows the final sample program and Figure 3 shows the output of Listing 2.
|Listing 2: The final solution that turns __LINE__ into a string|
|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".
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.