2010-12-01 11 views
8

que he encontrado recientemente que la mayoría de los errores en mi C++ programas son de una forma similar al ejemplo siguiente:¿Cómo detectar la referencia constante a problemas temporales durante la compilación o el tiempo de ejecución?

#include <iostream> 

class Z 
{ 
public: 
Z(int n) : n(n) {} 
int n; 
}; 

class Y 
{ 
public: 
Y(const Z& z) : z(z) {} 
const Z& z; 
}; 

class X 
{ 
public: 
X(const Y& y) : y(y) {} 
Y y; 
}; 

class Big 
{ 
public: 
Big() 
{ 
    for (int i = 0; i < 1000; ++i) { a[i] = i + 1000; } 
} 
int a[1000]; 
}; 

X get_x() { return X(Y(Z(123))); } 

int main() 
{ 
X x = get_x(); 
Big b; 
std::cout << x.y.z.n << std::endl; 
} 

SALIDA: 1000

que se puede esperar de este programa para dar salida a 123 (el valor de xyzn establecido en get_x()) pero la creación de "Big b" sobrescribe la Z temporal. Como resultado , la referencia a la Z temporal en el objeto Y ahora es sobrescrito con Big b, y por lo tanto la salida no es lo que yo esperaría .

Cuando compilé este programa con gcc 4.5 con la opción "-Wall", no dio ninguna advertencia.

La solución es obviamente para eliminar la referencia desde el miembro de Z en el Y. clase Sin embargo, a menudo la clase Y es parte de una biblioteca que no tengo desarrollado (boost :: fusión más recientemente), y además la situación es mucho más complicada que este ejemplo que he dado.

Esto tiene algún tipo de opción para gcc, o cualquier software adicional que me permita detectar tales problemas preferiblemente en tiempo de compilación, pero incluso el tiempo de ejecución sería mejor que nada?

Gracias,

Clinton

+0

Estoy un poco sorprendido de que obtenga 1000 en lugar de 1123. – Gabe

+0

Gabe: ¿Por qué 1123? El constructor de Big establece el elemento zeroth = 1000. No agrega 1000 al elemento zeroth. Si fue {a [i] + = i + 1000; } Pude ver de dónde vienes. – Clinton

+0

Relacionado: https://stackoverflow.com/q/42340073/946850 – krlmlr

Respuesta

2

Presenté tales casos en la lista de distribución de clang-dev hace unos meses, pero nadie tenía tiempo para trabajar en ella en ese entonces (y yo tampoco, Desafortunadamente).

Argyrios Kyrtzidis está trabajando en él sin embargo, y aquí es su última actualización en la materia (30 de noviembre 23h04 GMT):

me volvieron cometer el anterior, tanto mejor solución en http://lists.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20101129/036875.html. p.para

struct S { int x; }; 

int &get_ref() { S s; S &s2 = s; int &x2 = s2.x; return x2; } 

obtenemos

t3.cpp:9:10: warning: reference to stack memory associated with local variable 's' returned 
    return x2; 
     ^~ 
t3.cpp:8:8: note: binding reference variable 'x2' here 
    int &x2 = s2.x; 
    ^ ~~ 
t3.cpp:7:6: note: binding reference variable 's2' here 
    S &s2 = s; 
    ^ ~ 
1 warning generated. 

El intento anterior no pasó la prueba de auto-hosting, así que espero que va a pasar este intento. Estoy muy contento de que Argyrios esté investigando de todos modos :)

Aún no es perfecto, ya que es un problema bastante complicado de abordar (me recuerda al alias del puntero de alguna manera), pero no obstante es una gran paso en la dirección correcta.

¿Podría probar su código con esta versión de Clang? Estoy bastante seguro de que Argyrios apreciaría los comentarios (ya sea que se detecten o no).

0

[Editado tercera bala para demostrar una técnica que puede ayudar a] Esta es la madriguera del conejo se desciende cuando una lengua permite el paso de argumentos por valor o referencia con la misma persona que llama sintaxis. Tiene las siguientes opciones:

  • Cambie los argumentos a referencias no const. Un valor temporal no coincidirá con un tipo de referencia no constante.

  • Elimine las referencias por completo en los casos en que esto sea posible. Si sus referencias de const no indican un estado lógicamente compartido entre la persona que llama y la llamada (si lo hicieran, este problema no ocurriría con mucha frecuencia), probablemente se insertaron en un intento de evitar la copia ingenua de tipos complejos. Los compiladores modernos tienen optimizaciones avanzadas para el copiado optimizado que hacen que el valor por transferencia sea tan eficiente como la referencia de paso a paso en la mayoría de los casos; ver http://cpp-next.com/archive/2009/08/want-speed-pass-by-value para una gran explicación. La elusión de copia claramente no se realizará si está transfiriendo los valores a funciones externas de la biblioteca que pueden modificar los temporales, pero si ese fuera el caso, entonces no los está pasando como referencia de referencias o descartando deliberadamente la const -ness en la versión original. Esta es mi solución preferida, ya que le permite al compilador preocuparse por la optimización de copia y me libera de preocuparme por otras fuentes de error en el código.

  • Si su compilador admite referencias rvalue, úselos.Si al menos se puede editar los tipos de parámetros de las funciones en el que preocuparse por este problema, se puede definir una metaclase envoltorio de este modo:

plantilla < nombre de tipo T> need_ref clase {

T & ref_ ;

pública:

need_ref (T & & x) {/ * nada * /}

need_ref (T & x): ref_ (x) {/ * nada * /}

operador T &() {return ref_; }

};

y luego reemplace los argumentos de tipo T & con argumentos de tipo need_ref. Por ejemplo, si se define la siguiente

usuario de la clase {

int & z;

pública:

usuario (need_ref < int> arg): z (arg) {/ * nada * /}

};

luego puede inutilizar de forma segura un objeto de tipo usuario con el código de la forma "int a = 1, b = 2; usuario ua (a);", pero si intenta inicializar como "usuario suma (a + b) "o" usuario cinco (5) "su compilador debería generar un error de referencia no inicializado dentro de la primera versión del constructor need_ref(). La técnica obviamente no está limitada a los constructores, y no impone ninguna sobrecarga de tiempo de ejecución.

+0

No puedo cambiar el código de la biblioteca. Mi pregunta no era cómo solucionar el problema (sé cómo). La pregunta era cómo detectar el problema. Un problema como este me tomó varias horas para detectarlo y un minuto para arreglarlo. Es la detección con la que necesito ayuda, no la solución. – Clinton

+0

He editado mi respuesta para mostrar una técnica que generará errores en tiempo de compilación si puede modificar los tipos de argumentos en al menos un nivel de la pila de llamadas (por ejemplo, los constructores X, Y,/o/Z en su ejemplo anterior). Si no puede editar ningún código, entonces creo que está atascado con versiones experimentales de clang, ya que no conozco ningún conmutador de línea de comandos que permita este tipo de prueba en ningún compilador principal. – spillner

-1

El problema aquí es el código

Y(const Z& z) : z(z) {} 

como el 'z' miembro se ha inicializado con una referencia al parámetro formal 'z'. Una vez que el constructor devuelve la referencia se refiere a un objeto que ya no es válido.

No creo que el compilador detecte o pueda en muchos casos detectar tales fallas lógicas. La solución, entonces IMO es, obviamente, estar al tanto de tales clases y usarlas de una manera apropiada para su diseño. Esto realmente debería ser documentado por el proveedor de la biblioteca.

Por cierto, es mejor nombrar al miembro 'Y :: z' como 'Y :: mz' si es posible. La expresión 'z (z)' no es muy atractiva

+0

Chubsdad: Acabas de repetir lo que hice en la pregunta aquí. Ya he mencionado: "La solución es, obviamente, eliminar la referencia del miembro Z en la clase Y. Sin embargo, a menudo la clase Y es parte de una biblioteca que no he desarrollado (boost :: fusion más recientemente), y en Además, la situación es mucho más complicada que este ejemplo que he dado ". – Clinton

Cuestiones relacionadas