Wiem, że istnieją dwa różne podpisy do napisania głównej metody -
int main()
{
//Code
}
lub do obsługi argumentu wiersza poleceń, piszemy go jako-
int main(int argc, char * argv[])
{
//code
}
W C++
Wiem, że możemy przeciążyć metodę, ale w C
jaki sposób kompilator obsługiwać te dwa różne podpisy main
funkcji?
main
metodę w jednym programie wC
(a właściwie w prawie każdym języku z taką konstrukcją).main
- polecam klasyczną książkę Johna R. Levinesa „Linkers & Loaders”.int main(void)
nieint main()
(chociaż nigdy nie widziałem kompilatora, który odrzucaint main()
formularz).()
Formularz jest przestarzały i nie jest jasne, czy jest nawet dozwolonymain
(chyba że implementacja wyraźnie udokumentuje go jako dozwolony formularz). Standard C (patrz 5.1.2.2.1 Uruchamianie programu) nie wspomina o()
formie, która nie jest całkowicie równoważna z()
formularzem. Szczegóły są za długie dla tego komentarza.Odpowiedzi:
Niektóre cechy języka C zaczęły się od hacków, które po prostu zadziałały.
Wiele podpisów dla głównych list argumentów oraz list argumentów o zmiennej długości jest jedną z tych funkcji.
Programiści zauważyli, że mogą przekazywać dodatkowe argumenty do funkcji i nic złego się nie dzieje z ich kompilatorem.
Dzieje się tak, jeśli konwencje wywoływania są takie, że:
Jednym z zestawów konwencji wywoływania, które są zgodne z tymi regułami, jest przekazywanie parametrów na podstawie stosu, w którym wywołujący pobiera argumenty i są one przesuwane od prawej do lewej:
W kompilatorach, w których występuje taka konwencja wywoływania, nie trzeba nic robić, aby wspierać te dwa rodzaje
main
, a nawet dodatkowe.main
może być funkcją bez argumentów, w którym to przypadku jest nieświadoma elementów, które zostały odłożone na stos. Jeśli jest to funkcja dwóch argumentów, to znajdujeargc
iargv
jako dwa najwyższe elementy stosu. Jeśli jest to wariant trójargumentowy specyficzny dla platformy ze wskaźnikiem środowiskowym (typowe rozszerzenie), to również zadziała: ten trzeci argument znajdzie jako trzeci element od góry stosu.Tak więc stałe wywołanie działa we wszystkich przypadkach, umożliwiając połączenie jednego, stałego modułu startowego z programem. Ten moduł można zapisać w C, jako funkcję podobną do tej:
Innymi słowy, ten moduł startowy zawsze wywołuje trzyargumentowy main. Jeśli main nie przyjmuje argumentów lub tylko
int, char **
, zdarza się, że działa dobrze, a także jeśli nie przyjmuje żadnych argumentów, ze względu na konwencje wywoływania.Gdybyś miał zrobić tego rodzaju rzecz w swoim programie, byłoby to nieprzenoszalne i uważane za niezdefiniowane zachowanie przez ISO C: deklarowanie i wywoływanie funkcji w jeden sposób i definiowanie jej w inny. Ale sztuczka startowa kompilatora nie musi być przenośna; nie kierują się zasadami programów przenośnych.
Ale przypuśćmy, że konwencje wywoływania są takie, że nie może działać w ten sposób. W takim przypadku kompilator musi traktować
main
specjalnie. Kiedy zauważy, że kompilujemain
funkcję, może wygenerować kod, który jest zgodny, powiedzmy, z wywołaniem z trzema argumentami.To znaczy, piszesz to:
Ale kiedy kompilator to zobaczy, zasadniczo przeprowadza transformację kodu, aby funkcja, którą kompiluje, wyglądała bardziej tak:
poza tym, że nazwy
__argc_ignore
nie istnieją dosłownie. Żadne takie nazwy nie są wprowadzane do twojego zakresu i nie będzie żadnego ostrzeżenia o nieużywanych argumentach. Transformacja kodu powoduje, że kompilator emituje kod z poprawnym powiązaniem, który wie, że musi wyczyścić trzy argumenty.Inna strategia implementacji polega na tym, że kompilator lub konsolidator generuje niestandardowo
__start
funkcję (lub jakkolwiek to się nazywa) lub przynajmniej wybiera jedną z kilku wstępnie skompilowanych alternatyw. W pliku obiektowym mogą być przechowywane informacje o tym, która z obsługiwanych formmain
jest używana. Konsolidator może przejrzeć te informacje i wybrać poprawną wersję modułu startowego, który zawiera wywołaniemain
zgodne z definicją programu. Implementacje C mają zwykle tylko niewielką liczbę obsługiwanych form,main
więc takie podejście jest wykonalne.Kompilatory języka C99 zawsze muszą
main
, do pewnego stopnia, specjalnie traktować , aby wspierać hack, że jeśli funkcja kończy się bezreturn
instrukcji, zachowanie jest tak, jakbyreturn 0
zostało wykonane. To znowu może być potraktowane przez transformację kodu. Kompilator zauważa, żemain
kompilowana jest wywołana funkcja . Następnie sprawdza, czy koniec treści jest potencjalnie osiągalny. Jeśli tak, wstawiareturn 0;
źródło
Nie ma żadnego przeciążenia
main
nawet w C ++. Funkcja główna to punkt wejścia do programu i powinna istnieć tylko jedna definicja.W przypadku standardu C
Dla standardowego C ++:
Standard C ++ wyraźnie mówi, że „To [główna funkcja] powinna mieć zwracany typ typu int, ale poza tym jego typ jest zdefiniowany w implementacji” i wymaga tych samych dwóch podpisów, co standard C.
W środowisku hostowanym ( środowisko AC, które obsługuje również biblioteki C) - wywołuje system operacyjny
main
.W środowisku niehostowanym (przeznaczonym dla aplikacji wbudowanych) zawsze możesz zmienić punkt wejścia (lub wyjścia) programu za pomocą dyrektyw preprocesora, takich jak
Gdzie priorytet jest opcjonalną liczbą całkowitą.
Pragma startup wykonuje funkcję przed main (priorytetowo), a pragma exit wykonuje funkcję po funkcji main. Jeśli jest więcej niż jedna dyrektywa startowa, priorytet decyduje, która zostanie wykonana jako pierwsza.
źródło
Nie ma potrzeby przeciążania. Tak, istnieją 2 wersje, ale w danym momencie można używać tylko jednej.
źródło
Jest to jedna z dziwnych asymetrii i specjalnych reguł języka C i C ++.
Moim zdaniem istnieje tylko ze względów historycznych i nie kryje się za tym żadna poważna logika. Zauważ, że
main
jest to szczególne również z innych powodów (na przykładmain
w C ++ nie może być rekurencyjne i nie możesz wziąć jego adresu, aw C99 / C ++ możesz pominąć końcowereturn
stwierdzenie).Zauważ też, że nawet w C ++ nie jest to przeciążenie ... albo program ma pierwszą formę, albo ma drugą postać; nie może mieć obu.
źródło
return
instrukcję również w C (od C99).main()
i odebrać jego adres; C ++ stosuje ograniczenia, których nie ma w C.argc
podczas rekurencji (5.1.2.2.1 nie określa ograniczeńargc
iargv
mają zastosowanie tylko do pierwszego wezwania domain
).Niezwykłe
main
nie jest to, że można go zdefiniować na więcej niż jeden sposób, ale to, że można go zdefiniować tylko na jeden z dwóch różnych sposobów.main
jest funkcją zdefiniowaną przez użytkownika; implementacja nie deklaruje jej prototypu.To samo dotyczy
foo
lubbar
, ale możesz definiować funkcje o tych nazwach w dowolny sposób.Różnica polega na tym, że
main
jest wywoływana przez implementację (środowisko wykonawcze), a nie tylko przez Twój własny kod. Implementacja nie ogranicza się do zwykłej semantyki wywołań funkcji C, więc może (i musi) radzić sobie z kilkoma odmianami - ale nie jest wymagana do obsługi nieskończenie wielu możliwości.int main(int argc, char *argv[])
Forma pozwala na argumenty wiersza polecenia, aint main(void)
w C lubint main()
C ++ jest tylko wygoda dla prostych programów, które nie muszą przetwarzać argumenty wiersza polecenia.Jeśli chodzi o to, jak kompilator to obsługuje, zależy to od implementacji. Większość systemów prawdopodobnie ma konwencje wywoływania, które sprawiają, że te dwie formy są efektywnie kompatybilne, a wszelkie argumenty przekazywane do
main
zdefiniowanego bez parametrów są po cichu ignorowane. Jeśli nie, nie byłoby trudno kompilatorowi lub linkerowi traktować wmain
specjalny sposób. Jeśli jesteś ciekawy, jak to działa w twoim systemie , możesz spojrzeć na niektóre zestawienia.I podobnie jak wiele rzeczy w C i C ++, szczegóły są w dużej mierze wynikiem historii i arbitralnych decyzji podjętych przez projektantów języków i ich poprzedników.
Zauważ, że zarówno C, jak i C ++ zezwalają na inne definicje zdefiniowane przez implementację
main
- ale rzadko istnieje dobry powód, aby ich używać. W przypadku wdrożeń wolnostojących (takich jak systemy wbudowane bez systemu operacyjnego) punkt wejścia programu jest zdefiniowany w ramach implementacji i niekoniecznie jest nawet wywoływanymain
.źródło
To
main
tylko nazwa adresu początkowego ustalona przez konsolidatora, gdziemain
jest nazwą domyślną. Wszystkie nazwy funkcji w programie są adresami początkowymi, od których zaczyna się funkcja.Argumenty funkcji są wypychane / zdejmowane na / ze stosu, więc jeśli nie ma żadnych argumentów określonych dla funkcji, nie ma żadnych argumentów umieszczanych / zdejmowanych ze stosu. W ten sposób main może działać zarówno z argumentami, jak i bez nich.
źródło
gdzie zmienna argc przechowuje liczbę przekazanych danych, a argv jest tablicą wskaźników do znaku char, która wskazuje na przekazane wartości z konsoli. W przeciwnym razie zawsze dobrze jest iść
Jednak w każdym przypadku może istnieć jedna i tylko jedna funkcja main () w programie, ponieważ jest to jedyny punkt, w którym program rozpoczyna wykonywanie, a zatem nie może być więcej niż jeden. (mam nadzieję, że to jest warte)
źródło
Podobne pytanie zostało zadane wcześniej: dlaczego funkcja bez parametrów (w porównaniu z rzeczywistą definicją funkcji) jest kompilowana?
Jedną z najwyżej ocenianych odpowiedzi była:
Więc myślę, że tak
main
jest deklarowane (jeśli można zastosować termin „zadeklarowany” domain
). Właściwie możesz napisać coś takiego:i nadal będzie się kompilować i działać.
źródło
main
, ponieważ jest jeszcze nie wspomniany problem: jeszcze więcej argumentów zamain
! Dodaje się „Unix (ale nie Posix.1) i Microsoft Windows”char **envp
(pamiętam, że DOS również na to pozwalał, prawda?), A Mac OS X i Darwin dodają kolejny wskaźnik char * do „dowolnych informacji dostarczonych przez system operacyjny”. wikipediaNie musisz tego nadpisywać. Ponieważ tylko jeden będzie używany na raz. Tak, są 2 różne wersje funkcji głównej
źródło