2012-04-29 13 views
5

Możemy napisać funkcję głównego na kilka sposobów,Jak CRT nazywa główny, mający inny parametr

  1. int main()
  2. int main(int argc,char *argv[])
  3. int main(int argc,char *argv[],char * environment)

Jak run-time funkcja CRT wie, której głównym powinien zostać wywołany. Proszę zauważyć tutaj, nie pytam o obsługiwany kod Unicode.

Odpowiedz

5

Przyjęta odpowiedź jest niepoprawna, w CRT nie ma specjalnego kodu rozpoznającego rodzaj deklaracji main().

Działa z powodu konwencji wywoływania cdecl. Określa, że ​​argumenty są przesuwane na stosie od prawej do lewej, a osoba wywołująca czyści stos po wywołaniu. Tak więc CRT po prostu przekazuje argumenty main() do argumentówi ponownie je wyrzuca, gdy main() zwróci. Jedyne, co musisz zrobić, to podać argumenty we właściwej kolejności w deklaracji funkcji main(). Parametr argc musi być pierwszy, jest to ten na wierzchu stosu. argv musi być drugi, etcetera. Pominięcie argumentu nie ma znaczenia, o ile pominiesz także wszystkie następne.

Jest to również powód, dla którego funkcja printf() może działać, ma zmienną liczbę argumentów. Z jeden argument w znanej pozycji, pierwszy.

+0

To prawdopodobnie dokładne 32-bitowe środowiska wykonawcze systemu Windows C. Ale co z innymi platformami, gdzie ABI różni się? Na przykład x64 gdzie parametry są przekazywane w rejestrach nawet dla cdecl. A co z funkcją "main" zgodną z deklaracją taką jak "int main (double d)"? I gdzie w standardzie stwierdza, że ​​wszystkie funkcje "główne" muszą używać '__cdecl', który nie jest nawet częścią standardu? A co z 'WinMain'? –

+0

Cóż, przynajmniej pierwsze zdanie z twojej odpowiedzi jest teraz dokładne. ;-) –

+0

Kwaśne winogrona David? Twoja odpowiedź może być poprawna, po prostu nie znam żadnego kompilatora, który obsługuje rodzaj refleksji, który byłby wymagany, aby to działało. Mogę zaoferować tylko to, co widziałem w ośmiu z nich, które dokładnie studiowałem, wszystkie używały konwencji wywoływania cdecl. Prawdopodobnie dlatego, że jest bardzo łatwy w implementacji, choć nie tak łatwy do wygenerowania kodu. Refleksja jest bardzo rzadką cechą w C. Możesz być może uczynić ją bardziej przekonującą, dając przykład takiego kompilatora? –

4

Ogólnie rzecz biorąc, kompilator/łącznik musiałby rozpoznać konkretną postać main, której używasz, a następnie dołączyć kod, aby dostosować to z funkcji uruchamiania systemu do funkcji C lub C++ main.

To prawda, że ​​określone kompilatory na konkretnych platformach mogły uciec bez tego, używając metod opisanych przez Hansa w jego odpowiedzi. Jednak nie wszystkie platformy używają stosów do przekazywania parametrów i można pisać zgodne implementacje C i C++, które mają niezgodne listy parametrów. W takich przypadkach kompilator/łącznik musiałby określić, która postać musi zadzwonić pod numer main.

+0

Zgadzam się, ale jak rozpoznaje i generuje kod, nawet jeśli przyjmujemy go jako przeciążenie, C go nie obsługuje. –

+0

Dzięki. Odpowiedź została zaakceptowana. –

+0

Ten błąd jest zły, sprawdź moją odpowiedź. –

0

Pierwsza funkcja main traktuje się w szczególności w GCC (na przykład main_identifier_node w pliku gcc/c-family/c-common.c drzewa źródłowego GCC 4.7)

I C11 i C++ 11 standardów posiadają specyficzną sformułowania i specyfikacji temat.

Następnie konwencje ABI przywołujące ABI są zwykle tak, że dodatkowe argumenty nie zaszkodzą zbyt wiele.

Można więc myśleć o tym tak, jakby zarówno specyfikacja języka, jak i kompilator miały określone właściwości dotyczące "przeładowania" main.

Uważam nawet, że main może nie być zwykłą funkcją. Sądzę, że niektóre słowa w standardzie - których teraz nie mam - mogą być np. rozumiane jako zabraniające przyjmowania adresu lub powtarzania na numer main.

W praktyce kod main jest wywoływany przez kod instalacyjny skompilowany do crt*.o plików połączonych przez gcc. Użyj numeru gcc -v, aby dowiedzieć się więcej o tym, co się dzieje.

1

C 99 Standard (5.1.2.2.1 starcie programu) mówi, że realizacja wymusza nie prototyp dla funkcji main(), a program może określić ją jako jeden z:

1) int main (void);

2) int main (int argc, char * argv []);

lub w sposób semantycznie równoważny 2), np.

2 ') int main (int argc, char ** argv);

lub w inny sposób zdefiniowane sposoby realizacji. Nie jest to , a nie mandat, że prototyp:

3) int main (int argc, char * argv [], char * envp []);

będzie miało zamierzone działanie - mimo że prototyp musi się skompilować, ponieważ każdy prototyp musi się skompilować. 3) jest obsługiwany przez GCC i Microsoft C wśród innych kompilatorów. (N.B. 3. prototyp pytania ma char * envp zamiast char * envp [], czy to przez przypadek czy dlatego, że ma jakiś inny kompilator).

Zarówno GCC, jak i Microsoft C skompilują main() z dowolnym prototypem, tak jak powinny. Analizują prototyp, który faktycznie określasz i generują język asemblera, aby w odpowiedni sposób wykorzystać ewentualne argumenty. Tak więc na przykład każdy z nich będzie generować oczekiwane zachowanie dla programu:

#include <stdio.h> 

void main(double d, char c) 
{ 
    printf("%lf\n",d); 
    putchar(c); 
} 

jeśli można znaleźć sposób przekazywania podwójne i char bezpośrednio do programu, a nie za pośrednictwem tablicy ciągów.

Te obserwacje można zweryfikować, włączając listy języków asemblerowych dla programów eksperymentalnych.

Pytanie, w jaki sposób standardowy CRT kompilatora pozwala nam wywoływać wygenerowaną implementację metody main(), różni się od pytania, w jaki sposób można zdefiniować główną() kompilator.

Dla GCC i MS C, main() może zdefiniować każdy sposób, który nam się podoba. Jednak w każdym przypadku standardowa CRT implementacji, AFIK, obsługuje przekazywanie argumentów tylko do main(), jak w przypadku 3). Tak więc 1) - 2 ') będzie również mieć oczekiwane zachowanie, ignorując nadmiar argumentów, a my nie mamy innych opcji, poza zapewnieniem niestandardowego własnego środowiska wykonawczego.

Odpowiedź Hansa Passanta wydaje się incydentalnie myląca, sugerując, że argc mówi o liczbie kolejnych argumentów do skonsumowania w taki sam sposób, jak pierwszy argument do printf(). Jeśli w ogóle występuje argc, oznacza to tylko liczbę elementów w macierzy przekazywanych jako drugi argument jako drugi argument. Nie wskazuje, ile argumentów przekazano do main(). Zarówno GCC, jak i MS C, wykrywają, jakie argumenty są oczekiwane podczas analizowania prototypu, który piszesz - w zasadzie to, co kompilator robi z każdą funkcją z wyjątkiem takich, jak printf(), że są zdefiniowane do przyjmowania zmiennej liczby argumentów.

main() nie przyjmuje zmiennej liczby argumentów.Przyjmuje argumenty określone w definicji, a standardowe CRT zwykłych kompilatorów zakładają je (int, char * [], char * []).

2

Hmmm. Wydaje się, że być może obecnie przyjęta odpowiedź, która wskazuje, że przyjęta wcześniej odpowiedź jest błędna, sama w sobie jest błędna. Tagi na tym pytaniu wskazują, że dotyczy to zarówno C++, jak i C, więc pozostanę przy specyfikacji C++, a nie C99. Niezależnie od wszystkich innych wyjaśnień lub argumentów, podstawową odpowiedzią na to pytanie jest to, że "główny() traktowany jest jako specjalny w sposób określony przez implementację". Wierzę, że odpowiedź Davida jest technicznie poprawniejsza od Hansa, ale wyjaśnię to bardziej szczegółowo ....

Funkcja main() jest zabawna, traktowana przez linker kompilatora z zachowaniem, które nie pasuje do żadnej innej funkcji. Hans ma rację, że w CRT nie ma specjalnego kodu rozpoznającego różne sygnatury main(), ale jego stwierdzenie, że "działa z powodu konwencji wywoływania cdecl" odnosi się tylko do konkretnej platformy (platform), w szczególności Visual Studio. Prawdziwym powodem, dla którego nie ma specjalnego kodu w CRT do rozpoznawania różnych sygnatur funkcji main(), jest to, że nie ma takiej potrzeby. I choć jest to rodzaj dzielenia włosów, to linker, którego zadaniem jest powiązanie kodu startowego w main() w czasie łącza, nie jest to zadanie CRT w momencie uruchamiania.

Większość funkcji funkcji main() jest definiowana przez implementację zgodnie ze specyfikacją C++ (patrz Rozdział 3.6, "Rozpoczęcie i zakończenie"). Jest prawdopodobne, że większość kompilatorów implementacji traktuje main() niejawnie z czymś zbliżonym do zewnętrznego połączenia "C", pozostawiając main() w nieodkształconym stanie, tak że niezależnie od prototypu funkcji, jego symbol linkera jest taki sam. Alternatywnie, linker dla implementacji może być wystarczająco inteligentny, aby przeskanować tabelę symboli, szukając dowolnego, którego ozdobiona nazwa rozwiązuje jakąś formę "[int | void] main (...)" (zauważ, że pustka jako typ powrotu jest sama rzecz implementacyjna, jak sama specyfikacja mówi, że typ powrotu main() musi być "int"). Po znalezieniu takiej funkcji w dostępnych symbolach, linker może po prostu użyć tego, gdzie kod startowy odnosi się do "main()", więc dokładna nazwa symbolu niekoniecznie musi być dopasowana do czegokolwiek; może to być nawet wmain() lub inny, o ile linker wie, jakich odmian szukać, lub kompilator nadaje wszystkie odmiany o tej samej nazwie symbolu.

Należy również zwrócić uwagę na to, że specyfikacja mówi, że main() może nie być przeciążony, więc linker nie powinien "wybierać" między wieloma implementacjami użytkownika różnych form main(). Jeśli znajdzie więcej niż jeden, jest to duplikat błędu symbolu (lub innego podobnego błędu), nawet jeśli lista argumentów nie jest zgodna. I choć wszystkich implementacjach „zastępuje” pozwalają zarówno

int main() { /* ... */ } 

i

int main(int argc, char* argv[]) { /* ... */ } 

one dopuszczone są również w celu umożliwienia inne listy argumentów, w tym wersji wykazać, że zawiera wskaźnik tablicy ciąg środowisko oraz wszelkie inne wariacja, która ma sens w danej implementacji.

Jak wskazuje Hans, konwencja wywoływania cdecl Visual Studio (i konwencje wywoływania wielu innych kompilatorów) zapewnia strukturę, w której wywołujący może skonfigurować środowisko wywołujące (tj. Stos lub rejestry zdefiniowane przez ABI lub pewną kombinację z dwóch) w taki sposób, że można przekazać zmienną liczbę argumentów, a gdy wywoływany zwraca, wywołujący jest odpowiedzialny za czyszczenie (wyrzucanie używanej przestrzeni argumentu ze stosu, lub w przypadku rejestrów, nic nie jest potrzebne do sprzątania). Ta konfiguracja dobrze pasuje do kodu startowego, przekazując więcej parametrów, niż może być potrzebna, a implementacja main() użytkownika może swobodnie używać lub nie używać żadnego z tych argumentów, tak jak w przypadku wielu platform traktujących różne formy main(), którą wymieniasz w swoim pytaniu.Jednak nie jest to jedyny sposób, w jaki kompilator + linker może osiągnąć ten cel: zamiast tego linker może wybierać między różnymi wersjami kodu startowego w oparciu o definicję twojej głównej(). Mogłoby to pozwolić na szeroki zakres list argumentów main(), które w innym przypadku byłyby niemożliwe przy użyciu modelu czyszczenia wywołującego cdecl. A ponieważ wszystko to jest zdefiniowane przez implementację, jest zgodne z specyfikacją C++, o ile kompilator + linker obsługuje przynajmniej dwie kombinacje przedstawione powyżej (int main() i int main(int, char**)).

Powiązane problemy