Effective C ++ 2e Item33

zhaozj2021-02-11  207

Terms 33: Inline in use

Inline function ------ Double idea! They look like a function, running like a function, better than macro (Macro), much better (see Terms 1), and do not need to assume the overhead of the function call. Can you ask them more?

However, you get more from them than you think, because the overhead of avoiding function call is just one aspect of the problem. In order to deal with the code that does not have a function call, the compiler optimizer itself has specialized designs. So when a function is inline, the compiler can perform optimization work in a particular environment. This optimization is not possible to call the "normal" function.

We still don't want to be too far. Like the world and real life, there is no free lunch, and the inline function is no exception. The basic idea of ​​the inline function is to replace each function call to its code. It can be seen if you don't need to have a statistical expert. This approach is likely to increase the volume of the entire target code. In a computer limited computer, the procedure generated excessively using inline will result in a lot of space due to too large volume. Even if you can use virtual memory, the inline code expansion may also cause unreasonable page scheduling behavior (system bumps), which will make your program run slowly like climbing. (Of course, it also provides an excellent exercise method for the disk controller :)) Excessive inline will reduce the hit rate of the instruction cache, so that the speed of the instruction is reduced because of course from the main memory instruction. Slow than from the cache.

On the other hand, if the inline function is very short, the code generated by the compiler will really be much smaller than the code generated for the function call. If this is this, the inline function will really bring smaller target code and higher cache hit rate!

One thing to keep in mind is that the inline directive is like the register, it is just a prompt to the compiler, not a command. That is to say, as long as the compiler is willing, it can freely ignore your instructions, in fact the compiler will often do this. For example, most compilers reject inline "complex" functions (eg, including cyclic and recursive functions); and even, even the simplest virtual function call, the compiler's inline processing program also loves it. (This is not surprising. Virtual means "Which function when it is called when running,", "Inline means" instead of using the called function during the compilation ", if the compiler doesn't even know which one The function will be called, of course, it is not blame it to refuse to generate inline calls). The above can be attributed to: whether a given inline function is really inlined depends on the specific implementation of the compiler used. Fortunately, most compilers can set up diagnostics, when declaring is not inlined, actually not being inline, the compiler will issue a warning message for you (see Terms 48).

Suppose a function f is written and declared as inline, if something is made, the compiler decides not to be inline it, what will happen? The most obvious answer is to process F as a non-intramid function: When generating code for f, it is like a normal "outreach" function, and the call to F is also in the case of normal function calls.

In theory, it should be happening like this, but theoretical and reality will tend to deviate, and now they are here. Because this program is really ideal for solving "Outlined Inline" (Outlined Inline), but it is relatively late in the C standard. Earlier C specification (such as ARM ---- See Terms 50) Tell the compiler manufacturer to achieve additional behavior, and this old behavior is still very common in the current compiler, so you must Understand what it is. I can remember it slightly, and the definition of the inline function is actually placed in the header file. This makes multiple units (source files) to be compiled (source files) that can contain the benefits of the inline function defined within the shared head file. One will be given, and the source file in the example is ended with regular ".cpp", which should be the most common naming habit of C world:

// File EXAMPLE.H inline void f () {...} // f definition

...

/ / File Source1.cpp #include "example.h" // contains the definition of F

... // Contains call to F

/ / File Source2.cpp #include "example.h" // also contains definitions of F ... // also call f

Suppose the old "out-inline inline" rules are now used, and it is assumed that f is not inline, then when Source1.cpp is compiled, the generated target file will contain a function called F, just like f Nothing is declared as inline. Similarly, when Source2.cpp is compiled, the resulting target file will also contain a function called F. When you want to link two target files together, the compiler will report an error due to the definition of two F in the program.

In order to prevent this problem, the old rules are stipulated that for the inline function that is not inlined, the compiler is processed as static as static, ie, so that it is limited to the currently compiled file. In the example you just saw, follow the old rules of the compiler to handle f, like f, which is static in Source1.cpp; handling it in the process of fing in Source2.cpp, It is static in Source2.cpp. This strategy eliminates the error when the link is linked, but it brings overhead: the compilation unit containing F-definition (and call f) contains its own F. Static copy. If F itself defines a local static variable, then a copy of each F. There is inevitable to make the programmer's snacks, because in general, "static" means "only one Copy ".

Specific implementation will be surprising. Regardless of the new rules or the old rules, if the inline function is not inline, each calling the inline function is still aware of the overhead of the function call; if it is an old rule, it has to endure the volume of code volume, because each contains ( Or call) The compilation unit has a copy of the code and the copy of its static variable! (Worse, the copy of each F and the copy of the static variable of each F are often in different virtual memory pages, so the two different copies of F can be called to cause multiple page errors.)

anything else! Sometimes, it is always ready to give you a function of the inline function, even if you want to be in line with a function, but you have to generate a function body for this inline function. In particular, if you need to take an inline function in the program, the compiler must generate a function body for this. How can the compiler generate a pointer to a function that does not exist? Inline void f () {...} //

Void (* pf) () = f; // PF pointing f

INT main () {f (); // Inline-in-line call

PF (); // By non-intra non-intangial adjustment of F ...}

This situation seems to be ridiculous: F is inlined, but under the old rules, each of the compiled units that take the F address still generates a static copy of this function. (Under the new rules, regardless of the compilation unit involved, only the only F is external copy)

Even if you never use function pointers, this type of "inline function" will also find your door, because not just the programmer will use the function pointer, sometimes the compiler does this. In particular, the compiler sometimes generates an external copy of the constructor and the destructor, so that the object arrays of the function can be easily constructed and destructed with the pointer of those functions (see Terms M8).

In fact, a test can be proved to prove that the constructor and the destructor are often not suitable for inline; even, the situation is not bad than the test results. For example, look at the following Derived constructor:

Class base {public: ... private: string bm1, bm2; // base class member 1 and 2}; class derived: public base {public: derived () {} // derived constructor is empty, .. // ---- But is it really empty?

PRIVATE: STRING DM1, DM2, DM3; // Derived members 1-3};

This constructor looks like an inline material because it has no code. But the appearance often deceives people! Just because it has no code, it can't mean it does not contain code. In fact, it contains considerable code.

C has many regulations on events that occur when creating and destroying. Terms 5 and M8 describe how dynamically created objects are automatically initialized when using New, and how to be called when using Delete. Terms 13 illustrates that each base class of the object and each data of the object will be created automatically when an object is created; the opposite process is automatically performed when the object is destroyed. These provisions tell you that C specifies which must happen, but there is no "how" happened. "How to happen" depends on the implementation of the compiler, but it will be clear that these incidents do not take themselves. What code must have any code that occurs, especially those that are written by the compiler, which must be hidden in a place in the compiler, which is inserted into your program, inevitably hide in a place ------ Sometimes Just hide your constructor and destructor. So, for the Derived constructor of the above known as empty, some compilers will generate equivalent to the following code:

// Possible implementation of a derived constructor Derived :: Derived () {// If you create an object on the heap, assign a stack memory for it; // Operator new introduction See Terms 8 IF (this object is on the pile) this = :: Operator new; base :: base (); // Initialization Base section

DM1.String (); // Construct DM1 DM2.String (); // Construct DM2 DM3.String (); // Construction DM3}

Don't expect the above code to be compiled because it is illegal in C . First, it cannot be known in the constructor is not on the heap. (I want to know how to reliably determine if an object is on the stack, see the Terms M27) ​​In addition, it is illegal to this. Also, it is not allowed by the function call access constructor. However, there is no such restriction that the compiler works. However, the legality of the code is not the topic now discussed. The key points of the problem are, calling the code of the Operator New, constructing the code of the base class section, constructing the code of the data member, will never add the ghost to your constructor, increasing the volume of the constructor, The constructor is no longer suitable for inline. Of course, the same analysis is also applicable to the build function of the base. If the Base constructor is inline, all the code added to it will be added to the constructor of Derived (DERIVED constructor calls Base constructor) . If the String constructor is halved, the derived constructor will get 5 copies of its code, each copy corresponds to one of the 5 string in the DeriveD object (2 inheritance, 3 own statements) . Now you should understand that the constructor of the inline deerid is not very simple! Of course, similar situations apply to Derived destructive functions, all objects initialized by Derived constructor must be completely destroyed. The object that is just destroyed may, may take up dynamically allocated memory, then these memory needs to be released.

The designer of the library must pre-estimate the negative impact of the declaration of the inner function. Because it is impossible to upgrade the inline function in the library. In other words, if F is an inline function in the library, the user compiles the function of F to its own program. If the library's designer is later modified, all user programs that use F must be recompiled. This will be very annoying (see Terms 34). Conversely, if f is a non-inline function, the modification of F requires only the user to re-link, which is more reduced to the burden than the need to recompilation; if the library containing this function is dynamic link, the program library is modified to the user. It is completely transparent.

Static objects in the inline function often exhibit a violation of intuition. So, if a static object is included in the function, you usually avoid declaring it as an inline function. Specifically, see the Terms M26.

In order to improve the quality of program development, the above items must be kept in mind. However, in a specific programming, from a pure actual point of view, there is an important factor than the rest of the factors: Most debugger meets the inner function.

This is not a new thing. What do you want, how can I set a breakpoint in a function that does not exist? How can I do such a function? How to capture the call to it? Unless you are a millennium, or if you use a trick like Daxian Chen Cang, it is impossible to do it. What is happy is that this is one of the decision-making basis for deciding that the function declaration of the function. In general, the initial principle when actually programming is not inline any function, unless the function is really small, like this Age function:

Class Person {public: int Age () const {returnent

...

PRIVATE: INT PERSONAGE;

...

}

Introduced cautiously, not only gives more opportunities for the debugger, but also position the inline role to the correct position: it is an optimization tool used as needed. Don't forget this 80-20 law from countless experience (see Terms M16): A program often spends 80% of the time to execute 20% of the code in the program. This is a very important law, because it reminds you that as a very important goal of programmers, it is to find out that these 20% can really improve the entire program performance code. You can choose your function, or you don't have to be inline, but these choices are only meaningful in the "correct" function.

Once you find out those important functions in the program, and those functions that can indeed improve program performance after inline (these functions itself depends on the architecture of the system), it should not hesitate to declare the inline. At the same time, pay attention to the problem brought by code expansion and monitor the compiler's warning message (see Terms 48) to see if there is an inline function is not inlined.

If you can use inexpensively, the inline function will be an invaluable treasure in each C programmer treasure chest. Of course, as revealed by the previous discussion, they are not as simple as what they are imagined.

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

New Post(0)