2010-05-05 2 views
29

Некоторые старый код, который я только что наткнулся:Почему этот код по-прежнему работает?

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

Это называют построить связанный список, однако я заметил, что нет заявления:

return new_link; 

Даже без возвращения заявления там, список по-прежнему построен правильно. Почему это случилось?

Edit: Платформа: Mandriva 2009 64bit Linux 2.6.24.7-сервер GCC 4.2.3-6mnb1

Edit: ... Смешной этот код тоже побежал успешно на около 5 различных установок Linux, все разные версии/вкусы, а также Mac.

+2

Там нет никакого возможного ответа в стороне от тупого везения, если (или, может быть, даже если) вы дайте нам знать, какую платформу. – Potatoswatter

+1

Это хороший пример использования статической проверки кода. – semaj

+3

Интересно, что он работал на стольких платформах. Он имеет только одну локальную переменную, поэтому для GCC на Intel должно быть достаточно стандартно хранить одну локальную переменную в EAX (или ее 64-битный эквивалент), а также использовать этот регистр для возвращаемых значений. –

ответ

35

В 32-битной Windows в большинстве случаев возвращаемое значение из функции сохраняется в регистре EAX. Подобные установки используются в других операционных системах, хотя, конечно, они специфичны для компилятора. Эта конкретная функция предположительно сохранила новую переменную new_link в том же месте, поэтому, когда вы вернулись без возврата, переменная в этом месте была обработана вызывающим объектом как возвращаемое значение.

Это не переносная и очень опасная на самом деле, но также является одной из мелочей, которая делает программирование на C такой забавной.

+4

+1, добавив, что это ABI, а не операционная система, которая определяет соглашение о вызове. – danben

+1

Это объясняет, почему это сработало, но почему он скомпилирован - это следующий вопрос. : P Вы бы подумали, что компилятор будет проверять для оператора возврата (я знаю все, что у меня есть). В любом случае, пока он работает, его можно определенно обозначить как Bad Thing. – ssube

+1

Я думаю, что этот стиль кодирования был более распространен 30 лет назад, когда C стал популярным. Проект, из которого пришел этот код, вероятно, был написан старым кодером (или только давно), и с тех пор был отключен предупреждений компилятора, чтобы это было правильно скомпилировано. –

5

Это в основном удача; по-видимому, компилятор, похоже, придерживается new_link в том же месте, где он будет придерживаться возвращаемого значения.

-1

Возможно совпадение:

пространство для возвращаемого значения функции выделяется загодя. Поскольку это значение не инициализировано, оно могло бы указывать на то же место в куче, что и память, выделенная для структуры.

+1

Uhh, нет, возвращаемые значения обычно возвращаются в регистры или, я полагаю, в стеке. Это не случайный указатель, который загадочно правильный. Возвращаемые значения не выделяются. – WhirlWind

+1

Как указано выше, возвращаемые значения возвращаются в регистр, если они подходят, как в случае с указателем. – Joel

+4

@Whirl: на некоторых архитектурах пространство зарезервировано в стеке для возвращаемых значений при вызове процедуры. В других случаях результат возвращается в регистр. Это ** отличается **. –

1

Это работает по совпадению. Вы не должны полагаться на это.

7

Возможно, он просто использовал регистр EAX, который обычно сохраняет возвращаемое значение последней вызванной функции. Это совсем не хорошая практика! Поведение для такого рода вещей не определено. Но здорово видеть работу ;-)

1

Скорее всего, это вызовет очень трудную ошибку. Я не знаю, где я его читаю, но я помню, что если вы забудете включить оператор return, большинство компиляторов по умолчанию вернут void.

Вот краткий пример:

#include <iostream> 

using namespace std; 

int* getVal(); 

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

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

Для меня, это работает хорошо. Однако, когда я запускаю его, я получаю ошибку сегмента. Так что это действительно неопределенно. Просто потому, что он компилируется, это не значит, что он сработает.

+1

Уотсон, я полагаю? –

0

Это работает, потому что в 1940-х годах, когда был создан язык C, ключевое слово return не было.Если вы смотрите в разделе «Функции» стандарта C43 MINSI, то это сказать по этому вопросу (среди прочего):

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>

+4

Нет, это не так. Нет стандарта C43 MINSI, он просто это делает. –

+0

Теперь, когда мы все знаем юмор Джеймса. почему проголосовали? – tristan

5

Чтобы избежать этой проблемы, используйте:

-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 превратить выше в ошибку:

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.

(от GCCs warning options)