Czy „int main”; poprawny program C / C ++?

113

Pytam, ponieważ wydaje się, że mój kompilator tak myśli, chociaż ja nie.

echo 'int main;' | cc -x c - -Wall
echo 'int main;' | c++ -x c++ - -Wall

Clang nie wyświetla żadnego ostrzeżenia ani błędu, a gcc wydaje tylko łagodne ostrzeżenie:, 'main' is usually a function [-Wmain]ale tylko wtedy, gdy jest skompilowane jako C. Podanie a -std=nie wydaje się mieć znaczenia.

W przeciwnym razie kompiluje się i łączy poprawnie. Ale po wykonaniu kończy się natychmiast z SIGBUS(dla mnie).

Czytanie (doskonałych) odpowiedzi w Co powinno zwracać main () w C i C ++? i szybkie przeglądanie specyfikacji języka, z pewnością wydaje mi się, że wymagana jest główna funkcja . Ale słownictwo z gcc -Wmain(„main” jest zwykle funkcją) (i brak błędów tutaj) wydaje się sugerować coś innego.

Ale dlaczego? Czy istnieje jakieś dziwne, skrajne lub „historyczne” zastosowanie tego? Czy ktoś wie, co daje?

Chodzi mi o to, jak sądzę, że naprawdę uważam, że to powinien być błąd w środowisku hostowanym, co?

Geoff Nixon
źródło
6
Aby uczynić gcc (w większości) zgodnym ze standardami kompilatorem, potrzebujeszgcc -std=c99 -pedantic ...
pmg
3
@pmg To to samo ostrzeżenie, z lub bez -pedanticlub bez -std. Mój system c99również kompiluje to bez ostrzeżenia i błędu ...
Geoff Nixon
3
Niestety, jeśli jesteś „wystarczająco sprytny”, możesz tworzyć rzeczy, które kompilator akceptuje, ale nie mają sensu. W tym przypadku łączysz bibliotekę wykonawczą C, aby wywołać zmienną o nazwie main, co jest mało prawdopodobne, aby zadziałało. Jeśli zainicjujesz main z „właściwą” wartością, może ona faktycznie zwrócić ...
Mats Petersson
7
A nawet jeśli jest poprawny, jest to okropna rzecz (kod nieczytelny). BTW, to może być różny w hostowanych wdrożeń oraz w wolnostojących wdrożeń (który nie wiem main)
Basile Starynkevitch
1
Aby uzyskać więcej zabawy, spróbujmain=195;
imallett

Odpowiedzi:

97

Ponieważ pytanie jest podwójnie oznaczone jako C i C ++, rozumowanie C ++ i C byłoby inne:

  • C ++ używa zamiany nazw, aby pomóc linkerowi rozróżnić między tekstowo identycznymi symbolami różnych typów, np. Zmienną globalną xyzi wolnostojącą funkcją globalną xyz(int). Jednak nazwa mainnigdy nie jest zniekształcona.
  • C nie używa zniekształcania, więc program może zmylić konsolidator, dostarczając symbol jednego rodzaju w miejsce innego symbolu i pomyślnie dowiązać program.

Oto, co się tutaj dzieje: linker spodziewa się znaleźć symbol maini tak się dzieje. „Przewija” ten symbol tak, jakby to była funkcja, bo lepiej nie zna. Część biblioteki uruchomieniowej, która przekazuje kontrolę, aby mainpoprosić o nią konsolidator main, więc konsolidator nadaje jej symbol main, pozwalając na zakończenie fazy łączenia. Oczywiście to nie działa w czasie wykonywania, ponieważ mainnie jest to funkcja.

Oto kolejna ilustracja tego samego problemu:

plik xc:

#include <stdio.h>
int foo(); // <<== main() expects this
int main(){
    printf("%p\n", (void*)&foo);
    return 0;
}

plik yc:

int foo; // <<== external definition supplies a symbol of a wrong kind

kompilacja:

gcc x.c y.c

To kompiluje się i prawdopodobnie by działało, ale jest to niezdefiniowane zachowanie, ponieważ typ symbolu obiecanego kompilatorowi różni się od rzeczywistego symbolu dostarczonego do konsolidatora.

Jeśli chodzi o ostrzeżenie, myślę, że jest rozsądne: C pozwala ci budować biblioteki, które nie mają mainfunkcji, więc kompilator zwalnia nazwę maindo innych zastosowań, jeśli musisz zdefiniować zmienną mainz nieznanego powodu.

dasblinkenlight
źródło
3
Chociaż kompilator C ++ traktuje główną funkcję inaczej. Jego nazwa nie jest zniekształcona nawet bez zewnętrznego „C”. Wydaje mi się, że dzieje się tak, ponieważ w przeciwnym razie musiałby emitować swoją własną zewnętrzną magistralę „C”, aby zapewnić łączenie.
UldisK,
@UldisK Tak, sam to zauważyłem i uznałem za całkiem interesujące. To ma sens, ale nigdy o tym nie myślałem.
Geoff Nixon,
2
W rzeczywistości wyniki dla C ++ i C nie różnią się, jak wskazano tutaj - mainnie podlega zniekształceniu nazw (tak się wydaje) w C ++, niezależnie od tego, czy jest to funkcja.
Geoff Nixon,
4
@nm Myślę, że twoja interpretacja pytania jest zbyt wąska: oprócz zadania pytania w tytule postu OP wyraźnie szuka wyjaśnienia, dlaczego jego program został skompilowany w pierwszej kolejności („mój kompilator tak uważa, mimo że tego nie robię ”), a także sugestię, dlaczego warto byłoby zdefiniować maincoś innego niż funkcja. Odpowiedź zawiera wyjaśnienie obu części.
dasblinkenlight
1
To, że symbol main nie podlega zniekształceniu nazwy, nie ma znaczenia. W standardzie C ++ nie ma wzmianki o zniekształcaniu nazw. Zniekształcanie nazw jest problemem związanym z implementacją.
David Hammen
30

mainNie jest to słowo zarezerwowane jest to tylko predefiniowany identyfikator (jak cin, endl, npos...), więc można zadeklarować zmienną main, zainicjować go, a następnie wydrukować swoją wartość.

Oczywiście:

  • ostrzeżenie jest przydatne, ponieważ jest to dość podatne na błędy;
  • możesz mieć plik źródłowy bez main()funkcji (bibliotek).

EDYTOWAĆ

Niektóre odniesienia:

  • main nie jest słowem zastrzeżonym (C ++ 11):

    Funkcja mainnie może być używana w programie. Powiązanie (3.5) mainjest zdefiniowane w ramach implementacji. Program, który określa główne jako usunięte lub który deklaruje się być główną inline, staticalbo constexprjest źle sformułowane. Nazwa mainnie jest inaczej zastrzeżona. [Przykład: można wywołać funkcje składowe, klasy i wyliczenia main, podobnie jak jednostki w innych przestrzeniach nazw. - przykład końca]

    C ++ 11 - [basic.start.main] 3.6.1.3

    [2.11 / 3] [...] niektóre identyfikatory są zarezerwowane do użytku przez implementacje C ++ i standardowe biblioteki (17.6.4.3.2) i nie powinny być używane w inny sposób; nie jest wymagana żadna diagnostyka.

    [17.6.4.3.2 / 1] Pewne zestawy nazw i sygnatur funkcji są zawsze zastrzeżone dla implementacji:

    • Każda nazwa, która zawiera podwójne podkreślenie __ lub zaczyna się od podkreślenia, po którym następuje duża litera (2.12), jest zarezerwowana dla implementacji do dowolnego użytku.
    • Każda nazwa, która zaczyna się od podkreślenia, jest zarezerwowana dla implementacji do użytku jako nazwa w globalnej przestrzeni nazw.
  • Słowa zastrzeżone w językach programowania .

    Zarezerwowane słowa mogą nie zostać przedefiniowane przez programistę, ale predefiniowane często mogą zostać w pewnym stopniu nadpisane. Tak jest w przypadku main: istnieją zakresy, w których deklaracja używająca tego identyfikatora na nowo definiuje jego znaczenie.

manlio
źródło
- Myślę, że jestem raczej zmylony faktem, że (ponieważ jest tak podatny na błędy), dlaczego jest to ostrzeżenie (a nie błąd) i dlaczego jest to tylko ostrzeżenie po skompilowaniu jako C - Jasne, można kompilować bez main()funkcja, ale nie można połączyć go jako program. To, co się tutaj dzieje, to fakt, że „ważny” program jest łączony bez main(), tylko main.
Geoff Nixon,
7
cini endlnie znajdują się w domyślnej przestrzeni nazw - znajdują się w stdprzestrzeni nazw. nposjest członkiem std::basic_string.
AnotherParker,
1
main jest zarezerwowana jako nazwa globalna. Żadna z innych rzeczy, o których wspomniałeś, ani nie mainsą predefiniowane.
Potatoswatter
1
Zobacz C ++ 14 §3.6.1 i C11 §5.1.2.2.1, aby zapoznać się z ograniczeniami dotyczącymi tego, co mainmoże być. C ++ mówi „Implementacja nie powinna wstępnie definiować funkcji głównej”, a C mówi „Implementacja nie deklaruje żadnego prototypu dla tej funkcji”.
Potatoswatter
@manlio: proszę wyjaśnić, z czego cytujesz. Jeśli chodzi o zwykłe C, cytaty są błędne. Więc chyba jest to któryś ze standardów C ++, prawda?
dhein,
19

Czy int main;jest prawidłowy program C / C ++?

Nie jest do końca jasne, czym jest program C / C ++.

Czy int main;jest prawidłowy program w języku C?

Tak. Dopuszcza się samodzielną realizację takiego programu. mainnie musi mieć żadnego specjalnego znaczenia w wolnostojącym środowisku.

To nie ważne w hostowane środowiska.

Czy jest int main;to poprawny program w C ++?

Tak samo.

Dlaczego się psuje?

Program nie musi mieć sensu w Twoim środowisku. W środowisku wolnostojącym uruchamianie i kończenie programu oraz ich znaczenie mainsą zdefiniowane w ramach implementacji.

Dlaczego kompilator mnie ostrzega?

Kompilator może ostrzec Cię o wszystkim, co mu się podoba, o ile nie odrzuci programów zgodnych z wymaganiami. Z drugiej strony ostrzeżenie to wszystko, co jest potrzebne do zdiagnozowania niezgodnego programu. Ponieważ ta jednostka tłumaczeniowa nie może być częścią prawidłowego programu hostowanego, uzasadniony jest komunikat diagnostyczny.

Czy jest gccto środowisko wolnostojące, czy hostowane?

Tak.

gccdokumentuje -ffreestandingflagę kompilacji. Dodaj to, a ostrzeżenie zniknie. Możesz go użyć podczas budowania np. Jądra lub oprogramowania układowego.

g++nie dokumentuje takiej flagi. Wydaje się, że dostarczenie go nie ma wpływu na ten program. Prawdopodobnie można bezpiecznie założyć, że hostowane jest środowisko dostarczane przez g ++. Brak diagnostyki w tym przypadku jest błędem.

n. zaimki m.
źródło
17

Jest to ostrzeżenie, ponieważ nie jest to technicznie zabronione. Kod startowy użyje lokalizacji symbolu „main” i przeskoczy do niego z trzema standardowymi argumentami (argc, argv i envp). Tak nie jest, aw czasie łączenia nie może sprawdzić, czy jest to faktycznie funkcja, ani nawet czy ma te argumenty. Dlatego też działa int main (int argc, char ** argv) - kompilator nie wie o argumencie envp i po prostu nie jest używany, a jest to czyszczenie wywołania.

Jako żart możesz zrobić coś takiego

int main = 0xCBCBCBCB;

na maszynie x86 i ignorując ostrzeżenia i podobne rzeczy, będzie nie tylko kompilować, ale także działać.

Ktoś użył techniki podobnej do tej, aby napisać plik wykonywalny (rodzaj) działający bezpośrednio na wielu architekturach - http://phrack.org/issues/57/17.html#article . Został również użyty do wygrania IOCCC - http://www.ioccc.org/1984/mullender/mullender.c .

dascandy
źródło
1
„To ostrzeżenie, ponieważ nie jest technicznie zabronione” - jest nieprawidłowe w C ++.
Pozdrawiam i hth. - Alf
3
„trzy standardowe argumenty (argc, argv i envp)” - tutaj prawdopodobnie mówisz o standardzie Posix.
Pozdrawiam i hth. - Alf
W moim systemie (Ubuntu 14 / x64) następujący wiersz działa z gcc:int main __attribute__ ((section (".text")))= 0xC3C3C3C3;
csharpfolk
@ Cheersandhth.-Alf Pierwsze dwa są standardowe, trzeci to POSIX.
dascandy
9

Czy to ważny program?

Nie.

Nie jest to program, ponieważ nie zawiera części wykonywalnych.

Czy kompilacja jest ważna?

Tak.

Czy można go używać z ważnym programem?

Tak.

Nie każdy skompilowany kod musi być wykonywalny, aby był prawidłowy. Przykładami są biblioteki statyczne i dynamiczne.

Skutecznie utworzyłeś plik obiektowy. Nie jest to poprawny plik wykonywalny, jednak inny program mógłby połączyć się z obiektem mainw wynikowym pliku, ładując go w czasie wykonywania.

Czy to powinien być błąd?

Tradycyjnie C ++ pozwala użytkownikowi robić rzeczy, które mogą wydawać się nieprzydatne, ale pasują do składni języka.

Chodzi mi o to, że na pewno można to przeklasyfikować jako błąd, ale dlaczego? Czemu miałoby to służyć, czemu nie służy ostrzeżenie?

Dopóki istnieje teoretyczna możliwość wykorzystania tej funkcji w rzeczywistym kodzie, jest bardzo mało prawdopodobne, aby wywołanie obiektu niebędącego funkcją mainspowodowałoby błąd w zależności od języka.

Michael Gazonda
źródło
Tworzy widoczny na zewnątrz symbol o nazwie main. W jaki sposób prawidłowy program, który musi mieć nazwaną zewnętrznie widoczną funkcjęmain , może się z nią łączyć?
Keith Thompson,
@KeithThompson Load at runtime. Wyjaśni.
Michael Gazonda,
Może, ponieważ nie jest w stanie odróżnić typów symboli. Łączenie działa dobrze - wykonanie (z wyjątkiem starannie spreparowanego przypadku) nie.
Chris Stratton,
1
@ChrisStratton: Myślę, że argument Keitha jest taki, że łączenie nie powiodło się, ponieważ symbol jest wielokrotnie zdefiniowany ... ponieważ „prawidłowy program” nie byłby prawidłowym programem, gdyby nie zdefiniował mainfunkcji.
Ben Voigt,
@BenVoigt Ale jeśli pojawi się w bibliotece, to linkowanie nie powiedzie się (i prawdopodobnie nie będzie), ponieważ w czasie linkowania programu int main;definicja nie będzie widoczna.
6

Chciałbym uzupełnić odpowiedzi już udzielone, przytaczając aktualne standardy językowe.

Czy „int main”; ważny program w C?

Krótka odpowiedź (moja opinia): tylko jeśli Twoja implementacja korzysta z „wolnostojącego środowiska wykonawczego”.

Wszystkie poniższe cytaty z C11

5. Środowisko

Implementacja tłumaczy pliki źródłowe w języku C i wykonuje programy w języku C w dwóch środowiskach przetwarzania danych, które będą nazywane środowiskiem tłumaczenia i środowiskiem wykonawczym [...]

5.1.2 Środowiska wykonawcze

Zdefiniowano dwa środowiska wykonawcze: wolnostojące i hostowane. W obu przypadkach uruchomienie programu następuje, gdy wyznaczona funkcja C jest wywoływana przez środowisko wykonawcze.

5.1.2.1 Środowisko wolnostojące

W środowisku wolnostojącym (w którym wykonywanie programu w C może odbywać się bez żadnej korzyści ze strony systemu operacyjnego), nazwa i typ funkcji wywoływanej podczas uruchamiania programu są definiowane przez implementację.

5.1.2.2 Środowisko hostowane

Nie trzeba zapewniać hostowanego środowiska, ale musi ono być zgodne z następującymi specyfikacjami, jeśli istnieją.

5.1.2.2.1 Uruchomienie programu

Funkcja wywoływana podczas uruchamiania programu nosi nazwę main . [...] Powinien być określony za pomocą zwracanego typu int i bez parametrów [...] lub z dwoma parametrami [...] lub równoważnymi lub w inny sposób określony w implementacji.

Z nich obserwuje się, co następuje:

  • Program C11 może mieć niezależne lub hostowane środowisko wykonawcze i być poprawny.
  • Jeśli ma wolnostojącą, nie musi istnieć główna funkcja.
  • W przeciwnym razie musi istnieć jeden z wartością zwrotu typu int .

W wolnostojącym środowisku wykonawczym argumentowałbym, że jest to prawidłowy program, który nie pozwala na uruchomienie, ponieważ nie ma do tego żadnej funkcji, jak jest to wymagane w 5.1.2. W hostowanym środowisku wykonawczym, podczas gdy twój kod wprowadza obiekt o nazwie main , nie może dostarczyć wartości zwracanej, więc argumentowałbym, że nie jest to poprawny program w tym sensie, chociaż można również argumentować tak jak wcześniej, jeśli program nie jest przeznaczone do wykonania (np. on może chcieć podać dane tylko), to po prostu na to nie pozwala.

Czy „int main”; prawidłowy program w C ++?

Krótka odpowiedź (moja opinia): tylko jeśli Twoja implementacja korzysta z „wolnostojącego środowiska wykonawczego”.

Cytat z C ++ 14

3.6.1 Główna funkcja

Program powinien zawierać globalną funkcję zwaną main, która jest wyznaczonym początkiem programu. Jest zdefiniowane w implementacji, czy program w środowisku wolnostojącym jest wymagany do zdefiniowania funkcji głównej. […] Powinien mieć typ zwracany typu int, ale poza tym jego typ jest zdefiniowany w implementacji. […] Nazwa main nie jest inaczej zastrzeżona.

Tutaj, w przeciwieństwie do standardu C11, mniej ograniczeń dotyczy niezależnego środowiska wykonawczego, ponieważ w ogóle nie wspomniano o funkcji startowej, podczas gdy w przypadku hostowanego środowiska wykonawczego sprawa jest prawie taka sama jak w przypadku C11.

Ponownie argumentowałbym, że dla przypadku hostowanego Twój kod nie jest prawidłowym programem w C ++ 14, ale jestem pewien, że jest to przypadek wolnostojący.

Ponieważ moja odpowiedź dotyczy tylko środowiska wykonawczego , myślę, że w grę wchodzi odpowiedź dasblinkenlicht, ponieważ zmiana nazwy występująca w środowisku tłumaczenia ma miejsce wcześniej. Tutaj nie jestem pewien, czy powyższe cytaty są tak ściśle przestrzegane.

Ingo Schalk-Schupp
źródło
4

Chodzi mi o to, jak sądzę, że naprawdę uważam, że to powinien być błąd w środowisku hostowanym, co?

Błąd jest twój. Nie określiłeś funkcji o nazwie, mainktóra zwraca an inti próbujesz użyć programu w środowisku hostowanym.

Załóżmy, że masz jednostkę kompilacji, która definiuje zmienną globalną o nazwie main. Może to być zgodne z prawem w środowisku wolnostojącym, ponieważ to, co stanowi program, pozostaje do wdrożenia w środowiskach wolnostojących.

Załóżmy, że masz inną jednostkę kompilacji, która definiuje funkcję globalną o nazwie, mainktóra zwraca an inti nie przyjmuje argumentów. Właśnie tego potrzebuje program w środowisku hostowanym.

Wszystko jest w porządku, jeśli używasz tylko pierwszej jednostki kompilacji w środowisku wolnostojącym, a drugiej tylko w środowisku hostowanym. A jeśli użyjesz obu w jednym programie? W C ++ naruszyłeś regułę jednej definicji. To jest niezdefiniowane zachowanie. W C naruszyłeś zasadę, która mówi, że wszystkie odniesienia do pojedynczego symbolu muszą być spójne; jeśli nie są, jest to niezdefiniowane zachowanie. Niezdefiniowane zachowanie to „wyjdź z więzienia za darmo!” karta dla twórców implementacji. Wszystko, co implementacja robi w odpowiedzi na niezdefiniowane zachowanie, jest zgodne ze standardem. Implementacja nie musi ostrzegać, nie mówiąc już o wykrywaniu niezdefiniowanego zachowania.

Co się stanie, jeśli używasz tylko jednej z tych jednostek kompilacji, ale używasz niewłaściwej (co zrobiłeś)? W C sytuacja jest jasna. Brak zdefiniowania funkcji mainw jednym z dwóch standardowych formularzy w środowisku hostowanym jest nieokreślonym zachowaniem. Przypuśćmy, że w ogóle nie zdefiniowałeś main. Kompilator / linker nie mówi nic o tym błędzie. To, że narzekają, jest dla nich przyjemnością. To, że program C skompilowany i zlinkowany bez błędów jest twoją winą, a nie kompilatorem.

W C ++ jest to trochę mniej jasne, ponieważ brak zdefiniowania funkcji mainw środowisku hostowanym jest raczej błędem niż niezdefiniowanym zachowaniem (innymi słowy, należy go zdiagnozować). Jednak reguła jednej definicji w C ++ oznacza, że ​​konsolidatorzy mogą być raczej głupi. Zadaniem konsolidatora jest rozwiązywanie odwołań zewnętrznych, a dzięki zasadzie jednej definicji konsolidator nie musi wiedzieć, co oznaczają te symbole. Podałeś symbol o nazwie main, konsolidator oczekuje, że zobaczy symbol o nazwie main, więc wszystko jest w porządku, jeśli chodzi o konsolidator.

David Hammen
źródło
4

Na razie dla C jest to zachowanie zdefiniowane w ramach implementacji.

Jak mówi ISO / IEC9899:

5.1.2.2.1 Uruchomienie programu

1 Funkcja wywoływana podczas uruchamiania programu nosi nazwę main. Implementacja nie deklaruje żadnego prototypu dla tej funkcji. Powinien być zdefiniowany za pomocą zwracanego typu int i bez parametrów:

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

lub z dwoma parametrami (określanymi tutaj jako argc i argv, chociaż można użyć dowolnych nazw, ponieważ są one lokalne dla funkcji, w której zostały zadeklarowane):

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

lub odpowiednik; lub w inny sposób określony w implementacji.

dhein
źródło
3

Nie, to nie jest prawidłowy program.

W przypadku C ++ zostało to ostatnio wyraźnie źle sformułowane przez raport defektu 1886: Linkowanie językowe dla funkcji main (), które mówi:

Wydaje się, że nie ma żadnych ograniczeń w podawaniu main () wyraźnego powiązania językowego, ale prawdopodobnie powinno być źle sformułowane lub warunkowo obsługiwane.

a część uchwały obejmowała następującą zmianę:

Program, który deklaruje zmienną main w zasięgu globalnym lub który deklaruje nazwę main z łączeniem języka C (w dowolnej przestrzeni nazw) jest źle sformułowany.

Możemy znaleźć to sformułowanie w najnowszym szkicu standardu C ++ N4527, który jest szkicem C ++ 1z.

Najnowsze wersje clang i gcc powodują teraz błąd ( zobacz to na żywo ):

error: main cannot be declared as global variable
int main;
^

Przed zgłoszeniem wady było to niezdefiniowane zachowanie, które nie wymaga diagnostyki. Z drugiej strony źle sformułowany kod wymaga diagnostyki, kompilator może ustawić to jako ostrzeżenie lub błąd.

Shafik Yaghmour
źródło
Dziękuję za aktualizację! Wspaniale jest widzieć, że jest to teraz odbierane przez diagnostykę kompilatora. Muszę jednak powiedzieć, że zmiany w standardzie C ++ są dla mnie kłopotliwe. (Aby zapoznać się z tłem, zobacz komentarze powyżej dotyczące zniekształcania nazw main()). Rozumiem uzasadnienie zakazu main()posiadania wyraźnej specyfikacji powiązania, ale nie rozumiem, jak nakazuje to main()mieć powiązanie z C ++ . Oczywiście standard nie bezpośrednio adres jak radzić ABI powiązania / nazwa przekręcona, ale w praktyce (powiedzmy, ze Itanium ABI) spowodowałoby to magiel main()do _Z4mainv. czego mi brakuje?
Geoff Nixon
Myślę, że komentarz Supercat to obejmuje. Jeśli implementacja robi swoje przed wywołaniem main zdefiniowanej przez użytkownika, może z łatwością wybrać zamiast tego wywołanie zniekształconej nazwy.
Shafik Yaghmour