Abnormalities in C and C ++

zhaozj2021-02-16  59

1. Abnormal and standard C support on it

(Presence slight)

1.1 abnormal classification

Based on Dr. GUI suggestion, I put my first column into the "program anomalies" series. I realized that "Exception" is some unclear and context, especially C standard anomaly (C Standard Exceptions) and Microsoft's structural exception (Structured Exception Handling). Unfortunately, the term "abnormal" is too common, and there is always a standard and common programming literature at any time. Because I don't want to create a new name, I will try my best to clarify my use of "abnormal" in each part of this series.

l Part 1 summarizes the nature of the usual abnormality, and the method of processing them provided by the standard C library.

l Part 2 Overview Microsoft's extension of these standard C library methods: special macros and structural exception handling.

l Part 3 and the rest will be committed to standard C abnormal treatment systems.

(C language users may give up after Part 2, but I encourage you to insist on the end; many of my idea is equally applicable to C, although it is not very direct.)

In essence, the procedure is abnormal, and there are some few or unexpected states, usually showing a program error or requires a response that must be provided. Can't meet this response often causes the program function to weaken or die, sometimes causes the entire system to DOWN together. Unfortunately, trying to use traditional protection methods to prepare robust codes, often replace a problem (accidental crash) to another problem (more confusing design and code).

Too many programmers believe that this exchange can't arise to accidentally crash, so I choose to live in danger. After recognizing this, the C standard has increased an elegant and substantially invisible "exception system" to the language; in this way, this method is generated. As we have to see at the beginning of Part 4, this method is very successful, but it may fail in a very subtle case.

1.2 abnormal life phase

In this series, I will showcase the difference between the C and C processing exception systems in every stage of the abnormality:

l Phase 1: A software error occurred. This error may be generated in a hardware response event (e.g., 0 divided by 0) by the underlying drive or kernel mapping.

l Phase 2: The cause of the error and the nature are carried by an abnormal object. The type of this object can be simple and integrated to a complicated C class object.

l - Phase 3: Your program must detect this anomaly object: or poll its existence, or actively report it.

l Phase 4: Detecting code must determine how to handle exceptions. Typical methods are divided into three categories.

A ignore the abnormal object and expect others to handle it.

B is doing something on this object and allows others to continue to handle it.

c Get all ownership of abnormalities.

l Phase 5: Since the exception has been processed, the program usually restores and proceeds. Restore is divided into two types:

A Restore anomalies and continued from the abnormality.

b Terminate anomalies and continue to be executed from exceptions.

When an exception is terminated outside (by the running library or operating system), recovery is often impossible, and the program ends an exception.

I deliberately ignore hardware error events because they are all within the bottom platform. Instead, I assume that some software detectable errors have occurred and generated a software exception object in the first phase. 1.3 C standard library abnormal processing system

The C standard library provides several ways to handle exceptions. They are also valid in standard C , but the relevant header file name has changed: the old C standard header file mapped to the new C standard header file . (The prefix of the header file name "C" is a mission, implies that these are all C library files.)

Although based on backward compatibility, the old C header file is also reserved by C , but I suggest you use the new header file as much as possible. For most actual use, the biggest change is in the new header file, the file declared is included in the namespace STD. For example, C language is used

#include

FILE * f = fopen ("blarney.txt", "r");

Changed it in C

#include

Std :: file * f = std :: fopen ("blarney.txt", "r");

Or more

#include

Using namespace std;

FILE * f = fopen ("blarney.txt", "r");

Unfortunately, Microsoft's Visual C does not include these new headers in the namespace STD, although this is required by the C standard (Subclause D.5). Unless Visual C has been properly supported in these headers, I will have been named using vintage C style in my column.

(I like Microsoft Running Sellers to make reasonable, correctly implementing the header files of these C libraries can require maintenance and testing of two completely different underlying code, this is impossible to welcome popularity. Strengthen the work.)

1.4 unconditional termination

Second only to completely ignore an abnormality, approximately the easiest abnormality process is the program self-destruction. Sometimes, the most lazy way is actually the most correct.

Before you start laughing, you should realize that some abnormalities are so serious that it is impossible to restore reasonably. Perhaps the best example is Malloc to return NULL. If the idle heap manager does not provide available continuous space, your robustness will be severely damaged, and the possibility of recovery is embarrassing.

C library file provides two functions of two termination programs: Abort () and exit (). These two functions run in 4 and 5 of the abnormal life. They do not return to their caller and have caused the program to end. In this way, they are the last step of ending an abnormality.

Although the two functions are conceptual, their effect is different:

l Abort (): The program is endless. By default, call abort () leads to runtime diagnosis and program self-destruction. It may also not refresh the buffer, turn off the opened files and delete temporary files, depending on the specific implementation of your compiler.

l exit (): Civilized concluded procedure. In addition to closing the file and returns a status code to the running environment, exit () also calls the ATEXIT () handler you mounted.

Generally call Abort () handling catastrophic programs. Because the default behavior of Abort is an immediate termination program, you must be responsible for storing important data before calling Abort (). (When we talk about , you can make Abort () automatically call Clean Up code.) Instead, exit () performs a custom Clean UP code on the ATEXIT (). These codes are performed in the reverse sequence of their hook, you can treat them as a virtual destructure. With the necessary Clean UP code, you can safely terminate the program without leaving the tail. E.g:

#include

#include

Static void Atexit_Handler_1 (Void)

{

Printf ("within 'atexit_handler_1' / n");

}

Static void atExit_handler_2 (void)

{

Printf ("withnin 'atexit_handler_2' / n");

}

Int main (void)

{

Atexit (Atexit_Handler_1);

Atexit (Atexit_Handler_2);

Exit (exit_success);

Printf ("This Line Should Never APPEAR / N");

Return 0;

}

/ * WHEN Run Yields

WITHIN 'ATEXIT_HANDLER_2'

WITHIN 'ATEXIT_HANDLER_1'

And Returns a Chengs Code to Calling Environment.

* /

(Note, even if the program returns from main (), it is not clearly called, and the Atexit () code is still called.)

Regardless of Abort () or exit (), it will not return to its caller, and will result in the end of the program. In this sense, they all behave as the final step of termination exception.

1.5 Conditional termination

Abort () and exit () let you terminate the program unconditionally. You can also terminate the program. Its implementation system is a diagnostic tool for each programmer: assertions, defined in . The typical implementation of this macro is as follows:

#if Defined NDebug

#define assert (condition) ((void) 0)

#ELSE

#define assert (condition) /

_assert (condition), #condition, __file__, __line__)

#ENDIF

As indicated by the definition, when the macro NDebug is defined, it is not active, which suggests that it is only valid for the debug version. As a result, the assertion conditions are not obtained in the non-debug version, which will cause the same code to have wonderful differences between debugging and non-debugging versions.

/ * Debug version * /

#undef ndebug

#include

#include

Int main (void)

{

INT i = 0;

ASSERT ( i! = 0);

Printf ("I IS% D / N", I);

Return 0;

}

/ * WHEN Run Yields

I is 1

* /

Now, change from the Debug version to the Release version by defining ndebug.

/ * Release version * /

#defing ndebug

#include #include

Int main (void)

{

INT i = 0;

ASSERT ( i! = 0);

Printf ("I IS% D / N", I);

Return 0;

}

/ * WHEN Run Yields

I is 0

* /

To avoid this difference, it is necessary to ensure that the assessment of the assertion does not contain an affected side effect.

In the definition body for use only the test version, assert the assertion to the call _assert () function. I got this name, and the implementation of the runtime you used can call any internal functions it wants to call. No matter what it is called, this function usually has the following form:

Void_assert (int Test, Char const * test_image,

Char const * file, int line)

{

IF (! test)

{

Printf ("Assertion Failed:% S, File% S, line% D / N",

Test_image, file, line;

Abort ();

}

}

Therefore, failed assertions show the diagnostic conditions of the failure before calling Abort (), the source file name and the line number of the error. I'm a diagnostic agency in here, "Printf ()" is quite rough, and the implementation of the running library you can produce more feedback.

As a result, the abnormal stage 3 to 5 is processed. They are actually an Abort () with explanation information and do prerequisites, if the check failed, the program abort. Usually using assertion debugging logic errors and must never appear in the correct program.

/ * 'f' Never Called by other programs * /

Static void f (int * p)

{

Assert (p! = null);

/ * ... * /

}

Compare the logic errors and can exist during the correct program error:

/ * ... get file 'name' from user ... * /

FILE * file = fopen (name, mode);

Assert (file! = null); / * QuestionAble use * /

Such a mistake indicates an abnormal situation, but not a bug. For these periodless periods, assertions are probably not a suitable method of processing, you should use another system that will be introduced below.

1.6 non-local jump

The GOTO statement looks a more feasible solution for processing anomalies than the stimulus from Abort () and Exit (). Unfortunately, GOTO is local: it can only jump to the label inside the function, and cannot transfer control to any location of the program (of course, unless all of your code is in the main body).

To resolve this limit, the C-function library provides setJMP () and longjmp () functions, which are assigned non-local labels and goto. Header file stated that these functions and the JMP_BUF data type required for the same.

The principle is very simple:

l SetJMP (j) Set "jump" point, populate JMP_BUF object J with the correct program context. This context includes program storage location, stack, and frame pointer, other important registers and memory data. When the context of JUMP is initialized, setJMP () returns 0 value.

l The effect of calling longjmp (j, r) after LONGJMP (J, R) is a non-local GOTO or "long jump" to the context of J. (i.e., to set J MP ()). When it is called as a goal of long jump, setjmp () returns R or 1 (if R is set to 0). (Remember, setJMP () cannot return 0.) when there is two types of return values, setjmp () let you know how it is using. When set J, setJMP () is as expected; but when as a target of long jump, setjmp () is "awake" its context from the outside. You can use longjmp () to terminate anomalies, tag the corresponding exception handler with SetJMP ().

#include

#include

JMP_BUF J;

Void raise_exception (void)

{

Printf ("Exception Raised / N");

Longjmp (j, 1); / * jump to exception handler * /

Printf ("This Line Should Never APPEAR / N");

}

Int main (void)

{

IF (setjmp (j) == 0)

{

Printf ("'setjmp' is initializing 'j' / n");

Raise_exception ();

Printf ("This Line Should Never APPEAR / N");

}

Else

{

Printf ("'setjmp' Was Just Jumped INTO / N");

/ * this code is the exception handler * /

}

Return 0;

}

/ * WHEN Run Yields:

'setjmp' is initializing 'j'

Exception raised

'setjmp' WAS Just Jumped Into

* /

The function that fill JMP_BUF is not returned before calling longjmp (). Otherwise, there is a problem with the context stored in JMP_BUF:

JMP_BUF J;

Void f (void)

{

Setjmp (j);

}

Int main (void)

{

f ();

Longjmp (J, 1); / * logic error * /

Return 0;

}

So, you must handle setjmp () into a non-local jump to its location.

Longjmp () and setjmp () The union runs in the 2 and 3 phases of the abnormal life. Longjmp (J, R) generates an exception object R (an integer), and is transferred to SetJMP (J) as the return value. In fact, the setjmp () function notified the abnormality R.

1.7 signal

The C function library also provides standards (although the original) "event" processing package. This package defines a set of events and signals, as well as standard methods to trigger and process them. These signals represent an abnormal state or a uncoordinated external event; based on the topic discussed, I will only focus on the abnormal signal.

In order to use these packages, you need to include the standard header file . This header file has declated the function raise () and Signal (), data type SIG_ATOMIC_T, and the signal event macro started with SIG. The standard requires six signal macros, maybe the running library you use is added. These signals are fixed in , you cannot increase the custom signal. The signal is generated by calling Raise () and is processed by the function. The runtime system provides the default handler, but you can install your own processing functions via the Signal () function. The processing function can communicate through the SIG_ATOMIC_T type object and the outside; if the type name is shown, the operation of such an object is an atomic operation or interrupt security. When you his signal processing functions, a function address is usually provided. This function must accept a full value (signal event to be processed) and have not returned. In this way, the signal processing function is like setjmp (); the only exception information they receive is a single integer:

Void Handler (intiGnal_value);

Void f (void)

{

Signal (SIGFPE, HANDLER); / * Register Handler * /

/ * ... * /

Raise (SIGFPE); / * Invoke Handler, Passing IT 'Sigfpe' * /

}

Only one of them, you can install two special processing functions:

l Signal (SIGXXX, SIG_DFL), the default processing function for the specified signal hook system.

l Signal (SIGXXX, SIG_IGN) tells the system to ignore the specified signal.

Signal () function returns the address of the previously hidden processing function (indicating that the hook is successful), or returns SIG_ERR (indicating that the mount failed).

The processing function is called to indicate that the signal is trying to recover an exception. Of course, you can freely call Abort (), exit () or longjmp () in the processing function, effectively interpret the signal as termination exception. Interestingly, Abort () is in fact call Raise (Sigabrt) internally. Sigabrt's default handler initiates a diagnosis and termination program, of course, you can install your handle function to change this behavior. The behavior of the termination program of Abort () cannot be changed. The theoretical implementation of Abort () is as follows:

Void Abort (Void)

{

Raise (SIGABRT);

EXIT (exit_failure);

}

That is, even if your SigabRT process is returned, Abort () still stops your program.

C language standard adds some restrictions and interpretation on the behavior of signal processing functions. If you have a C language standard, I suggest you check the details of the Terms 7.7.1.1. (Unfortunately, C language and C language standards are not available in Internet.)

The stated that covers an abnormal survival period, from the resulting death. In a standard C language run library, they are closest to abnormal complete solutions.

1.8 global variable

and Generally use an exception notification system: Wake a handler when trying to inform an exception event. If you are more willing to use a polling system, the C Standard Library provides an example in . This header file defines Errno and some of its possible values. The standard requires three values: EROM, ERANGE, and EILSEQ, which apply to domain, range, and multi-byte sequential errors, your compiler may add some other, and they start with letters "E".

Errno, by setting it by the code of the runtime, the user code querying it, the two connections are connected, running on the exception life period 1 to 3: Running to generate an exception object (a simple integer), copy the value to Errno , Then rely on the user's code to poll and detect this exception. The run library is mainly used in the and function using Errno. Errno is set to 0 at the beginning of the program, and the library program will not set it to 0 again. Therefore, to detect errors, you must first set Errno to 0, then call the runtime program, check the value of errno after calling:

#include

#include

#include

Int main (void)

{

Double X, Y, Result;

/ * ... Somehow set 'x' and 'y' ... * /

Errno = 0;

Result = POW (X, Y);

IF (errno == EDOM)

Printf ("Domain Error On X / Y PAIR / N);

Else IF (errno == ERANGE)

Printf ("Range Error On Result / N);

Else

Printf ("x to the y =% d / n", (int) result);

Return 0;

}

Note: Errno does not have to be tied to an object:

INT * _ERRNO_FUNCTION ()

{

Static int real_errno = 0;

RETURN & REAL_ERRNO;

}

#define errno (* _ERRNO_FUNCTION ())

Int main (void)

{

Errno = 0;

/ * ... * /

IF (errno == EDOM)

/ * ... * /

}

You can use this skill in your own program, simulate Errno and its value. If you use C , you can of course extend this policy to the objects and functions of the class or namespace. (In fact, in C , this tip is the foundation of Singleton Pattern.)

1.9 return value and post parameters

Abnormal objects like Errno are not unlimited:

l All associated parts must be consistent, make sure the settings and check the same object.

The L-independent part may accidentally modify the object.

l If the object is not reset before the call, you may miss an exception before calling the next step.

l Macro and internal code will cover an abnormal object when you reign.

l Static object is born to be (multiple) threads.

In short, these objects are very fragile: You are too easy to use, and the compiler does not have a warning program but has unpredictable behavior. To eliminate these shortcomings, you need this object:

l Access to two correct partials - an exception, one detection exception.

l With a correct value.

l N name cannot be covered

1.10 thread security.

The function return value satisfies these requirements because they are unknown temporary variables, which can only be accessed by the caller. Calling one completion, the caller can check or copy the return value; then the original return object will disappear without being reused. It is because it is unknown, it can't be covered. (For C , I assume that only the right value function calls express, that is, can't return references. Since I define only the skill of C-compatible skills, C does not support reference, this assumption is reasonable.)

The return value appears at the stage 2 of the abnormal life. This is just a complete exception handler in the consortium that calls and the modified function:

INT f ()

{

Int error;

/ * ... * /

IF (error) / * stage 1: error occurred * /

Return -1; / * Stage 2: Generate Exception Object * /

/ * ... * /

}

Int main (void)

{

IF (f ()! = 0) / * Stage 3: Detect Exception * /

{

/ * Stage 4: Handle Exception * /

}

/ * Stage 5: Recover * /

}

The return value is the abnormal propagation method like the C standard library. Look at the example below:

IF ((p = malloc (n)) == null)

/ * ... * /

IF ((c = getchar ()) == EOF)

/ * ... * /

IF ((ticks = clock ()) <0)

/ * ... * /

Note that typical C habits: Receive return values ​​and detection exceptions in the same statement. Such compression expressions are overloaded with a channel (return value object) to carry two different meanings: legitimate data values ​​and exception values. The code must be in two ways to explain this channel until it is correct.

This function returns the value of the value is common in many languages, especially the language-independent of Microsoft Developed Component Object Model (COM). The COM method prompts an abnormality by returning a type of objects for HRESULT (Special Arrange 32-bit unsigned values). Unlike the examples of the example, the return value of the COM is only carried by state and exception information; the return information is performed by the pointer in the parameter list.

Recycling pointer and C reference parameters are deformations of the function return value, but some obvious differences:

l You can ignore and discard the return value. The return parameters are bound to the corresponding acts, so it is impossible to completely ignore them. Compared to the return value, the parameters form a tight coupling between functions and their calvity.

l The value of any number can be returned by the return parameter, and a value can only be returned by the return value. So the return parameter provides multiple return values.

l The return value is a temporary object: they do not exist before the call is called, and the end of the call is disappeared. The life stage of the arguments is far longer than the function of the function.

1.11 small knot

This probably introduced the traditional support of the abnormality and standard C on it. The second part, I will study Microsoft's extension of standard C methods: unique exception handling macros, structural exception handling or SEH. I will summarize all the limitations of all C compatible methods (including SEH) and pull the C abnormality in the third part.

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

New Post(0)