2011-09-08 16 views
7

Tenía la esperanza de que alguien pudiera aclarar exactamente qué se entiende por comportamiento indefinido en C++. Dada la siguiente definición de clase:Comportamiento indefinido con const_cast

class Foo 
{ 
public: 
    explicit Foo(int Value): m_Int(Value) { } 
    void SetValue(int Value) { m_Int = Value; } 

private: 
    Foo(const Foo& rhs); 
    const Foo& operator=(const Foo& rhs); 

private: 
    int m_Int; 
}; 

Si he entendido bien los dos const_casts a la vez una referencia y un puntero en el código siguiente se eliminará el const-dad del objeto original de tipo Foo, pero cualquier intento hecho para modificar este objeto a través del puntero o la referencia dará como resultado un comportamiento indefinido.

int main() 
{ 
    const Foo MyConstFoo(0); 
    Foo& rFoo = const_cast<Foo&>(MyConstFoo); 
    Foo* pFoo = const_cast<Foo*>(&MyConstFoo); 

    //MyConstFoo.SetValue(1); //Error as MyConstFoo is const 
    rFoo.SetValue(2);   //Undefined behaviour 
    pFoo->SetValue(3);   //Undefined behaviour 

    return 0; 
} 

Lo que me es desconcertante es por qué esto parece funcionar y modificará el objeto constante original, pero ni siquiera me rápido con una advertencia para notificarme que este comportamiento no está definido. Sé que const_casts son, a grandes rasgos, mal visto, pero puedo imaginar un caso en el que se nota la falta de conciencia de que la conversión de estilo C puede resultar en una const_cast está hecho podría ocurrir sin, por ejemplo:

Foo& rAnotherFoo = (Foo&)MyConstFoo; 
Foo* pAnotherFoo = (Foo*)&MyConstFoo; 

rAnotherFoo->SetValue(4); 
pAnotherFoo->SetValue(5); 

¿En qué circunstancias podría este comportamiento causar un error de tiempo de ejecución fatal? ¿Hay alguna configuración del compilador que pueda configurar para advertirme sobre este comportamiento (potencialmente) peligroso?

NB: Yo uso MSVC2008.

+1

posible duplicado de [¿Es este comportamiento indefinido const_cast?] (Http://stackoverflow.com/questions/5969867/is-this-const-cast-undefined-behavior) – spraff

+1

Si está usando un molde de estilo C , cualquier compilador decente debería advertirte que estás "descartando calificadores". Asegúrate de habilitar siempre todas las advertencias. –

+0

Demonios nasales. ¿Qué más? – PlasmaHH

Respuesta

11

Esperaba que alguien pudiera aclarar exactamente qué se entiende por comportamiento indefinido en C++.

Técnicamente, "Comportamiento no definido" significa que el lenguaje no define ninguna semántica para hacer tal cosa.

En la práctica, esto generalmente significa "no lo hagas; se puede romper cuando tu compilador realiza optimizaciones, o por otros motivos".

Lo que me es desconcertante es por qué esto parece funcionar y modificará el objeto constante originales, pero ni siquiera me rápido con una advertencia para notificarme que este comportamiento no está definido.

En este ejemplo concreto, intentar modificar cualquier objeto mutable no pueden "aparecer a trabajar", o puede sobrescribir la memoria que no pertenece al programa o que pertenece a [parte de] algún otro objeto , porque el objeto no mutable podría haberse optimizado en tiempo de compilación, o puede existir en algún segmento de datos de solo lectura en la memoria.

Los factores que pueden llevar a que ocurran estas cosas son simplemente demasiado complejos para enumerarlos. Considere el caso de desreferenciar un puntero no inicializado (también UB): el "objeto" con el que está trabajando tendrá una dirección de memoria arbitraria que depende del valor que haya estado en la memoria en la ubicación del puntero; ese "valor" es potencialmente dependiente de invocaciones de programas previos, trabajos previos en el mismo programa, almacenamiento de información proporcionada por el usuario, etc. Simplemente no es factible tratar de racionalizar los posibles resultados de invocar el Comportamiento Indefinido, así que, una vez más, generalmente no lo hacemos moleste y en su lugar solo diga "no lo haga".

Lo que me es desconcertante es por qué esto parece funcionar y modificará el objeto constante originales , pero ni siquiera me rápido con una advertencia para notificarme que este comportamiento no está definido.

no Como una complicación adicional, los compiladores se requieren para diagnosticar (emitir avisos/errores) para un comportamiento indefinido, porque el código que invoca un comportamiento indefinido no es el mismo que el código que se forma mal (es decir, explícitamente ilegal). En muchos casos, no es posible que el compilador detecte UB, por lo que este es un área donde es responsabilidad del programador escribir el código correctamente.

El tipo de sistema — que incluye la existencia y la semántica de la palabra clave const — presenta una protección básica contra el código de escritura que se romperá; un programador de C++ siempre debe tener en cuenta que subvirtiendo este sistema — p. por la piratería de distancia const ness — se hace bajo su propio riesgo, y en general es una mala idea. ™

puedo imaginar un caso en el que la falta de conciencia de que la conversión de estilo C puede resultar en una const_cast está haciendo podría ocurrir sin ser notado

Absolutamente. Con niveles de advertencia establecidos lo suficientemente altos, un compilador sane puede optar por advertirle sobre esto, pero no tiene por qué y no puede. En general, esta es una buena razón por la cual los moldes de estilo C son mal vistos, pero aún así son compatibles con la compatibilidad con C. Es solo una de esas cosas desafortunadas.

2

Un ejemplo clásico sería tratar de modificar un literal de cadena const, que puede existir en un segmento de datos protegidos.

0

Los compiladores pueden colocar datos const en partes de solo lectura de la memoria por razones de optimización e intentar modificar estos datos dará como resultado UB.

0

Los datos estáticos y const a menudo se almacenan en otra parte de su programa que las variables locales. Para las variables const, estas áreas suelen estar en modo de solo lectura para imponer la constness de las variables. Intentar escribir en una memoria de solo lectura produce un "comportamiento indefinido" porque la reacción depende de su sistema operativo. "Comportamiento indefinido" significa que el idioma no especifica cómo se manejará este caso.

Si desea una explicación más detallada sobre la memoria, le sugiero que lea this. Es una explicación basada en UNIX, pero se utilizan mecanismos similares en todos los sistemas operativos.

4

Comportamiento no definido significa literalmente eso: comportamiento que no está definido por el estándar de lenguaje. Suele ocurrir en situaciones donde el código está haciendo algo mal, pero el error no puede ser detectado por el compilador. La única forma de detectar el error sería introducir una prueba en tiempo de ejecución, lo que dañaría el rendimiento. Entonces, en cambio, la especificación del lenguaje le dice que no debe hacer ciertas cosas y, si lo hace, entonces podría pasar cualquier cosa.

En el caso de la escritura a un objeto constante, utilizando const_cast subvertir los controles de tiempo de compilación, existen tres posibles escenarios:

  • se trata igual que un objeto no constante, y la escritura a lo modifica;
  • se coloca en la memoria protegida contra escritura, y escribir en ella provoca un error de protección;
  • se reemplaza (durante la optimización) por valores constantes incrustados en el código compilado, por lo que después de escribir en él, todavía tendrá su valor inicial.

En su prueba, terminó en el primer escenario: el objeto se creó (casi con certeza) en la pila, que no está protegido contra escritura. Puede encontrar que obtiene el segundo escenario si el objeto es estático y el tercero si habilita más optimización.

En general, el compilador no puede diagnosticar este error; no hay forma de saber (excepto en ejemplos muy simples como el suyo) si el destino de una referencia o puntero es constante o no. Depende de usted asegurarse de que solo usa const_cast cuando puede garantizar que es seguro, ya sea cuando el objeto no es constante o cuando de todos modos no va a modificarlo.

4

Lo que me es desconcertante es por qué esto parece funcionar

Eso es lo que significa un comportamiento indefinido.
Puede hacer cualquier cosa, incluso parece que funcione.
Si aumenta su nivel de optimización a su valor máximo, probablemente deje de funcionar.

pero ni siquiera me pide una advertencia para notificarme que este comportamiento no está definido.

En el punto de que eran lo hace la modificación el objeto no es const. En el caso general, no puede decir que el objeto era originalmente una const, por lo tanto, no es posible advertirlo. Incluso si fuera cada afirmación se evalúa por sí misma sin referencia a las demás (al observar ese tipo de generación de advertencia).

En segundo lugar mediante el uso de cast que está diciendo al compilador "Sé lo que estoy haciendo anular todas sus características de seguridad y simplemente hacerlo".

Por ejemplo los siguientes funciona bien: (o va a parecer demasiado (en el tipo de demonio nasal de forma))

float aFloat; 

int& anIntRef = (int&)aFloat; // I know what I am doing ignore the fact that this is sensable 
int* anIntPtr = (int*)&aFloat; 

anIntRef = 12; 
*anIntPtr = 13; 

sé que const_casts son, a grandes rasgos, mal visto

Esa es la forma incorrecta de verlos. Son una forma de documentar en el código que estás haciendo algo extraño que necesita ser validado por personas inteligentes (ya que el compilador obedecerá al elenco sin cuestionarlo). La razón por la que necesita una persona inteligente para validar es que puede conducir a un comportamiento indefinido, pero lo bueno es que ahora lo ha documentado explícitamente en su código (y la gente definitivamente analizará detenidamente lo que ha hecho).

pero puedo imaginar un caso en el que se nota la falta de conciencia de que la conversión de estilo C puede resultar en una const_cast está hecho podría ocurrir sin, por ejemplo:

En C++ no hay necesidad de usa un molde de estilo C.
En el peor de los casos, la conversión C-Style puede reemplazarse por reinterpret_cast <> pero cuando se transfiere el código que desea ver, puede haber usado static_cast <>.El objetivo de los modelos de C++ es hacer que se destaquen para que pueda verlos y de un vistazo detectar la diferencia entre los moldes peligrosos de los moldes benignos.

3

comportamiento indefinido depende de la forma del objeto nació, se puede ver Stephan explicarla en torno 00:10:00 pero en esencia, sigue el código de abajo:

void f(int const &arg) 
{ 
    int &danger(const_cast<int&>(arg); 
    danger = 23; // When is this UB? 
} 

Ahora hay dos casos para llamar f

int K(1); 
f(k); // OK 
const int AK(1); 
f(AK); // triggers undefined behaviour 

para resumir, K nació un no constante, por lo que el reparto es aceptable cuando se llama a f, mientras que AK nació un const entonces ... UB lo es.

Cuestiones relacionadas