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:
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:
| |||||||||
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.
| |
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".
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.