Usar variáveis ​​de vazamento goto?

94

É verdade que gotosalta pedaços de código sem chamar destruidores e coisas assim?

por exemplo

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

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

Não vai xvazar?

Lightness Races in Orbit
fonte
Relacionado: stackoverflow.com/questions/1258201/… (mas eu queria fazer isso do zero, de forma limpa!)
Lightness Races in Orbit
15
O que isso "Won't x be leaked"significa? O tipo de xé um tipo de dados integrado. Por que você não escolhe um exemplo melhor?
Nawaz
2
@Nawaz: O exemplo é perfeito do jeito que está. Quase toda vez que falo com alguém sobre goto, eles pensam que até mesmo as variáveis ​​automáticas de duração do armazenamento são de alguma forma "vazadas". Que você e eu saibamos de outra forma é completamente fora de questão.
Lightness Races in Orbit
1
@David: Concordo que esta pergunta faz muito mais sentido quando a variável tem um destruidor não trivial ... e eu olhei na resposta de Tomalak e encontrei um exemplo assim. Além disso, embora um intnão possa vazar, pode estar vazando . Por exemplo: void f(void) { new int(5); }vazamentos e int.
Ben Voigt,
Por que não mudar a pergunta para algo como "No exemplo dado, o caminho de execução do código será transferido de f () para main () sem limpar a pilha e outras funcionalidades de retorno da função? Importaria se um destruidor precisasse ser chamado? É o mesmo em C? " Isso manteria a intenção da pergunta, ao mesmo tempo que evitaria os possíveis equívocos?
Jack V.

Respostas:

210

Aviso: Esta resposta refere-se a C ++ única ; as regras são bastante diferentes em C.


Não vai xvazar?

Não, absolutamente não.

É um mito que gotoé alguma construção de baixo nível que permite que você substitua os mecanismos de escopo embutidos do C ++. (Se houver alguma coisa, é longjmpque pode estar sujeito a isso.)

Considere a seguinte mecânica que evita que você faça "coisas ruins" com rótulos (o que inclui caserótulos).


1. Escopo do rótulo

Você não pode saltar entre as funções:

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

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

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

[n3290: 6.1/1]:[..] O escopo de um rótulo é a função na qual ele aparece. [..]


2. Inicialização do objeto

Você não pode pular na inicialização do objeto:

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

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

Se você voltar para a inicialização do objeto, a "instância" anterior do objeto será destruída :

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]:[..] A transferência de um loop, de um bloco ou de volta após uma variável inicializada com duração de armazenamento automático envolve a destruição de objetos com duração de armazenamento automático que estão no escopo no ponto transferido, mas não no ponto transferido para . [..]

Você não pode saltar para o escopo de um objeto, mesmo que não seja explicitamente inicializado:

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

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

... exceto para certos tipos de objetos , que a linguagem pode manipular independentemente porque eles não requerem uma construção "complexa":

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

// OK

[n3290: 6.7/3]:É possível transferir para um bloco, mas não de uma forma que ignore as declarações com a inicialização. Um programa que salta de um ponto onde uma variável com duração de armazenamento automático não está no escopo para um ponto onde está no escopo é malformado, a menos que a variável tenha tipo escalar, tipo de classe com um construtor padrão trivial e um destruidor trivial, um Versão qualificada de cv de um desses tipos ou uma matriz de um dos tipos anteriores e é declarada sem um inicializador. [..]


3. O salto obedece ao escopo de outros objetos

Da mesma forma, objetos com duração de armazenamento automático não são "vazados" quando você está fora de seu escopogoto :

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

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

lol:
   return 0;
}

// *T~T

[n3290: 6.6/2]:Ao sair de um escopo (embora realizado), os objetos com duração de armazenamento automático (3.7.3) que foram construídos naquele escopo são destruídos na ordem inversa de sua construção. [..]


Conclusão

Os mecanismos acima garantem que gotovocê não quebra o idioma.

Claro, isso não significa automaticamente que você "deveria" uso gotopara qualquer problema, mas não significa que ele não é tão "mal", como as ligações mito comum que as pessoas acreditam.

Lightness Races in Orbit
fonte
8
Você pode notar que C não impede que todas essas coisas perigosas aconteçam.
Daniel,
13
@Daniel: A pergunta e a resposta são muito específicas sobre C ++, mas é justo. Talvez possamos ter outro FAQ dissipando o mito de que C e C ++ são a mesma coisa;)
Lightness Races in Orbit
3
@Tomalak: Não acho que estamos discordando aqui. Muitas das respostas fornecidas no SO estão explicitamente documentadas em algum lugar. Eu estava apenas enfatizando que pode ser tentador para um programador C ver essa resposta e presumir que, se funciona em C ++, deve funcionar da mesma forma em C.
Daniel
2
Você também pode querer acrescentar que todas essas coisas de inicialização são as mesmas para rótulos de caso.
PlasmaHH
12
Uau, eu tinha acabado de presumir que a semântica do C ++ estava quebrada para goto, mas eles são surpreendentemente lógicos! Ótima resposta.
Joseph Garvin