C++ for Scientists - Technische Universität Dresden

C++ for Scientists - Technische Universität Dresden C++ for Scientists - Technische Universität Dresden

math.tu.dresden.de
from math.tu.dresden.de More from this publisher
03.12.2012 Views

40 CHAPTER 2. C++ BASICS 2.6.1 Inline Functions Calling a function requires a fair amount of activities: • The arguments (or at least their addresses) must be copied on the stack; • The current program counter must be copied on the stack to continue the execution at this point when the function is finished; • Save registers to allow the function using them; • Jump to the code of the function; • Execute the function; • Clean the arguments from the stack; • Copy the result on the stack; • Jump back to the calling code; • Store back registers. What happens exactly depends on the hardware. The good news is that the function call overhead is dramatically lower than in the past. Furthermore, the compiler can optimize out those activities not needed in a specific call. Nonetheless, for small functions, like the square above, the effort for calling the function is still significantly higher than what the function actually does. C programmers avoid the function-call overhead by macros. Macros create so many problems in the software development that they must only be used when there is absolutely no alternative whatsoever. Bjarne Stroustrup says “Almost every macro demonstrates a flaw in the programming language, in the program, or in the programmer.” We like to add a flaw “in the compiler optimization”. 16 Fortunately, we have an excellent alternative to macros: inline functions. The programmer just adds the keyword inline to the function definition: inline double square(double x) { return x ∗ x; } and all the overhead of the function call vanishes into thin air. An excessive use of inline can have a negative effect on performance. When many large functions are inlined then the binary executable becomes very large. The consequence is that a lot of time is spend loading the binary from memory and lots of cache memory is wasted for it as well. This decreases the memory bandwidth and cache available for data, causing more slow down than what is saved on function calls. 16 Advanced: Compilers are today really smart in eliminating unused code. However, we experienced that arguments of inline functions might be constructed although they are not used. This are usually only few machine instructions. But when this happens extremely frequently as in an index range check that should disappear in release mode, it can ruin the overall performance. We hope that further compiler improvement can rescue us from this kind of macro usage.

2.6. FUNCTIONS 41 It should be mentioned here that the inline keyword is not mandatory. The compiler can decide against inlining for the reasons given in the previous paragraph. On the other hand, the compiler is free to inline functions without the inline keyword. For obvious reasons, the definition of an inline function must be visible in every compile unit where it is called. In contrast to other functions, it cannot be compiled separately. Conversely, a non-inline function cannot be visible in multiple compile units because it collides when the compiled parts are ‘linked’ together. Thus, there are two ways to avoid such collisions: assuring that the function definition is only present in one compile unit or declaring the function as inline. 2.6.2 Function Arguments If we pass an argument to a function it creates by default a copy. For instance, the following would not work (as expected): void increment(int x) { x++; } int main() { int i= 4; increment(i); cout ≪ ”i is ” ≪ i ≪ ’\n’; } The output would be 4. The operation x++ in the second line only increments a local copy but not the original value. This kind of argument transfer is called ‘call-by-value’ or ‘pass-by-value’. To modify the value itself we have to ‘pass-by-reference’ the variable: void increment(int& x) { x++; } Now the variable itself is increment and the output will be 5 as expected. We will discuss references more detailed in § 2.10.2. Temporary variables — like the result of an operation — cannot be passed by reference: increment(i + 9); // error We could not compute (i + 9)++ anyway. In order to call such a function with some temporary value one needs to store it first in a variable and pass this variable to the function. Larger data structures like vectors and matrices are almost always passed by reference for avoiding expensive copy operations: double two norm(vector& v) { ... } An operation like a norm should not change its argument. But passing the vector by reference bears the risk of accidentally overwriting it.

2.6. FUNCTIONS 41<br />

It should be mentioned here that the inline keyword is not mandatory. The compiler can decide<br />

against inlining <strong>for</strong> the reasons given in the previous paragraph. On the other hand, the compiler<br />

is free to inline functions without the inline keyword.<br />

For obvious reasons, the definition of an inline function must be visible in every compile unit<br />

where it is called. In contrast to other functions, it cannot be compiled separately. Conversely,<br />

a non-inline function cannot be visible in multiple compile units because it collides when the<br />

compiled parts are ‘linked’ together. Thus, there are two ways to avoid such collisions: assuring<br />

that the function definition is only present in one compile unit or declaring the function as<br />

inline.<br />

2.6.2 Function Arguments<br />

If we pass an argument to a function it creates by default a copy. For instance, the following<br />

would not work (as expected):<br />

void increment(int x)<br />

{<br />

x++;<br />

}<br />

int main()<br />

{<br />

int i= 4;<br />

increment(i);<br />

cout ≪ ”i is ” ≪ i ≪ ’\n’;<br />

}<br />

The output would be 4. The operation x++ in the second line only increments a local copy but<br />

not the original value. This kind of argument transfer is called ‘call-by-value’ or ‘pass-by-value’.<br />

To modify the value itself we have to ‘pass-by-reference’ the variable:<br />

void increment(int& x)<br />

{<br />

x++;<br />

}<br />

Now the variable itself is increment and the output will be 5 as expected. We will discuss<br />

references more detailed in § 2.10.2.<br />

Temporary variables — like the result of an operation — cannot be passed by reference:<br />

increment(i + 9); // error<br />

We could not compute (i + 9)++ anyway. In order to call such a function with some temporary<br />

value one needs to store it first in a variable and pass this variable to the function.<br />

Larger data structures like vectors and matrices are almost always passed by reference <strong>for</strong><br />

avoiding expensive copy operations:<br />

double two norm(vector& v) { ... }<br />

An operation like a norm should not change its argument. But passing the vector by reference<br />

bears the risk of accidentally overwriting it.

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!