views:

793

answers:

8

Some old code that I just came across:

MLIST * new_mlist_link()
{
    MLIST *new_link = (MLIST * ) malloc(sizeof(MLIST));
    new_link->next  = NULL;
    new_link->mapi  = NULL;
    new_link->result = 0;
}

This was being called to build a linked list, however I noticed there is no statement:

return new_link;

Even without the return statement there, the list still got built properly. Why did this happen?

Edit: Platform: Mandriva 2009 64bit Linux 2.6.24.7-server GCC 4.2.3-6mnb1

Edit: Funny... this code also ran successfuly on about 5 different Linux installations, all different versions/flavors, as well as a Mac.

+34  A: 

On 32-bit Windows, most of the time, the return value from a function is left in the EAX register. Similar setups are used in other OSes, though of course it's compiler-specific. This particular function presumably stored the new_link variable in that same location, so when you returned without a return, the variable in that location was treated as the return value by the caller.

This is non-portable and very dangerous to actually do, but is also one of the little things that makes programming in C so much fun.

Aric TenEyck
+1, with the addition that it is the ABI, not the operating system that defines the calling convention.
danben
That explains why it ran, but why it compiled is the next question. :P You'd think the compiler would check for a return statement (I know all mine do). Either way, while it works, it can definitely be labeled a Bad Thing.
peachykeen
I think that this style of coding was more common 30ish years ago when C was becoming popular. The project that this code came from was probably written by an older coder (or just a long time ago) and had since had compiler warnings disabled so that this would compile correctly.
Aric TenEyck
Nice, specific answer to a strange phenomenon. I should ask you about the flying objects I've been seeing late at night ;-)
WhirlWind
I think that this is not 32-bit Windows specific... I think that this is the calling convention in the whole IA-32 architecture, but I had to check with the Intel manuals.
Juliano
@peachykeen gcc will warn about this with `-Wall` but what confounds me is why the language spec does not make this a mandatory compiler error. I can't think of any reason why it would be a good idea to allow this.
Tyler McHenry
This would be one of the most subtley sadistic pieces of intentionally smelly code i have ever seen - some old timer who first learnt to program straight machine code on a mainframe has decided to leave a little gem for future generations to ponder over. Nice.
slugster
This sounds like an excellent trick to pull in the Underhanded C contest. Change a method so it has a path without a return on it.
Jason
Does -Wall catch this on gcc?
Bryan Ross
@Bryan: Yes, [`-Wall`](http://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html) turns on `-Wreturn-type`.
Georg Fritzsche
+1 for "the little things that make programming in C so much fun"
Norman Ramsey
The return value was set by malloc and simply not changed by the remainder of the code in the function. If you compare the assembly output with and without a return statement it should be identical.
drawnonward
@Tyler: The compiler is allowed to (non-fatally) diagnose this, as it always is for anything. However, requiring that lack of a return statement be an error fails in the general case with standard C, since you can't indicate functions that never return (e.g. something that calls abort or longjmp): `int f() { int n = might_abort(); if (n) return n; do_longjmp(); }`
Roger Pate
+5  A: 

It's basically luck; apparently, the compiler happens to stick new_link into the same place it would stick a returned value.

WhirlWind
A: 

Probably a coincidence:

The space for the return value of the function is allocated ahead of time. Since that value is uninitialized, it could have pointed to the same space on the heap as the memory allocated for the struct.

George Edison
Uhh, no, returned values are usually returned in registers, or, I suppose, on the stack. It's not a random pointer being mysteriously correct. Return values are not allocated.
WhirlWind
As indicated above, return values are returned in a register if they fit, as would be the case for a pointer.
Joel
@Whirl: On some architectures, space is reserved on the stack for return values when a procedure is invoked. On others, the result is returned in a register. It **differs**.
George Edison
+1  A: 

This works by coincidence. You shouldn't rely on that.

el.pescado
+7  A: 

Possible it just used the EAX register which normally stores the return value of the last function that was called. This is not good practice at all! The behavior for this sort of things is undefined.. But it is cool to see work ;-)

in70x
+2  A: 

Most likely is that it'll produce a very hard to find bug. I'm unsure where I read it, but I recall that if you forget to put in a return statement, most compilers will default to return void.

Here's a short example:

#include <iostream>

using namespace std;

int* getVal();

int main()
{
        int *v = getVal();
        cout << "Value is: " << *v << endl;
        return 0;
}

int* getVal()
{
        // return nothing here
}

For me, this works as well. However, when I run it, I get a segment fault. So it's really undefined. Just because it compiles, doesn't mean it'll work.

Daniel
Watson I presume?
James Morris
A: 

It works because in the 1940's when the C language was created, there was no return keyword. If you look in the "functions" section of the C43 MINSI standard, it has this to say on the subject (amongst other things):

16.4.3b For backwards compatibility the EAX register MUST be used to return
        the address of the first chunk of memory allocated by malloc.

</humour>

James Morris
No, it isn't. There is no C43 MINSI standard, he's just making this up.
Aric TenEyck
Now that we all know James' humor. why down-voted?
Tristan Su
+6  A: 

To avoid this problem, use:

-Wreturn-type:

Warn whenever a function is defined with a return-type that defaults to int. Also warn about any return statement with no return-value in a function whose return-type is not void (falling off the end of the function body is considered returning without a value), and about a return statement with an expression in a function whose return-type is void.

-Werror=return-type to turn the above into an error:

Make the specified warning into an error. The specifier for a warning is appended, for example -Werror=switch turns the warnings controlled by -Wswitch into errors. This switch takes a negative form, to be used to negate -Werror for specific warnings, for example -Wno-error=switch makes -Wswitch warnings not be errors, even when -Werror is in effect. You can use the -fdiagnostics-show-option option to have each controllable warning amended with the option which controls it, to determine what to use with this option.

(from GCCs warning options)

Georg Fritzsche