| Bullseye Error Reportingby Curtis KrauskopfOne of the challenges in reporting runtime errors is 
                          providing enough information to find where the error 
                          originates. 
                         Wouldn't it be nice if there was a way to unambiguously 
                          report the exact line that an error occurs on? Wouldn't 
                          it be even nicer if the compiler would do all of the 
                          bookkeeping for you so that you could focus on the coding? 
                          Fortunately, there is a solution. 
                         The Borland C++ Builder provides three predefined macros 
                          that provide information about the line being compiled: 
                          __FILE__, __LINE__ and __FUNC__. 
                         The __FILE__ C++ macro contains the drive, path and 
                          name of the file being compiled. The __LINE__ macro 
                          contains the line number of the original source file 
                          that is being compiled. The __FUNC__ macro contains 
                          the name of the function being compiled. On other compilers, 
                          this might be called __FUNCTION__ [1] or __func__ [2]. 
                         All three of these macros have two underscores both 
                          before and after the word. Separating each character 
                          of the __FILE__ macro looks like: 
                         _ _ F I L E _ _ 
                         Listing A shows Simple_Error_Report.cpp 
                          and Figure A shows its output. 
                          Simple_Error_Report.cpp is a console-mode application 
                          that shows an easy way to associate runtime errors with 
                          the original source line. 
                         
 
                           
                            | Listing 
                              A |   
                            | 
// Simple_Error_Report.cpp
#include <stdio.h>
int main(int , char**)
{
  printf("Error in %s on line %d, in %s\n", __FILE__, __LINE__, __FUNC__);
  return 0;
}
 |   
                            | hash.cpp sample program. |  
 
                           
                            | Figure A |   
                            | Error in C:\Sandbox\Error_Location\Simple_Error_Report.cpp 
                              on line 5, in main |   
                            
                           The result in Figure A shows some interesting information. 
                          The __FILE__ macro expands to a filename with the 
                            drive and path of the original compilation unit. 
                          The __LINE__macro is an integer, as evident from 
                            the %d printf() format specifier. 
                          The __FUNC__ macro does not contain the return type 
                            or the parameter list of the function. 
                         Taking It One Step FurtherI 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. Because the name of the function is 
                          available, I might want to take advantage of that by 
                          doing special processing based on the name of the function. 
                          For example, an error caused in a getter or setter will 
                          have a get_ or set_ prefix on the function name. 
                         A prototype for such an error logging function is: 
                         
void error(const char *file, const unsigned long line, const char *name, const char *msg);
 and it would be called like this:  
error(__FILE__, __LINE__, __FUNC__, "my error message");
 Preprocessor MagicThere are three awkward parts to the above solution: 
                         
                          The __FILE__, __LINE__ and __FUNC__ macros need 
                            to be added to every error() function call. 
                          It's easy to forget to put both underscores on both 
                            parts of the __FILE__, __LINE__ and __FUNC__ macros. 
                            Getting it wrong will lead to a compile-time error. 
                          __LINE__ is an integer. Doing string manipulation 
                            on an integer just adds another complexity to any 
                            error() function I create. It is unlikely that I will 
                            ever 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__, __LINE__ and __FUNC__ 
                          could somehow be handled automatically so I couldn't 
                          get them wrong every time I write an error() call. 
                         What I want to write is something like:  
error(AT, "my error message");
 Using the program in Listing A as an example, the AT 
                          macro in that program would expand to be:  "C:\Sandbox\Error_Location\Simple_Error_Report.cpp:5:(main)" 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__ ":(" __FUNC__ ")"
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 ":(" "main" ")"That is an invalid string because strings can't have 
                          an unquoted integer in the middle 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__ ":(" __FUNC__ ")"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 create 
                          a macro called STRINGIFY and change my AT macro to look 
                          like this:  
#define STRINGIFY(x) #x
#define AT __FILE__ ":" STRINGIFY(__LINE__) ":(" __FUNC__ ")"
These lines compile and the sample program in Listing 
                          B shows the test program.  
                           
                            | Listing 
                              B |   
                            | // Developing_Solution.cpp
// This solution does not work because the __LINE__ macro is *itself*
// part of the output!
#include <stdio.h>
#define STRINGIFY(x) #x
#define AT __FILE__ ":" STRINGIFY(__LINE__) ":(" __FUNC__ ")"
void error(const char *location, const char* msg)
{
  printf("Error: %s at %s\n", msg, location);
}
int main(int , char**)
{
  error(AT, "example");
  return 0;
}
 |   
                            | Developing_Solution.cpp sample 
                              program. |  
 
                           
                            | Figure B |   
                            | Error: example at c:\sandbox\Error_Location\Developing_Solution.cpp:__LINE__:(main) |   
                            
                           As shown in Figure B, 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__) ":(" __FUNC__ ")"
The complete program is in listing 
                          C and its output is in figure C. 
                           
                            | Listing 
                              C |   
                            | 
// Better_Solution.cpp
// This solution provides a complete example of using __FILE__, __LINE__
// and __FUNC__ to report an error's location.
#include <stdio.h>
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define AT __FILE__ ":" TOSTRING(__LINE__) ":(" __FUNC__ ")"
void error(const char *location, const char* msg)
{
  printf("Error: %s at %s\n", msg, location);
}
int main(int , char**)
{
  error(AT, "example");
  return 0;
}
 |   
                            | Better_Solution.cpp sample program. |  
 
                           
                            | Figure C |   
                            | Error: example at C:\Sandbox\Error_Location\Better_Solution.cpp:17:(main) |   
                            
                           Too Much InformationThe pendulum for this problem started at no debug information 
                          and now it has swung so far that I'm getting too much 
                          information. I really don't need, for most of my projects, 
                          to know the drive and the complete folder path to the 
                          source file. This is especially true for my projects 
                          that are nested five or more folder levels deep. 
                         Because all of my error reports are going through the 
                          same error() function, I can easily customize the report 
                          by stripping the drive and path information at runtime. 
                         If you don't want the drive and path information on 
                          the error report, then add a #include <string.h> 
                          and change the error() function to:  
void error(const char *location, const char* msg)
{
  const char *no_path = strrchr(location, '\\');
  if (no_path)
    ++no_path;  // move off of '\\'
  else
    no_path = location;
  printf("Error: %s at %s\n", msg, no_path);
}
The sample code is available in Strip_Path.cpp in the 
                          .zip file. The output of the sample program is in Figure 
                          D. 
                           
                           
                            | Figure D |   
                            | Error: example at Strip_Path.cpp:25:(main) |   
                            
                           As expected, the error message is a little bit easier 
                          to read. For small or medium-sized projects, removing 
                          the drive and folder path does not reduce your ability 
                          to find the location of the error.  ConclusionThe macros __FILE__, __LINE__ and __FUNC__ can provide 
                          some very 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__ macro into a string turned 
                          out to be much more difficult than originally imagined. 
                          Through the use of some #define preprocessor magic, 
                          though, the __LINE__ macro was tamed and forced to compile 
                          as a string. 
                         This has the advantage that the string is automatically 
                          merged with the values of the __FILE__ and __FUNC__ 
                          macros to create one string for error processing. This 
                          also has the advantage of removing the need for integer 
                          to string conversion in the error() function and then 
                          merging the resulting strings at runtime. 
                         [1]: GNU GCC Compilers before version 3 define __FUNCTION__ 
                          and all GNU GCC compilers starting at version 3 recognize 
                          both __FUNCTION__ 
                          and __func__. 
                         [2]: __func__ 
                          is part of the C99 standard but the Borland C++ 
                          Builder compiler (at least as of version 6) does not 
                          recognize __func__ as a predefined symbol. 
                         Contact Curtis 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: |