Apakah ini kasus penggunaan yang layak untuk goto di C?

59

Saya benar-benar ragu untuk menanyakan hal ini, karena saya tidak ingin "meminta debat, argumen, jajak pendapat, atau diskusi panjang" tetapi saya baru mengenal C dan ingin mendapatkan lebih banyak wawasan tentang pola umum yang digunakan dalam bahasa.

Saya baru-baru ini mendengar beberapa kebencian untuk gotoperintah, tetapi saya juga baru-baru ini menemukan kasus penggunaan yang layak untuk itu.

Kode seperti ini:

error = function_that_could_fail_1();
if (!error) {
    error = function_that_could_fail_2();
    if (!error) {
        error = function_that_could_fail_3();
        ...to the n-th tab level!
    } else {
        // deal with error, clean up, and return error code
    }
} else {
    // deal with error, clean up, and return error code
}

Jika bagian pembersihan semuanya sangat mirip, dapat ditulis sedikit lebih cantik (menurut saya?) Seperti ini:

error = function_that_could_fail_1();
if(error) {
    goto cleanup;
}
error = function_that_could_fail_2();
if(error) {
    goto cleanup;
}
error = function_that_could_fail_3();
if(error) {
    goto cleanup;
}
...
cleanup:
// deal with error if it exists, clean up
// return error code

Apakah ini kasus penggunaan yang umum atau dapat diterima gotodi C? Apakah ada cara berbeda / lebih baik untuk melakukan ini?

Robz
sumber
3
Lihat juga pertanyaan ini (dan jawaban saya ).
Keith Thompson
2
Lebih dari lapisan gula, kompiler menjamin pembersihan dan pengecekan kesalahan, sedangkan di C Anda harus melakukannya secara manual, melanggar KERING dan menjamin bahwa di beberapa tempat Anda terlalu malas untuk memeriksa kode kesalahan atau pergi ke label yang salah atau sesuatu seperti itu.
DeadMG
3
@Izkata: Tidak, intinya adalah memiliki fungsi (seperti destruktor) yang secara implisit dipanggil oleh kompiler ketika sebuah pengecualian dilemparkan.
DeadMG
6
@DeadMG: Menggunakan bahasa lain tidak selalu merupakan pilihan.
Benjamin Kloster
13
Saya pikir dengan penamaan label yang sesuai, contohnya mungkin terlihat cukup baik:goto hell;
agung

Jawaban:

48

The gotopernyataan (dan label yang sesuai) adalah kontrol aliran primitif (bersama dengan eksekusi bersyarat dari pernyataan). Maksud saya, mereka ada di sana untuk memungkinkan Anda membangun jaringan kendali aliran program. Anda dapat menganggap mereka sebagai pemodelan panah di antara simpul diagram alur.

Beberapa di antaranya dapat dioptimalkan segera, di mana ada aliran linier langsung (Anda hanya menggunakan urutan pernyataan dasar). Pola lain yang terbaik diganti dengan konstruksi pemrograman terstruktur di mana ini tersedia; jika terlihat seperti whileloop, gunakan whileloop , OK? Pola pemrograman terstruktur jelas setidaknya berpotensi lebih jelas niat daripada kekacauan gotopernyataan.

Namun C tidak mencakup semua kemungkinan konstruksi pemrograman terstruktur. (Tidak jelas bagi saya bahwa semua yang relevan telah ditemukan; tingkat penemuannya lambat sekarang, tapi saya ragu untuk mengatakan bahwa semua telah ditemukan.) Dari yang kita ketahui, C jelas tidak memiliki try/ catch/ finallystruktur (dan pengecualian juga). Ini juga tidak memiliki multi-level break-dari-loop. Ini adalah hal-hal yang gotodapat digunakan untuk diterapkan. Ini mungkin untuk menggunakan skema lain untuk melakukan ini juga - kita tahu bahwa C memiliki cukup set nongotoprimitif - tetapi ini sering melibatkan pembuatan variabel flag dan kondisi loop atau guard yang jauh lebih kompleks; meningkatkan keterjeratan analisis kontrol dengan analisis data membuat program lebih sulit untuk dipahami secara keseluruhan. Ini juga membuat lebih sulit bagi kompiler untuk mengoptimalkan dan bagi CPU untuk mengeksekusi dengan cepat (sebagian besar konstruksi kontrol aliran - dan tentunya goto - sangat murah).

Jadi, sementara Anda tidak boleh menggunakan gotokecuali diperlukan, Anda harus menyadari bahwa itu ada dan mungkin diperlukan, dan jika Anda membutuhkannya, Anda seharusnya tidak merasa terlalu buruk. Contoh kasus di mana diperlukan adalah alokasi sumber daya ketika fungsi yang dipanggil mengembalikan kondisi kesalahan. (Yaitu, try/ finally.) Adalah mungkin untuk menulis itu tanpa gototetapi melakukan itu dapat memiliki kelemahan sendiri, seperti masalah mempertahankannya. Contoh kasus:

int frobnicateTheThings() {
    char *workingBuffer = malloc(...);
    int i;

    for (i=0 ; i<numberOfThings ; i++) {
        if (giveMeThing(i, workingBuffer) != OK)
            goto error;
        if (processThing(workingBuffer) != OK)
            goto error;
        if (dispatchThing(i, workingBuffer) != OK)
            goto error;
    }

    free(workingBuffer);
    return OK;

  error:
    free(workingBuffer);
    return OOPS;
}

Kode bisa lebih pendek, tetapi cukup untuk menunjukkan intinya.

Donal Fellows
sumber
4
+1: Di C goto secara teknis tidak pernah "diperlukan" - selalu ada cara untuk melakukannya, akan berantakan ..... Untuk seperangkat pedoman yang kuat untuk penggunaan tampilan goto di MISRA C.
mattnz
1
Apakah Anda lebih suka try/catch/finallyuntuk gototerlepas dari mirip namun lebih luas (karena dapat menyebar ke seluruh beberapa fungsi / modul) berupa kode spaghetti itu mungkin menggunakan, try/catch/finally?
autis
65

Iya.

Ini digunakan dalam, misalnya, kernel linux. Berikut ini email dari ujung utas dari hampir satu dekade lalu , cetak tebal milik saya:

Dari: Robert Love
Subjek: Re: ada kemungkinan 2.6.0-test *?
Tanggal: 12 Jan 2003 17:58:06 -0500

On Sun, 2003-01-12 jam 17:22, Rob Wilkens menulis:

Saya mengatakan "tolong jangan gunakan goto" dan sebagai gantinya memiliki fungsi "cleanup_lock" dan tambahkan itu sebelum semua pernyataan kembali .. Ini seharusnya tidak menjadi beban. Ya, ia meminta pengembang untuk bekerja sedikit lebih keras, tetapi hasil akhirnya adalah kode yang lebih baik.

Tidak, ini kotor dan membengkak kernel . Itu inline sekelompok sampah untuk jalur kesalahan N, yang bertentangan dengan memiliki kode keluar sekali di akhir. Cache footprint adalah kuncinya dan Anda baru saja membunuhnya.

Juga tidak lebih mudah dibaca.

Sebagai argumen akhir, tidak membiarkan kita bersih melakukan hal yang biasa tumpukan-esque angin dan bersantai , yaitu

        do A
        if (error)
            goto out_a;
        do B
        if (error)
            goto out_b;
        do C
        if (error)
            goto out_c;
        goto out;
        out_c:
        undo C
        out_b:
        undo B:
        out_a:
        undo A
        out:
        return ret;

Sekarang hentikan ini.

Robert Love

Karena itu, diperlukan banyak disiplin untuk menjaga diri Anda dari membuat kode spaghetti setelah Anda terbiasa menggunakan goto, jadi kecuali Anda menulis sesuatu yang membutuhkan kecepatan dan jejak memori yang rendah (seperti kernel atau sistem embedded) Anda harus benar - benar pikirkan dulu sebelum Anda menulis goto pertama.

Izkata
sumber
21
Perhatikan bahwa kernel berbeda dari program non-kernel mengenai prioritas pada kecepatan mentah versus keterbacaan. Dengan kata lain, mereka telah SUDAH diprofilkan dan menemukan bahwa mereka perlu mengoptimalkan kode mereka untuk kecepatan dengan goto.
11
Menggunakan stack un-wind untuk menangani pembersihan kesalahan tanpa benar-benar mendorong ke stack! Ini adalah penggunaan goto yang luar biasa.
mike30
1
@ user1249, Sampah, Anda tidak dapat membuat profil setiap {app, masa lalu, masa depan} aplikasi yang menggunakan sepotong {perpustakaan, kernel} kode. Anda hanya perlu cepat.
Pacerier
1
Tidak terkait: Saya kagum bagaimana orang dapat menggunakan milis untuk menyelesaikan sesuatu, apalagi proyek besar seperti itu. Itu begitu ... primitif. Bagaimana orang-orang datang dengan rumah pemadam kebakaran pesan ?!
Alexander
2
Saya tidak begitu peduli dengan moderasi. Jika seseorang cukup lunak untuk ditolak oleh orang brengsek di internet, proyek Anda mungkin lebih baik tanpanya. Saya lebih peduli tentang ketidakpraktisan dalam mengikuti rentetan pesan yang masuk, dan bagaimana Anda bisa melakukan diskusi bolak-balik secara alami dengan begitu sedikit alat untuk melacak kutipan, misalnya.
Alexander
14

Menurut pendapat saya, kode yang Anda posting adalah contoh penggunaan yang valid goto, karena Anda hanya melompat ke bawah dan hanya menggunakannya seperti pengendali pengecualian primitif.

Namun , karena perdebatan lama, para programmer telah menghindari gotoselama 40 tahun dan karena itu mereka tidak terbiasa membaca kode dengan goto. Ini adalah alasan yang sah untuk menghindari kebotakan: itu bukan standar.

Saya akan menulis ulang kode sebagai sesuatu yang lebih mudah dibaca oleh programmer C:

Error some_func (void)
{
  Error error;
  type_t* resource = malloc(...);

  error = some_other_func (resource);

  free (resource);

  /* error handling can be placed here, or it can be returned to the caller */

  return error;
}


Error some_other_func (type_t* resource)  // inline if needed
{
  error = function_that_could_fail_1();
  if(error)
  {
    return error;
  }

  /* ... */

  error = function_that_could_fail_2();
  if(error)
  {
    return error;
  }

  /* ... */

  return ok;
}

Keuntungan dari desain ini:

  • Fungsi melakukan pekerjaan aktual tidak perlu memusatkan perhatian pada tugas-tugas yang tidak relevan dengan algoritmanya, seperti mengalokasikan data.
  • Kode tersebut akan terlihat kurang asing bagi pemrogram C, karena mereka takut pada goto dan label.
  • Anda dapat memusatkan penanganan kesalahan dan deallokasi di tempat yang sama, di luar fungsi yang melakukan algoritma. Tidak masuk akal bagi suatu fungsi untuk menangani hasilnya sendiri.
Doc Brown
sumber
11

Sebuah makalah terkenal yang menggambarkan kasus penggunaan yang valid adalah Pemrograman Terstruktur dengan Pernyataan GOTO oleh Donald E. Knuth (Stanford University). Makalah ini muncul pada hari-hari di mana penggunaan GOTO dianggap dosa oleh sebagian orang dan ketika gerakan untuk Pemrograman Terstruktur adalah pada puncaknya. Anda mungkin ingin melihat GoTo Dianggap Berbahaya.

Tidak mungkin
sumber
9

Di Jawa Anda akan melakukannya seperti ini:

makeCalls:  {
    error = function_that_could_fail_1();
    if (error) {
        break makeCalls;
    }
    error = function_that_could_fail_2();
    if (error) {
        break makeCalls;
    }
    error = function_that_could_fail_3();
    if (error) {
        break makeCalls;
    }
    ...
    return 0;  // No error code.
}
// deal with error if it exists, clean up
// return error code

Saya sering menggunakan ini. Seperti yang saya tidak suka goto, di sebagian besar bahasa C-style lainnya saya menggunakan kode Anda; tidak ada cara lain yang baik untuk melakukannya. (Melompat keluar dari loop bersarang adalah kasus serupa; di Jawa saya menggunakan label breakdan di mana pun saya menggunakan a goto.)

RalphChapin
sumber
3
Ah, itu struktur kontrol yang rapi.
Bryan Boettcher
4
Ini sangat menarik. Saya biasanya berpikir untuk menggunakan try / catch / akhirnya struktur untuk ini di java (melempar pengecualian daripada melanggar).
Robz
5
Itu benar-benar tidak dapat dibaca (setidaknya untuk saya). Jika ada, pengecualian jauh lebih baik.
m3th0dman
1
@ m3th0dman Saya setuju dengan Anda dalam contoh khusus ini (penanganan kesalahan). Tetapi ada kasus lain (tidak luar biasa) di mana idiom ini bisa berguna.
Konrad Rudolph
1
Pengecualian itu mahal, mereka perlu menghasilkan kesalahan, stacktrace dan lebih banyak sampah. Label break ini memberikan jalan keluar yang bersih dari loop pemeriksaan. kecuali seseorang tidak peduli dengan memori dan kecepatan, maka untuk semua saya peduli gunakan pengecualian.
Tschallacka
8

Saya pikir ini adalah kasus penggunaan yang layak, tetapi dalam kasus "kesalahan" tidak lebih dari nilai boolean, ada cara berbeda untuk mencapai apa yang Anda inginkan:

error = function_that_could_fail_1();
error = error || function_that_could_fail_2();
error = error || function_that_could_fail_3();
if(error)
{
     // do cleanup
}

Ini memanfaatkan evaluasi hubung singkat dari operator boolean. Jika ini "lebih baik", terserah selera pribadi Anda dan bagaimana Anda terbiasa dengan idiom itu.

Doc Brown
sumber
1
Masalah dengan ini adalah bahwa errornilai bisa menjadi tidak berarti dengan semua OR'ing.
James
@ James: mengedit jawaban saya karena komentar Anda
Doc Brown
1
Ini tidak cukup. Jika kesalahan terjadi selama fungsi pertama, saya tidak ingin menjalankan fungsi kedua atau ketiga.
Robz
2
Jika dengan evaluasi tangan pendek yang Anda maksud evaluasi hubungan pendek , ini sama sekali bukan apa yang dilakukan di sini karena penggunaan bitwise ATAU alih-alih logis ATAU.
Chris berkata Reinstate Monica
1
@ChristianRau: terima kasih, edit jawaban saya
Doc Brown
6

Panduan gaya linux memberikan alasan khusus untuk menggunakan gotoyang sesuai dengan contoh Anda:

https://www.kernel.org/doc/Documentation/process/coding-style.rst

Alasan untuk menggunakan gotos adalah:

  • pernyataan tanpa syarat lebih mudah dipahami dan diikuti
  • bersarang berkurang
  • kesalahan dengan tidak memperbarui titik keluar individu ketika membuat modifikasi dicegah
  • menyimpan pekerjaan kompiler untuk mengoptimalkan kode berlebihan;)

Penafian saya tidak seharusnya membagikan pekerjaan saya. Contoh-contoh di sini sedikit dibuat-buat jadi tolong tolong bersamaku.

Ini bagus untuk manajemen memori. Saya baru-baru ini bekerja pada kode yang secara dinamis mengalokasikan memori (misalnya char *dikembalikan oleh suatu fungsi). Fungsi yang melihat jalur dan memastikan apakah jalur tersebut valid dengan menguraikan token jalur:

tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
    ...
    some statements, some involving dynamically allocated memory
    ...
    if ( check_this() ){
        free(var1);
        free(var2);
        ...
        free(varN);
        return 1;
    }
    ...
    some more stuff
    ...
    if(something()){
        if ( check_that() ){
            free(var1);
            free(var2);
            ...
            free(varN);
            return 1;
        } else {
            free(var1);
            free(var2);
            ...
            free(varN);
            return 0;
        }
    }
    token = strtok(NULL,delim);
}

free(var1);
free(var2);
...
free(varN);
return 1;

Sekarang bagi saya, kode berikut ini jauh lebih baik dan lebih mudah dirawat jika Anda perlu menambahkan varNplus1:

int retval = 1;
tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
    ...
    some statements, some involving dynamically allocated memory
    ...
    if ( check_this() ){
        retval = 1;
        goto out_free;
    }
    ...
    some more stuff
    ...
    if(something()){
        if ( check_that() ){
            retval = 1;
            goto out_free;
        } else {
            retval = 0;
            goto out_free;
        }
    }
    token = strtok(NULL,delim);
}

out_free:
free(var1);
free(var2);
...
free(varN);
return retval;

Sekarang kode memiliki segala macam masalah lain dengan itu, yaitu bahwa N berada di suatu tempat di atas 10, dan fungsinya lebih dari 450 baris, dengan 10 tingkat bersarang di beberapa tempat.

Tapi saya menawarkan penyelia saya untuk memperbaiki itu, yang saya lakukan dan sekarang banyak fungsi yang semuanya singkat, dan mereka semua memiliki gaya linux

int function(const char * param)
{
    int retval = 1;
    char * var1 = fcn_that_returns_dynamically_allocated_string(param);
    if( var1 == NULL ){
        retval = 0;
        goto out;
    }

    if( isValid(var1) ){
         retval = some_function(var1);
         goto out_free;
    }

    if( isGood(var1) ){
         retval = 0;
         goto out_free;
    }

out_free:
    free(var1);
out:
    return retval;
}

Jika kami menganggap yang setara tanpa gotos:

int function(const char * param)
{
    int retval = 1;
    char * var1 = fcn_that_returns_dynamically_allocated_string(param);
    if( var1 != NULL ){

       if( isValid(var1) ){
            retval = some_function(var1);
       } else {
          if( isGood(var1) ){
               retval = 0;
          }
       }
       free(var1);

    } else {
       retval = 0;
    }

    return retval;
}

Bagi saya, dalam kasus pertama, jelas bagi saya bahwa jika fungsi pertama kembali NULL, kami keluar dari sini dan kami kembali 0. Dalam kasus kedua, saya harus gulir ke bawah untuk melihat bahwa if berisi seluruh fungsi. Memang yang pertama menunjukkan ini kepada saya dengan gaya (nama " out") dan yang kedua melakukannya secara sintaksis. Yang pertama masih lebih jelas.

Juga, saya lebih suka memiliki free()pernyataan di akhir fungsi. Itu sebagian karena, dalam pengalaman saya, free()pernyataan di tengah fungsi berbau tidak enak dan menunjukkan kepada saya bahwa saya harus membuat subrutin. Dalam hal ini, saya membuat var1dalam fungsi saya dan tidak bisa free()dalam subrutin, tapi itu sebabnya goto out_free, gaya goto out sangat praktis.

Saya pikir pemrogram perlu dibesarkan dengan keyakinan bahwa gotoitu jahat. Kemudian, ketika mereka sudah cukup dewasa, mereka harus menelusuri kode sumber Linux dan membaca panduan gaya linux.

Saya harus menambahkan bahwa saya menggunakan gaya ini dengan sangat konsisten, setiap fungsi memiliki int retval, out_freelabel dan label out. Karena konsistensi gaya, keterbacaan ditingkatkan.

Bonus: Istirahat dan berlanjut

Katakanlah Anda memiliki loop sementara

char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
    var1 = functionA(line,count);
    var2 = functionB(line,count);

    if( functionC(var1, var2){
         count++
         continue;
    }

    ...
    a bunch of statements
    ...

    count++;
    free(var1);
    free(var2);
}

Ada hal lain yang salah dengan kode ini, tetapi satu hal adalah pernyataan berlanjut. Saya ingin menulis ulang semuanya, tetapi saya ditugaskan untuk memodifikasinya dengan cara yang kecil. Butuh waktu berhari-hari untuk memperbaikinya dengan cara yang memuaskan saya, tetapi perubahan sebenarnya adalah pekerjaan setengah hari. Masalahnya adalah bahwa bahkan jika continuekita masih harus bebas var1dan var2. Saya harus menambahkan var3, dan itu membuat saya ingin muntah harus mencerminkan pernyataan bebas ().

Saya adalah pekerja magang yang relatif baru pada saat itu, tetapi saya telah melihat kode sumber linux untuk bersenang-senang beberapa waktu lalu, jadi saya bertanya kepada penyelia saya apakah saya bisa menggunakan pernyataan goto. Dia berkata ya, dan saya melakukan ini:

char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
    var1 = functionA(line,count);
    var2 = functionB(line,count);
    var3 = newFunction(line,count);

    if( functionC(var1, var2){
         goto next;
    }

    ...
    a bunch of statements
    ...
next:
    count++;
    free(var1);
    free(var2);
}

Saya pikir terus baik-baik saja tetapi bagi saya itu seperti goto dengan label yang tidak terlihat. Hal yang sama berlaku untuk istirahat. Saya masih lebih suka melanjutkan atau merusak kecuali, seperti yang terjadi di sini, itu memaksa Anda untuk mencerminkan modifikasi di banyak tempat.

Dan saya juga harus menambahkan bahwa penggunaan goto next;dan next:label ini tidak memuaskan bagi saya. Mereka hanya lebih baik daripada mencerminkan pernyataan free()dan count++pernyataan.

gotoHampir selalu salah, tetapi orang harus tahu kapan itu baik untuk digunakan.

Satu hal yang tidak saya diskusikan adalah penanganan kesalahan yang telah dicakup oleh jawaban lain.

Performa

Seseorang dapat melihat implementasi strtok () http://opensource.apple.com//source/Libc/Libc-167/string.subproj/strtok.c

#include <stddef.h>
#include <string.h>

char *
strtok(s, delim)
    register char *s;
    register const char *delim;
{
    register char *spanp;
    register int c, sc;
    char *tok;
    static char *last;


    if (s == NULL && (s = last) == NULL)
        return (NULL);

    /*
     * Skip (span) leading delimiters (s += strspn(s, delim), sort of).
     */
cont:
    c = *s++;
    for (spanp = (char *)delim; (sc = *spanp++) != 0;) {
        if (c == sc)
            goto cont;
    }

    if (c == 0) {       /* no non-delimiter characters */
        last = NULL;
        return (NULL);
    }
    tok = s - 1;

    /*
     * Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
     * Note that delim must have one NUL; we stop if we see that, too.
     */
    for (;;) {
        c = *s++;
        spanp = (char *)delim;
        do {
            if ((sc = *spanp++) == c) {
                if (c == 0)
                    s = NULL;
                else
                    s[-1] = 0;
                last = s;
                return (tok);
            }
        } while (sc != 0);
    }
    /* NOTREACHED */
}

Harap perbaiki saya jika saya salah, tetapi saya percaya bahwa cont:label dan goto cont;pernyataannya ada untuk kinerja (mereka pasti tidak membuat kode lebih mudah dibaca). Mereka dapat diganti dengan kode yang dapat dibaca dengan melakukan

while( isDelim(*s++,delim));

untuk melewati pembatas. Tetapi untuk secepat mungkin dan menghindari pemanggilan fungsi yang tidak perlu, mereka melakukannya dengan cara ini.

Saya membaca koran oleh Dijkstra dan saya merasa cukup esoterik.

google "pernyataan goto dijkstra dianggap berbahaya" karena saya tidak memiliki reputasi yang cukup untuk memposting lebih dari 2 tautan.

Saya telah melihatnya dikutip sebagai alasan untuk tidak menggunakan goto dan membacanya tidak mengubah apa pun sejauh penggunaan goto saya disetujui.

Adendum :

Saya telah datang dengan aturan yang rapi sambil memikirkan semua ini tentang terus dan rusak.

  • Jika dalam loop sementara, Anda memiliki lanjutan, maka tubuh loop sementara harus menjadi fungsi dan melanjutkan menjadi pernyataan kembali.
  • Jika dalam loop sementara, Anda memiliki pernyataan break, maka loop sementara itu sendiri harus berfungsi dan break harus menjadi pernyataan kembali.
  • Jika Anda memiliki keduanya, maka ada sesuatu yang salah.

Itu tidak selalu mungkin karena masalah ruang lingkup tetapi saya telah menemukan bahwa melakukan ini membuatnya lebih mudah untuk alasan tentang kode saya. Saya telah memperhatikan bahwa setiap kali loop sementara istirahat atau melanjutkan itu memberi saya perasaan buruk.

Philippe Carphin
sumber
2
+1 tetapi dapatkah saya tidak setuju pada satu titik? "Saya pikir para programmer perlu dibesarkan untuk meyakini bahwa kebotakan itu jahat." Mungkin begitu, tetapi saya pertama kali belajar memprogram dalam BASIC, dengan nomor baris dan GOTO, tanpa editor teks, pada tahun 1975. Saya bertemu pemrograman terstruktur sepuluh tahun kemudian, setelah itu butuh satu bulan untuk berhenti menggunakan GOTO sendiri, tanpa tekanan untuk berhenti. Hari ini, saya menggunakan GOTO beberapa kali setahun karena berbagai alasan, tetapi tidak banyak muncul. Tidak dibesarkan untuk percaya bahwa GOTO adalah kejahatan telah membuat saya tidak membahayakan yang saya tahu, dan bahkan mungkin ada gunanya. Itu hanya aku.
thb
1
Saya pikir Anda benar tentang itu. Saya dibesarkan dengan gagasan bahwa GOTO tidak dapat digunakan dan secara kebetulan, saya sedang menelusuri kode sumber Linux pada saat saya sedang mengerjakan kode yang memiliki fungsi-fungsi ini dengan beberapa titik keluar dengan memori bebas. Kalau tidak, saya tidak akan pernah tahu tentang teknik ini.
Philippe Carphin
1
@ THB Juga, cerita lucu, saya meminta atasan saya pada saat itu, sebagai magang izin untuk menggunakan GOTO dan saya memastikan bahwa saya menjelaskan kepadanya bahwa saya akan menggunakannya dengan cara tertentu seperti cara itu digunakan dalam Kernel Linux dan dia berkata "Ok, itu masuk akal, dan juga, saya tidak tahu Anda bisa menggunakan GOTO di C".
Philippe Carphin
1
@ THB saya tidak tahu apakah itu baik untuk masuk ke loop (bukan melanggar loop) seperti ini ? Yah, ini adalah contoh yang buruk, tetapi saya menemukan bahwa quicksort dengan pernyataan goto (contoh 7a) pada Pemrograman Terstruktur Knuth dengan pergi ke Pernyataan tidak terlalu komprehensif.
Yai0Phah
@ Yai0Phah saya akan menjelaskan maksud saya tetapi penjelasan saya tidak mengurangi contoh 7a Anda! Saya menyetujui contoh itu. Tetap saja, mahasiswi yang suka memerintah suka memberi kuliah tentang goto. Sulit untuk menemukan penggunaan praktis dari goto sejak tahun 1985 yang menyebabkan masalah signifikan, sedangkan orang dapat menemukan goto tidak berbahaya yang memudahkan pekerjaan programmer. Goto sangat jarang muncul dalam pemrograman modern, bagaimanapun, bahwa, ketika itu muncul, saran saya adalah, jika Anda ingin menggunakannya, maka Anda mungkin harus menggunakannya. Goto baik-baik saja. Masalah utama dengan goto adalah bahwa beberapa orang percaya bahwa kebo goto membuat mereka terlihat pintar.
thb
5

Secara pribadi saya akan refactor lebih seperti ini:

int DoLotsOfStuffThatCouldFail (paramstruct *params)
{
    int errcode = EC_NOERROR;

    if ((errcode = FunctionThatCouldFail1 (params)) != EC_NOERROR) return errcode;
    if ((errcode = FunctionThatCouldFail2 (params)) != EC_NOERROR) return errcode;
    if ((errcode = FunctionThatCouldFail3 (params)) != EC_NOERROR) return errcode;
    if ((errcode = FunctionThatCouldFail4 (params)) != EC_NOERROR) return errcode;

    return EC_NOERROR;
}

void DoStuff (paramstruct *params)
{
    int errcode = EC_NOERROR;

    InitStuffThatMayNeedToBeCleaned (params);

    if ((errcode = DoLotsOfStuffThatCouldFail (params)) != EC_NOERROR)
    {
         CleanupAfterError (params, errcode);
    }
}

Itu akan lebih termotivasi dengan menghindari sarang yang dalam daripada menghindari kebotakan (IMO masalah yang lebih buruk dengan sampel kode pertama), dan tentu saja akan bergantung pada CleanupAfterError menjadi mungkin di luar ruang lingkup (dalam hal ini "params" bisa menjadi struct yang berisi beberapa memori yang dialokasikan yang harus Anda bebaskan, FILE * yang perlu Anda tutup atau apa pun).

Satu keuntungan utama yang saya lihat dengan pendekatan ini adalah lebih mudah dan lebih bersih untuk menempatkan langkah tambahan di masa depan yang hipotetis antara, katakanlah, FTCF2 dan FTCF3 (atau hapus langkah saat ini), sehingga lebih cocok untuk pemeliharaan (dan orang yang mewarisi kode saya tidak ingin lynch saya!) - kesampingkan, versi bersarang kurang itu.

Maximus Minimus
sumber
1
Saya tidak menyatakan ini dalam pertanyaan saya, tetapi mungkin saja FTCF TIDAK memiliki parameter yang sama, membuat pola ini sedikit lebih rumit. Terimakasih Meskipun.
Robz
3

Lihatlah panduan pengkodean C MISRA (Asosiasi Keandalan Perangkat Lunak Industri Motor) yang memungkinkan goto di bawah kriteria ketat (yang memenuhi contoh Anda)

Di mana saya bekerja, kode yang sama akan ditulis - tidak perlu kebersamaan - Menghindari perdebatan agama yang tidak perlu tentang mereka adalah nilai tambah yang besar di setiap rumah perangkat lunak.

error = function_that_could_fail_1();
if(!error) {
  error = function_that_could_fail_2();
}
if(!error) {
  error = function_that_could_fail_3();
} 
if(!error) {
...
if (error) {
  cleanup:
} 

atau untuk "goto in drag" - sesuatu yang bahkan lebih cerdik dari goto, tetapi berkeliling "No goto Ever !!!" camp) "Tentunya itu harus OK, tidak menggunakan Goto" ....

do {
  if (error = function_that_could_fail_1() ){
    break 
  }
  if (error = function_that_could_fail_2() ){
    break 
  }
  ....... 
} while (0) 
cleanup();
.... 

Jika fungsi memiliki tipe parameter yang sama, masukkan ke dalam tabel dan gunakan satu loop -

mattnz
sumber
2
Pedoman MISRA-C: 2004 saat ini tidak mengizinkan goto dalam bentuk apa pun (lihat aturan 14.4). Perhatikan komite MISRA selalu bingung tentang hal ini, mereka tidak tahu harus berdiri di mana. Pertama, mereka tanpa syarat melarang penggunaan goto, melanjutkan dll. Tetapi dalam rancangan untuk MISRA 2011 mendatang mereka ingin memperbolehkan mereka lagi. Sebagai seorang pengenal, harap dicatat bahwa MISRA melarang penugasan di dalam if-statement, untuk alasan yang sangat baik karena jauh lebih berbahaya daripada penggunaan goto.
1
Dari perspektif analitis, menambahkan bendera ke program setara dengan menduplikasi semua kode kode di mana bendera berada dalam cakupan, dengan meminta setiap if(flag)dalam satu salinan mengambil cabang "jika", dan meminta setiap pernyataan terkait dalam salinan lain mengambil " lain". Tindakan yang mengatur dan menghapus bendera benar-benar "goto" yang melompat di antara dua versi kode. Ada saat-saat ketika penggunaan bendera lebih bersih daripada alternatif lain, tetapi menambahkan bendera untuk menyimpan satu gototarget bukanlah pertukaran yang baik.
supercat
1

Saya juga menggunakan gotojika do/while/continue/breakperetasan alternatif kurang dapat dibaca.

gotoAda keuntungan bahwa target mereka memiliki nama dan mereka membaca goto something;. Ini mungkin lebih mudah dibaca daripada breakatau continuejika Anda tidak benar-benar menghentikan atau melanjutkan sesuatu.

aib
sumber
4
Di mana saja di dalam do ... while(0)atau konstruksi lain yang bukan merupakan loop aktual tetapi upaya yang salah untuk mencegah penggunaan a goto.
aib
1
Ah, terima kasih, aku tidak tahu merek khusus ini, "Kenapa seseorang melakukan itu ?!" konstruksi belum.
Benjamin Kloster
2
Biasanya, peretasan do / while / continue / break hanya menjadi tidak dapat dibaca ketika modul yang memuatnya terlalu panjang.
John R. Strohm
2
Saya tidak dapat menemukan apa pun di sini sebagai pembenaran untuk menggunakan goto. Istirahat dan terus memiliki konsekuensi yang jelas. pergi kemana? Di mana labelnya? Istirahat dan goto memberi tahu Anda dengan tepat di mana langkah berikutnya dan yang terdekat.
Rig
1
Label tentunya harus terlihat dari dalam loop. Saya setuju dengan bagian panjang komentar @John R. Strohm. Dan maksud Anda, diterjemahkan ke peretasan loop, menjadi "Keluar dari apa? Ini bukan loop!". Bagaimanapun, ini menjadi apa yang ditakuti OP, jadi saya mengabaikan diskusi.
aib
-1
for (int y=0; y<height; ++y) {
    for (int x=0; x<width; ++x) {
        if (find(x, y)) goto found;
    }
}
found:
aib
sumber
Jika hanya ada satu loop, breakbekerja persis seperti goto, meskipun tanpa stigma.
9000
6
-1: Pertama, x dan y di luar jangkauan ditemukan di, jadi ini tidak membantu Anda. Kedua, dengan kode yang ditulis, fakta bahwa Anda tiba di ditemukan: tidak berarti Anda menemukan apa yang Anda cari.
John R. Strohm
Itu karena ini adalah contoh terkecil yang dapat saya pikirkan untuk kasus putusnya banyak loop. Silakan mengeditnya untuk label yang lebih baik atau cek yang sudah selesai.
aib
1
Tetapi juga perlu diingat bahwa fungsi C tidak harus bebas efek samping.
aib
1
@ JohnR.Strohm Itu tidak masuk akal ... Label 'found' digunakan untuk memutus loop, bukan untuk memeriksa variabel. Jika saya ingin memeriksa variabel saya bisa melakukan sesuatu seperti ini: for (int y = 0; y <height; ++ y) {for (int x = 0; x <width; ++ x) {if (find ( x, y)) {doSomeThingWith (x, y); goto ditemukan; }}} ditemukan:
YoYoYonnY
-1

Akan selalu ada kamp yang mengatakan satu cara dapat diterima dan yang lain tidak. Perusahaan tempat saya bekerja telah mengernyit atau sangat tidak suka penggunaan goto. Secara pribadi, saya tidak dapat memikirkan kapan saya menggunakannya, tetapi itu tidak berarti mereka buruk , hanya cara lain dalam melakukan sesuatu.

Di C, saya biasanya melakukan hal berikut:

  • Tes untuk kondisi yang dapat mencegah pemrosesan (input buruk, dll) dan "kembali"
  • Lakukan semua langkah yang membutuhkan alokasi sumber daya (mis. Mallocs)
  • Lakukan pemrosesan, di mana beberapa langkah memeriksa keberhasilan
  • Lepaskan sumber daya apa pun, jika berhasil dialokasikan
  • Kembalikan hasil apa pun

Untuk pemrosesan, menggunakan contoh goto Anda, saya akan melakukan ini:

error = function_that_could_fail_1 (); if (! error) {error = function_that_could_fail_2 (); } if (! error) {error = function_that_could_fail_3 (); }

Tidak ada sarang, dan di dalam klausa if, Anda dapat melakukan pelaporan kesalahan apa pun, jika langkah tersebut menghasilkan kesalahan. Jadi, itu tidak harus "lebih buruk" daripada metode menggunakan gotos.

Saya belum menemukan kasus di mana seseorang memiliki goto yang tidak dapat dilakukan dengan metode lain dan hanya bisa dibaca / dimengerti dan itulah kuncinya, IMHO.

pcm
sumber