Exploring function calls
The following is something interesting I came across when playing around with function calls in C++. This time, the code will not compile on GCC. It only compiles on the Visual Studio compiler (only tested with VS2008). Therefore I will provide the output as well in this case.
The code provides an object, Lifetime that tracks creation, copy and deletion
(not assignment, but that is not required here). Lifetime objects also contain an
unique identifier that helps us tracking the lifetime of specific objects.
Using the Lifetime object, the program implements two different summation functions.
One uses the C-style variable argument list, and the other only takes two parameters.
The one that takes two parameters is written to be similar to the variable argument
version (it contains unneeded copies in an unrolled loop). This is so that we should
be able to compare them.
The main function runs two test programs, resetting the Lifetime id-counter in between.
And since the two methods does the same thing, it should produce identical output, right?
Both methods call a function with call-by-value semantics, and the functions are implemented
so that the program flow is the same, right?
However, when we run this program, it produces the following output:
Static args: - created 0(1) - created 1(2) Calling function - copied 1 to 2 - copied 0 to 3 Adding parameter 1 - copied 3 to 4 - deleted 4 Adding parameter 2 - copied 2 to 5 - deleted 5 Done adding - deleted 3 - deleted 2 The sum is: 3 - deleted 1 - deleted 0 Variable args: - created 0(1) - created 1(2) Calling function Adding parameter 0 - copied 0 to 2 - deleted 2 Adding parameter 1 - copied 1 to 3 - deleted 3 Done adding The sum is: 3 - deleted 1 - deleted 0
Why are they different? This is related to why the code does not compile on GCC. My guess is that the Visual Studio compiler extends the standard a bit here. In what way? How would we get the same behavior in GCC? Why is this not allowed in GCC?
main.cpp
#include <iostream>
#include <stdarg.h>
using namespace std;
/**
* Tracks lifetime, and keeps a simple value.
*/
class Lifetime {
public:
Lifetime(int d) : data(d), id(counter++) {
cout << "- created " << id << "(" << d << ")" << endl;
}
Lifetime(const Lifetime &o) : data(o.data), id(counter++) {
cout << "- copied " << o.id << " to " << id << endl;
}
~Lifetime() {
cout << "- deleted " << id << endl;
}
// Our data.
int data;
// Our id
int id;
// ID counter (to uniqely identify objects).
static int counter;
};
int Lifetime::counter = 0;
// Sum 'count' # of Lifetime objects.
int sumVa(int count, ...) {
va_list l;
va_start(l, count);
int r = 0;
for (int i = 0; i < count; i++) {
cout << "Adding parameter " << i << endl;
Lifetime lt = va_arg(l, Lifetime);
r += lt.data;
}
va_end(l);
cout << "Done adding" << endl;
return r;
}
// Sum 2 Lifetime objects.
int sum(Lifetime a, Lifetime b) {
int r = 0;
{
cout << "Adding parameter 1" << endl;
Lifetime lt = a;
r += lt.data;
}
{
cout << "Adding parameter 2" << endl;
Lifetime lt = b;
r += lt.data;
}
cout << "Done adding" << endl;
return r;
}
void variableArgs() {
Lifetime a(1), b(2);
cout << "Calling function" << endl;
int s = sumVa(2, a, b);
cout << "The sum is: " << s << endl;
}
void staticArgs() {
Lifetime a(1), b(2);
cout << "Calling function" << endl;
int s = sum(a, b);
cout << "The sum is: " << s << endl;
}
// Test the code!
int main() {
cout << "Static args:" << endl;
staticArgs();
Lifetime::counter = 0;
cout << endl << "Variable args:" << endl;
variableArgs();
return 0;
}