Bu C de goto için iyi bir kullanım örneği mi?


59

Bunu sormakta tereddüt ediyorum, çünkü "tartışma, tartışma, tartışma veya genişletilmiş tartışma istemek" istemiyorum ama C konusunda yeniyim ve dilde kullanılan ortak kalıplar hakkında daha fazla bilgi edinmek istiyorum.

Geçenlerde gotoemir için bazı hoşnutsuzluklar duydum , ancak son zamanlarda bunun için uygun bir kullanım durumu da buldum.

Bunun gibi bir kod:

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
}

Temizlik bölümünün hepsi birbirine çok benziyorsa, bunun gibi biraz daha güzel (bence?) Şöyle yazılabilir:

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

Bu goto, C de yaygın veya kabul edilebilir bir kullanım mıdır? Bunu yapmanın daha iyi / farklı bir yolu var mı?


3
Ayrıca bu soruya bakınız (ve cevabım ).
Keith Thompson

2
Şeker kaplamanın ötesinde, derleyici temizleme ve hata kontrolünü garanti ederken, C'de bunu manuel olarak yapmanız, DRY'yi ihlal etmeniz ve bir yerde hata kodunu kontrol etmek için çok tembel olduğunuzu veya yanlış bir etikete veya benzeri bir şeye gittiğinizi garanti eder.
DeadMG

3
@ Izkata: Hayır, bir istisna atıldığında derleyici tarafından dolaylı olarak adlandırılan fonksiyonlara (yıkıcılar gibi) sahip olmaktır .
DeadMG

6
@DeadMG: Başka bir dil kullanmak her zaman bir seçenek değildir.
Benjamin Kloster

13
Uygun etiket isimlendirmesi ile, bence örnek oldukça iyi görünebilir:goto hell;
gnat

Yanıtlar:


48

gotoBildirimi (ve karşılık gelen etiketler) bir akış kontrol olarak basit (açıklamada şartlı yürütülmesi ile birlikte). Bununla, program akış kontrol ağları kurmanıza izin vermek için orada olduklarını kastediyorum. Onları bir akış çizelgesinin düğümleri arasındaki okları modelleme olarak düşünebilirsiniz.

Bunlardan bazıları, doğrudan doğrusal bir akışın olduğu yerlerde (sadece bir temel deyimler dizisini kullanırsınız) anında optimize edilebilir. Diğer modeller en iyi bunların mümkün olduğu yerlerde yapılandırılmış programlama yapılarıyla değiştirilir; Bir benziyor eğer whiledöngü, bir kullanmak whiledöngü tamam? Yapısal programlama kalıpları kesinlikle en azından potansiyel olarak bir karışıklık gotoifadesinden daha açık bir niyete sahiptir .

Yine de C, olası tüm yapılandırılmış programlama yapılarını içermez. (İlgili tüm kişilerin henüz keşfedildiği henüz belli değil; keşif oranı şimdi yavaş, ancak hepsinin bulunduğunu söylemeye atlamakta tereddüt ediyorum.) Bildiklerimizden kesinlikle C kesinlikle yoksun. try/ catch/ finallyyapı (ve istisnalar da hariç). Aynı zamanda breakdöngüden çoklu seviye yoksundur . Bunlar, gotouygulamak için kullanılabilecek şeylerdir . Bunları yapmak için başka şemalar kullanmak da mümkündür - C'nin yeterli olmayan bir seti olduğunu biliyoruz.gotoilkel - ancak bunlar genellikle bayrak değişkenleri ve çok daha karmaşık döngü veya koruma koşulları yaratmayı içerir; Kontrol analizinin veri analizi ile karıştırılmasını artırmak, programı genel olarak anlaşılmasını zorlaştırır. Ayrıca, derleyicinin optimizasyonunu ve CPU'nun hızlı bir şekilde çalışmasını zorlaştırır (çoğu akış kontrol yapısı - ve kesinlikle goto - çok ucuzdur).

Böylece kullanmamalısınız ederken, gotogerekli olmadıkça, bunu varolduğunu ve onu bilmelidir olabilir gerekli ve bunu gerekirse, çok kötü hissetme. Gerekli olduğu bir durumun bir örneği, çağrılan bir işlev bir hata koşulu döndürdüğünde kaynakların serbest bırakılmasıdır. (Bu, try/ finally.) Bunu gotosürdürmek, sürdürme problemleri gibi, kendi dezavantajlarına sahip olan ancak yapmadan yapmak mümkündür. Dava örneği:

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;
}

Kod daha da kısa olabilir, ancak konuyu göstermek için yeterli.


4
1: C git teknik olarak "gerekli" asla yılında - Bunu yapmanın bir yolu her zaman vardır, o MISRA C'de git görünüm kullanımı için kurallar bir dizi güçlü İçin ..... pis alır
mattnz

1
Kullanılması mümkün olan spagetti kodu biçimine rağmen benzer, ancak daha yaygın olanı (çoklu işlevler / modüller arasında yayılabildiği gibi) tercih try/catch/finallyeder misiniz ? gototry/catch/finally
otistik

65

Evet.

Örneğin linux çekirdeğinde kullanılır. İşte yaklaşık on yıl öncesine ait bir iş parçacığının sonundan gelen bir e-posta , benimkileri kodlayan:

Kimden: Robert Love
Konu: Re: 2.6.0-test * herhangi bir şansı?
Tarih: 12 Ocak 2003 17:58:06 -0500

Sun, 2003-01-12 17:22, Rob Wilkens yazdı:

"Lütfen goto kullanmayın" diyorum ve bunun yerine "cleanup_lock" işlevine sahip olmalı ve tüm iade ifadelerinden önce bunu ekleyiniz .. Bu bir yük olmamalıdır. Evet, geliştiriciden biraz daha fazla çalışmasını istiyor ancak sonuç daha iyi bir kod.

Hayır, iğrenç ve çekirdeği şişiriyor . Çıkış kodunun sonunda bir kez olmak yerine, N hata yolları için bir grup önemsiz çizgiyi işaretler. Önbellek ayakizi anahtar ve az önce öldürdün.

Okumak da kolay değil.

Son bir argüman olarak, normal yığın-esque rüzgarını temiz bir şekilde yapmamıza ve gevşememize izin vermez , yani

        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;

Şimdi şunu durdur.

Robert Love

Bununla birlikte, goto kullanmaya alışınca bir kere spagetti kodu oluşturmaktan kaçınmak için çok fazla disiplin gerekiyor, bu nedenle hız ve düşük bellek alanı gerektiren (bir çekirdek veya gömülü sistem gibi) bir şey yazmıyorsanız, gerçekten ilk gotoyu yazmadan önce düşün .


21
Bir çekirdeğin, ham hıza karşı okunabilirlik önceliğine ilişkin olarak çekirdek olmayan bir programdan farklı olduğunu unutmayın. Başka bir deyişle, ALREADY profiline sahipler ve kodlarını, goto ile hız için optimize etmeleri gerektiğini buldular.

11
Yığın üzerine gerçekten itmeden hata durumunda temizlik işlemek için un-wind yığını kullanma! Bu harika bir goto kullanımı.
mike30

1
@ user1249, Rubbish, bir parça {library, kernel} kodu kullanan her {geçmiş, var olan, gelecekteki} uygulamanın profilini çıkaramazsınız. Sadece hızlı olmalısın.
Pacerier

1
İlgisiz: İnsanların herhangi bir şeyi yapmak için e-posta listelerini nasıl kullanabileceklerini, bu tür büyük projeleri bile şaşırtmaya şaşırdım. Bu sadece çok ... ilkel. İnsanlar mesajların itfaiyesine nasıl giriyor ?!
Alexander,

2
Denetleme konusunda pek endişelenmiyorum. Biri internette bir pislik tarafından çevrilecek kadar yumuşaksa, projeniz muhtemelen onlarsız daha iyi olur. Örneğin, gelen mesajların sınırlarına yetişmeme konusundaki pratiksizlik ve örneğin, teklifleri takip etmek için çok az araç kullanarak nasıl doğal bir ileri geri görüşme yapabileceğiniz konusunda endişeliyim.
Alexander,

14

Benim düşünceme göre, gönderdiğiniz kod geçerli bir kullanım örneğidir goto, çünkü yalnızca aşağıya atlar ve yalnızca ilkel bir istisna işleyicisi gibi kullanırsınız.

Bununla birlikte , eski goto tartışmalarından dolayı, programcılar goto40 yıldır kaçınıyor ve bu nedenle goto ile kod okumak için kullanılmıyorlar. Bu, kaçınmaktan kaçınmak için geçerli bir neden: basitçe standart değil.

Kodu C programcıları tarafından daha kolay okunabilen bir şey olarak yeniden yazdım:

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;
}

Bu tasarımın avantajları:

  • Asıl işi yapan işlevin, veri ayırma gibi algoritmasıyla ilgisi olmayan görevlerle ilgilenmesi gerekmez.
  • Bu kod C programcılarına daha az yabancı gözükecek, çünkü goto ve etiketlerden korkuyorlar.
  • Algoritmayı yapan fonksiyonun dışında hata yönetimi ve ayrılması aynı noktada merkezileştirilebilir. Bir fonksiyonun kendi sonuçlarını ele alması mantıklı değildir.


9

Java’da şöyle yapardınız:

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

Bunu çok kullanıyorum. Sevmediğim kadarıyla goto, çoğu C tarzı dilde kodunuzu kullanıyorum; Bunu yapmanın başka iyi bir yolu yok. (İç içe döngülerden atlamak benzer bir durumdur; Java'da etiketli breakve kullandığım her yerde kullanıyorum goto.)


3
Ah, temiz bir kontrol yapısı.
Bryan Boettcher

4
Bu gerçekten ilginç. Normalde java'da bunun için try / catch / finally yapısını kullanmayı düşünürdüm (kırma yerine istisnalar atma).
Robz

5
Bu gerçekten okunamıyor (en azından benim için). Varsa, istisnalar çok daha iyidir.
m3th0dman

1
@ m3th0dman Bu özel örnekte size katılıyorum (hata yönetimi). Ancak bu deyimin işe yarayabileceği başka (istisnai olmayan) durumlar da vardır.
Konrad Rudolph

1
İstisnalar pahalıdır, bir hata, istif ve daha fazla önemsiz şeyler üretmeleri gerekir. Bu etiket sonu kontrol döngüsünden temiz bir çıkış sağlar. Eğer biri hafıza ve hızı umursamıyorsa, o zaman umurumda olan bir istisna kullanın.
Tschallacka

8

Ben düşünüyorum olduğunu iyi bir kullanım durumu, ancak durumunda "hata" mantıksal bir değer başka bir şey değildir, ne istediğinizi gerçekleştirmek için farklı bir yol yoktur:

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

Bu, boole operatörlerinin kısa devre değerlendirmesini kullanır. Eğer bu "daha iyi" ise, kişisel zevkinize ve bu deyime nasıl alıştığınıza bağlıdır.


1
Bununla ilgili problem, errordeğerin tüm OR''lerde anlamsız hale gelebilmesidir.
James

@James: Yorumunuz nedeniyle cevabımı düzenledi
Doc Brown

1
Bu yeterli değil. İlk işlev sırasında bir hata meydana gelirse, ikinci veya üçüncü işlevi yürütmek istemiyorum.
Robz

2
İle Eğer kısa taraftaki değerlendirme Şunu kısa devre değerlendirme, bu durum Bitwise kullanımına VEYA yerine mantıksal VEYA burada yaptığı şey bu değildi.
Christian Rau,

1
@ChristianRau: teşekkürler, cevabımı buna göre düzenledi
Doc Brown

6

Linux stili kılavuzu goto, örneğinize uygun olarak kullanmak için belirli nedenler sunar :

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

Gotos kullanmanın mantığı şudur:

  • koşulsuz ifadelerin anlaşılması ve takip edilmesi daha kolaydır
  • yuvalama azaltılır
  • Değişiklik yaparken bireysel çıkış noktalarını güncellememekle hatalar önlenir
  • gereksiz kodu uzağa optimize etmek için derleyici çalışmasını kaydeder;)

Sorumluluk reddi Çalışmalarımı paylaşmamam gerekiyor. Buradaki örnekler biraz tartışmalı, bu nedenle lütfen benimle birlikte taşıyın.

Bu hafıza yönetimi için iyidir. Geçenlerde dinamik olarak ayrılmış hafızaya sahip kod üzerinde çalıştım (örneğin char *bir fonksiyon tarafından döndürüldü). Yola bakan ve yolun belirteçlerini ayrıştırıp yolun geçerli olup olmadığını belirleyen bir işlev:

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;

Şimdi bana göre, aşağıdaki kodu eklemeniz gerekiyorsa, bakımı daha kolay ve kolaydır 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;

Şimdi kodda N ile 10'un üzerinde bir yerdeydi ve fonksiyonun 450 satırdan fazla olduğu, bazı yerlerde 10 iç içe geçmişlikten başka bir sorun yoktu.

Ama amirime refactor yapmasını teklif ettim, yaptığım ve şimdi hepsi kısa olan bir sürü fonksiyon ve hepsinde linux tarzı var.

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;
}

gotoS olmadan eşdeğerini düşünürsek :

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;
}

Bana göre, ilk durumda, ilk işlev dönerse NULL, buradan çıkacağımız ve geri döndüğüm bana açıktır 0. İkinci durumda, eğer tüm işlevi içeriyorsa görmek için aşağı kaydırmam gerekir. İlk verilen bana bunu stilistik olarak gösterir (" out" adı ) ve ikincisi sözdizimsel olarak yapar. İlki hala daha açık.

Ayrıca, free()bir fonksiyonun sonunda ifadeleri tercih ederim . Bu kısmen, deneyimlerime göre, free()işlevlerin ortasındaki ifadeler kötü kokuyor ve bana bir alt program oluşturmam gerektiğini gösteriyor. Bu durumda, var1fonksiyonumda yarattım ve bunu free()bir alt programda yapamadım , ama bu yüzden, goto out_freedışarı çıkma tarzı bu kadar pratik.

Bence programcıların bunun gotokötü olduğuna inanmakla yetinmek zorunda . Sonra, yeterince olgun olduklarında, Linux kaynak koduna göz atmalı ve linux stil kılavuzunu okumalılar.

Bu stili çok tutarlı kullandığımı eklemeliyim, her fonksiyonun bir int retval, bir out_freeetiketi ve bir çıkışı vardır. Stilistik tutarlılık nedeniyle okunabilirlik artırıldı.

Bonus: Sonları ve devam ediyor

Bir süre döngü olduğunu söyle

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);
}

Bu kodda yanlış olan başka şeyler var, ancak bunlardan biri, devamı ifadesidir. Her şeyi yeniden yazmak istiyorum, ancak küçük bir şekilde değiştirmekle görevlendirildim. Beni memnun edecek bir şekilde yeniden canlandırmam günler alırdı, ancak asıl değişiklik yaklaşık yarım günlük bir işti. Sorun biz 'bile olmasıdır continuebiz hala serbest gerekiyor' var1ve var2. Bir tane eklemek zorunda kaldım var3ve bu da free () ifadelerini yansıtmak zorunda kalmam için kusmama neden oldu.

O zamanlar nispeten yeni bir stajyerdim, ancak bir süre önce linux kaynak koduna bakıyordum, bu yüzden amirime bir goto ifadesi kullanıp kullanamayacağımı sordum. Evet dedi ve ben bunu yaptım:

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);
}

Bence en iyisi devam ediyor ama benim için görünmez bir etiketi var. Aynı molalar için de geçerli. Burada olduğu gibi, sizi birçok yerde değişiklik yapmaya zorlamadığı sürece, devam etmeyi veya kırılmayı tercih ederim.

Ayrıca şunu da eklemeliyim ki, bu kullanımın goto next;ve next:etiketin benim için tatmin edici olmadığını. Onlar yansıtma daha sadece iyidir free()'s ve count++ifadeleri.

gotoNeredeyse her zaman yanlıştır, ancak ne zaman kullanmanın iyi olduğunu bilmek gerekir.

Tartışmadığım bir şey, diğer cevapların kapsadığı hata yönetimi.

Verim

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 */
}

Lütfen hatalıysam beni düzelt, ancak cont:etiketin ve goto cont;ifadenin performans için orada olduğuna inanıyorum (kesinlikle kodu daha okunaklı hale getirmiyorlar). Yaparak okunabilir kod ile değiştirilebilir

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

sınırlayıcıları atlamak için. Ancak olabildiğince hızlı olmak ve gereksiz işlev çağrıları yapmaktan kaçınmak için bunu bu şekilde yaparlar.

Makaleyi Dijkstra'dan okudum ve çok ezoterik buldum.

google "dijkstra goto bildirimi zararlı olarak değerlendirildi" çünkü 2'den fazla bağlantı gönderecek kadar itibarım yok.

Goto'ları kullanmamak için bir neden olarak alıntı yaptığımı ve goto'ların kullanımları birleştiğinde okuduğumda hiçbir şeyin değişmediğini gördüm.

Zeyilname :

Devam eden ve molalar hakkındaki her şeyi düşünürken temiz bir kural ile geldim.

  • Bir süre döngüsünde devam ederseniz, süre döngüsünün gövdesi bir işlev olmalı ve devam bir return ifadesi olmalıdır.
  • Bir süre döngüsünde, bir break ifadesine sahipseniz, while döngüsünün kendisi bir işlev olmalı ve break bir return deyimi olmalıdır.
  • Her ikisine de sahipseniz, bir şeyler yanlış olabilir.

Kapsam sorunları nedeniyle her zaman mümkün olmamakla birlikte, bunu yapmanın kodumla ilgili mantıklı olmayı çok daha kolay hale getirdiğini gördüm. Ne zaman bir döngü bir mola veya devam ettiğinde bana kötü bir his verdiğini fark etmiştim.


2
+1 ama bir noktada aynı fikirde olmayabilir miyim? “Bence programcıların, insanların kötülük olduğuna inandıkları için yetiştirilmesi gerekiyor.” Belki öyleyse, ancak ilk kez 1975'te BASIC'de satır numaraları ve GOTO'lar ile bir metin editörü olmadan programlamayı öğrendim. On yıl sonra yapılandırılmış programlama ile tanıştım, bundan sonra GOTO'yu tek başıma kullanmayı bıraktım. durdurmak için herhangi bir baskı. Bugün, GOTO'yu yılda birkaç kez çeşitli nedenlerle kullanıyorum, ancak fazla gelmiyor. GOTO’nun şeytan olduğuna inanmak için büyütülmedim, bildiğim kadarıyla bana zarar vermedi, hatta bazılarının da yararı olabilirdi. Sadece ben.
thb

1
Bence bu konuda haklısın. GOTO'ların kullanılmayacağı düşüncesiyle büyüdüm ve saf olaylarla, boş zamanlarında boş hafızaya sahip çoklu çıkış noktalarına sahip kod üzerinde çalışırken Linux kaynak koduna göz atıyordum. Aksi takdirde, bu teknikleri asla bilemezdim.
Philippe Carphin,

1
@thb Ayrıca, komik hikaye, o zaman amirime GOTO'ları kullanma izni için stajyer olarak sordum ve onlara, kullandığım şekilde kullanacağım gibi kullanacağımı açıkladım. Linux çekirdeği ve “Tamam, bu mantıklı ve ayrıca GOTO’ları C’de kullanabileceğinizi bilmiyordum” dedi.
Philippe Carphin,

1
@thb ben gibi (yerine döngüler kırma) döngüler içine Goto iyi olup olmadığını bilmiyorum bu bir ? Evet, bu kötü bir örnek, ancak Knuth'un Yapısal Programlaması ile ilgili hızlı ifadenin (örneğin 7a) hızlı bağlantı noktasının çok anlaşılır olmayan İfadelere gittiğini görüyorum .
Yai0Phah

@ Yai0Phah Amacımı açıklayacağım ancak açıklamam sizin 7a örneklerinizi azaltmıyor! Örneği onaylıyorum. Yine de, otoriter sophomores insanlara goto hakkında ders vermeyi sever. 1985'ten bu yana önemli bir soruna neden olan pratik bir goto kullanımı bulmak zordur, oysaki programcının işini kolaylaştıran zararsız gotolar bulabilir. Goto, nadiren modern programlamada ortaya çıkar, her neyse, ortaya çıktığında, tavsiyem, eğer kullanmak istersen, o zaman muhtemelen kullanmalısın. Goto iyi. Gotoyla ilgili asıl sorun, bazılarının gotoyu bırakmanın onları akıllı gösterdiğine inanmasıdır .
THB

5

Şahsen ben bu şekilde daha fazla refaktör olur:

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);
    }
}

Bu, derin yuvalamadan kaçınılmasıyla daha motive edici olacaktır, ancak (IMO ilk kod örneğiyle daha kötü bir sorun) ve elbette CleanupAfterError'ın kapsam dışı kalmasının (bu durumda "params" olabilir boşaltmanız gereken ayrılmış bir bellek, kapatmanız veya her neyse) bir DOSYA * içeren bir yapı olabilir.

Bu yaklaşımla gördüğüm en büyük avantajlardan biri, FTCF2 ve FTCF3 arasında (ya da mevcut bir mevcut adımı kaldırmak için) varsayımsal bir geleceğe adım atmanın hem daha kolay hem de daha temiz olmasıdır. kodumu bana linç etmek istemediğimi devraldı!) - Bir kenara, iç içe geçmiş sürüm yoksun.


1
Bunu soruma söylemedim, ancak FTCF'lerin aynı parametreleri taşımaması olasıdır, bu da bu kalıbı biraz daha karmaşık hale getirir. Yine de teşekkürler.
Robz

3

MISRA (Motor Endüstrisi Yazılımı Güvenilirlik Derneği) sıkı kriterler altında goto sağlayan C kodlama kurallarına bakınız (Örneğinizin karşıladığı)

Çalıştığım yerde aynı kod yazılırdı - buna gerek yok - onlar hakkında gereksiz dini tartışmalardan kaçınmak herhangi bir yazılım evinde büyük bir artı.

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:
} 

veya "sürüklenmeye git" için - goto'dan daha tehlikeli bir şey, ancak "Hiç gitme !!!" kampı) "Elbette tamam olmalı, Goto kullanmıyor" ...

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

İşlevler aynı parametre türüne sahipse, bunları bir tabloya koyun ve bir döngü kullanın -


2
Mevcut MISRA-C: 2004 kuralları, hiçbir şekilde goto'ya izin vermemektedir (bkz. Kural 14.4). MISRA komitesinin her zaman bu konuda karışık olduğunu, hangi ayağa kalkacağını bilmediklerini unutmayın. Birincisi, koşulsuz olarak goto kullanımını yasakladılar, devam etti vb. Bir yazı tipi olarak, MISRA'nın ifl ifadeleri içinde atamayı yasakladığını, bunun çok iyi nedenlerden dolayı yasaklandığını lütfen unutmayın.

1
Analitik bir bakış açısıyla, bir programa bayrak eklemek, bayrağın kapsamda olduğu tüm kod kodunu çoğaltmakla eşdeğerdir, her if(flag)birinde bir kopya "if" dalını alır ve diğer kopyada karşılık gelen ifadeleri alır " Başka". Bayrağı belirleyen ve silen eylemler, kodun bu iki sürümü arasında atlayan gerçekten "gotos" dur. Bayrak kullanımının herhangi bir alternatiften daha temiz olduğu zamanlar vardır, ancak bir gotohedefi kurtarmak için bayrak eklemek iyi bir takas değildir.
supercat

1

gotoAlternatif do/while/continue/breakhackery'nin daha az okunabilir olması durumunda da kullanıyorum .

gotoHedeflerinin bir ismi olması ve okur olması avantajı var goto something;. Bu daha okunabilir olabilir breakveya continueaslında durdurma falan devam değilseniz.


4
do ... while(0)Gerçek bir döngü olmayan, fakat kullanımının önlenmesi için harebrained bir girişim olan veya başka bir yapının içindeki herhangi bir yer goto.
Aib

1
Ah, teşekkürler, bu özel markayı bilmiyordum "Neden birileri bunu yapıyor ?!" henüz inşa ediyor.
Benjamin Kloster

2
Genellikle, do / while / Devam Et / Kesilen Korsanlığı, yalnızca onu içeren modül çok uzun sürdüğü için okunamaz hale gelir.
John R. Strohm

2
Goto kullanmanın bir gerekçesi olarak bunda hiçbir şey bulamıyorum. Mola ve devam etmenin belirgin bir sonucu var. git ... Nerede? Etiket nerede? Ara ver ve bir sonraki adımın tam olarak nerede olduğunu söyle.
Rig

1
Etiket elbette döngü içinden görünmelidir. John R. Strohm'un yorumunda, @John R.'nin sürtünme uzunluğu kısmına katılıyorum. Ve amacınız döngü korsanlığına çevrilmiş, "Neyin kopması? Bu bir döngü değil!" Olur. Her durumda, OP'nin olabileceğinden korktuğu şey bu oluyor, bu yüzden tartışmayı bırakıyorum.
aib,

-1
for (int y=0; y<height; ++y) {
    for (int x=0; x<width; ++x) {
        if (find(x, y)) goto found;
    }
}
found:

Eğer sadece bir ilmek varsa, hiçbir şekilde damgalanmasa da breaktam olarak aynı şekilde çalışır goto.
9000,

6
-1: İlk olarak, x ve y, bulunan KAPSAMINDAKİ KAPSAMIDIR: yani bu size yardımcı olmaz. İkincisi, kodun yazıldığı haliyle, ulaştığınız gerçeği bulundu: aradığınızı bulduğunuz anlamına gelmez.
John R. Strohm

Bunun nedeni, çok sayıda döngüden kopma durumu için aklıma gelen en küçük örnek olmasıdır. Lütfen daha iyi bir etiket veya bitmiş bir kontrol için düzenlemekten çekinmeyin.
aib

1
Ancak, C işlevlerinin mutlaka yan etki içermemesi gerektiğini de unutmayın.
aib

1
@ JohnR.Strohm Bu pek bir anlam ifade etmiyor ... 'Found' etiketi, değişkenleri kontrol etmek için değil, döngüyü kırmak için kullanılır. Değişkenleri kontrol etmek isteseydim şöyle bir şey yapabilirdim: için (int y = 0; y <height; ++ y) {için (int x = 0; x <genişlik; ++ x) {if (find ( x, y)) {doSomeThingWith (x, y); Goto bulundu; }}} bulunan:
YoYoYonnY 14:15

-1

Her zaman bir yolun kabul edilebilir olduğunu ve diğerinin olmadığını söyleyen kamplar olacaktır. Çalıştığım şirketler kaşlarını çattı veya kullanması kesinlikle önerilmez. Şahsen, bir zamanını kullandığım zamanı düşünemiyorum, ama bu onların kötü olduğu anlamına gelmez , başka bir şey yapmanın başka bir yoludur.

C'de, tipik olarak aşağıdakileri yaparım:

  • İşlemeyi engelleyebilecek koşulları (hatalı girişler, vb.) Ve "dönüş" test edin
  • Kaynak tahsisi gerektiren tüm adımları uygulayın (örneğin mallocs)
  • Birden fazla adımın başarıyı kontrol ettiği yerde işlemi gerçekleştirin
  • Başarıyla atanırsa, kaynakları serbest bırakın
  • Herhangi bir sonuç döndür

İşleme için goto örneğinizi kullanarak şunu yapardım:

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

İç içe geçme yok ve if cümlelerinin içinde, adım bir hata oluşturduysa, herhangi bir hata raporlaması yapabilirsiniz. Bu nedenle, gotos kullanan bir yöntemden "daha kötü" olması gerekmez.

Birinin başka bir yöntemle yapılamayan ve aynı okunabilir / anlaşılabilir olduğu ve bu da IMHO'nun anahtarının olduğu bir kişinin goto olduğu bir durumla karşı karşıya gelmedim.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.