вторник, 14 сентября 2010 г.

cpp tips&trics

http://www.decompile.com/cpp/faq/file_and_line_error_string.htm

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.