The Database Managers, Inc.

Contact The Database Managers, Inc.


Use an RSS enabled news reader to read these articles.Use an RSS enabled news reader to read these articles.

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:

  1. The __FILE__ and __LINE__ preprocessor directives need to be added to every error() function call.
  2. 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.
  3. __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.
C++ FAQ Services | Programming | Contact Us | Recent Updates
Send feedback to: