Oświadczenie o zwrocie - Return statement

W programowaniu komputerowym , o powrót rachunku przyczyny wykonanie opuścić aktualny podprogram i wznowić w momencie w kodzie bezpośrednio po instrukcji, która nazywa się podprogram, znany jako adres zwrotny . Adres powrotu jest zapisywany przez procedurę wywołującą, obecnie zwykle na stosie wywołań procesu lub w rejestrze . Instrukcje Return w wielu językach umożliwiają funkcji określenie wartości zwracanej, która ma być przekazana z powrotem do kodu, który wywołał funkcję.

Przegląd

W C i C++ , (gdzie jest wyrażeniem ) jest instrukcją, która mówi funkcji, aby zwróciła wykonanie programu do funkcji wywołującej i zgłosiła wartość . Jeśli funkcja ma zwracany typ void , instrukcja return może być użyta bez wartości, w którym to przypadku program po prostu wyłamuje się z bieżącej funkcji i powraca do funkcji wywołującej. return exp;expexp

W Pascalu nie ma instrukcji powrotu. (Jednak w nowszych Pascalach the może być użyty do natychmiastowego zwrócenia wartości. Bez parametrów, po prostu przerywa procedurę.) Podprogram automatycznie powraca, gdy wykonanie osiągnie ostatnią wykonywalną instrukcję. Wartości mogą być zwracane przez przypisanie do identyfikatora, który ma taką samą nazwę jak podprogram, funkcja w terminologii Pascala. W ten sposób identyfikator funkcji jest używany do wywołań rekurencyjnych i jako posiadacz wyniku; jest to syntaktycznie podobne do jawnego parametru wyjściowego . Ta sama składnia jest używana w Fortran 66 i Fortran 77, chociaż w FORTRAN II dodano instrukcję powrotu . W niektórych innych językach zmienna wynikowa zdefiniowana przez użytkownika jest używana zamiast identyfikatora funkcji. Exit(exp);

Oberon ( Oberon-07 ) ma klauzulę return zamiast instrukcji return. Klauzula return jest umieszczana po ostatniej instrukcji treści procedury. Umożliwia to sprawdzanie w czasie kompilacji poprawności zwracanej i zwracanej wartości z procedury.

Niektóre zorientowane na wyrażenia języki programowania , takie jak Lisp , Perl i Ruby , pozwalają programiście pominąć jawną instrukcję powrotu, określając zamiast tego, że ostatnio oceniane wyrażenie jest wartością zwracaną podprogramu.

W innych przypadkach zwracana jest wartość Null, jeśli nie ma wyraźnej instrukcji return: w Pythonie wartość Nonejest zwracana, gdy instrukcja return jest pominięta, podczas gdy w JavaScript zwracana undefinedjest wartość .

W Windows PowerShell wszystkie ewaluowane wyrażenia, które nie są przechwycone (np. przypisane do zmiennej, rzutowane na void lub potokowe do $null ) są zwracane z podprogramu jako elementy w tablicy lub jako pojedynczy obiekt w przypadku, gdy tylko jeden obiekt nie został schwytany.

W Perlu zwracana wartość lub wartości podprogramu mogą zależeć od kontekstu, w którym została wywołana. Najbardziej fundamentalne rozróżnienie jest skalarne kontekst gdzie kod nazywając spodziewa się jedną wartość, listy kontekst, w którym kod wywołujący oczekuje listę wartości i void kontekst, w którym kod wywołujący nie przewiduje wartości zwracanej w ogóle. Podprogram może sprawdzić kontekst za pomocą wantarrayfunkcji. Specjalna składnia zwrotu bez argumentów jest używana do zwracania niezdefiniowanej wartości w kontekście skalarnym i pustej listy w kontekście listowym. Kontekst skalarny można dalej podzielić na Boolean , number, string i różne konteksty typów odwołań . Również kontekstu obiektu mogą być zwracane przy kontekstowe sekwencję powrotny z lazy oceny wartości skalarnej.

Wiele systemów operacyjnych pozwala programowi zwrócić wynik (oddzielony od normalnego wyjścia ) po zakończeniu jego procesu; te wartości są określane jako kody powrotu , a dokładniej statusy wyjścia . Ilość informacji, które można przekazać w ten sposób, jest dość ograniczona, w praktyce często ogranicza się do sygnalizowania sukcesu lub porażki. Z poziomu programu ten powrót jest zazwyczaj osiągany przez wywołanie Exit (wywołanie systemowe) (częste nawet w C, gdzie dostępny jest alternatywny mechanizm powrotu z funkcji main ).

Składnia

Oświadczenia zwrotne przybierają różne formy. Najczęściej spotykane są następujące składnie:

Język Oświadczenie o zwrotach Jeśli pominięto wartość, Return
Ada , powłoka Bourne'a , C , C++ , Java , PHP , C# , JavaScript , D
return value;
w powłoce Bourne'a wartość wyjściowa ostatniego polecenia wykonanego w funkcji

w C i C++, niezdefiniowane zachowanie, jeśli funkcja zwraca wartość

w PHP zwraca NULL

w JavaScript zwraca wartość undefined

w Javie i C#, niedozwolone, jeśli funkcja zwraca wartość

PODSTAWOWY
RETURN
Seplenienie
(return value)
wartość ostatniego zestawienia
Perl , Rubin
return @values;
return $value;
return;

lub kontekstowa sekwencja zwrotów

wartość ostatniego zestawienia
PL/I
return(expression);
return;

niezdefiniowane zachowanie, jeśli procedura jest zadeklarowana jako zwracająca wartość
Pyton
return value
None
Pogawędka
^ value
Tcl
return
return $value
return -code error "Error message"

lub bardziej skomplikowana kombinacja opcji

wartość ostatniego zestawienia
Visual Basic .NET
Return value
Windows PowerShell
return value;
obiekt
montaż x86
ret
zawartość rejestru eax (według konwencji)

W niektórych językach asemblerowych , na przykład dla MOS Technology 6502 , używany jest mnemoniczny "RTS" (ReTurn from Subroutine).

Wiele deklaracji zwrotów

Języki z jawną instrukcją return stwarzają możliwość wielu instrukcji return w tej samej funkcji. To, czy to dobrze, czy nie, jest kontrowersyjne.

Silni zwolennicy programowania strukturalnego zapewniają, że każda funkcja ma jedno wejście i jedno wyjście (SESE). W związku z tym argumentowano, że należy unikać stosowania wyraźnej instrukcji powrotu z wyjątkiem tekstowego końca podprogramu, biorąc pod uwagę, że kiedy jest używane do „wcześniejszego powrotu”, może cierpieć z powodu tego samego rodzaju problemów, które pojawiają się w przypadku GOTO oświadczenie. I odwrotnie, można argumentować, że użycie instrukcji return jest opłacalne, gdy alternatywą jest bardziej zawiły kod, taki jak głębsze zagnieżdżanie, pogarszające czytelność.

W swoim podręczniku z 2004 roku David Watt pisze, że „często pożądane są przepływy sterowania z jednym wejściem i wieloma wyjściami”. Używając ramowego pojęcia sekwencera Tennenta, Watt jednolicie opisuje konstrukcje przepływu sterowania występujące we współczesnych językach programowania i próbuje wyjaśnić, dlaczego niektóre typy sekwencerów są preferowane od innych w kontekście przepływów sterowania z wieloma wyjściami. Watt pisze, że nieograniczone goto (sekwencery skoku) są złe, ponieważ przeznaczenie skoku nie jest oczywiste dla czytelnika programu, dopóki czytelnik nie znajdzie i nie zbada faktycznej etykiety lub adresu, który jest celem skoku. W przeciwieństwie do tego, Watt twierdzi, że konceptualna intencja sekwencera powrotnego jest jasna z jego własnego kontekstu, bez konieczności badania jego przeznaczenia. Co więcej, Watt pisze, że klasa sekwenserów znanych jako sekwencery ucieczki , definiowana jako „sekwencer, który kończy wykonywanie polecenia lub procedury obejmującej tekst”, obejmuje zarówno przerwy w pętli (w tym przerwy wielopoziomowe), jak i instrukcje return. Watt zauważa również, że chociaż sekwencery skoku (goto) zostały nieco ograniczone w językach takich jak C, gdzie cel musi znajdować się wewnątrz bloku lokalnego lub otaczającego bloku zewnętrznego, samo to ograniczenie nie jest wystarczające, aby intencja gotos w C była -opisywanie i tak mogą nadal produkować " kod spaghetti ". Watt analizuje również różnice między sekwencerami wyjątków a sekwencerami ucieczki i skoku; szczegółowe informacje na ten temat znajdziesz w artykule o programowaniu strukturalnym .

Według badań empirycznych cytowanych przez Erica S. Robertsa studenci programiści mieli trudności z formułowaniem poprawnych rozwiązań kilku prostych problemów w języku takim jak Pascal , który nie dopuszcza wielu punktów wyjścia. W przypadku problemu zapisania funkcji do liniowego wyszukiwania elementu w tablicy, badanie Henry'ego Shapiro z 1980 r. (cytowane przez Robertsa) wykazało, że używając tylko struktur kontrolnych dostarczonych przez Pascala, poprawne rozwiązanie podało tylko 20% badanych. , podczas gdy żaden podmiot nie napisał niepoprawnego kodu dla tego problemu, jeśli zezwolono na napisanie zwrotu ze środka pętli.

Inni, w tym Kent Beck i Martin Fowler, twierdzą, że jedna lub więcej klauzul ochronnych – warunkowe instrukcje powrotu „wczesnego wyjścia” w pobliżu początku funkcji – często sprawiają, że funkcja jest łatwiejsza do odczytania niż alternatywa.

Najczęstszym problemem we wczesnym wyjściu jest to, że czyszczenie lub końcowe instrukcje nie są wykonywane – na przykład przydzielona pamięć nie jest nieprzydzielona lub otwarte pliki nie są zamykane, co powoduje wycieki. Należy to zrobić w każdym miejscu zwrotu, co jest kruche i może łatwo spowodować błędy. Na przykład w późniejszym rozwoju programista może przeoczyć instrukcję return, a akcja, która powinna zostać wykonana na końcu podprogramu (np. instrukcja trace ), może nie być wykonywana we wszystkich przypadkach. Języki bez instrukcji return, takie jak standardowy Pascal , nie mają tego problemu. Niektóre języki, takie jak C++ i Python, wykorzystują koncepcje, które umożliwiają automatyczne wykonywanie akcji po zwróceniu (lub rzuceniu wyjątku), co łagodzi niektóre z tych problemów – często są one znane jako „try/finally” lub podobne. Funkcjonalność taka jak te klauzule "finally" może być zaimplementowana przez goto do pojedynczego punktu powrotu podprogramu. Alternatywnym rozwiązaniem jest użycie normalnego odwijania stosu (zmiennego cofania alokacji) przy wyjściu z funkcji w celu anulowania alokacji zasobów, na przykład poprzez destruktory zmiennych lokalnych lub podobnych mechanizmów, takich jak instrukcja „with” Pythona.

Niektóre wczesne implementacje języków, takie jak oryginalny Pascal i C, ograniczały typy, które mogą być zwracane przez funkcję (np. nie obsługują typów rekordów lub struktur ), aby uprościć ich kompilatory .

W Javie — i podobnych wzorowanych na niej językach, takich jak JavaScript — możliwe jest wykonanie kodu nawet po instrukcji return, ponieważ blok finally struktury try-catch jest zawsze wykonywany. Więc jeśli instrukcja return zostanie umieszczona gdzieś w blokach try lub catch, kod w końcu (jeśli zostanie dodany) zostanie wykonany. Możliwa jest nawet zmiana zwracanej wartości typu nieprymitywnego (właściwość już zwróconego obiektu), ponieważ wyjście następuje również później.

Oświadczenia o zyskach

Krewnym sprawozdania obie są stwierdzenia wydajność : gdzie zwrot Powoduje ramach rutynowych rozwiązania, z wydajnością powoduje CO rutynowych do zawieszenia. Współprogram będzie później kontynuowany od miejsca, w którym został zawieszony, jeśli zostanie wywołany ponownie. Współprogramy są znacznie bardziej zaangażowane w implementację niż podprogramy, a zatem instrukcje wydajności są mniej powszechne niż instrukcje return, ale można je znaleźć w wielu językach.

Sekwencje połączeń/powrotów

W zależności od zestawu instrukcji sprzętowych możliwe są różne sekwencje wywołania/powrotu, w tym:

  1. CALLInstrukcja popycha adres następnej instrukcji na stos i oddziałów na podany adres. RETURNInstrukcja wyskakuje adres powrotu ze stosu na wskaźnik instrukcji i wznawia wykonanie pod tym adresem. (Przykłady x86, PDP-11)
  2. Te CALLmiejsca instruktażowe adres następnej instrukcji do rejestru oraz oddziałów na podany adres. RETURNSekwencja instrukcji umieszcza adres zwrotny z rejestru na wskaźnik instrukcji i wznawia wykonanie pod tym adresem. (Przykład IBM System/360)
  3. Te CALLmiejsca instruktażowe adres następnego (lub prądu ) instrukcji w miejscu pamięci pod adresem połączeń i oddziałów na podany adres + 1. W RETURNsekwencji instrukcji gałęzie do adresu zwrotnego przez pośrednią Skocz do pierwszej instrukcji podprogramu. (Przykłady IBM 1130, SDS9XX)

Zobacz też

Bibliografia