I recently had a serious bug, where I forgot to return a value in a function. The problem was that even though nothing was returned it worked fine under Linux/Windows and only crashed under Mac. I discovered the bug when I turned on all compiler warnings.
So here is a simple example:
#include <iostream>
class A{
public:
A(int p1, int p2, int p3): v1(p1), v2(p2), v3(p3)
{
}
int v1;
int v2;
int v3;
};
A* getA(){
A* p = new A(1,2,3);
// return p;
}
int main(){
A* a = getA();
std::cerr << "A: v1=" << a->v1 << " v2=" << a->v2 << " v3=" << a->v3 << std::endl;
return 0;
}
My question is how can this work under Linux/Windows without crashing? How is the returning of values done on lower level?
First off, you need to slightly modify your example to get it to compile. The function must have at least an execution path that returns a value.
Second, it's obviously undefined behavior, which means anything can happen, but I guess this answer won't satisfy you.
Third, in Windows it works in Debug mode, but if you compile under Release, it doesn't.
The following is compiled under Debug:
The second instruction, the call to
operator new
, moves intoeax
the pointer to the newly created instance.The calling context expects
eax
to contain the returned value, but it does not, it contains the last pointer allocated bynew
, which is incidentally,p
.So that's why it works.
Regarding the following statement from n3242 draft C++ Standard, paragraph 6.6.3.2, your example yields undefined behavior:
The best way to see what actually happens is to check the assembly code generated by the given compiler on a given architecture. For the following code:
...VS2010 compiler (in Debug mode, on Intel 32-bit machine) generates the following assembly:
The result of addition operation in
foo()
is stored ineax
register (accumulator) and its content is used as a return value of the function, moved to variablen
.eax
is used to store a return value (pointer) in the following example as well:Assembly code:
VS2010 compiler issues warning 4716 in both examples. By default this warning is promoted to an error.
On Intel architecture, simple values (integers and pointers) are usually returned in
eax
register. This register (among others) is also used as temporary storage when moving values in memory and as operand during calculations. So whatever value left in that register is treated as the return value, and in your case it turned out to be exactly what you wanted to be returned.The way of returning of value from the function depends on architecture and the type of value. It could be done thru registers or thru stack. Typically in the x86 architecture the value is returned in EAX register if it is an integral type: char, int or pointer. When you don't specify the return value, that value is undefined. This is only your luck that your code sometimes worked correctly.
There are two major ways for a compiler to return a value:
The #1 is usually used with anything that fits into a register; #2 is for everything else (large structs, arrays, et cetera).
In your case, the compiler uses #1 both for the return of
new
and for the return of your function. On Linux and Windows, the compiler did not perform any value-distorting operations on the register with the returned value between writing it into the pointer variable and returning from your function; on Mac, it did. Hence the difference in the results that you see: in the first case, the left-over value in the return register happened to co-inside with the value that you wanted to return anyway.Probably by luck, 'a' left in a register that happens to be used for returning single pointer results, something like that.
The calling/ conventions and function result returns are architecture-dependent, so it's not surprising that your code works on Windows/Linux but not on a Mac.