2014-09-29 12 views
15

znalazłem jakiś kod C, który ma tę strukturę:do..while wewnątrz przełączyć

switch (n) { 
    do { 
    case 1: 
     // do some things 
     if (some condition) 
      goto go_on; 
    case 2: 
     // do some things 
     if (some condition) 
      goto go_on; 
    case 3: 
     // do some things 
     if (some condition) 
      goto go_on; 
    } while (1); 
    do { 
    case 4: 
     // do some things 
     if (some condition) 
      goto go_on; 
    case 5: 
     // do some things 
     if (some condition) 
      goto go_on; 
    } while (1); 
} 

go_on: 

I zaprogramowany w języku C dla lat (wiele lat temu) i by pomyślał, że będzie to błąd składni. Myślę, że ma to coś wspólnego z optymalizacją pętli, ale zastanawiałem się, czy ktoś może wyjaśnić, co robi. Co dzieje się po osiągnięciu while(1), czy skutecznie wraca do przełącznika? A w szczególności dlaczego są tam dwie osoby?

Przy okazji, I nie chcesz rozpocząć dyskusję na temat korzystania z goto lub jeśli jest to zły projekt. Nie napisałem tego i zakładam, że autor zrobił to w ten sposób, ponieważ sprawił, że pętla działała tak szybko, jak to możliwe.

+13

Wygląda na odmianę [Urządzenia Duffa] (https://en.wikipedia.org/wiki/Duff's_device). Tutaj jest [wyjaśnienie] (https://stackoverflow.com/questions/514118/how-does-duffs-device-work). – tangrs

+2

Pomyśl o instrukcjach 'case' jako etykietach z' switch' jako 'goto'. – chux

+1

to rodzaj techniki rozwijania pętli. pomyśl o końcu pętli. – Alex

Odpowiedz

6

Równoważny kod, który może lepiej pokazać przebieg programu.

Akin do Duff's device, aby umożliwić wprowadzenie pętli w pewnym położeniu pośrednim. @ tangrs

Generalnie zmarszczył brwi w tych dniach: 1) kompilatory zwykle wykonują lepszą pracę przy optymalizacji & 2), jak OP stwierdził, mogą z łatwością przesłonić znaczenie kodu. Używaj ostrożnie.

W kodzie OP, po warunku while, przepływ programu nie powraca do instrukcji switch. Modele switch i case wpływają tylko na początkowe wprowadzenie w pętle .

if (n == '1') goto case1; 
if (n == '2') goto case2; 
... 
if (n == '5') goto case5; 
goto go_on; 

do { 
    case1: 
    // do some things 
    if (some condition) goto go_on; 
    case2: 
    // do some things 
    if (some condition) goto go_on; 
    case3: 
    // do some things 
    if (some condition) goto go_on; 
} while (1); 

do { 
    case4: 
    // do some things 
    if (some condition) goto go_on; 
    case5: 
    // do some things 
    if (some condition) goto go_on; 
} while (1); 

go_on: 

[Edycja]

Istnieją 2 while pętle, aby pomieścić przepływu pierwotnego koder do skoku w punktach pośrednich na jeden z dwóch pętli.

Ponowne pisanie kandydata następuje. Mając dostęp do ogólnego kodu, z pewnością można uzyskać czystsze rozwiązanie.

int n2 = n; // Only evaluate n once as in the switch statement. 
if (n2 >= 1) { 
    if (n2 <= 3) { 
    while (1) { 
     if (n2 <= 1) { 
     // do some things 
     if (some condition) { break; } 
     } 
     if (n2 <= 2) { 
     // do some things 
     if (some condition) { break; } 
     } 
     // do some things 
     if (some condition) { break; } 
     n2 = 1; 
    } 

    else if (n2 <= 5) { 
    while (1) { 
     if (n2 <= 4) { 
     // do some things 
     if (some condition) { break; } 
     } 
     // do some things 
     if (some condition) { break; } 
     n2 = 4; 
    } 
    } 

} 
+0

Dziękuję, Tobie i innym, którzy odpowiedzieli. Teraz ma to sens. Właściwie to jestem trochę zmartwiony, że sam nie mogłem tego rozgryźć. Może jestem już za stary na te rzeczy! – kh99

+0

@ kh99, wątpliwość, czy jest za stara. Ta technika działa dobrze i może zaspokoić potrzebę w bardzo wybranych sytuacjach. Ale IMO, konserwacja jest droższa, ponieważ oryginalne kodowanie i kodowanie powinno unikać trudnych do utrzymania nieudokumentowanych fragmentów w ten sposób. Nie tak wielka strata, że ​​nie dotarło to wcześniej. – chux

+0

Nie jestem pewien co do "z pewnością czystszego" części drugiego bloku. Ale jestem pewny, że nie ma sobie równych. W pierwszym bloku 'n' służy tylko do określenia miejsca początkowego w pętli i nie jest ponownie sprawdzany po zakończeniu pętli. – John

6

Pozwól mi przepisać ten kod dla Ciebie, może to uczyni to bardziej oczywistym. Poniższy kod jest bardziej lub mniej równoważne do jednego z nich napisali:

if (n == 1) goto ONE; 
if (n == 2) goto TWO; 
if (n == 3) goto THREE; 
if (n == 4) goto FOUR; 
if (n == 5) goto FIVE; 
goto SKIP_ALL; 

while (true) { 
ONE: 
    // do some things 
    if (some condition) goto go_on; 
TWO: 
    // do some things 
    if (some condition) goto go_on; 
THREE: 
    // do some things 
    if (some condition) goto go_on; 
} 

while (true) { 
FOUR: 
    // do some things 
    if (some condition) goto go_on; 
FIVE: 
    // do some things 
    if (some condition) goto go_on; 
} 

SKIP_ALL: 
go_on: 

Pętle są w przełączniku, nie powodują one przełącznik odbywać się częściej. Przełącznik w zasadzie decyduje o tym, która pętla przepływa przez program i od której instrukcji zaczyna się ta pętla. Po tym jak skoczył, pętle kontynuują normalnie. Zauważ też, że przełącznik jest zwykle szybszy niż wszystkie te instrukcje if.

I nie, goto nie jest ogólnie złym projektem. A switch to tylko goto i przełącznik nie jest złym projektem. W rzeczywistości prawie każda gałąź kodu w funkcji/metodzie wykonywanej na CPU lub w VM jest prostym goto (czasami warunkowym, czasem nie). Chodzi tylko o to, że goto jest najbardziej prymitywnym, niskonakładowym sposobem rozgałęzienia i niewiele mówi czytelnikowi o intencji. Ilekroć istnieje wyższy poziom jeden, który sprawia, że ​​twoja intencja jest bardziej oczywista, lepiej jest użyć tego, zamiast tego. Używanie goto jest tylko złym pomysłem, jeśli mógłbyś z łatwością napisać ten sam kod bez użycia goto i również nie byłoby gorzej. W niektórych (choć bardzo rzadkich) przypadkach goto jest prawie nieuniknione lub każda próba uniknięcia go tworzy brzydki, nieczytelny, bardzo skomplikowany kod lub bardzo słabą wydajność.

"Goto uważany za szkodliwy" esej pochodzi z czasu, w którym niektórzy ludzie używali goto na wszystko: dla if/else gałęzi kodu, dla pętli, dla przełączników, aby wyjść z pętli/przełączników, aby uniknąć rekursji, itd. Jeśli nadużywasz goto w ten sposób i jeśli robisz rzeczy takie jak skakanie do etykiety, która natychmiast przeskakuje do innej etykiety, ludzie tracą orientację. Kod taki jak ten jest nieczytelny i bardzo trudny do debugowania. W ten sposób zapisujesz kod assemblera, ale nie powinno to być sposób w jaki piszemy kod C.

1

Wszystkie opakowania są po prostu etykietami. Więc jeśli do usunięcia instrukcji switch kod będzie wyglądał

do { 
    case '1': 
     // do some things 
     if (some condition) 
      goto go_on; 
    case '2': 
     // do some things 
     if (some condition) 
      goto go_on; 
    case '3': 
     // do some things 
     if (some condition) 
      goto go_on; 
    } while (1); 
    do { 
    case '4': 
     // do some things 
     if (some condition) 
      goto go_on; 
    case '5': 
     // do some things 
     if (some condition) 
      goto go_on; 
    } while (1); 

go_on: 

więc istnieją dwa zwykłe nieskończonej pętli do-while, które zatrzymują powtórzeń w zależności od pewnych warunków wewnętrznych w pętlach. Możesz napisać ten kod jeszcze prostszy:

do { 
     // do some things 
     if (some condition) 
      goto go_on; 
     // do some things 
     if (some condition) 
      goto go_on; 
     // do some things 
     if (some condition) 
      goto go_on; 
    } while (1); 

    do { 
     // do some things 
     if (some condition) 
      goto go_on; 
     // do some things 
     if (some condition) 
      goto go_on; 
    } while (1); 

go_on: 

Co dodaje przełącznik do powyższego kodu? Dostarcza tylko punktów wejścia do pętli z pominięciem normalnych punktów wejścia pętli. To pierwsze iteracje zaczynają się od jakiejś etykiety sprawy i nic więcej. Również jeśli nie ma odpowiedniej etykiety przypadku, pętle zostaną pominięte.

0

Nie jestem pewien, czy mój wariant jest lepszy, ale będę pisać

switch (n) { 
    LOOP1: 
     // do some things 
     if (some condition) 
      break; 
    case 2: 
     // do some things 
     if (some condition) 
      break; 
    case 3: 
     // do some things 
     if (some condition) 
      break; 
     goto LOOP1; 
    case 4: 
    LOOP4: 
     // do some things 
     if (some condition) 
      break; 
    case 5: 
     // do some things 
     if (some condition) 
      break; 
     goto LOOP4; 
} 

Oryginalny jest dość kłopotliwe ze względu na switch-do-while. Wszystkie rozwiązania zastępcze wykorzystują niektóre z nich, a właściwie wiele z nich i/lub zagnieżdżają się w skomplikowanych warunkach. Zamiast tego zamieniłem mylące do-while przez goto.


Można by uprościć

 if (some condition) 
      break; 
     goto LOOP4; 

do

 if (!some condition) goto LOOP4; 

ale wolę zachować jednolite.


Jeśli można wyodrębnić

// do some things 
    if (some condition) 
     break; 

do funkcji, można napisać

switch (n) { 
    case 1: while (true) { 
     if (body1()) break; 
     if (body2()) break; 
     if (body3()) break; 
    } 
    break; 
    case 2: while (true) { 
     if (body2()) break; 
     if (body3()) break; 
     if (body1()) break; 
    } 
    break; 
    case 3: while (true) { 
     if (body3()) break; 
     if (body1()) break; 
     if (body2()) break; 
    } 
    break; 
    case 4: while (true) { 
     if (body4()) break; 
     if (body5()) break; 
    } 
    break; 
    case 5: while (true) { 
     if (body5()) break; 
     if (body4()) break; 
    } 
} 

To nieco monotonne, ale bardzo jasne i nie używa, choć spadek.

+0

@chux Dzięki, naprawione. Ale teraz jest jeszcze bardziej powtarzalny. – maaartinus

+0

To prawda, ale przynajmniej na pewno jasne. – chux

0

Widziałem już tego rodzaju strukturę. Możliwe, że autor pisał pewnego rodzaju maszynę państwową. (Patrz przykład: źródło: zlib).

Czy część "zrób to sam" zmienia się w jakąkolwiek sprawę będzie następować?

+0

Oryginalny kod, na który patrzyłem, znajduje się tutaj: [link] (http://wwwhomes.uni-bielefeld.de/achim/prime_sieve.html). Jak widać, jest to implementacja Sieve of Eratostenes. Nie wierzę, że n zmienia się w pętli. – kh99