Odkąd wiele lat temu zdałem sobie sprawę, że domyślnie nie powoduje to błędu (przynajmniej w GCC), zawsze zastanawiałem się, dlaczego?
Rozumiem, że możesz wysyłać flagi kompilatora, aby wygenerować ostrzeżenie, ale czy nie powinno to zawsze być błędem? Dlaczego ma sens, aby funkcja nieważna, która nie zwraca wartości, była prawidłowa?
Przykład zgodnie z prośbą w komentarzach:
#include <stdio.h>
int stringSize()
{
}
int main()
{
char cstring[5];
printf( "the last char is: %c\n", cstring[stringSize()-1] );
return 0;
}
... kompiluje się.
-Werror=return-type
potraktuje to ostrzeżenie jako błąd. Właśnie zignorowałem ostrzeżenie, a kilka minut frustracji w poszukiwaniu nieprawidłowegothis
wskaźnika doprowadziło mnie tutaj i do tego wniosku.std::optional
funkcji bez zwracania zwraca „true” opcjonalneOdpowiedzi:
Standardy C99 i C ++ nie wymagają funkcji do zwracania wartości. Brakująca instrukcja return w funkcji zwracającej wartość zostanie zdefiniowana (do zwrócenia
0
) tylko wmain
funkcji.Uzasadnienie obejmuje stwierdzenie, że sprawdzenie, czy każda ścieżka kodu zwraca wartość, jest dość trudne, a wartość zwracaną można ustawić za pomocą wbudowanego asemblera lub innych skomplikowanych metod.
Z wersji roboczej C ++ 11 :
§ 6.6.3 / 2
§ 3.6.1 / 5
Zauważ, że zachowanie opisane w C ++ 6.6.3 / 2 nie jest takie samo w C.
gcc da ci ostrzeżenie, jeśli wywołasz go z opcją -Wreturn-type.
Jako ciekawostkę spójrz, co robi ten kod:
Ten kod ma formalnie niezdefiniowane zachowanie iw praktyce wywołuje zależne od konwencji i architektury . W jednym konkretnym systemie, z jednym konkretnym kompilatorem, wartość zwracana jest wynikiem oceny ostatniego wyrażenia, przechowywanej w
eax
rejestrze procesora tego systemu.źródło
throw
lublongjmp
, czy kompilator powinien wymagać nieosiągalnegoreturn
po wywołaniu funkcji niezwracającej? Przypadki, w których nie jest to potrzebne, nie są zbyt częste, a wymóg jego uwzględnienia nawet w takich przypadkach prawdopodobnie nie byłby uciążliwy, ale decyzja, by tego nie wymagać, jest rozsądna.gcc domyślnie nie sprawdza, czy wszystkie ścieżki kodu zwracają wartość, ponieważ generalnie nie można tego zrobić. Zakłada, że wiesz, co robisz. Rozważmy typowy przykład z wykorzystaniem wyliczeń:
Ty programista wiesz, że poza błędem ta metoda zawsze zwraca kolor. gcc ufa, że wiesz, co robisz, więc nie zmusza cię do umieszczania powrotu na dole funkcji.
Z drugiej strony javac próbuje zweryfikować, czy wszystkie ścieżki kodu zwracają wartość i zgłasza błąd, jeśli nie może udowodnić, że wszystkie to robią. Ten błąd jest wymagany przez specyfikację języka Java. Zauważ, że czasami jest to błędne i musisz wstawić niepotrzebną instrukcję powrotu.
To filozoficzna różnica. Języki C i C ++ są bardziej liberalne i ufają bardziej niż Java lub C #, dlatego niektóre błędy w nowszych językach są ostrzeżeniami w C / C ++, a niektóre ostrzeżenia są domyślnie ignorowane lub wyłączane.
źródło
System.exit()
nigdy nie wróci.System.exit()
nigdy nie powróci. Sprawdziłem to ( java.sun.com/j2se/1.4.2/docs/api/java/lang/… ), a dokumentacja po prostu mówi, że „normalnie nigdy nie wraca”. Zastanawiam się, co to znaczy ...Masz na myśli, dlaczego wypłynięcie z końca funkcji zwracającej wartość (tj. Wyjście bez jawności
return
) nie jest błędem?Po pierwsze, w C to, czy funkcja zwraca coś znaczącego, czy nie, jest krytyczne tylko wtedy, gdy wykonujący kod faktycznie używa zwróconej wartości. Może język nie chciał zmusić Cię do zwrotu czegokolwiek, kiedy wiesz, że i tak nie będziesz go używać przez większość czasu.
Po drugie, najwyraźniej specyfikacja języka nie chciała zmusić autorów kompilatora do wykrycia i zweryfikowania wszystkich możliwych ścieżek kontrolnych pod kątem obecności jawnego
return
(chociaż w wielu przypadkach nie jest to takie trudne). Ponadto niektóre ścieżki kontrolne mogą prowadzić do funkcji niezwracających - cecha, która jest generalnie nieznana kompilatorowi. Takie ścieżki mogą stać się źródłem irytujących fałszywych alarmów.Zauważ również, że C i C ++ różnią się definicjami zachowania w tym przypadku. W C ++ samo wypłynięcie z końca funkcji zwracającej wartość jest zawsze niezdefiniowanym zachowaniem (niezależnie od tego, czy wynik funkcji jest używany przez wywołujący kod). W C powoduje to niezdefiniowane zachowanie tylko wtedy, gdy kod wywołujący próbuje użyć zwróconej wartości.
źródło
return
instrukcji na końcumain()
?main
jest pod tym względem wyjątkowy.W C / C ++ legalne jest, aby nie powracać z funkcji, która twierdzi, że coś zwraca. Istnieje wiele przypadków użycia, takich jak wywołanie
exit(-1)
lub funkcja, która ją wywołuje lub zgłasza wyjątek.Kompilator nie odrzuci legalnego C ++, nawet jeśli prowadzi to do UB, jeśli o to poprosisz. W szczególności prosisz o nie generowanie ostrzeżeń . (Gcc nadal domyślnie włącza niektóre, ale po dodaniu wydaje się, że są one zgodne z nowymi funkcjami, a nie nowymi ostrzeżeniami dla starych funkcji)
Zmiana domyślnego gcc bezargumentowego, aby emitował pewne ostrzeżenia, może być przełomową zmianą dla istniejących skryptów lub stworzyć systemy. Dobrze zaprojektowane
-Wall
i radzą sobie z ostrzeżeniami lub przełączają poszczególne ostrzeżenia.Nauka korzystania z łańcucha narzędzi C ++ jest barierą w nauce bycia programistą C ++, ale łańcuchy narzędzi C ++ są zazwyczaj pisane przez ekspertów i dla nich.
źródło
Makefile
mam to uruchomione-Wall -Wpedantic -Werror
, ale to był jednorazowy skrypt testowy, do którego zapomniałem podać argumenty.-Wduplicated-cond
części-Wall
zepsutego bootstrapu GCC. Niektóre ostrzeżenia, które wydają się odpowiednie w większości kodu, nie są odpowiednie w całym kodzie. Dlatego nie są one domyślnie włączone.int foo() { exit(-1); }
nie zwracaint
funkcji, która „twierdzi, że zwraca int”. Jest to legalne w C ++. Teraz nie zwraca niczego ; koniec tej funkcji nigdy nie zostanie osiągnięty. W rzeczywistości osiągnięcie końcafoo
byłoby niezdefiniowanym zachowaniem. Ignorowanie przypadków zakończenia procesuint foo() { throw 3.14; }
również twierdzi, że zwraca,int
ale nigdy tego nie robi.void* foo(void* arg) { pthread_exit(NULL); }
jest w porządku z tego samego powodu (gdy jego jedyne użycie jest przezpthread_create(...,...,foo,...);
)Uważam, że jest to spowodowane starszym kodem (C nigdy nie wymagało instrukcji powrotu, podobnie jak C ++). Prawdopodobnie istnieje ogromna baza kodu zależna od tej „funkcji”. Ale przynajmniej jest
-Werror=return-type
flaga na wielu kompilatorach (w tym gcc i clang).źródło
W niektórych ograniczonych i rzadkich przypadkach może być przydatne wypłynięcie z końca funkcji, która nie jest pusta, bez zwracania wartości. Podobnie jak następujący kod specyficzny dla MSVC:
Ta funkcja zwraca liczbę pi przy użyciu zestawu x86. W przeciwieństwie do montażu w GCC, nie znam żadnego sposobu
return
na zrobienie tego bez angażowania narzutu w wyniku.O ile wiem, główne kompilatory C ++ powinny emitować przynajmniej ostrzeżenia dla pozornie nieprawidłowego kodu. Jeśli uczynię
pi()
treść pustą, GCC / Clang zgłosi ostrzeżenie, a MSVC zgłosi błąd.Ludzie wspominali o wyjątkach i
exit
w niektórych odpowiedziach. To nie są ważne powody. Albo rzuca wyjątek, lub wywołanieexit
, będzie nie sprawiają, że wykonanie funkcji odpływać do końca. Kompilatory to wiedzą: napisanie instrukcji throw lub wywołanieexit
pustej treści poleceniapi()
zatrzyma wszelkie ostrzeżenia lub błędy kompilatora.źródło
void
funkcji niebędącej funkcją po tym, jak wbudowany asm pozostawia wartość w rejestrze wartości zwracanej. (st0
w tym przypadku x87 , EAX dla liczby całkowitej. I może xmm0 w konwencji wywoływania, która zwraca float / double w xmm0 zamiast st0). Definiowanie tego zachowania jest specyficzne dla MSVC; nawet nie zadzwonić-fasm-blocks
do obsługi tej samej składni sprawia, że jest to bezpieczne. Zobacz czy __asm {}; zwrócić wartość eax?W jakich okolicznościach nie powoduje to błędu? Jeśli deklaruje typ zwracany i nic nie zwraca, brzmi to dla mnie jak błąd.
Jedynym wyjątkiem, o którym przychodzi mi do głowy, jest
main()
funkcja, która w ogóle nie potrzebujereturn
instrukcji (przynajmniej w C ++; nie mam pod ręką żadnego ze standardów C). Jeśli nie ma powrotu, będzie działać tak, jakbyreturn 0;
była ostatnią instrukcją.źródło
main()
potrzebujereturn
w C.return <value>;
oświadczeniareturn 0;
na końcumain()
(imain()
tylko) znajduje się niejawny . Ale i tak warto dodać stylreturn 0;
.Wygląda na to, że musisz podkręcić ostrzeżenia kompilatora:
źródło
return;
elementu w funkcji nieważnej jest naruszeniem ograniczenia, a użyciereturn <value>;
w funkcji nieważnej jest naruszeniem ograniczenia . Ale myślę, że to nie jest temat. OP, jak to zrozumiałem, polega na wyjściu z funkcji, która nie jest pusta bez znakureturn
(po prostu pozwala sterowaniu spływać z końca funkcji). To nie jest naruszenie ograniczenia, AFAIK. Standard mówi tylko, że zawsze jest to UB w C ++, a czasami UB w C.Jest to naruszenie ograniczenia w c99, ale nie w c89. Kontrast:
c89:
c99:
Nawet w
--std=c99
trybie gcc wyśle tylko ostrzeżenie (chociaż bez potrzeby włączania dodatkowych-W
flag, jak jest to wymagane domyślnie lub w c89 / 90).Edytuj, aby dodać, że w c89 „osiągnięcie tego,
}
co kończy funkcję, jest równoważne wykonaniureturn
instrukcji bez wyrażenia” (3.6.6.4). Jednak w c99 zachowanie jest niezdefiniowane (6.9.1).źródło
return
stwierdzeń. Nie obejmuje wypadnięcia końca funkcji bez zwrócenia wartości.