开发者

Why did this code still work?

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-ser开发者_开发技巧ver 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.


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.


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 ;-)


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


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)


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


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.


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>


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜