Wątek przeniesiony 2014-06-10 15:24 z C/C++ przez ŁF.

Czy return jest odpowiednikiem goto?

0

Witam,
Nie jestem jakiś super zawansowany w cpp, lecz sprzeczałem juz sie z dwoma osobami na ten temat, jedną nawet był niekompetentny (moje zdanie) wykładowca.
A mianowicie osoby te uważają, że return jest instrukcją skoku i jest odpowiednikiem goto, ja tak nie uważam i jestem praktycznie pewien swojego że return jest instrukcja sterującą ktora na pewno zwraca sterowanie w miejsce gdzie funkcja została wywołana. Idąc tropem tych dwóch osób i kierując się zasadami dobrego programowania instrukcja return nie zawiera się w tych regułach i powinna być potępiana więc wtedy wszystko trzeba by było robić na wskaźnikach niepotrzebnie zabierając pamięć?
Jestem praktycznie pewien swojego, lecz może się myle, bo już zgłupiałem.
Z góry dzięki za pomoc

0

23:46 Elles • does return work like break continue?
23:46 Elles • meaning goto
23:46 Codex_ • elles: no
23:47 lewpus_ • Elles: return literally returns
23:48 Elles • sure but how it works for the program
23:48 lewpus_ • But in a sense, it does work like break/continue
23:48 lewpus_ • its a "jump"
23:48 Elles • thx
23:48 lewpus_ • What happens when you call a function is that your, err, "program", jumps there
23:49 lewpus_ • When return is ran, it jumps back
23:49 Codex_ • cpus have special instructions for functions to work.
23:49 lewpus_ • and you are free to collect your return value in a register
23:49 lewpus_ • if its not void
23:49 Elles • but for continue break; you have a jump too?
23:49 lewpus_ • yes, essentially
23:49 Elles • so whats the difference :D
23:50 lewpus_ • in C++, a lot, in terms of CPU, depends on arch, but strictly speaking, it jumps in a specific way
23:50 Codex_ • returning from function takes address from stack
23:50 lewpus_ • but, in terms of C++
23:51 ProN00b • Elles, a function can return to anywhere its called from, while a break/continue always goes to the same place
23:51 lewpus_ • return will, "return to where I was", aswell as allow you to "return" a value
23:51 lewpus_ • back out
23:52 ProN00b • Elles, in x86, a break/continue/goto would translate to a opcode starting with "j", while a return very likely has a "ret" unless the function is inlined

1

goto - skok nie wiadomo dokąd, trzeba odłożyć na stos wszystko co dotychczas odczytałeś i zacząć szukać label który może być wyżej lub niżej czyli nie wiadomo gdzie.
return - wiadomo że kończy funkcje czyli skok na 100% w dół do klamry zamykającej funkcje.

Jeżeli już mówimy o skokach to wywołanie funkcji też jest skokiem, if, while, for, break, continue- też są skokami, z tym że to są skoki w miejsce które bardzo łatwo znaleźć a właściwie to nawet nie trzeba szukać, przy przyzwoitym formatowaniu widoczne z pierwszego rzutu oka.

0

Jak już mowa o goto, spotkałem się z opiniami że goto jest złe, dlaczego tak jest?

0

W przypadku goto bardzo dobrze wiadomo, dokąd jest wykonywany skok - po prostu do odpowiedniego labela, który wiadomo gdzie jest (w końcu sam go napisałeś). Label ów nie jest wyszukiwany w trakcie działania programu - kompilator zamienia każde goto na statyczny skok, który akurat jest jednym z najszybszych możliwych skoków. Goto jest zamieniane zwykle przez kompilator na instrukcję JMP, lub jeśli było poprzedzone warunkiem, którąś z innych instrukcji skoku warunkowego (np. JZ, JE, JNE itp).

Return to nie to samo co goto, ponieważ adres docelowy nie jest znany w trakcie kompilacji. Return w środku funkcji może być przez kompilator zrealizowany jako skok w jakieś miejsce w pobliżu końca funkcji (zwykle tuż przed epilog funkcji), ale to już bardzo mocno zależy od kompilatora, poziomu optymalizacji itp. Natomiast każdy return i tak ostatecznie kończy się skokiem do miejsca z którego funkcja została wywołana (na x86 instrukcją RET).

Zarówno goto jak i return mogą pociągać za sobą wywołania odpowiednich destruktorów przy opuszczaniu zasięgu, więc mogą pociągnąć za sobą "lawinę różnych zdarzeń".

Skoro jedno i drugie jest technicznie wykonywane jako jedna lub więcej instrukcji skoku, to czemu nie lubimy goto? Ano dlatego goto jest be, ponieważ jest instrukcją bardzo ogólną, niskopoziomową, słabo przekazującą intencję autora. Za pomocą goto można zrealizować każdy rodzaj standardowej pętli, oraz tysiąc innych rzeczy, których nie da się wyrazić prostymi pętlami. Nawet jeżeli użyjesz goto do zrobienia typowej pętli while, to słabo taką pętlę widać na pierwszy rzut oka. W przypadku jawnego while czy for kod jest dużo bardziej czytelny (a jeszcze bardziej czytelny jeśli zamiast pętli wywołasz odpowiednią metodę z biblioteki standardowej np. std::transform).

0
Krolik napisał(a):

do odpowiedniego labela, który wiadomo gdzie jest (w końcu sam go napisałeś).
Witam Panie Rainman na forum 4programmers. Z całą pewnością po kilku latach będziesz pamiętać gdzie w tym programie wsadziłeś ten label.
Oczywistym też jest fakt że nigdy nie musiałeś czytać kodu napisanego nie przez siebie ani też nigdy nikomu swego kodu nie pokazałeś.

3

@Krolik: pisanie w tym wątku o kodzie maszynowym nic nie wnosi, bo dla kompilatora goto czy break to naprawdę żadna różnica.

@topic: Tu się rozchodzi o zrozumiałość kodu dla człowieka. Z goto bardzo łatwo zrobić głupie błędy, bo ono zupełnie nie powiązane z logiczną strukturą kodu.
tak jak pisł ktoś wyżej return, break, continue jest ściśle związane z konkretnym blokiem kodu (klamrami).
Ta mała róznica powoduje, że kod z goto jest trudniejszy w czytaniu, a kod go używający o wiele łatwiejszy do zepsucia. Tu masz przykład wpadki apple wynikający z używania goto.

2

Owszem, łatwo zrobić głupie błędy, ale w czystym C jest sytuacja, kiedy goto jest bardzo przydatne: obsługa błędów.

int jakas_funkcja() {
  zmienna1 = malloc(...);
  zmienna2 = malloc(...);

  if (jakies_wywolanie(....) != 0)
    goto exit;
  if (cos_innego(....) != 0)
    goto exit;
  ...

exit:
  if (zmienna1 != NULL)
    free(zmienna1)
  if (zmienna2 != NULL)
    free(zmienna2)
  ...
  return errno;
}

Z goto bardzo łatwo zrobić głupie błędy, bo ono zupełnie nie powiązane z logiczną strukturą kodu.
tak jak pisł ktoś wyżej return, break, continue jest ściśle związane z konkretnym blokiem kodu (klamrami).

A z wczesnym return, break, continue to niby nie można zrobić łatwo głupich błedów? Te instrukcje są akurat tylko troszkę lepsze od goto; ot po prostu lepiej nazwane goto specjalnego użytku (continue = goto koniec pętli; break = goto za koniec pętli; return = goto koniec funkcji); ale jakiejś przepaści w czytelności tu nie ma. Dlatego niektóre języki potraktowały je tak samo jak goto - pozbyły się ich.

3

Idąc tropem tych dwóch osób i kierując się zasadami dobrego programowania instrukcja return nie zawiera się w tych regułach i powinna być potępiana

Jutro się dowiemy, że nie należy używać #include bo to jest niskopoziomowe copy&paste, alboint bo to typ prymitywny i nie wiadomo co zawiera.

0

Wczesny return w środku ciała funkcji jest potępiany w wielu poradnikach dotyczących stylu kodowania. Właśnie dlatego, że działa jak zakamuflowane goto.

0

return jest tłumaczone na rozkaz "ret", który wcale nie jest magiczny, bo po prostu ze stosu zdejmuje adres i skacze do niego - takie ułatwienie dla programistów żeby nie pisać za każdym razem dwóch instrukcji w assemblerze, ale jednocześnie wskazówka dla procesora który planuje skoki naprzód - ogólnie skoki są dla procesora dość bolesne bo patrzy on na kilka instrukcji naprzód - w przypadku nagłego skoku musi wymazać wszystko co wiedział o przyszłych instrukcjach i trochę zwolnić

możesz podmienić wartość adresu na stosie i po wykonaniu ret będziesz miał skok w dowolnie upatrzone miejsce (tak działają różnego typu exploity)

w uproszczeniu - tak, return jest instrukcją skoku i jest odpowiednikiem goto

skoków w kodzie maszynowym jest miliony, ale w językach wysokiego poziomu nie lubimy ich tylko ze względu na czytelność - kod z goto się dużo dłużej analizuje i łatwiej o błędy

0
Azarien napisał(a):

alboint bo to typ prymitywny i nie wiadomo co zawiera.

z tym akurat się zgadzam
bardzo dużo błędów wynika z tego że funkcja przyjmuje jakiś identyfikator w formie inta, zamiast obiektu z którego sobie ten identyfikator wydobędzie - w rezultacie w kodzie zdarzają się błędy przez podanie błędnego identyfikatora, bo innego nawet typu obiektu

1
Krolik napisał(a):

Wczesny return w środku ciała funkcji jest potępiany w wielu poradnikach dotyczących stylu kodowania. Właśnie dlatego, że działa jak zakamuflowane goto.

przy czym opakowywanie całego body funkcji w if zamiast zrobienia return na jej początku też jest potępiane...
myślę że nie ma złotej reguły i trzeba samemu zdecydować czy coś jest czytelne czy nie

2

OT. Co do używania goto, return, int, wszystkich tych porad czego i jak używać:

Najważniejszą regułą jest zdrowy rozsądek. Żaden standard kodowania czy stylu tego nie zastąpi (może pomóc oczywiście).

2

Co za problem opakować w if, a ciało przenieść do osobnej funkcji?

I mnożyć bez sensu funkcje? I jak nazwiesz później taką funkcję jeżeli już ta pierwsza ma właściwą nazwę a druga ma się różnić od pierwszej tym że nie sprawdza czy coś nie jest nullem

0

I mnożyć bez sensu funkcje? I jak nazwiesz później taką funkcję jeżeli już ta pierwsza ma właściwą nazwę a druga ma się różnić od pierwszej tym że nie sprawdza czy coś nie jest nullem

Jeśli wczesny return jest tylko do walidacji, to ja osobiście bym go nie rozbijał na dwie funkcje, tylko użył mechanizmu wyjątków. Natomiast w czystym C bez wczesnego returna i bez goto raczej ciężko się obejść.
Inna rzecz, że funkcja najeżona returnami/break/continue może być tak równie nieczytelna jak funkcja najeżona goto.

1 użytkowników online, w tym zalogowanych: 0, gości: 1