Akankah menggunakan variabel kebocoran goto?

94

Benarkah itu gotomelompati bit kode tanpa memanggil destruktor dan benda-benda?

misalnya

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

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

Tidak xakan bocor?

Balapan Ringan dalam Orbit
sumber
Terkait: stackoverflow.com/questions/1258201/… (tapi saya ingin melakukannya dari awal, bersih!)
Lightness Races di Orbit
15
Apa "Won't x be leaked"maksudnya Jenis xadalah tipe data bawaan. Mengapa Anda tidak memilih contoh yang lebih baik?
Nawaz
2
@Nawaz: Contoh itu sempurna apa adanya. Hampir setiap kali saya berbicara dengan seseorang goto, mereka berpikir bahwa bahkan variabel durasi penyimpanan otomatis entah bagaimana "bocor". Bahwa Anda dan saya tahu sebaliknya sama sekali tidak penting.
Balapan Ringan di Orbit
1
@ David: Saya setuju bahwa pertanyaan ini jauh lebih masuk akal ketika variabel memiliki destruktor non-sepele ... dan saya melihat jawaban Tomalak dan menemukan contoh seperti itu. Selain itu, meskipun tidak intbisa bocor, bisa juga bocor . Misalnya: void f(void) { new int(5); }kebocoran int.
Ben Voigt
Mengapa tidak mengubah pertanyaan menjadi sesuatu seperti "Dalam contoh yang diberikan, akankah jalur eksekusi kode ditransfer dari f () ke main () tanpa membersihkan stack dan fungsi kembali-dari-fungsi lainnya? Apakah penting jika destruktor diperlukan disebut? Apakah sama di C? " Akankah keduanya mempertahankan maksud pertanyaan, sambil menghindari kemungkinan kesalahpahaman?
Jack V.

Jawaban:

210

Peringatan: Jawaban ini berkaitan dengan C ++ hanya ; aturannya sangat berbeda di C.


Tidak xakan bocor?

Tidak, sama sekali tidak.

Ini adalah mitos yang gotomerupakan beberapa konstruksi tingkat rendah yang memungkinkan Anda untuk mengganti mekanisme pelingkupan bawaan C ++. (Jika ada, itu longjmpmungkin rentan terhadap ini.)

Pertimbangkan mekanisme berikut yang mencegah Anda melakukan "hal buruk" dengan label (yang termasuk case label).


1. Lingkup label

Anda tidak dapat melompati fungsi:

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

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

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

[n3290: 6.1/1]:[..] Ruang lingkup label adalah fungsi di mana label itu muncul. [..]


2. Inisialisasi objek

Anda tidak dapat melompati inisialisasi objek:

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

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

Jika Anda melompat kembali melintasi inisialisasi objek, "instance" objek sebelumnya akan dimusnahkan :

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]:[..] Transfer keluar dari loop, keluar dari blok, atau kembali melewati variabel yang diinisialisasi dengan durasi penyimpanan otomatis melibatkan penghancuran objek dengan durasi penyimpanan otomatis yang berada dalam lingkup pada titik yang ditransfer dari tetapi tidak pada titik yang ditransfer ke . [..]

Anda tidak dapat melompat ke dalam cakupan suatu objek, meskipun objek tersebut tidak dijalankan secara eksplisit:

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

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

... kecuali untuk jenis objek tertentu , yang dapat ditangani oleh bahasa karena tidak memerlukan konstruksi "rumit":

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

// OK

[n3290: 6.7/3]:Dimungkinkan untuk mentransfer ke dalam blok, tetapi tidak dengan cara yang mengabaikan deklarasi dengan inisialisasi. Sebuah program yang melompat dari titik di mana sebuah variabel dengan durasi penyimpanan otomatis tidak dalam cakupan ke titik di mana ia berada dalam ruang lingkup tidak terbentuk kecuali variabel tersebut memiliki tipe skalar, tipe kelas dengan konstruktor standar yang sepele dan destruktor sepele, a versi yang memenuhi syarat cv dari salah satu jenis ini, atau larik dari salah satu jenis sebelumnya dan dideklarasikan tanpa penginisialisasi. [..]


3. Melompat mematuhi ruang lingkup objek lain

Demikian juga, objek dengan durasi penyimpanan otomatis yang tidak "bocor" ketika Anda gotokeluar dari ruang lingkup mereka :

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

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

lol:
   return 0;
}

// *T~T

[n3290: 6.6/2]:Saat keluar dari ruang lingkup (bagaimanapun tercapai), objek dengan durasi penyimpanan otomatis (3.7.3) yang telah dibangun dalam lingkup itu dihancurkan dalam urutan kebalikan dari konstruksi mereka. [..]


Kesimpulan

Mekanisme di atas memastikan bahwa gotoAnda tidak merusak bahasa.

Tentu saja, ini tidak secara otomatis berarti bahwa Anda "harus" menggunakan gotountuk setiap masalah tertentu, tetapi ini berarti bahwa masalah itu hampir tidak "jahat" seperti yang diyakini oleh mitos umum.

Balapan Ringan dalam Orbit
sumber
8
Anda mungkin memperhatikan bahwa C tidak mencegah semua hal berbahaya ini terjadi.
Daniel
13
@Daniel: Pertanyaan dan jawaban sangat spesifik tentang C ++, tapi cukup adil. Mungkin kita bisa mendapatkan FAQ lain untuk menghilangkan mitos bahwa C dan C ++ adalah sama;)
Lightness Races di Orbit
3
@Tomalak: Saya tidak berpikir kami tidak setuju di sini. Banyak dari jawaban yang diberikan pada SO secara eksplisit didokumentasikan di suatu tempat. Saya baru saja menunjukkan bahwa mungkin tergoda bagi programmer C untuk melihat jawaban ini dan berasumsi bahwa jika berfungsi di C ++, ini akan berfungsi serupa di C.
Daniel
2
Anda juga mungkin ingin menambahkan bahwa semua hal yang melompati inisialisasi ini sama untuk label kasus.
PlasmaHH
12
Wow, saya baru saja berasumsi semantik C ++ rusak karena kebagian, tapi ternyata sangat waras! Jawaban yang bagus.
Joseph Garvin