c++ - reales - ¿Por qué devolver una referencia a un valor local de función no es un error de compilación?
libro de android studio en español pdf (6)
El siguiente código invoca un comportamiento indefinido.
int& foo()
{
int bar = 1234;
return bar;
}
g ++ emite una advertencia:
advertencia: referencia a la variable local ''bar'' devuelta [-Wreturn-local-addr]
clang ++ también:
advertencia: referencia a la memoria de pila asociada con la variable local ''bar'' devuelta [-Wreturn-stack-address]
¿Por qué no es esto un error de compilación (ignorando -Werror
)?
¿Hay algún caso en el que sea válido devolver una referencia a una var local?
EDITAR Como se señaló, la especificación exige que sea compilable. Entonces, ¿por qué la especificación no prohíbe dicho código?
Además, debido a que es posible que desee obtener el puntero de la pila actual (lo que eso significa en su implementación particular).
Esta función:
void* get_stack_pointer (void) { int x; return &x; };
AFAIK, no es un comportamiento indefinido si no desreferencia el puntero resultante.
es mucho más portátil que este:
void* get_stack_pointer (void) {
register void* sp asm ("%esp"); return sp; }
En cuanto a por qué quieres obtener el puntero de la pila: bueno, hay casos en los que tienes una razón válida para obtenerlo: por ejemplo, el recolector de basura Boehm conservador necesita escanear la pila (así que quiere el puntero de la pila y la pila inferior) .
Y si devolvió una referencia de C ++ en la que solo tomaría su dirección usando el operador unario, obtener dicha dirección es legal para el IIUC (en mi humilde opinión es la única operación lícita que puede hacer con ella).
Otra razón para obtener el puntero de pila sería obtener una dirección de puntero no NULL
(que podría, por ejemplo, hash) diferente de cualquier montón, datos locales o estáticos. Sin embargo, puede usar (void*)1
o (void*)-1
para ese fin.
Entonces el compilador tiene razón al solo advertir contra esto.
Supongo que un compilador de C ++ debería aceptar
int& get_sp_ref(void) { int x; return x; }
void show_sp(void) {
std::cout << (&(get_sp_ref())) << std::endl; }
Algo que otras respuestas no mencionaron es que este código está bien si nunca se llama a la función.
El compilador no está obligado a diagnosticar si alguna vez se puede llamar o no a una función. Por ejemplo, puede configurar un programa que busque contraejemplos en el último teorema de Fermat y llame a esta función si encuentra una. Sería un error para el compilador rechazar dicho programa.
Devolver la referencia a la variable local es una mala idea, sin embargo, algunas personas pueden crear código que lo requiera, por lo que el compilador solo debe advertir sobre eso y no determinar el código válido (estructura válida) como erróneo.
Angew ya publicó una muestra con una variable local que en realidad es global. Sin embargo, hay alguna otra muestra (en mi humilde opinión mejor).
Object& GetSmth()
{
Object* obj = new Object();
return *obj;
}
En este caso, la referencia al objeto local es válida y la persona que llama después del uso debe desalojar la memoria.
NOTA IMPORTANTE No recomiendo y no recomiendo usar dicho estilo de codificación, porque es malo , generalmente es difícil entender lo que está sucediendo y conduce a algún tipo de problemas como pérdidas de memoria o fallas. Es solo una muestra que muestra por qué esta situación particular no se puede tratar como error.
Para ampliar las respuestas anteriores, el estándar ISO C ++ no captura la distinción entre advertencias y errores para empezar; simplemente usa el término "diagnóstico" cuando se refiere a lo que un compilador debe emitir al ver un programa mal formado. Citando N3337 , 1.4, párrafos 1 y 2:
El conjunto de reglas diagnosticables consta de todas las reglas sintácticas y semánticas en este Estándar Internacional, excepto aquellas que contienen una notación explícita de que "no se requiere diagnóstico" o que se describen como resultado de un "comportamiento indefinido".
Aunque esta Norma Internacional solo establece requisitos en las implementaciones de C ++, esos requisitos a menudo son más fáciles de entender si se expresan como requisitos en programas, partes de programas o ejecución de programas. Dichos requisitos tienen el siguiente significado:
Si un programa no contiene violaciones de las reglas en este Estándar Internacional, una implementación conforme deberá, dentro de sus límites de recursos, aceptar y ejecutar correctamente ese programa.
Si un programa contiene una violación de cualquier regla diagnosticable o una ocurrencia de un constructo descrito en este Estándar como "condicionalmente respaldado" cuando la implementación no admite ese constructo, una implementación conforme deberá emitir al menos un mensaje de diagnóstico.
Si un programa contiene una violación de una regla para la cual no se requiere diagnóstico, esta Norma Internacional no impone ningún requisito sobre las implementaciones con respecto a ese programa.
Por la misma razón, C le permite devolver un puntero a un bloque de memoria que se ha liberado.
Es válido según la especificación del idioma. Es una idea horriblemente mala (y no está en absoluto cerca de que se garantice que funcione) pero sigue siendo válida en la medida en que no está prohibida.
Si está preguntando por qué el estándar lo permite, es probable que sea porque, cuando se introdujeron las referencias, esa era la forma en que funcionaban. Cada iteración de la norma tiene ciertas pautas a seguir (como minimizar la posibilidad de "romper cambios", las que invalidan los programas bien formados existentes) y el estándar es un acuerdo entre el usuario y el implementador, con indudablemente más implementadores que los usuarios sentados en los comités :-)
Puede valer la pena presentar esa idea como un cambio potencial y ver lo que ISO dice, pero sospecho que se consideraría uno de esos "cambios decisivos" y, por lo tanto, muy sospechoso.
Yo diría que requerir esto para hacer que el programa esté mal formado (es decir, convertir esto en un error de compilación) complicaría considerablemente el estándar con pocos beneficios. Tendría que escribir exactamente en el estándar cuando se diagnosticarán tales casos, y todos los compiladores tendrían que implementarlos.
Si especifica muy poco, no será demasiado útil. Y los compiladores probablemente ya verifiquen que esto emita advertencias, y los programadores reales compilan de todos modos -Wall_you_can_give_me -Werror
.
Si especifica demasiado, será difícil (o imposible) para los compiladores implementar el estándar.
Considere esta clase (para la cual solo tiene el encabezado y una biblioteca):
class Foo
{
int x;
public:
int& getInteger();
};
Y este código:
int& bar()
{
Foo f;
return f.getInteger();
}
Ahora, ¿debería escribirse el estándar para hacerlo mal formado o no? Probablemente no, ¿y si Foo
se implementa así?
#include "Foo.h"
int global;
int& Foo::getInteger()
{
return global;
}
Al mismo tiempo, podría implementarse así:
#include "Foo.h"
int& Foo::getInteger()
{
return x;
}
Lo que por supuesto te daría una referencia colgante.
Mi punto es que el compilador no puede saber realmente si devolver una referencia es correcto o no, excepto en algunos casos triviales (devolviendo una referencia a una variable automática de ámbito de función o parámetro de tipo no de referencia). No creo que valga la pena complicar el estándar para eso. Especialmente porque la mayoría de los compiladores ya advierten sobre esto como una cuestión de calidad de implementación.