2010-05-05 22 views
29

Algunos código antiguo que yo acabo de encontrar:¿Por qué este código todavía funciona?

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

Esto estaba siendo llamado a construir una lista enlazada, sin embargo me di cuenta de que no hay declaración:

return new_link; 

Incluso sin la instrucción de retorno allí, la lista aún se construyó correctamente. ¿Por qué pasó esto?

Editar: Plataforma: Mandriva Linux 2009 de 64 bits GCC 2.6.24.7-servidor 4.2.3-6mnb1

Editar: divertido ... este código también funcionó exitosamente en unos 5 diferentes instalaciones de Linux, todas diferentes versiones/sabores, así como una Mac.

+2

No hay una respuesta posible al margen de la pura suerte, a menos que (o tal vez incluso si) usted nos deja saber qué plataforma. – Potatoswatter

+1

Esto es un buen argumento para usar un verificador de código estático. – semaj

+3

Es interesante que se ejecutara en tantas plataformas. Solo tiene una variable local, por lo que debe ser bastante estándar que GCC en Intel almacene una sola variable local en el EAX (o su equivalente de 64 bits), y también que use ese registro para los valores de retorno. –

Respuesta

35

En Windows de 32 bits, la mayoría de las veces, el valor de retorno de una función se deja en el registro EAX. Configuraciones similares se utilizan en otros sistemas operativos, aunque, por supuesto, es específico del compilador. Esta función particular supuestamente almacenaba la variable new_link en esa misma ubicación, por lo que cuando regresaba sin retorno, la variable que se encontraba en esa ubicación era tratada como el valor de retorno por la persona que llamaba.

Esto no es portátil y es muy peligroso de hacer, pero también es una de las pequeñas cosas que hace que la programación en C sea tan divertida.

+4

+1, con la adición de que es el ABI, no el sistema operativo que define la convención de llamadas. – danben

+1

Eso explica por qué se ejecutó, pero por qué se compiló es la siguiente pregunta. : P Usted pensaría que el compilador verificará una declaración de devolución (sé que todo lo que hago). De cualquier manera, mientras funciona, definitivamente puede etiquetarse como una cosa mala. – ssube

+1

Creo que este estilo de codificación fue más común hace 30 años cuando C se estaba volviendo popular. El proyecto del que procede este código probablemente haya sido escrito por un codificador anterior (o hace mucho tiempo) y desde entonces tenía deshabilitadas las advertencias del compilador para que se compilara correctamente. –

5

Es básicamente suerte; aparentemente, el compilador pasa a pegar new_link en el mismo lugar donde pegaría un valor devuelto.

-1

Probablemente una coincidencia:

El espacio para el valor de retorno de la función se asigna de antemano. Como ese valor no está inicializado, podría haber apuntado al mismo espacio en el montón que la memoria asignada a la estructura.

+1

Uhh, no, los valores devueltos generalmente se devuelven en registros, o, supongo, en la pila. No es un puntero aleatorio siendo misteriosamente correcto. Los valores devueltos no están asignados. – WhirlWind

+1

Como se indicó anteriormente, los valores devueltos se devuelven en un registro si encajan, como sería el caso de un puntero. – Joel

+4

@Whirl: en algunas arquitecturas, el espacio se reserva en la pila para valores devueltos cuando se invoca un procedimiento. En otros, el resultado se devuelve en un registro. Difiere**. –

1

Esto funciona por coincidencia. No deberías confiar en eso.

7

Es posible que solo use el registro EAX que normalmente almacena el valor de retorno de la última función que se llamó. ¡Esta no es una buena práctica en absoluto! El comportamiento para este tipo de cosas no está definido. Pero es genial ver el trabajo ;-)

1

Lo más probable es que produzca un error muy difícil de encontrar. No estoy seguro de dónde lo leí, pero recuerdo que si olvida poner una declaración de devolución, la mayoría de los compiladores se volverán inválidos.

Aquí hay un pequeño ejemplo:

#include <iostream> 

using namespace std; 

int* getVal(); 

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

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

Para mí, esto funciona así. Sin embargo, cuando lo ejecuto, aparece un error de segmento. Entonces realmente no está definido. Solo porque compila, no significa que funcionará.

+1

Watson supongo? –

0

Funciona porque en la década de 1940, cuando se creó el lenguaje C, no existía la palabra clave return.Si nos fijamos en la sección "funciones" de la norma C43 Minsi, tiene esto que decir sobre el tema (entre otras cosas):

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

No, no lo es. No existe el estándar C43 MINSI, solo lo está inventando. –

+0

Ahora que todos conocemos el humor de James. ¿por qué down-vote? – tristan

5

Para evitar este problema, utilice:

-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 para encender la anterior en un 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.

(de GCCs warning options)

Cuestiones relacionadas