16

Estoy experimentando choques extraños. Y me pregunto si es un error en mi código o en el compilador. Cuando compilo el siguiente código C++ con Microsoft Visual Studio 2010 como una versión de lanzamiento optimizado, se estrella en la línea marcada:Fallo en el código C++ debido a un comportamiento indefinido o error del compilador?

struct tup { int x; int y; }; 

class C 
{ 
public: 
    struct tup* p; 

    struct tup* operator--() { return --p; } 
    struct tup* operator++(int) { return p++; } 

    virtual void Reset() { p = 0;} 
}; 

int main() 
{ 
    C c; 
    volatile int x = 0; 
    struct tup v1; 
    struct tup v2 = {0, x}; 

    c.p = &v1; 
    (*(c++)) = v2; 

    struct tup i = (*(--c)); // crash! (dereferencing a NULL-pointer) 
    return i.x; 
} 

Mirando hacia el desmontaje, es obvio que debe bloquearse:

int _tmain(int argc, _TCHAR* argv[]) 
{ 
00CE1000 push  ebp 
00CE1001 mov   ebp,esp 
00CE1003 sub   esp,0Ch 
    C c; 
    volatile int x = 0; 
00CE1006 xor   eax,eax 
00CE1008 mov   dword ptr [x],eax 
    struct tup v1; 
    struct tup v2 = {0, x}; 
00CE100B mov   ecx,dword ptr [x] 

    c.p = &v1; 
    (*(c++)) = v2; 
00CE100E mov   dword ptr [ebp-8],ecx 

    struct tup i = (*(--c)); 
00CE1011 mov   ecx,dword ptr [x] 
00CE1014 mov   dword ptr [v1],eax 
00CE1017 mov   eax,dword ptr [ecx] 
00CE1019 mov   ecx,dword ptr [ecx+4] 
00CE101C mov   dword ptr [ebp-8],ecx 
return i.x; 
} 
00CE101F mov   esp,ebp 
00CE1021 pop   ebp 
00CE1022 ret 

En el desplazamiento 00CE1008, escribe un 0 en x.

en el offset 00CE100B se lee x (el 0) en ecx

en el offset 00CE1017 que elimina referencias que 0-puntero.

Veo dos razones posibles:

  • O hay algún caso sutiles (o no tan sutil?) De un comportamiento indefinido en mi código y el compilador "optimiza" este comportamiento no definido en un accidente.

  • o hay un error del compilador

¿Alguien ve lo que podría causar el problema?

Gracias,

Jonas

EDIT: Para hacer frente a los comentarios relativos a "puntero a la ubicación no válida"

Si cambio v1 ser struct tup v1[10]; y establecer c.p = &v1[0];, entonces habrá sin puntero a una ubicación no válida. Pero todavía puedo observar el mismo comportamiento. El desmontaje parece marginalmente diferente, pero todavía hay un bloqueo y sigue causando la carga de 0 en ecx y la desreferenciación.

EDIT: Conclusión

Por lo tanto, probablemente es un error. Me di cuenta de que el accidente se desvanece si cambio

struct tup* operator--() { return --p; } 

a

struct tup* operator--() { --p; return p; } 

Como bames53 nos dice, el accidente no se produce en VS2011 y concluye que debe haber sido fijada.

nontheless, que decidió presentar ese error por dos razones:

  • El error todavía podría estar presente en VS2011. Tal vez el optimizador simplemente ha cambiado de una manera que mi código ya no dispara el error.(El error parece ser muy sutil, que no se produce cuando se quita la volative o la virtual void Reset())

  • Quiero saber si mi solución es un método confiable para descartar los accidentes, o si los cambios de código en otros lugares pueden reintroducir el error.

Aquí está el enlace:

https://connect.microsoft.com/VisualStudio/feedback/details/741628/error-in-code-generation-for-x86

+1

Estás confundiendo a tantas personas ... Parece un error de compilación para mí. Debería contactarlos en Microsoft Connect. – Blindy

+0

@cambiando 'v2' a' {0, 0}; 'y eliminando' volatile int x' muestra el mismo comportamiento, por lo que no está relacionado. –

+0

@ildjarn: bames53 confirma que ya se ha corregido en vs11. –

Respuesta

17

El código está bien. Es un error del compilador.

El código *(c++) = v2 aumentará posteriormente el c.p y obtendrá el valor original. Ese valor se asignó en la línea anterior y es &v1. Entonces, en efecto, hace v1 = v2;, lo cual está perfectamente bien.

c.p ahora se comporta como una sola past-the-final de una matriz de un elemento que tiene solamente v1, por §5.7p4 de la norma:

Para los fines de estos operadores [+ y - ], un puntero a un objeto no asociado se comporta de la misma manera que un puntero al primer elemento de una matriz de longitud uno con el tipo del objeto como su elemento tipo.

Entonces *(--c) se mueve ese puntero de nuevo a &v1 y elimina referencias a ella, que también está muy bien.

+2

Parece bastante probable; reemplazar C++/- c con (c.p) ++/- (c.p) elimina el bloqueo en vs10, y vs11 no exhibe el bloqueo. – bames53

+0

@ bames53 ¡gracias por registrarse en VC11! –

+0

@ bames53: ¿Ya está arreglado en VC11? Hurra. : -] – ildjarn

-3

Se toma en &v1 partidos comunistas y más tarde usando el operador ++ se avanza ella. No puede confiar en el orden de la pila, por lo tanto, viene un comportamiento indefinido ((&v1)+1 != &v2)

+2

No espero '(& v1) +1 == & v2'. Básicamente, espero '((& v1) +1) -1 == & v1' (¿tal vez mal, como dice Luchian?) – m3tikn0b

+0

@ m3tikn0b: No importa lo que esperas. Eso no es lo que dice el Estándar y no lo que tiene que hacer ninguna implementación. – Puppy

+1

@DeadMG Si los elementos individuales se pueden tratar como una matriz de uno, entonces ((& v1) +1) -1) debe ser igual a v1. Aunque no encontré una cita para esa afirmación de que los elementos individuales se pueden tratar como matrices de uno. – bames53

1

No tiene que ser un error del compilador o UB. No puede ser ni por la forma en que se produjo VS2010.

Estrictamente hablando, su programa muestra un comportamiento bien definido. Sin embargo, eso solo puede ser de acuerdo con el último estándar de C++. VS2010 solo se implementa en comparación con un borrador del estándar que puede no haber incluido esta disposición. Si no lo hizo, entonces su código no es UB, pero VS no es incorrecto para producir UB, ya que esos eran los requisitos del momento en que se realizó.

Por supuesto, si ha sido legal tratar objetos de pila como una matriz de un objeto en C++ 03, entonces es un error del compilador.

Editar: Si sigues obteniendo el bloqueo de una matriz como lo dices, entonces definitivamente es un error del compilador.

Cuestiones relacionadas