2009-11-14 13 views
24

Estaba tratando de responder la pregunta this. Según lo sugerido por la respuesta aceptada, el problema con ese código es que no todas las rutas de control devuelven un valor. Probé este código en el compilador VC9 y me dio una advertencia sobre lo mismo. Mi pregunta es: ¿por qué es solo una advertencia y no un error? Además, en caso de que se ejecute la ruta que no devuelve un valor, ¿qué devolverá la función (tiene que devolver algo)? ¿Es solo lo que hay encima de la pila o es el temido comportamiento indefinido otra vez?¿Por qué "no todas las rutas de control devuelven un valor" es advertencia y no es un error?

+3

La mayoría de los lugares consideran las advertencias como errores. En consecuencia, la mayoría de los compiladores tienen una bandera que le dice al compilador que trate las advertencias como errores. Nunca he tenido un trabajo (aparte de uno trabajando en código heredado) donde no encendimos esta bandera e insistimos en 0 advertencias antes de registrarnos. –

+2

Y 0 errores, solo en caso de que el pensamiento no estuviera implícito. –

Respuesta

29

No devolver un valor de una función que tiene un tipo de devolución no void da como resultado un comportamiento indefinido, pero no es un error semántico.

La razón de esto, hasta donde puedo determinar, es en gran parte histórica.

C originalmente no tenía void e implícita int significaba que la mayoría de las funciones devuelven un int menos que se declare explícitamente a volver otra cosa, aunque no había ninguna intención de utilizar el valor de retorno.

Esto significa que muchas funciones devolvieron un int pero sin establecer explícitamente un valor de retorno, pero eso estaba bien porque las personas que llaman nunca usarían el valor de retorno para estas funciones.

Algunas funciones devolvieron un valor, pero utilizaron implícitamente int porque int era un tipo de devolución adecuado.

Esto significa que el código pre void tenía un montón de funciones que devuelven nominalmente int pero que podrían ser declaradas para volver void y muchas otras funciones que debe devolver un int y no hay forma clara a notar la diferencia. La aplicación de return en todas las rutas de código de todas las funciones que no sean void en cualquier etapa rompería el código heredado.

También existe el argumento de que algunas rutas de código en una función pueden ser inalcanzables, pero esto puede no ser fácil de determinar a partir de un análisis estático simple, entonces ¿por qué imponer un innecesario return?

+2

El análisis estático se puede explicar fácilmente si se piensa en 'cambiar', mientras que se recomienda encarecidamente que proporcione un 'valor predeterminado', no es necesario y puede que no sea necesario ... pero ¿cómo se supone que el compilador lo sabe? ? Por otro lado ... debes tratar las advertencias como errores al compilar, es mejor. –

+0

Es una advertencia muy útil cuando realmente es un error y se le olvidó el retorno, uno que me ha sorprendido varias veces y las advertencias como errores al menos para este en particular le aseguran que no lo pase por alto. – CashCow

-2

No es un error porque puede ser el comportamiento previsto. Por ejemplo, algunas bibliotecas de encriptación usan datos locales no inicializados como un paso para la siembra. Como los valores de retorno se guardan en convenciones de llamadas y ubicaciones específicas de plataforma, esto puede ayudar en algunas situaciones inusuales (como las anteriores). En este caso, la función devuelve lo que queda en el registro utilizado para devolver el valor devuelto.

+1

¿Puede citar un ejemplo específico de una biblioteca de cifrado que utiliza datos no inicializados como siembra? Parece muy poco probable que una biblioteca competente haga eso ya que los datos no inicializados no son aleatorios en términos criptográficos. –

+0

¿De verdad? ¿Qué bibliotecas de cifrado hacen eso? Confiar en un comportamiento indefinido en una biblioteca de cifrado parece un gran agujero de seguridad potencial. –

+0

Algún día preferiría un número aleatorio generado adecuadamente en lugar de datos no inicializados como una semilla. Si utiliza el depurador MSVC++, verá regularmente que los datos no inicializados tienen valores fijos (en la versión de depuración del programa 0xCCCCCCCC, 0xCDCDCDCD). –

9

Supongo que es solo una advertencia porque el compilador no siempre puede estar 100% seguro de que es posible no golpear una devolución.

es decir, si tiene que:


-= source1.c =- 
int func() 
{ 
    if(doSomething()) 
    { 
     return 0; 
    } 
} 

-= source2.c =- 
int doSomething() 
{ 
    return 1; 
} 

El compilador en este caso podría no ser capaz de saber que siempre llegará a la devolución, pero sí. Por supuesto, esta es una terrible práctica de programación para confiar en saber cómo funciona el código externo.

En cuanto a lo que realmente se devolverá depende de la plataforma. En los ABI x86, EAX se utiliza para el valor de retorno (hasta 32 bits), por lo que devolverá lo que se haya colocado en ese registro (que podría ser un retorno de otra cosa, un valor temporal o una pérdida total).

+0

En este caso, ¿por qué incluso tiene una declaración if? Este código es simplemente incorrecto y no debe compilarse. Es una advertencia por razones históricas, compatibilidad con versiones anteriores y demás. Ahora C++ debería descartar esto y manejarlo como un error. – Melkon

1

aquí es otra razón por la que no es un error

la siguiente información le dará la misma advertencia ya que el compilador espera que devuelva algo del bloque catch a pesar de que usted está lanzando no

int foo(){ 
    try{ 
     return bar(0); 
    } catch(std::exception& ex){ 
     //do cleanup 
     throw ex; 
    } 
} 

int bar(unsigned int i){ 
    if(i == 0){ 
     throw std::string("Value must be greater than 0"); 
    } else{ 
     return 0; 
    } 
} 
+0

No espera que regrese si está lanzando, al menos no lo hace VC9, no sé sobre otros compiladores. Sin embargo, si llamas a una función que siempre arroja (a propósito, por supuesto, tienes una rutina que compila mensajes de error y lanza) y la llamas y el compilador no puede "verla", obtienes la advertencia. Por lo tanto, mi solución a continuación. – CashCow

5

Técnicamente no se garantiza que sea un error si llama a una función y esa función siempre arroja una excepción. Por ejemplo, aquí hay un pseudo código, y sabes que raiseError siempre arroja.

MyClass func(params) 
{ 
    if(allIsValid()) 
    { 
     return myObject; 
    } 
    else 
    { 
     raiseError(errorInfo); 
    } 
} 

Si el compilador no puede ver la implementación de raiseError, no va a saber que la función va a lanzar. Entonces realmente no hay un comportamiento indefinido aquí. Por supuesto, es bueno silenciar el compilador aquí, lo cual se puede hacer escribiendo una declaración de devolución "ficticia" después de raiseError, o un "throw" ficticio. Los llamo "tontos" porque nunca serán alcanzados en la realidad. (También puede suprimir la advertencia si realmente insiste). Sin embargo, no hay ningún error o comportamiento indefinido.

-3

cuenta la situación siguiente:

UINT GenderID(GENDER gender) 
{ 
    switch(gender) 
    { 
    case MALE: 
     return MALE_ID; 
    case FEMALE: 
     return FEMALE_ID; 
    } 
// default not required because [GENDER] in our 'Matrix' CAN be either M or F 
} 

un compilador C++ debe permitirá tiene su 'matriz' a su manera; Por lo tanto, no es un error.

+0

Básicamente dice que no es un error porque se le ocurrió un ejemplo artificial que cree que debería funcionar. – Andy

+0

@Andy como ** no es un error ** - no tengo que demostrarlo. Además de que mi ejemplo es muy real, y el objetivo es presentar una situación en la que declararlo, un error sería un error en sí mismo. –

+1

No responde * por qué * está permitido en primer lugar, que es lo que el OP desea saber. Su respuesta es una tautología y solo proporciona otro ejemplo de lo que el OP está preguntando, no responde la pregunta. Lo único que puedo deducir de su declaración de respuesta es que cree que el código debe compilarse porque debería 'dejar que lo haga a su manera'. – Andy

1

Otro ejemplo en el que puede ser un problema para algunas rutas de control que no devuelven un valor:

enum E : int {A, B}; 

int foo(E e) { 
    switch (e) { 
    case A: return 30; 
    case B: return 50; 
    } 
} 

Es posible que e no habrá A o B, pero la implicación es que siempre será uno de esos valores. Si ese es el caso, entonces el código está bien y no hay problema. Convertir esta advertencia en un error obligatorio requeriría un desorden innecesario e "inalcanzable".

Si desea que la advertencia sea un error de todos modos, puede configurar su compilador para hacer eso con un indicador como/WX o -Werror. Aunque, por supuesto, debe tener en cuenta que los diferentes compiladores pueden hacer diferentes determinaciones sobre lo que no se puede alcanzar, por lo que puede estar arreglando cosas diferentes para compiladores diferentes.

Cuestiones relacionadas