GURU Of The Week Terms 12: Control Flow

zhaozj2021-02-08  350

GotW # 12 Control Flow

Author: Herb Sutter

Translation: Kingofark

[Declaration]: This article takes the Guru of The Week column on www.gotw.ca website, and its copyright belongs to the original person. Translator Kingofark translated this article without the consent of the original person. This translation is only for self-study and reference, please read this article, do not reprint, spread this translation; people download this translation content, please delete its backup immediately after reading. Translator Kingofark is not responsible for people who violate the above two principles. This declaration.

Revision 1.0

GURU Of The Week Terms 12: Control Flow

Difficulty: 6/10

(How much do you know about the execution order of C code? Use the following questions to check your knowledge!)

[problem]

The so-called "The Devil Is In The Details.", Now you now find as many abruptness as possible from the CONTROL FLOW.

#include

#include

#include

#include

Using namespace std;

// The few sentences from other headers

Char * ITOA (int value, char * workarea, int radix);

Extern int fileidcounter;

/ / Make the type of non-variable inspection of automation

Template

Inline void aassert (t & p) {

Static int LocalFileId = fileidcounter;

IF (! p.invariant ()) {

CERR << "INVARIANT FAILED: FILE" << LocalFileId

<< "," << TypeId (p) .name ()

<< "AT" << static_cast (& p) << ENDL;

Assert (false);

}

}

Template

Class ainvariant {

PUBLIC:

AINVARIANT (T & P): p_ (p) {aassert (p_);

~ Ainvariant () {aassert (p_);}

Private:

T & P_;

}

#define ainvariant_guard ainvariant /

INVARIANTCHECKER (* this)

/ / -------------------------------------------------------------------------------------------- -

Class Array: Private Arraybase, Public Container {

Typedef array aitype;

PUBLIC:

Array (size_t startingsize = 10)

: Container (Startingsize),

Arraybase (Container :: gettype ()),

USED_ (0),

SIZE_ (Startingsize), Buffer_ (New Char [Size_])

{

Ainvariant_guard;

}

Void Resize (size_t news {

Ainvariant_guard;

Char * oldbuffer = buffer_;

Buffer_ = new char [newsize];

MEMSET (Buffer_, '', Newsize);

Copy (Oldbuffer, Oldbuffer min (size_, newsize),

Buffer_);

Oldbuffer;

SIZE_ = Newsize;

}

Strintsizes () {

Ainvariant_guard;

Char buf [30];

Return string ("size =") ITOA (Size_, BUF, 10)

", USED =" ITOA (USED_, buf, 10);

}

Bool invariant () {

IF (USED_> 0.9 * size_) resize (2 * size_);

Return used_ <= size_;

}

Private:

Char * buffer_;

SIZE_T USED_, SIZE_;

}

INT F (int & x, int y = x) {return x = y;}

INT G (INT & X) {RETURN X / = 2;}

INT main (int, char * []) ​​{

INT i = 42;

COUT << "f (" << i << ") =" << f (i) << ","

<< "g (" << i << ") =" << g (i) << Endl;

Array A (20);

Cout << a.printsizes () << Endl;

}

[answer]

"Lions and Tigers and Bears, Oh my!" - dorothy]

["My God, the lion, the tiger is still a dog!" Tao Le was called. ]

#include

#include

#include

#include

Using namespace std;

// The few sentences from other headers

Char * ITOA (int value, char * workarea, int radix);

Extern int fileidcounter;

The emergence of global variables should have already caused our alert to make us pay special attention to those attempts to use its code before it is initialized. The initialization sequence of those global variables (including the static variables including the class "between the translation units is not defined.

/ / Make the type of non-variable inspection of automation

Template

Inline void aassert (t & p) {

Static int LocalFileId = fileidcounter;

Ah, there is a problem here. If the compiler happens to initialize FileIDCounter before initializing any AASSERT :: LocalFileID, it is still good. Otherwise, the value here will be content in the memory area occupied by FileIdCounter before it is initialized. IF (! p.invariant ()) {

CERR << "INVARIANT FAILED: FILE" << LocalFileId

<< "," << TypeId (p) .name ()

<< "AT" << static_cast (& p) << ENDL;

Assert (false);

}

}

Template

Class ainvariant {

PUBLIC:

AINVARIANT (T & P): p_ (p) {aassert (p_);

~ Ainvariant () {aassert (p_);}

Private:

T & P_;

}

#define ainvariant_guard ainvariant /

INVARIANTCHECKER (* this)

A function using these auxiliary is a very interesting idea, which allows a class to automatically check before and after the function call. As long as you simply come to a AITYPE TYPEDEF, then "ainvariant_guard;" as the first statement of the member function. In essence, this is not entirely bad.

However, in the following code, this approach is unfortunately not interesting. The main reason is that Ainvariant hides the call to assert (), and when the program is established (Build) in Non-Debug mode, the compiler will automatically delete Assert (). Programmers like writing the following code are likely to not recognize this dependency of the build mode and the corresponding different results thereof.

/ / -------------------------------------------------------------------------------------------- -

Class Array: Private Arraybase, Public Container {

Typedef array aitype;

PUBLIC:

Array (size_t startingsize = 10)

: Container (Startingsize),

Arraybase (Container :: gettype ()),

The constructor's initialization table here has two potential errors. The first error may not be called an error, but let it stay in the code will also form a confusing eye-catching method (a red herring). We explain this error in two points:

1. If gettype () is a static member function, or a member function affects the influence of side effects (such as static use count) that is neither using the 'this' pointer (ie, any data member) is not affected by the constructor, such as a static use count). Just just have a bad coding style, it still can run correctly.

2. Otherwise, "Generally speaking, gettype () is the case of ordinary non-static member functions), we have trouble. Non-Virtual base classes are initialized from left to right in order they are declared. So here, ArrayBase will be initialized before Container. Unfortunately, this means an attempt to use a member of the Container Subobject that has not been initialized. USED_ (0),

Size_ (Startingsize),

Buffer_ (New Char [Size_])

This second error is uninoufrypicious, because the actual variable will be initialized in the order in which they are defined in class definitions:

Buffer_ (New Char [Size_])

USED_ (0),

Size_ (Startingsize),

Written like this, the error is very obvious. The call to New [] will make the buffer get a size that cannot be predicted - generally 0 or a large, depending on whether the compiler is in the calling constructor to initialize the memory area of ​​the object into empty (NULL) . In any case, the spatial size of the initialization allocation is almost impossible for Startingsize.

{

Ainvariant_guard;

}

Small problems in efficiency: Here, the invariant () function is not necessary to call twice (which is in the construction process and destructuring process of potential temporary objects). Of course, this is just a small problem.

Will not cause big trouble.

Void Resize (size_t news {

Ainvariant_guard;

Char * oldbuffer = buffer_;

Buffer_ = new char [newsize];

MEMSET (Buffer_, '', Newsize);

Copy (Oldbuffer, Oldbuffer min (size_, newsize),

Buffer_);

Oldbuffer;

SIZE_ = Newsize;

}

There is a problem with a serious control flow here. I have never seen this point (if you have pointed out, then I am really sorry), but in fact, this is my deliberate design, just want to see if anyone will point out.

Before you describe this, you can read the code again and see if you can find this problem (prompt: actually obvious).

* * * * * * * * * * * * *

Ok, the answer is: the code is not an abnormal - Exception-Safe. If a call to New [] causes an exception, then not only the current object will be in an invalid state, but the original buffer will have a memory leak, because all the pointers pointing to it are lost Can't delete it.

The problem of this function, so far, there is almost no programmer to develop an exception-safe (ExcePton-Safe) habit - even in the recent GotW Terms for exception security (Exception Safety) After a wide range of discussion!

Strintsizes () {

Ainvariant_guard;

Char buf [30];

Return string ("size =") ITOA (Size_, BUF, 10)

", USED =" ITOA (USED_, buf, 10);

}

Among them, the ITOA () prototype function uses BUF as a place for storage results (the translation: means that this time is invoked to call this function, the content set by the function is invoked last time, thereby causing potential order issues). There is also a problem with control flow in this code. We cannot expect the order in the final return statement to the expression of the expression, because the order of operation of the function parameters is not clearly defined, which is completely dependent on a particular implementation. (Also note that built-in "Operator does not exist, once you provide your own Operator version, your version will be considered a function call.) The last return statement is displayed The severity of the problem has been better displayed in the following statement (even if the call to Operator is from left to right):

Return

Operator

Operator

Operator (String ("size ="),

ITOA (SIZE_, BUF, 10)),

", use ="), // translation: Here the original ", y ="), it should be wrong.

ITOA (USED_, BUF, 10));

Here we assume that the size_ value is 10, the USED_ value is 5. If the outer Operator ()

The first parameter is first requested, then the result will be the correct "size = 10, usd = 5",

Because the first ITOA () function stores the result in the BUF in the second ITOA () function multiplexed BUF

It was read before. But if the outer Operator () of Operator () is first requested

(For example, in MSVC 4.x), then the result will be an error "size = 10,

Used = 10 ",

Because the ITOA () function of the outer layer is first requested, but the result (the translation: 即 characters '5') will be

The ITOA () function of the inner layer was destroyed again before use (the translation: BUF became '10').

Bool invariant () {

IF (USED_> 0.9 * size_) resize (2 * size_);

Return used_ <= size_;

There are two questions about the call to resize ():

1. In this case, the program is not working properly because if the condition is judged, then

Resize () will be called, which will also cause immediate () immediately again, and then the conditional judgment

It will still be true, then call resize (), which will lead to invarient () again.

Then ... you must understand what is wrong with this (the translation: this is a recursive call that cannot be terminated).

2. If the writer of AASSERT () makes an error report (Error Reporting) for efficiency considerations

The code is deleted and replaced by "Assert (p-> invarient ());" What will it?

The result will only make the code here more sad because it adds it in Assert () call.

Side-effect code. This means that the program is in two different compilation modes and Release modes.

The executable code produced will have different behaviors when executed. This is also very bad even without the problems described above, because this means that the Array object will be based on the build mode (Build Mode)

Different times and the number of different times, and make the software tester have lived from the world of hell - when

He tried to reproduce the problem encountered in a program built in Debug Mode (the translation: of course, customers

The program that is established and released in Release Mode, the characteristics of the running memory image he gets

The characteristics of the running memory image under Release Mode are different. [Summary]: Never be right

Assrt () is added to the use of side effects, and always confirm that the recursive process will definitely terminate.

}

Private:

Char * buffer_;

SIZE_T USED_, SIZE_;

}

INT F (int & x, int y = x) {return x = y;}

The second setting default parameter is not a legitimate C usage, in an ideal compiler, should be compiled (although some compilers in reality can compile them). It is said that this usage is not good or because the compiler can use any order to value the function parameter, Y may be initialized before X.

INT G (INT & X) {RETURN X / = 2;}

INT main (int, char * []) ​​{

INT i = 42;

COUT << "f (" << i << ") =" << f (i) << ","

<< "g (" << i << ") =" << g (i) << Endl;

Here is the order of the parameter evaluation. Since the order of the F (I) and G (I) is not determined (or even the designer order of the two pairs I itself), the results displayed may be erroneous. The results in the MSVC are "f (22) = 22, g (21) = 21"; this is to say that the compiler evaluates the function parameters in the order from the left.

However, you will say that this result is not wrong? Tell you, the result is true (!), The compiler has not erroneous ... Moreover, another compiler may have a completely different result, you still can't blame the compiler, because the result relies on one in C There is no operational process that is defined.

Array A (20);

Cout << a.printsizes () << Endl;

}

Maybe Peach is not completely correct in this clause ... The following sentence is approaching the truth:

["Parameters and globals and exceptions, oh my!" - Dorothy, After an intermediate C course]

["My God, the parameters, the global variables have an abnormal handling!" Tao Le was called after the intermediate C course. ]

(Finish)

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

New Post(0)