sharp programacion lenguaje ejemplos descargar definicion caracteristicas c++ compiler-construction standards

c++ - programacion - ¿Por qué los compiladores dan una advertencia sobre devolver una referencia a una variable de pila local si es un comportamiento no definido?



lenguaje de programacion c (7)

El comportamiento indefinido no es un error de compilación, simplemente no es un programa de C ++ bien formado. No todos los programas mal formados son incompatibles, es simplemente imprevisible . Apostaría a que, en principio, ni siquiera es posible que una computadora decida si un texto de programa dado es un programa de C ++ bien formado.

¡Siempre puede agregar -Werror a gcc para hacer que las advertencias terminen la compilación con un error!

Para agregar otro tema favorito de SO: ¿Le gustaría que ++i++ cause un error de compilación?

El estándar de C ++ indica que devolver la referencia a una variable local (en la pila) es un comportamiento indefinido, entonces ¿por qué muchos (si no todos) de los compiladores actuales solo dan una advertencia para hacerlo?

struct A{ }; A& foo() { A a; return a; //gcc and VS2008 both give this a warning, but not a compiler error }

¿No sería mejor que los compiladores den un error en lugar de advertir este código?

¿Existen grandes ventajas para permitir que este código se compile con solo una advertencia?

Tenga en cuenta que no se trata de una referencia const que pueda alargar la vida útil del temporizador hasta la vida útil de la referencia en sí.


Es casi imposible verificar desde el punto de vista del compilador si está devolviendo una referencia a un temporal. Si el estándar dictara que para ser diagnosticado como un error, escribir un compilador sería casi imposible. Considerar:

bool not_so_random() { return true; } int& foo( int x ) { static int s = 10; int *p = &s; if ( !not_so_random() ) { p = &x; } return *p; }

El programa anterior es correcto y seguro de ejecutar, en nuestra implementación actual se garantiza que foo devolverá una referencia a una variable static , lo cual es seguro. Pero desde una perspectiva del compilador (y con una compilación separada en su lugar, donde la implementación de not_so_random() no es accesible, el compilador no puede saber que el programa está bien formado.

Este es un ejemplo de juguete, pero puede imaginar un código similar, con diferentes rutas de retorno, donde p podría referirse a diferentes objetos de larga duración en todas las rutas que devuelven *p .


Es una práctica bastante malísima confiar en esto, pero creo que en muchos casos (y eso nunca es una buena apuesta), esa referencia de memoria aún sería válida si no se llama a ninguna función entre el momento en que foo() regresa y el hora en que la función que llama utiliza su valor de retorno. En ese caso, esa área de la pila no tendría la oportunidad de ser sobrescrita.

En C y C ++, puede elegir acceder a secciones arbitrarias de la memoria de todos modos (dentro del espacio de la memoria del proceso, por supuesto) a través de la aritmética de punteros, ¿por qué no permitir la posibilidad de construir una referencia donde sea que uno elija?


Los compiladores no deben rehusarse a compilar programas a menos que la norma diga que están autorizados a hacerlo. De lo contrario, sería mucho más difícil portar programas, ya que podrían no compilarse con un compilador diferente, a pesar de que cumplen con el estándar.

Considera la siguiente función:

int foobar() { int a=1,b=0; return a/b; }

Cualquier compilador decente detectará que estoy dividiendo por cero, pero no debería rechazar el código ya que es posible que desee activar una señal SIG_FPE.

Como ha señalado David Rodríguez, hay algunos casos que son indecisos, pero también hay algunos que no lo son. Algunas versiones nuevas de la norma pueden describir algunos casos en los que el compilador debe / puede rechazar programas. Eso requeriría que el estándar sea muy específico sobre el análisis estático que se realizará.

El estándar de Java en realidad especifica algunas reglas para verificar que los métodos no nulos siempre devuelven un valor. Desafortunadamente, no he leído lo suficiente del estándar C ++ para saber qué está permitido hacer el compilador.


Porque el estándar no nos restringe.

¡Si quieres disparar a tu propio pie puedes hacerlo!

Sin embargo veamos y ejemplo donde puede ser útil:

int &foo() { int y; } bool stack_grows_forward() { int &p=foo(); int my_p; return &my_p < &p; }


También puede devolver una referencia a una variable estática, que sería un código válido, por lo que el código debe poder compilarse.


Si devuelve un puntero / referencia a una función interna interna, el comportamiento está bien definido siempre que no elimine la referencia al puntero / referencia devuelto por la función.

Es un comportamiento indefinido solo cuando uno elimina el puntero devuelto.

Si se trata de un comportamiento indefinido o no, depende del código que llame a la función y no de la función en sí.

Entonces, mientras compila la función, el compilador no puede determinar si el comportamiento es indefinido o bien definido. ¡Lo mejor que puede hacer es advertirle de un problema potencial y lo hace!

Un ejemplo de código:

#include <iostream> struct A { int m_i; A():m_i(10) { } }; A& foo() { A a; a.m_i = 20; return a; } int main() { foo(); //This is not an Undefined Behavior, return value was never used. A ref = foo(); //Still not an Undefined Behavior, return value not yet used. std::cout<<ref.m_i; //Undefined Behavior, returned value is used. return 0; }

Referencia al estándar C ++:
sección 3.8

Antes de que la vida útil de un objeto haya comenzado, pero después de que el almacenamiento que ocupará el objeto se haya asignado 34) o, una vez que haya finalizado la vida útil de un objeto y antes de que el almacenamiento que ocupa el objeto se reutilice o libere, cualquier puntero que se refiere a la ubicación de almacenamiento donde se ubicará o se ubicará el objeto, pero solo se puede usar de manera limitada. Dicho puntero se refiere al almacenamiento asignado (3.7.3.2), y el uso del puntero como si el puntero fuera de type void* , está bien definido. Dicho puntero puede ser referenciado pero el valor de l resultante solo puede usarse de manera limitada , como se describe a continuación. Si el objeto será o fue de un tipo de clase con un destructor no trivial, y el puntero se utiliza como el operando de una expresión de eliminación, el programa tiene un comportamiento indefinido. Si el objeto será o no perteneció a un tipo de clase POD, el programa tiene un comportamiento indefinido si:

- .......