I know the answer to this but we can have some fun analyzing it. And we'll learn having fun!
I've used gcc 4.1.2 for those tests.
First of all, this code is not standard, since an inline function will have different definitions in different translation units. I know that. But let's analyze what's going on and give the answer to three questions I'll make. We'll learn from it :)
I'll keep files simple (no #ifndef guards for example).
Suppose I have those files:
increment.h:
inline int increment()
{
static int value = 0;
return ++value;
}
decrement.h:
int decrement();
decrement.cpp:
inline int increment()
{
static int value = 0;
return --value; // Attention to this
}
int decrement()
{
return increment();
}
main.cpp:
#include <iostream>
#include "increment.h"
#include "decrement.h"
using namespace std;
int main()
{
cout << increment() << endl;
cout << increment() << endl;
cout << decrement() << endl;
}
If I compile them with this Makefile:
CC=gcc
CFLAGS=-I. -O2
crazy: main.o decrement.o
$(CC) -lstdc++ main.o decrement.o -o crazy
main.o: main.cpp increment.h decrement.h
$(CC) $(CFLAGS) -c main.cpp -o main.o
decrement.o: decrement.cpp decrement.h
$(CC) $(CFLAGS) -c decrement.cpp -o decrement.o
clean:
rm -f *.o *.~ crazy
The output is:
1
2
1
If I remove the -O2 flag from the Makefile:
CFLAGS=-I.
The output is:
1
2
3
If I also change the order of main.o and decrement.o (leaving it without the -O2 flag as I just did):
$(CC) -lstdc++ decrement.o main.o -o crazy
the result is:
-1
-2
-3
What is going on here? Why the -O2 flag and the order of the object files linking change the output this way?
There are two translation units here: main.cpp and decrement.cpp
Lets replace our #include directives for increment.h and decrement.h to see how main.cpp and decrement.cpp look like after the preprocessor pass (I won't replace other includes):
decrement.cpp:
main.cpp:
In the case of decrement.cpp it exports symbols similar to those to the linker:
In the case of main.cpp, it exports:
If increment is actually inlined (-O2 flag set), "int::increment value = 0" gets defined somewhere by the linker and those translation units become:
decrement.cpp
main.cpp
So we get this behavior:
But if we remove the -O2 flag, the compiler doesn't inline the functions, but it creates a body for them instead (so it will be easier to debug, since the debugger will have a unique place to put a breakpoint for that function). Therefore two bodies will be created for "inline int increment()". One in decrement.cpp (decrement.o), which will decrement the value, another in main.cpp (main.o), which will increment the value.
At link time, if "int increment()" were not inline, multiple body definitions would lead to linker error since the body of a function should be defined only once. But since it is inline, the linker supposes that all bodies for "inline int increment()" are the same throughout all translation units, since the standard states that all inline functions must have the same body everywhere, the linker just picks the first of them.
If you link:
The first body is the one in main.o, which increments value. So you get:
But if you link:
The linker picks ther body from decrement.o, which actually decrements value. So you get:
Question: What could happen if one body (main.o) initializes value to 0 and the other (decrement.o) initializes it to, for example, value = 3? What do you think?