Inline and const functions (C++)

Started by
8 comments, last by iMalc 13 years, 11 months ago
I'm pretty new to programming and I haven't found the need to use 'inline' or 'const' functions (i.e. I haven't been forced to by errors :)) Searching I found the following explanation: Quote: advantages of inline-no function calls need to be generated.no stack required. disadvantage -inline functions require more space.. the whole definition is copied. by using const we insure the value of variable is not goin to change.. Well now I might have been vaguely aware there is some sort of function stack, does anyone know of a good explanation of this online somewhere? I don't really know what they mean by 'more space' more space where? and of what? memory? bundled in the exe? And for the 'const' part, what variable do they mean? Is it just there to indicate that the function is not going to change any member variables of the class? Does it prevent any changes to member variables in the case of something being coded wrong in the function? Thanks.
Advertisement
take for example the following function:
inline void foo()
{
method1();method2();method3();method4();method5();
}

so if i call foo multiple times:
foo(); foo();foo();
this will be transformed in
method1();method2();method3();method4();method5();method1();method2();method3();method4();method5();method1();method2();method3();method4();method5();

by this they mean more space , because all that method calling takes a lot of space
. By removing inline the code will rezult in only 8 method calls insted of 15: 3 for foo and 5 for method1..5

The extra space is actually in the code segment so the final .exe file will be bigger.
every time you call foo the content of foo will be copied there. So you realize that if you have a big function called many times, every call will add to the final size the size of foo.

const has no relevance , it is the came const as for any function , and it will behave the same.
The stack is the area of memory where local variables are created and, depending on the calling convention, sometimes (some of) the function arguments and possibly the return value(s) as well. It's also where the compiler shuffles the contents of the working registers when it needs more registers to work on a new problem.

Starting at the bottom, most CPUs either prefer (CISC) or require (RISC) that operations be performed on operands stored in the CPU's working registers. 32bit Intel-compatible CPUs (x86) have 8 Integer registers, and 64bit Intel-Compatible CPUs (x64) have 16, many of them are 'special purpose' meaning that certain instructions treat them specially, even if they can be used normally by other instructions. Most other popular architectures have 32 registers (PowerPC, MIPS, SPARC), or 16 (ARM, SH4) and commonly they eshew special-purpose registers altogether -- any instruction can operate on any register. Some older or smaller CPUs had very few registers (like the 6502 in the NES or Apple II), some even have only a single register (pure accumulator architectures -- obviously these are able to pull some operands from main memory directly).

Anyhow, when you call a function, the CPU needs some room to work, and that room comes in the form of these registers. But what if that register was holding something important that the function that called the current function will need after it returns? The new function can't just over-write the register, or the calling function is going to find something unexpected when it gets control of the CPU again. We need to save those values before we write over them, and we do this on the stack. The stack is just a memory buffer that we can easily get memory from. Every time a function is called, the very first thing it does is make a copy of each register it's going to use on the stack, and the very last thing a function does is copy those values back from the stack and into the registers it found them in. Now the function that called us is happy because it doesn't find anything unexpected, and the new function is happy because its got all the space it needs to work.

This is just one of the ways that the stack is used. Another way is that your local variables are created there -- Say you have a function that uses a small, local array. Its likely too big to fit in the registers all at once, and even if it did it probably needs to support pointer-based access, so it needs to live somewhere in memory, so it goes on the stack.

As I alluded to earlier, depending on the Platform ABI and calling convention, sometimes the stack will contain some (or all) of the function arguments, and sometimes it might even hold the return value(s) of the previously called function as well (they'll be just beyond where the calling function believes the stack ends, and it needs to be moved before the next function is called).

Now, to conceptualize the stack, imagine it like a stack of plates at a buffet-line. You can put a plate on the stack, or take one as you need, but you can't take one from the middle or bottom. The advantage of this model is that its *really* cheap to put thing on the top of the stack, or to take them off. A CPU stack works similarly, but the plates are values -- every time a function is called, the function puts some plates on the stack. Another function may be called which will put its plates on top of the previous plates. Just before the function returns, it takes back the plates it put there so that the function before it will find the plates it expects. Now, this all works on, essentially, an honor system which relies on each function taking the plates it put there, no more, no less. If a function takes an extra plate, or forgets one, then the previous function is going to have no idea what's going on. This is what we call stack corruption.

These are CPU stacks, but as it turns out, the idea of a stack is one of the fundamental data structures to all of computer science (In fact, there are CPUs which have only a stack and no registers at all!) so we also have the stack as a data structure (like std::stack). It works basically as described here, except that you can only do things (push, pop, peek) with the *very last* element -- in a CPU stack, the CPU can freely access all of the stack elements (plates) that it 'owns'.

Now, we can finally get to why 'inline' might reduce the amount of stack required. The reason is that, by inlining the function body directly into the caller, we can get rid of all the stack overhead of calling a new function. Sure, this might mean that the caller needs a little bit more stack space itself, but generally less than is involved in calling a separate function. Inlining may also allow the optimizer to see more of what's going on and apply more and better optimizations. The reason that inline functions might require more space is not because the function itself is bigger, but because part of it gets copied many times into all the functions that call it. On a final note, the compiler will usually keep a non-inline version of the function as well, and may choose to call that version, rather than inline it, if the compiler determines that the former will generate better code. Since modern CPUs have deep pipelines, large caches, and operate on several re-ordered instruction streams at once, its too complex of an issue to simply assume whether a function should be inlined or not at all times -- the 'inline' keyword is only a hint (and not even a strong one these days) that the compiler might want to consider inlining this function, but its free to not inline 'inline' functions, and *to* inline non-inline functions, if it so chooses.


As for 'const' member functions, you have it basically right. The 'const' keyword, as it applies to member functions, tells the compiler not to allow you to modify any member variables, but there are a couple exceptions. First, the keyword 'mutable' as applied to member variables, makes them exempt from enforcement of member function 'constness' -- in other words, 'mutable' member variables can be modified in 'const' member functions, which is useful because it allows you to define a set of member variables which constitute the 'logical constness' of the object, and a set of member variables which don't affect the 'logical constness' of the object (those that are mutable). Its pretty rare to use mutable though. I've been coding for many, many years and have only used it in real code once or twice.

Finally, 'const' in any form doesn't ever protect you from subverting the type system and gaining write access to stuff in creative (and often undefined) ways. 'const' is merely a way to ask the compiler to help stop you from modifying something in the usual ways, but it can usually be gotten around with a little effort (const_cast, reinterpret_cast). 'const', at least in C and C++, doesn't provide any sort of memory protection that would prevent this type of deliberate subversion. The only exception is that the compiler may choose to place certain types of global const data into the 'TEXT' section of the executable binary (strings commonly go here, for one example), and you'll usually cause some kind of exception if you try to write into anything in 'TEXT' -- but its important to remember that this is an artifact of where the compiler happened to put the data, not a direct consequence of 'const' itself.

throw table_exception("(? ???)? ? ???");

Actually, you can change member variables in a const function in a completely legal way.

Const is basically just a guideline to help the compiler to catch certain kinds of bugs. In general, it makes no guarantee about the actual behavior, only the logical behavior according to the programmer. It is up to the programmer to use it in a reasonable manner.

#include <iostream>class Foo{    Foo* myptr;    unsigned int value;    public:    Foo(): myptr(this), value(0) {}    void Increment() {++value;}    void Display() const {std::cout << "My value is " << value << ".\n";}    void SupposedlyConstFunction() const    {        myptr->Increment();    }};int main(){    Foo myfoo;    myfoo.Display();    myfoo.SupposedlyConstFunction();    myfoo.Display();    return 0;}


Actually, due to the existence of pointers, it is theoretically impossible for the compiler to determine whether a given function actually modifies anything anyway.
I trust exceptions about as far as I can throw them.
Quote:Original post by Storyyeller
Actually, you can change member variables in a const function in a completely legal way.


Your code does not seem legal. (at least things like this do not compile here).
You cannot call a const member function from a non-const.

Quote:Original post by SriLumpa
Your code does not seem legal. (at least things like this do not compile here).
You cannot call a const member function from a non-const.
It compiles for me...
The key is that inside SupposedlyConstFunction the member variable myptr becomes a Foo* const, not a const Foo*.
Yes sorry, I did not reproduce the same thing ... (I just called Increment(); instead of myptr->Increment(); ).
Quote:Original post by Storyyeller
Actually, you can change member variables in a const function in a completely legal way.

Const is basically just a guideline to help the compiler to catch certain kinds of bugs. In general, it makes no guarantee about the actual behavior, only the logical behavior according to the programmer. It is up to the programmer to use it in a reasonable manner.

*** Source Snippet Removed ***

Actually, due to the existence of pointers, it is theoretically impossible for the compiler to determine whether a given function actually modifies anything anyway.


Thats so crazy! Thanks for this post. But can someone explain again why
  myptr->Increment();
is legal? I mean, you are calling a member
variable's function that is not const-corrected.
Edge cases will show your design flaws in your code!
Visit my site
Visit my FaceBook
Visit my github
You aren't calling a member function directly. You're calling a function through a non constant pointer which just happens to point to the same object.

this->Increment(); fails because this is a constant pointer, when you are inside a const function. But myptr is not constant.
I trust exceptions about as far as I can throw them.
Another example:
struct Foo {    int x;    Foo() : x(7) {}    foobar(int &xx) const {        //cout << x;        xx = 42;        //cout << x;    }};int main() {    Foo f;    f.foobar(f.x);}

This illustrates that const is not just about what wont be changed, it's about how the function wont change things.
Inside 'foobar' the value of its member 'x' is changed, but it is-not/cannot-be changed through x itself.
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms

This topic is closed to new replies.

Advertisement