Inline-inline usage

Source: Internet
Author: User
Tags naming convention
Use inline wisely

Inline Function ------ a wonderful idea! They look like functions and operate like functions, which are much better than macro (macro) (see clause 1) and do not have to bear the overhead of function calls when using them. Can you have more requirements on them?

However, you actually get more from them than you think, because avoiding the overhead of function calls is only a problem. To process those with no function callsCode, Compiler OptimizationProgramIt has been specially designed. Therefore, when a function is inline, the compiler can optimize the function body in a specific environment. Such optimization is impossible for "normal" function calls.

We should not go too far. Like in real life, there has never been a free lunch, and inline functions are no exception. The basic idea of inline functions is to replace each function call with its code body. It can be seen from statistics experts that this method may increase the size of the entire target code. In a computer with limited memory, excessive use of the program generated by the internal connection will lead to insufficient available space due to a large volume. Even if you can use virtual memory, the Code expansion caused by inline may lead to unreasonable page scheduling behavior (system bumps), which will make your program run as slowly as crawling. (Of course, it also provides an excellent way for the disk controller to exercise) Too much inline will also reduce the hit rate of instruction high-speed cache, thus reducing the speed of instruction obtaining, because the sub-Master Access command is certainly slower than the sub-cache.

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

One thing to keep in mind is that the inline command is like register, which is just a prompt to the compiler, not a command. That is to say, as long as the compiler is willing, it can ignore your instructions at will. In fact, the compiler often does this. For example, most compilers reject inline "complex" functions (such as cyclic and recursive functions); and, even the simplest virtual function call, the compiler's inline handler cannot help it. (This is not surprising. Virtual means "Wait until the runtime to decide which function to call", inline means "Replace the called place with the called function during compilation ", if the compiler does not even know which function will be called, it cannot be blamed for rejecting inline calls ). The above can be attributed to: whether a given inline function is actually inline depends on the specific implementation of the compiler used. Fortunately, most compilers can set the diagnostic level. When a function declared as inline is not actually inline, the compiler will issue a warning message for you (see clause 48 ).

Assume that a function f is written and declared as inline. If the compiler decides not to inline it for any reason, what will happen? The most obvious answer is to treat F as a non-inline function: when generating code for F, just like it is a common "external" function, the call to F is also like the call to a common function.

In theory, this should happen, but the theory and reality often deviate. This is the case now. This solution is ideal for solving the problem of "outlined inline", but it takes a relatively late time to add it to the C ++ standard. Earlier C ++ specifications (for example, arm ------ refer to clause 50) Tell the compiler manufacturer to implement a different behavior, in addition, this old behavior is still common in the current compiler, so you must understand what it is.

Just think about it and you can remember that the definitions of inline functions are actually put in the header file. This allows multiple units (source files) to be compiled to contain the same header file and share the benefits of inline functions defined in the header file. The following is an example. The source file name in the example ends with the regular ". cpp". This should be the most common naming convention in the C ++ world:

// File example. h
Inline void F () {...} // F Definition

...

// File source1.cpp
# Include "example. H" // contains the definition of F

... // Contains the call to F

// File source2.cpp
# Include "example. H" // also contains the definition of F

... // ALSO CALLS F
Assuming that the old "inline by external" rule is used and F is not inline, the generated target file will contain a function called F when source1.cpp is compiled, just as F is not declared as inline. Similarly, when source2.cpp is compiled, the generated target file will also contain a function called F. When you want to link two target files together, the compiler will report an error because there are two F definitions in the program.

To prevent this problem, the old rule stipulates that for inline functions that are not inline, the compiler treats them as declared as static, that is, limiting them to the currently compiled files. As shown in the preceding example, when the compiler that follows the old rule processes F in source1.cpp, it is like F is static in source1.cpp; when processing F in source2.cpp, it is also treated as static in source2.cpp. This policy eliminates Link errors, but brings about overhead: Each compiled unit containing the definition of F (and calling f) contains its own static copy of F. If f defines a local static variable, each copy of F will have a copy of this local variable, which will surprise programmers, because in general, the "static" in the function means "only one copy ".

The specific implementation will also be surprising. Regardless of the new rule or old rule, if the inline function is not inline, each place where the inline function is called must bear the overhead of the function call; if it is an old rule, it must endure the increase in the size of the Code, because each compiled unit that contains (or calls) F has a copy of the Code of F and its static variables! (Worse, copying each F and copying each F's static variables are usually on different virtual memory pages, therefore, calling different copies of F may cause multiple page errors .)

What else! Sometimes, a poor compiler ready to serve you at any time has to generate a function body for this inline function even if you really want to inline a function. In particular, if the program needs to obtain the address of an inline function, the compiler must generate a function body for this. How can the compiler generate a pointer to a non-existent function?

Inline void F () {...} // same as above
Void (* PF) () = F; // pf points to F
Int main ()
{
F (); // inline call to F
PF (); // non-inline call to F through pf
...
}
This situation seems ridiculous: F calls are inline, but under the old rule, each compilation unit that obtains the f address still generates a static copy of this function. (Under the new rule, no matter how many units are involved, only one external copy of F will be generated)

Even if you never use function pointers, this kind of "inline functions that are not inline" will find your door, because not only programmers use function pointers, but sometimes compilers do the same. In particular, the compiler sometimes generates external copies of constructor and destructor, so that the pointers of those functions can be obtained, it is convenient to construct and analyze the object array of the class (see the M8 clause ).

In fact, just one test can prove that constructor and destructor are often not suitable for inline, or even worse than the test results. For example, see the following constructor of this class derived:

class base {
Public:
...
PRIVATE:
string bm1, bm2; // base class member 1 and 2
};
class derived: public base {
public:
derived () {}// the derived constructor is empty.
... // ------ but is it really empty?
PRIVATE:
string DM1, dm2, dm3; // member 1-3 of the derived class
};
This constructor looks like a good inline material because it has no code. But appearance often deceives people! Because it does not have code, it does not mean that it really does not contain code. In fact, it contains a lot of code.

C ++ provides multiple rules on the events that occur when objects are created and destroyed. Article 5 and M8 describe how dynamically created objects are automatically initialized by their constructors when new is used, and how the Destructor is called when Delete is used. Clause 13 states that when an object is created, each base class of the object and each data member of the object will be automatically created. When the object is destroyed, the opposite process (that is, the analysis structure) is automatically executed ). These terms tell you what must happen in C ++, but do not specify "how" to happen. "How to happen" depends on the implementer of the compiler, but it should be clear that these events did not happen by themselves. There must be some code in the program to make them happen, especially the code written by the compiler implementers and inserted into your program during compilation. Sometimes, they are hidden in your constructor and destructor. Therefore, for the derived constructor called null above, some compilers will generate the following code for it:

// Possible implementation of a derived Constructor
Derived: erived ()
{
// Allocate heap memory to an object created on the stack;
// For operator New introduction, refer to Clause 8.
If (the object is on the stack)
This =: perator new (sizeof (derived ));
Base: Base (); // initialize the base part.
Dm1.string (); // construct DM1
Dm2.string (); // construct DM2
Dm3.string (); // construct dm3
}
Don't expect the above Code to be compiled because it is invalid in C ++. First, the constructor cannot know whether the object is on the heap. (For more information about how to reliably determine whether an object is on the stack, see section M27.) In addition, this assignment is invalid. Also, you cannot access the constructor by calling the function. However, the compiler does not have these restrictions when working, and it can do whatever it wants. But the legitimacy of the code is not the topic to be discussed now. The main point of the problem is that the Code that calls operator new (if needed), the code that constructs the base class, and the code that constructs data members will be unknowingly added to your constructor, this increases the size of the constructor so that the constructor is no longer suitable for inline operations. Of course, the same analysis applies to the base constructor. If the base constructor is inline, all the code added to it will also be added to the derived Constructor (the derived constructor will call the base constructor ). If the string constructor happens to be inline, the derived constructor will get five copies of its code, each copy corresponds to one of the five strings in the derived object (Two Inherited and three declared ). Now you should understand that the constructor of inline derived is not easy to decide! Of course, similar situations also apply to the derived destructor. In any case, you must be clear that all objects initialized by the derived constructor will be completely destroyed. Previously, the destroyed object may occupy the dynamically allocated memory, so the memory needs to be released.

The library designer must estimate in advance the negative effects of declaring inline functions. It is impossible to upgrade binary code for inline functions in the library. In other words, if F is an inline function in the library, you will compile F's function body into your own program. If the library designer wants to modify F later, all user programs that use F must be re-compiled. This is annoying (see clause 34 ). On the contrary, if F is a non-inline function, you only need to relink the modification to F, which greatly reduces the workload compared to re-compilation; if the library containing this function is dynamically linked, the modification of the library is completely transparent to users.

Static objects in inline functions often violate intuition. Therefore, if a function contains a static object, avoid declaring it as an inline function. For more information, see M26.

In order to improve the quality of program development, the above items must be kept in mind. But in specific programming, from a purely practical point of view, there is a fact that more than one factor is important: Most debuggers will be powerless in case of inline functions.

This is nothing new. How do you set a breakpoint in a non-existent function? How to execute such a function in one step? How can we capture calls to it? Unless you are a hundred-year-old monster, or you have used a dark horse such as Chen Cang, it is impossible to do so. Fortunately, this can be used as a basis for deciding whether to declare the inline function.

In general, in actual programming, the original principle is not to inline any function, unless the function is really small and simple, as shown in the following age function:

Class person {
Public:
Int age () const {return personage ;}
...
PRIVATE:
Int personage;
...
};
Careful use of inline not only gives the debugger more opportunities to play its role, but also positions the role of inline in the correct position: it is an optimization tool used as needed. Don't forget the 80-20 law gained from countless experiences (see the M16 clause): A Program often spends 80% of its time executing 20% of the Code in the program. This is a very important law, because it reminds you that, as a programmer, a very important goal is to find out the code that can truly improve the performance of the entire program. You can choose to inline your function, or do not need to inline it, but these options only make sense on the "correct" function.

Once the important functions in the program are identified, and those functions that can improve program performance after being inline (these functions depend on the system architecture of the system ), we will not hesitate to declare it as inline. At the same time, pay attention to the problems caused by code expansion and monitor the warning information of the compiler (see clause 48) to see if any inline function is not inline by the compiler.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.