|
||||||||||||||||||||||
|
||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Table of contentPurpose of the provided macros
IntroductionDesign by Contract"Design by Contract" is a very good method for programming. (Read Object Oriented Software Construction from Bertrand Meyer.) For people who have never heard about DbC, I do a very simple and quick
introduction to the principle. For example I choose to write a linear interpolation function.
{
_o = _s + ((_e - _s) * _t);
}
We can add some preconditions like :
We can add as postcondition :
Imagine the same function but working with vectors or matrix. We should add
conditions saying that the input parameters should not be changed by the
function. But for the others conditions we should use the assert function. void lerp( float& _o, const float _t, const float _s, const float _e ){
assert((_t >= 0.f)&&(_t <= 1.f));
assert(!isnan(_s));
assert(!isnan(_e));
_o = _s + ((_e - _s) * _t);
assert(!isnan(_o));
}
For more specialized lerp functions you would probably add more sophisticated postconditions like insure othogolalization on matrix ... Little personal trick : Don't ignore "stupid" conditions, because those are often the ones which appears. Most of the time it is because the code changes, late in the night, by copy paste block of code..., and the "stupid" condition which has been set becomes really relevant. The Digital Mars C/C++ compiler is DbC compliant. DbC with no DbC compliant C++ compilators.Usually, I write contract condition like this:
{
assert( p ); // precondition : p cannot be null.
... code ...
if(...)
{
assert( post condition); // post condition documentation
return ...
}
... code ...
if(...)
{
assert( post condition ); // doc
// this condition didn't have to be check in the last return
assert( an other post condition); // documentation
return ...
}
... code ...
... once again check all post conditions ...
return ... ouf
}
So "debug" code, or "check" code really pollute my code, mainly because of repetitions. And I didn't want to have those stuff compiled for the the final release. So I really want to be able to write the function in a better way .. like:
{
#ifdef contract_check
PRECONDITION( p, "p can't be 0" ); // the phrase is to display to the user
// why the assertion fail.
POSTCONDITION( postcondition, "description of my post condition" );
POSTCONDITION( checkpostcondition2(), "an other post condition");
#endif // contract_check
... code ...
if(...)
return ...
... code ...
if(...)
return ...
... code ...
return ...
}
And, because I'm using a super code documentation generator (Doxygen), I wanted to see the documentation of my assertions (conditions) in the documentation. Of course, without writing it twice ... Do you follow me? (redundancy means future problems) So the little set of macro I wrote gives you the ability to write something like:
{
DBC_BEGIN
DBC_PRE_BEGIN
DBC_PRE( argA >= 0, precond 1 documentation and message )
DBC_PRE( argB != 0, precond 2 documentation and message )
DBC_PRE_END
DBC_POST_BEGIN
DBC_POST( argA + argB >= 0, post condition 1 documentation and message )
DBC_POST( ChechDataFct( argA, argB ), post cond 2 : cf. ChechDataFct() )
DBC_POST_END
DBC_END
... code ...
return ...;
}
And (if you use Doxygen) the documentation will be up to date. Example:Take a look at this picture taken from Doxygen documentation of the
While compiling for WIN32, you have this following message when the condition fails:
Click "OK" to continue (break when using a debugger). Click "Cancel" to ignore (means that it will not popup a message (or break) if the condition fails again). CodeIt works with VC6 and VC7. The code is composed of 2 parts:
For example, you can choose to send an exception when a condition fails. The demo zip file is exactly the same as the source zip file, plus a compressed HTML doc. This documentation file can be generated from the source zip file if you have Doxygen. How it works?Because 100% of the feedback I receive (...2) asks me for explanation about the trick I have used to make the tool working, I added this section to the article. Doxygen documentation:The Doxygen documentation works using the Doxygen preprocessor : In source file, the line: DBC_PRE( argA >= 0, precond 1 )
Using in the Doxygen preprocessor: DBC_PRE(a,b) =\pre b \code a \endcode
the code becomes for Doxygen: \pre precond 1 \code argA >= 0 \endcode
which is a valid command for Doxygen parser. C++ code:Note: I assume you know what are functions The main problem is ... post condition of course. You see easily that calling the preconditions at the begin of a function is very easy ... because you have written it at the beginning :) But I use a little trick to call the post conditions after returning the function. The process is very simple: Meanwhile the epilog of the function (so when the function ends/returns/leaves), we go to the start of the postcondition, execute postcondition block, and finish the epilog. Before continuing to explain the trick, you should see that there is another problem: do not call POSTCONDITION block before the end of the function. As it is written just before the body of the function, we have to skip it. The algorithm to execute the function becomes:
So the first step is to tag the poscondition block. It is very easy because
it is enclosed by 2 macros: We also need to know where the start of the body of the function is. The
answer is ... after the "Design by Contract" block ... enclosed by
The pseudo macros become something like : define DBC_BEGIN // declare start of body label. define DBC_END __dbc_body : define DBC_PRE_BEGIN define DBC_PRE_END define DBC_POST_BEGIN if(not in epilog) __asm je __dbc_body define DBC_POST_END continue epilog (means jump after caller instruction in epilog ...) Using C++ local variable declaration:I often use C++ local variable declaration, or even local static variable declaration to do many things. I've plan to write something about that in another article because it is a powerful feature of C++, and I never read anything about taking benefits of that. Here, I just use the fact that when we declare a local instance of a class, the destructor will be called during the epilog of the function. So declaring an instance of a class with destructor gives you an "epilog trap", which is the destructor of the class. I also use the fact that constructors are called where the variable is declared... but it is not very relevant for this tool. I mean, it can be implemented differently I suppose. Here is the code of the class declaration used by this tool: class structDBC { public : structDBC(); // constructor ~structDBC(); // destructor // store caller address of post condition block // (means instruction address which call destructor in epilog). long dbc_postblockRETaddr; // store start address of postcondition block. long dbc_postblockaddr; }; Add our class in the macros: define DBC_POST_BEGIN structDBC autoDBC; if(!autoDBC.dbc_postblockRETaddr) goto __dbc_body; define DBC_POST_END goto autoDBC.dbc_postblockRETaddr; So post condition block is enclosed by:
The structDBC constructor and destructor implementation:Note:
| |||||||||||||||||||||
| PermaLink | Privacy | |