2010-07-07 9 views
12

Supongamos que el siguiente fragmento de códigoC++ captura referencia colgando

struct S { 
    S(int & value): value_(value) {} 
    int & value_; 
}; 

S function() { 
    int value = 0; 
    return S(value); // implicitly returning reference to local value 
} 

compilador no produce advertencia (-Wall), este error puede ser difícil de atrapar.

¿Qué herramientas están ahí para ayudar a atrapar a este tipo de problemas

+0

Eso ni siquiera debería compilarse . Estás vinculando una referencia a un literal entero. ¿Qué compilador estás usando? – jalf

+0

@jalf lo siento, debería haber sido S (valor) – Anycorn

+1

¿Por qué necesita herramientas para detectar esto? Simplemente no escriba un código obviamente erróneo como ese. Es muy, muy poco probable que una clase con un miembro de referencia sea correcta. –

Respuesta

8

Existen soluciones basadas en tiempo de ejecución que instrumentan el código para comprobar accesos de puntero no válidos. Hasta ahora, solo he utilizado mudflap (que está integrado en GCC desde la versión 4.0).mudflap intenta rastrear cada puntero (y referencia) en el código y verifica cada acceso si el puntero/referencia realmente apunta a un objeto vivo de su tipo base. He aquí un ejemplo:

#include <stdio.h> 
struct S { 
    S(int & value): value_(value) {} 
    int & value_; 
}; 

S function() { 
    int value = 0; 
    return S(value); // implicitly returning reference to local value 
} 
int main() 
{ 
    S x=function(); 
    printf("%s\n",x.value_); //<-oh noes! 
} 

compilar este con mudflap habilitado:

g++ -fmudflap s.cc -lmudflap 

y en funcionamiento da:

$ ./a.out 
******* 
mudflap violation 1 (check/read): time=1279282951.939061 ptr=0x7fff141aeb8c size=4 
pc=0x7f53f4047391 location=`s.cc:14:24 (main)' 
     /opt/gcc-4.5.0/lib64/libmudflap.so.0(__mf_check+0x41) [0x7f53f4047391] 
     ./a.out(main+0x7f) [0x400c06] 
     /lib64/libc.so.6(__libc_start_main+0xfd) [0x7f53f358aa7d] 
Nearby object 1: checked region begins 332B before and ends 329B before 
mudflap object 0x703430: name=`argv[]' 
bounds=[0x7fff141aecd8,0x7fff141aece7] size=16 area=static check=0r/0w liveness=0 
alloc time=1279282951.939012 pc=0x7f53f4046791 
Nearby object 2: checked region begins 348B before and ends 345B before 
mudflap object 0x708530: name=`environ[]' 
bounds=[0x7fff141aece8,0x7fff141af03f] size=856 area=static check=0r/0w liveness=0 
alloc time=1279282951.939049 pc=0x7f53f4046791 
Nearby object 3: checked region begins 0B into and ends 3B into 
mudflap dead object 0x7089e0: name=`s.cc:8:9 (function) int value' 
bounds=[0x7fff141aeb8c,0x7fff141aeb8f] size=4 area=stack check=0r/0w liveness=0 
alloc time=1279282951.939053 pc=0x7f53f4046791 
dealloc time=1279282951.939059 pc=0x7f53f4046346 
number of nearby objects: 3 
Segmentation fault 

Un par de puntos a tener en cuenta:

  1. mudflap se puede ajustar en qué exactamente debería comprobar una y hazlo lea http://gcc.gnu.org/wiki/Mudflap_Pointer_Debugging para más detalles.
  2. El comportamiento predeterminado es generar un SIGSEGV en una violación, esto significa que puede encontrar la violación en su depurador.
  3. mudflap puede ser una perra, en particular cuando estás interactuando con bibliotecas que no están compiladas con soporte mudflap.
  4. No ladrará en el lugar donde se crea la referencia colgante (devolver S (valor)), solo cuando se desreferencia la referencia. Si lo necesita, necesitará una herramienta de análisis estático.

P.S. Una cosa a tener en cuenta era agregar NO PORTABLE al constructor de copia de S(), que afirma que value_ no está vinculado a un entero con una vida útil más corta (por ejemplo, si * está ubicado en un "más viejo" ranura de la pila que el número entero está obligado a). Esto es altamente específico para la máquina y posiblemente sea complicado hacerlo bien, por supuesto, pero debería estar bien, siempre y cuando solo sea para la depuración.

+0

gracias. Lo intentaré. No sabía acerca de esta herramienta antes de – Anycorn

1

No creo que cualquier herramienta estática puede coger eso, pero si se utiliza Valgrind junto con algunas pruebas unitarias o lo que sea de código se bloquea (fallo seg), puede encontrar fácilmente dónde se hace referencia a la memoria y dónde se asignó originalmente.

+1

Mientras que valgrind es _awesome_, como todo está asignado en la pila en este caso, valgrind no captará el problema. –

1

Su código ni siquiera debería compilar. Los compiladores que conozco no compilarán el código o, por lo menos, lanzarán una advertencia.

En su lugar, significó return S(value), luego, por el bien del cielo COPIA PEGUE EL CÓDIGO QUE PUBLICA AQUÍ.

Reescribir e introducir errores tipográficos solo significa que es imposible para nosotros adivinar de qué errores está preguntando acerca de, y cuáles fueron los accidentes que debemos ignorar.

Cuando publique una pregunta en cualquier lugar de Internet, si esa pregunta incluye el código, PUBLIQUE EL CÓDIGO EXACTO.

Ahora, asumiendo que esto fue realmente un error tipográfico, el código es perfectamente legal, y no hay ninguna razón por la cual cualquier herramienta debería advertirle.

Siempre que no intente desreferenciar la referencia colgante, el código es perfectamente seguro.

Es posible que algunas herramientas de análisis estático (Valgrind o MSVC con/analice, por ejemplo) puedan advertirle sobre esto, pero no parece tener mucho sentido porque no está haciendo nada incorrecto. Estás devolviendo un objeto que contiene una referencia colgante. No está devolviendo directamente una referencia a un objeto local (sobre qué compiladores normalmente llama el ), sino un objeto de nivel superior con un comportamiento que podría hacer que sea perfectamente seguro de usar, aunque contenga una referencia a un objeto local que sea fuera de alcance.

4

Creo que no es posible detectar todos estos, aunque algunos compiladores pueden dar advertencias en algunos casos.

Es bueno recordar que las referencias son en realidad punteros bajo el capó, y muchos de los escenarios de rodaje-auto-en-pie posibles con los punteros todavía son posibles ..

para aclarar lo que quiero decir acerca de "punteros debajo del capó ", toma las siguientes dos clases. Uno usa referencias, los otros indicadores.

class Ref 
{ 
    int &ref; 
public: 
    Ref(int &r) : ref(r) {}; 
    int get() { return ref; }; 
}; 

class Ptr 
{ 
    int *ptr; 
public: 
    Ptr(int *p) : ptr(p) {}; 
    int get() { return *ptr; }; 
}; 

Ahora, compare en el código generado para los dos.

@@[email protected]$bctr$qri proc near // Ref::Ref(int &ref) 
    push  ebp 
    mov  ebp,esp 
    mov  eax,dword ptr [ebp+8] 
    mov  edx,dword ptr [ebp+12] 
    mov  dword ptr [eax],edx 
    pop  ebp 
    ret 

@@[email protected]$bctr$qpi proc near // Ptr::Ptr(int *ptr) 
    push  ebp 
    mov  ebp,esp 
    mov  eax,dword ptr [ebp+8] 
    mov  edx,dword ptr [ebp+12] 
    mov  dword ptr [eax],edx 
    pop  ebp 
    ret 

@@[email protected]$qv proc near // int Ref:get() 
    push  ebp 
    mov  ebp,esp 
    mov  eax,dword ptr [ebp+8] 
    mov  eax,dword ptr [eax] 
    mov  eax,dword ptr [eax] 
    pop  ebp 
    ret 

@@[email protected]$qv proc near // int Ptr::get() 
    push  ebp 
    mov  ebp,esp 
    mov  eax,dword ptr [ebp+8] 
    mov  eax,dword ptr [eax] 
    mov  eax,dword ptr [eax] 
    pop  ebp 
    ret 

¿Encuentra la diferencia? No hay ninguno.

+0

Definitivamente no son punteros bajo el capó, no tienen ubicación (no son indirectos), son un alias. – oscode

+4

@Steven - ¡Es hora de que mires debajo del capó! He actualizado la respuesta. – Roddy

+0

Tienes razón y he aprendido algo así que te agradezco, pero citando la página 89 de la tercera edición de Effective C++, "Si miras bajo el capó de un compilador de C++, encontrarás que las referencias se implementan típicamente como punteros". ¿Puede alguien elaborar? – oscode

1

Hay una directriz sigo después de haber sido golpeado por esta cosa exacta:

Cuando una clase tiene un elemento de referencia (o un puntero a algo que puede tener una vida que no controlas), hacer que el objeto no se pueda copiar.

De esta manera, reduce las posibilidades de escapar del alcance con una referencia que cuelga.

+0

gracias. Desafortunadamente, no tengo control sobre classis en esa biblioteca. Tenía la esperanza de encontrar la herramienta de análisis estático – Anycorn

2

Tienes que utilizar una tecnología basada en la instrumentación en tiempo de compilación. Si bien valgrind podía verificar todas las llamadas a funciones en tiempo de ejecución (malloc, libre), no podía verificar solo código.

Según su arquitectura, IBM PurifyPlus encuentra alguno de estos problemas. Por lo tanto, debe encontrar una licencia válida (o usar una de su compañía) para usarla o probarla con la versión de prueba.

0

Este es un código perfectamente válido.

Si llama a su función y vincula el temporal a una referencia constante, el alcance se prolonga.

const S& s1 = function(); // valid 

S& s2 = function(); // invalid 

Esto está explícitamente permitido en el C++ standard.

Ver 12.2.4:

Hay dos contextos en los que los temporales se destruyen en un punto diferente al final de la expresión completa.

y 12.2.5:

El segundo contexto es cuando una referencia está unido a un temporal. El temporal al que está vinculada la referencia o el temporal que es el objeto completo de un subobjeto al que está vinculada la referencia persiste durante el tiempo de vida de la referencia excepto: [...]

+1

S puede permanecer en el alcance, pero el objeto (un int en este caso) al que hace referencia S saldrá del ámbito. – MSN

Cuestiones relacionadas