Wild: assertion

zhaozj2021-02-16  99

Wide : assertion

Andrei alexandrescu

This section "Wild " discussion assertions (Assertions), a very powerful tool in your weapon library. Based on Assert, we build a stronger tool to help you build better procedures. We can see soon, the assertions are not only a simple tool / macro / function. This is a lifestyle, a deep gap divides the programmers into two categories: understanding, and does not understand the power of the discovery.

Assert (COOL);

So, what is asserted? Why do you pay attention to assertions? When do you need to use asserties and are equally important, when you don't want to use it.

My opinions are assertions (for example, using standard Assert macros) is the most simple and powerful tool to ensure the correctness of the program. The power of assertions is usually underestimated, and at least in the project I have participated. It can be said that the success of a project is how developers use assertions in the code.

An important feature of assertions is that it generates code only in the debug mode (when the NDEBUG macro is not defined), it is "free" in a sense. This allows you to test the fact that it looks very clearly. It has been tested for these efficient waste, but there is no waste in the publishing mode, so you will not feel uncomfortable when you insert a lot of assertions.

The philosopher said that the fact is better than the imaginary, and the fact is more complicated than our minds. This is also applicable in software development. There is countless example, the assertion of "absolutely impossible trigger" is triggered. History once again repeat: "This value is of course positive! That pointer is not empty! This array? I dare to fight without quarter. Why do you have to check again?" If you are a newbie, you think you write some Assert is a waste of your time, but their trigger can save your life. This is because software development is complicated, and any situation in the program being modified is possible. As a result, you think "obviously true" is actually true.

An assertion is the more "stupid", the more information you can bring to you when it is triggered - and it is also worth it. This is because the information in the event decreases with the increase in the incidence of the incident in accordance with the theory of information. The more impossible to trigger a Assert, the more information that is given to you when it triggers. For example, when you debug some code without assertion, you may first check the more obvious possible cause, you will only be later (late, maybe "to eliminate all the midnights after exclude all possible") Conditions to those "impossible".

So when using Assert, and when using the true point of the wrong runtime check? Anyway, Assert is used to point out the error, but there are other methods to point out errors. How do you decide what error reporting method?

There is an effective test method to distinguish which occasions you need to use Assert, when you need to use a real error check: You use an error check for possible conditions, even if these conditions are unlikely. You only use Assert on what you believe in any situation under any circumstances. A failure asserts always indicate an error in a design or programmer - not a user error.

The case of assertion is almost always detected at compile time. But just theoretical. Verify that certain things are actually inventory, such as unacceptable compilation time, lack of source code, and more. ,

On the other hand, you will not use the assertion to check the function returned to the value. You don't use Assert to make sure Malloc works normally [5], create a workmail window, or start a thread. But you can use Assert to ensure that the API works as written in the document. For example, if a document for an API function says it only returns a positive value, but because of some reason you feel that it may have problems, you need to write an Assert. Realize assertion

The standard provided assert has a very simple implementation, similar to the following:

// From the standard included file cart or askERT.H

#ifdef ndebug

Void __ASSERT (const char *, const char *, int);

#define assert (e) (void) 0: __ASSERT (#E, __FILE__, __LINE__))

#ELSE

#define assert (unused) ((void) 0)

#ENDIF

__ASSERT Auxiliary Function Displays an error message in the standard error output stream and interrupts the program by calling Abort (). Various different implementations may exist, such as Microsoft Visual C displays a dialog window to allow you to have the opportunity to enter the debugger to view the source code. You still can't ignore the assertion and continue to perform the original action - will still call Abort () when you continue to operate in the debugger - you are like Russian earples, in the European Di-mind I saw her before bringing back to Plim. But there is a way to overcome this situation - for Microsoft Visual C users rather than Russian earples [6])

Use the Abort () termination program too rude. In many cases, you may want to ignore a special assertion because you think it is harmless. Moreover, some operating systems and debuggers allow you to enter your source code to track the problems, and in this case you don't need Abort (). Instead, you need to have freedom to continue to track the program code.

This is also why it usually recommends that you write your own assertion mechanism. Before doing this, you have to view your compiler document to see how to enter the debugger. For example, the magic spell of Microsoft Visual C running on the x86 processor is __asm ​​{INT 3}. We can use a small macro to express interruption points:

#define break_here __ASM {INT 3}

Now we can implement the assertion mechanism:

Inline void assert (const char * e, const char * file, int line)

{

Switch (Askuser (E, File, Line))

{

Case Giveup:

Abort ();

Case Debug:

Break_here;

Break;

Case ignore:

// Nothing

Break;

}

}

#define assert (e) (void) 0: assert (#e, __file__, __line__))

You must also define the AskUser function, which uses some I / O to ask what kind of action needs to be taken by the user program. A better assertion mechanism will also provide selection in the program termination, commissioning or ignore the log log.

One problem with Break_here is that it is often interrupted into the debugger in the precise position called. But what you need code interrupt is where you call the Assert's position instead of askUser definition. This requires you to insert Break_here in the ASSERT macro, instead of the function inside the ASSERT macro [7].

Expression or statement

This brings us a question: until now, Assert is an expression. However, if you want an Assert to call Break_here, you need to turn Assert into a statement. This is because BREAK_HERE may be a statement in your system. Expression macros is always more flexible, because you can use expressions in more cases, and you can convert expressions into statements by following a semicolon behind. However, the statement macro makes you define the macro (more careful).

This is the case after an Assert is converted into a statement:

#define assert (e) DO /

IF (! (e)) Switch (askUSER (#E, __File__, __line __)) /

{/

Case Giveup: /

Abort (); /

Case Debug: /

BREAK_HERE; /

Break; /

} /

WHILE (FALSE)

The surface useless "do / while" structure is a syntax skill, its purpose is to ask the programmer to follow a semicolon behind Assert, which is not confusing.

In release mode, you can define Assert:

#define assert (unused) do {} while (false)

Although it doesn't do anything, it is very clever.

Assertion and accident

An accident often needs to be thrown when the assertion fails. First, you can also test your code well in an accident, so that it is more meaningful in the "Beta" version using assertions. In this case, you need to check the design problem, but your user does not interrupt the selection of the debugger. In this case, it will be more natural to throw an accident.

From a syntax perspective, we hope to use Assert:

Assert (a! = B); //

AskERT (! Santa.bag.empty (),

"Looks Like Santa's Bag Is Empty - And It Surely Shouldn't!");

Ideally, use the same name twice for Assert, which will not contaminate the macro namespace that has been stuffed. The second version will throw an accident containing the file name and the number of rows, plus some useful programmers custom error messages.

We have two questions when redefining Assert. One is Assert must support template syntax and ordinary non-template syntax. Another problem is that Assert must support one or two parameters. If you write a macro, you must know the ability of these issues that are outside the macro [8].

But why must you use a macro to implement Assert? We do this because you must do nothing in NDEBUG mode. As mentioned above, the programmer is very important to think of Assert as a no-price debugging tool. Under the current compiler technology, even if there is active inline, only the macro can guarantee that an expression will not be executed, just like it is not included and meaningless.

An idea is to let Assert become a macro that is expanded to function, so we can use template parameters and overload:

Void assert (BOOL Expression);

Template void assert (bool expression, const char * message = ");

#define assert assert

Now Assert (a! = B) and assert (a! = B) are working very well. However, things are often not as simple as they look. Now we have lost three important information: the text expression form of the detected expression, the current file name, and the current line number. Some version of the information may also be useful, this may be used to use three standard predefined macros, named __date__, __time__, and __timestamp__. Non-standard but increasingly popular extension macros __function__ and __pretty_function__ will also be useful. But how do we let Assert entrust a function? We need to embed some code before calling the function, which will execute after the function is called, and an interesting solution is to define a class in the ASSERT macro:

#define assert /

Struct local {/

Local (Const Assert & Info) /

{/

IF (! info.handle (__ file__, __line __)) /

BREAK_HERE; /

} /

| Localasserter = askERTER :: Make

Yep? What does these mean? In fact, there is nothing big, but I must say some details within this code need to pay attention. Thus, these codes depends on the definition of Asserter definitions, and create a local structure called, grace, local. Now you write:

Assert (a! = B);

Code expansion is:

Struct local {

Local (Const Asserter & Info)

{

IF (! info.handle (__ file__, __line__))

Break_here;

}

} localasserter = askERTER :: Make (a! = b);

If you are like me, you will be excited about the syntax and grammar skills here: We put in the macro in the macro, in the macro, put other code in Hongli . If you are excited now, that is so good, you may be more exciting in the later sections, if you don't, who knows, maybe you will be exciting.

Now you need to do is to define an Asserter class:

Class Asserter

{

Ptotace:

Const bool holds_;

PUBLIC:

Asserter (BOOL HOLDS): Holds_ (Holds)

{

}

Virtual Bool Handle (Const Char * File, Int Line) Const

{

IF (Holds_) Return True;

Switch (askuser (file, line))

{

Case Giveup:

Abort ();

Case ignore:

Return True;

}

Return False;

}

Static Asserter make (Bool Flag)

{

Return Asserter (FLAG);

}

}

This is like this: When Assert (a! = B) is called, call Asserter :: Make (a! = B) to create and initialize the type of Local, which is incorporated into the Boolean condition to the configuration function of Asserter and Incoming this Asserter object to the Local constructor.

The constructor of LOCAL evaluates the asserter :: hand__, __line__. If returned is true, local has taken into account the problem that the problem is handled and nothing. Otherwise, Local interrupts into your debugger. this is all! Have a vulnerability? are not there?

The alert reader may have noticed a fatal disadvantage in Assert it. Consider the following statement:

Assert (a! = B);

Assert (c! = D);

Indeed, when the macro is unfold, you will see some compile time error on repeating symbols. In fact, local and localasserter are defined twice in the same scope! Ouch! How to do?

I tried two solutions. One is trouble, complex and cannot be available on all compilers. The other is simple, beautiful, and can run without problems on all compilers.

It is not difficult to do a choice, but in order to enrich the knowledge, it is worth talking.

The bad solution is to rely on __line__ and some complex preprocessing skills to generate a unique identifier for each line of the program. This I have never been 100%. I will not bother you with details. This skill in Microsoft Visual C is not available, and it seems that it will not work in the future. Based on this, they define another macro __counter__, through it you can use this hateful skill.

Even if you pay all these efforts, when you call Assert twice in the same line, the technology that produces a unique identifier will fail:

AskERT (a! = B); assert (c! = D); // error! Identifier is defined!

Ok, until it.

A good solution is ... just here. I won't give you anything. Collect it into your macro skills

#define assert /

IF (false); Else Struct Local /

{/

Local (Const Asserter & Info) /

{/

IF (! info.handle (__ file__, __line __)) /

BREAK_HERE; /

} /

} localasserter = askERTER :: Make

It's now a template now.

(Please note: Please keep tie from this moment.)

Ok, how to deal with the promised template architecture now:

AskERT (! Santa.bag.empty (),

"Looks Like Santa's Bag Is Empty - And It Surely Shouldn't!");

no problem. All we have to do is inheriting a parameterization class from Asserter, as follows:

Template

Class assertEREX: Public Asserter

{

Const char * const msg_;

PUBLIC:

AssertEREX (Bool Holds, Const Char * MSG)

: Asserter (Holds), MSG_ (MSG)

{

}

Virtual Bool Handle (Const Char * File, Int Line) Const

{

Const action action = askUSER (File, Line, MSG_);

IF (action == throwup) Throw e (MSG_);

Return Asserter :: Handle (file, line);

}

}

Such AssertEREX :: Handle requires the user to make a decision, or throw an accident or make the asserter :: handle to process. Next, we add a template function Make in Asserter:

Class Asserter

{

…GR…

Template static assertEREX make (Bool Flag, Const Char * MSG)

{

Return AssertEREX (FLAG. MSG);

}

}

Dachel - now have two versions of Asserter :: Maker. One is what we just said correspond to Assert (a! = B, "Something Weird IS Going ON, Watson").

Ok, everything is very beautiful and ... Hey, and so on. Didn't see a dynamic allocation, how is the Handle Virtual? Asserter is a polymorphism or a pure value semantic?

What a complicated detail! Let's review what process is created by LOCAL. Local constructor accepts a const asserter reference, and the template function asserter :: Make returns a value of an object from Asserter inherited. This object is binding directly to the reference to the constructor of LOCAL without being cut. In the above-described constructor, referenced behavior is completely polymorphic, so there is no bug.

The cows who read Petru Marginean and I wrote about ScopeGuard may notice that here and it uses similar techniques to complete "hidden polymorphism" without polymorphism.

Finally, how to define Assert in Publishing Mode? There are many answers. look down:

#define assert /

IF (true); Else Struct Local /

{/

Local (Const Asserter & Info) /

{/

IF (! info.handle (__ file__, __line __)) /

BREAK_HERE; /

} /

} /

} Localasserter = askERTER :: Make

All code is in, but it will be skipped by the IF (TRUE). The current compiler can easily eliminate the redundant code in the unused ELSE clause.

Additional features

Experience has shown that there are two functions to be very useful.

Sometimes you will find an assertion is benign, and in order to continue to perform the remaining procedures, you want to ban this assertion. If you save a static Boolean variable in the constructor, you can easily achieve your goals:

#define assert /

IF (false); Else Struct Local /

{/

Local (Const Asserter & Info) /

{/

Static bool ignore;

IF (! ignore&! info.handle (__ file__, __line__, ignore) /

BREAK_HERE; /

} /

| Localasserter = askERTER :: Make

If the user chooses "In order to continue to perform ignore the Bank," Asserter :: handle will set the Ignore to True, and will not be annoyed again - very useful.

Another cool character is to ignore all assertions during a program execution. This can be easily achieved by saving a static Boolean member in the Asserter class and operates it.

We can further support more useful features, such as incoming more macros (__date__, __timestamp__, __function__, etc.), but please pay attention to the points and not confused.

You can see the source code, the code contains an Assert's function version, which is based on Microsoft Visual C Everett Beta. in conclusion

As an assertion is very important, it is also very simple. Unfortunately some people think that it is not important because it is too simple. Adhere to the use of assertions in your code, it is a vigilant and reliable guards, protect you (and your program) will not fall into chaos.

This article describes an assertion mechanism that supports direct interrupt into the debugger (decisive to your compiler and OS whether it supports this feature), overload, unexpectedly thrown, unlocking or completely ignored in one process.

Assert focuses all the work in the macro skills, template code, and polymorphisms to get a useful assertion gadget.

Thank you

Many of the ideas in front of Assert implementation directly from Jason Shirk at the ACCU conference in April (with "The Visual C 7.1 Compiler IS SO Cool!").

Reference book and notes

[1] Andrei Alexandrescu. "Generic : Move Constructionors," C / C Uses Journal C Experts Forum, February 2003, .

[2]

[3] USENET POSTING BY RANI Sharoni, .

[4] Standard C Defect Report, .

[5] Shun, I have recently allocated memory failure on my 256M desktop computer, and is in normal use. Although many applications have been launched, it is normal - this confirms a fact: memory consumption will happen in the real world.

[6] If you right-click the editor while debugging, select "Set Next Statement", you can use "Go ON" to override Assert, thus continues.

[7] The problem is that inline Assert is useless: Most debuggers can enter the intraline function.

[8] Need to note that GNU C (GCC) has non-standard extensions, allowing macros to have variable number variables.

[9] Andrei Alexandrescu and Petru Marginean "Generic : Simplify Your Exception-Safe Code,". C / C Users Journal C Experts Forum, December 1999, .

Code download

Andrei Alexandrescu is a doctor in Washington, Seattle, is also the author of the book "Modern C Design" book. You can contact him via www.moderncppdesign.com. Andrei is also a C seminar (). A superior lecturer. Welcome to my column: http://www.9cbs.net/develop/author/netauthor/merced1976/

转载请注明原文地址:https://www.9cbs.com/read-10701.html

New Post(0)