¿Utilizará goto variables de fuga?

94

Es cierto que goto salta a través de bits de código sin llamar a los destructores y cosas?

p.ej

void f() {
   int x = 0;
   goto lol;
}

int main() {
   f();
lol:
   return 0;
}

¿No xse filtrará?

Carreras de ligereza en órbita
fuente
Relacionado: stackoverflow.com/questions/1258201/… (¡pero quería hacerlo desde cero, limpiamente!)
Lightness Races in Orbit
15
¿Qué "Won't x be leaked"significa? El tipo de xes un tipo de datos integrado. ¿Por qué no eliges un mejor ejemplo?
Nawaz
2
@Nawaz: El ejemplo es perfecto tal como está. Casi cada vez que hablo con alguien goto, piensa que incluso las variables de duración del almacenamiento automático se "filtran" de alguna manera. Que tú y yo sepamos lo contrario está completamente fuera del punto.
Lightness Races in Orbit
1
@David: Estoy de acuerdo en que esta pregunta tiene mucho más sentido cuando la variable tiene un destructor no trivial ... y miré la respuesta de Tomalak y encontré un ejemplo. Además, aunque intno puede tener fugas, puede tener fugas . Por ejemplo: void f(void) { new int(5); }fugas an int.
Ben Voigt
¿Por qué no cambiar la pregunta a algo como "En el ejemplo dado, la ruta de ejecución del código se transferirá de f () a main () sin borrar la pila y otras funciones de retorno de la función? ¿Importaría si fuera necesario un destructor? ¿Es lo mismo en C? " ¿Ambos mantendrían la intención de la pregunta y evitarían los posibles malentendidos?
Jack V.

Respuestas:

210

Advertencia: esta respuesta se refiere a C ++ solamente ; las reglas son bastante diferentes en C.


¿No xse filtrará?

No absolutamente no.

Es un mito que gotoes una construcción de bajo nivel que le permite anular los mecanismos de alcance integrados de C ++. (En todo caso, es longjmpque puede ser propenso a esto).

Considere los siguientes mecanismos que le impiden hacer "cosas malas" con las etiquetas (que incluye caseetiquetas).


1. Alcance de la etiqueta

No puede saltar entre funciones:

void f() {
   int x = 0;
   goto lol;
}

int main() {
   f();
lol:
   return 0;
}

// error: label 'lol' used but not defined

[n3290: 6.1/1]:[..] El alcance de una etiqueta es la función en la que aparece. [..]


2. Inicialización de objetos

No puede saltar a través de la inicialización de objetos:

int main() {
   goto lol;
   int x = 0;
lol:
   return 0;
}

// error: jump to label ‘lol’
// error:   from here
// error:   crosses initialization of ‘int x’

Si retrocede a través de la inicialización del objeto, la "instancia" anterior del objeto se destruye :

struct T {
   T() { cout << "*T"; }
  ~T() { cout << "~T"; }
};

int main() {
   int x = 0;

  lol:
   T t;
   if (x++ < 5)
     goto lol;
}

// Output: *T~T*T~T*T~T*T~T*T~T*T~T

[n3290: 6.6/2]:[..] La transferencia fuera de un bucle, fuera de un bloque o retrocediendo más allá de una variable inicializada con duración de almacenamiento automático implica la destrucción de objetos con duración de almacenamiento automático que están dentro del alcance en el punto transferido pero no en el punto transferido a . [..]

No puede saltar al alcance de un objeto, incluso si no está inicializado explícitamente:

int main() {
   goto lol;
   {
      std::string x;
lol:
      x = "";
   }
}

// error: jump to label ‘lol’
// error:   from here
// error:   crosses initialization of ‘std::string x’

... excepto para ciertos tipos de objetos , que el lenguaje puede manejar independientemente porque no requieren una construcción "compleja":

int main() {
   goto lol;
   {
      int x;
lol:
      x = 0;
   }
}

// OK

[n3290: 6.7/3]:Es posible transferir a un bloque, pero no de una manera que omita las declaraciones con la inicialización. Un programa que salta desde un punto donde una variable con duración de almacenamiento automático no está dentro del alcance a un punto donde está dentro del alcance está mal formado a menos que la variable tenga un tipo escalar, un tipo de clase con un constructor predeterminado trivial y un destructor trivial, un versión calificada por cv de uno de estos tipos, o una matriz de uno de los tipos anteriores y se declara sin un inicializador. [..]


3. El salto se rige por el alcance de otros objetos.

Del mismo modo, los objetos con una duración de almacenamiento automático son no "filtrado" cuando gotofuera de su alcance :

struct T {
   T() { cout << "*T"; }
  ~T() { cout << "~T"; }
};

int main() {
   {
      T t;
      goto lol;
   }

lol:
   return 0;
}

// *T~T

[n3290: 6.6/2]:Al salir de un alcance (sin importar cómo se haya logrado), los objetos con duración de almacenamiento automático (3.7.3) que se han construido en ese alcance se destruyen en el orden inverso de su construcción. [..]


Conclusión

Los mecanismos anteriores aseguran que gotono le permita romper el lenguaje.

Por supuesto, esto no significa automáticamente que "debería" usarlo gotopara un problema dado, pero significa que no es tan "malvado" como el mito común hace creer a la gente.

Carreras de ligereza en órbita
fuente
8
Puede notar que C no evita que sucedan todas estas cosas peligrosas.
Daniel
13
@Daniel: La pregunta y la respuesta son muy específicamente sobre C ++, pero es un punto justo. Tal vez podamos tener otro FAQ que disipe el mito de que C y C ++ son lo mismo;)
Lightness Races in Orbit
3
@Tomalak: No creo que estemos en desacuerdo aquí. Muchas de las respuestas dadas sobre SO están explícitamente documentadas en alguna parte. Estaba haciendo el punto de que podría ser tentador para un programador C para ver esta respuesta y asumir que si funciona en C ++, que debería funcionar de manera similar en C
Daniel
2
También es posible que desee agregar que todos estos elementos de inicialización de salto son los mismos para las etiquetas de caso.
PlasmaHH
12
Vaya, acababa de asumir que la semántica de C ++ estaba rota para goto, ¡pero son sorprendentemente cuerdos! Gran respuesta.
Joseph Garvin