Jak można zadeklarować nic wewnątrz funkcji main () w C ++ i mieć działającą aplikację po kompilacji?

86

W wywiadzie stanąłem przed pytaniem takim jak to:

Twój przyjaciel dał ci pojedynczy plik kodu źródłowego, który drukuje liczby Fibonacciego na konsoli. Zauważ, że blok main () jest pusty i nie zawiera żadnych instrukcji.

Wyjaśnij, jak to jest możliwe (wskazówka: instancja globalna!)

Naprawdę chcę o tym wiedzieć, jak to jest w ogóle możliwe!

Rika
źródło
26
Spójrz na wskazówkę!
R. Martinho Fernandes
14
Ponieważ jest to coś, o czym 1) nie słyszałem, 2) to przydatne ciekawostki, ponieważ ludzie pytają o to w wywiadach, 3) ciekawe zastosowanie języka do poznania, aby 4) móc go rozpoznać i dźgnąć każdego w twarz zardzewiały shiv, jeśli widzę, że faktycznie używają go w kodzie produkcyjnym.
OmnipotentEntity,
4
Kompetentny, profesjonalny programista C ++ znał odpowiedź na to pytanie. Jeśli celem tego pytania podczas rozmowy kwalifikacyjnej jest ustalenie, czy osoba przesłuchiwana jest kompetentnym, profesjonalnym programistą C ++, to pytanie nie powinno dać jej odpowiedzi.
John Dibling,
1
W przypadku rozmowy kwalifikacyjnej jedną z alternatyw byłoby umieszczenie logiki wewnątrz dowolnej funkcji w kodzie i zarejestrowanie danych wyjściowych przy użyciu assertlub #pragma messageitp. Spowoduje to przekierowanie danych wyjściowych do konsoli podczas kompilacji. Program może nigdy nie zostać w pełni skompilowany, ale z pewnością jest to świetny sposób na pokazanie swojego „nieszablonowego” myślenia podczas rozmowy kwalifikacyjnej. To spełnia cytowane pytanie, ponieważ NIE wspomina o generowaniu plików binarnych; raczej mówi o pliku C, który może wyświetlać "rzeczy" na konsoli. ;-)
TheCodeArtist
1
Czy to był wywiad dla IOCC ? :-) Ok, przyznaję, że często robię to w celu zainicjowania moich fabryk lub wykonania kodu testowego. Przy okazji, „ pojedynczy plik kodu źródłowego” jest również wskazówką, że pint wejściowy (domyślnie main) nie jest zastępowany przez linker.
Valentin Heinitz

Odpowiedzi:

127

Najprawdopodobniej jest zaimplementowany jako (lub jego wariant):

 void print_fibs() 
 {
       //implementation
 }

 int ignore = (print_fibs(), 0);

 int main() {}

W tym kodzie zmienna globalna ignoremusi zostać zainicjowana przed wejściem do main()funkcji. Teraz, aby zainicjować globalną, print_fibs()należy wykonać, gdzie możesz zrobić wszystko - w tym przypadku oblicz liczby Fibonacciego i wydrukuj je! Podobną rzecz pokazałem w następującym pytaniu (które zadałem dawno temu):

Należy pamiętać, że taki kod nie jest bezpieczny i należy go generalnie unikać. Na przykład std::coutobiekt może nie zostać zainicjowany podczas print_fibs()wykonywania, jeśli tak, to co zrobiłoby std::coutw funkcji? Jeśli jednak w innych okolicznościach nie zależy to od takiej kolejności inicjalizacji, można bezpiecznie wywołać funkcje inicjalizacyjne (co jest powszechną praktyką w C i C ++).

Nawaz
źródło
3
@Nawaz Prawdopodobnie warto przytoczyć dokładne gwarancje. Gwarantuje się, że obiekty w jednostce tłumaczeniowej zostaną zainicjowane w kolejności. Gwarantuje się, że standardowe obiekty strumienia zostaną zainicjowane przed lub podczas pierwszej inicjalizacji std::ios_base::Initobiektu. I <iostream>gwarantuje zachowywać się „jak gdyby” zawierała ona instancję std::ios_base_Initobiektu w zakresie przestrzeni nazw.
James Kanze,
3
@ Steve314: To nic nie zwraca, dlatego użyłem operatora przecinka, aby upewnić się, że typ całego wyrażenia (print_fibs(), 0)jest int. Oto demo online .
Nawaz,
1
@Nawaz Alternatywą dla funkcji void i operatora przecinka byłoby zwrócenie a booli zmiennej bool fibsPrinted. To prawdopodobnie nieco czystsze, jeśli funkcja służy tylko tutaj. (Ale różnica prawdopodobnie nie jest wystarczająca do zmartwień.)
James Kanze,
1
+1, Porozmawiaj o niesamowitych. Musiałem dołączyć do stackoverflow, aby zagłosować za tym pytaniem i tą odpowiedzią.
Naprawiono punkt
1
@Nawaz Nie jestem pewien, o co ci chodzi. Definicja std::coutjest gdzieś w bibliotece. Ale jak już wspomniałem, standard wymaga zainicjowania go przed zakończeniem pierwszego konstruktora std::ios_base::Initobiektu i wymaga, aby w tym <iostream>zachowywał się tak, jakby std::ios_base::Initobiekt był zdefiniowany w zakresie przestrzeni nazw. Jeśli jednostka translacyjna zawiera <iostream>przed definicją inicjowanego obiektu, std::coutto jest gwarantowane.
James Kanze,
18

Mam nadzieję że to pomoże

class cls
{
  public:
    cls()
    {
      // Your code for fibonacci series
    }
} objCls;

int main()
{
}

Tak więc, gdy tylko zostanie zadeklarowana zmienna globalna klasy, wywoływany jest konstruktor i dodajesz logikę do wydrukowania serii Fibonacciego.

Saksham
źródło
9

Tak to mozliwe. Musisz zadeklarować globalne wystąpienie obiektu, który oblicza liczby Fibonacciego w konstruktorze obiektów.

Panie Beer
źródło
6
Musisz zadeklarować globalne wystąpienie obiektu, którego inicjator oblicza liczby Fibonacciego.
James Kanze
4

Znam kilka przykładów, które opowiadasz. Jednym ze sposobów uzyskania tego jest użycie metaprogramowania szablonu. Używając go, możesz przenieść jakiś proces obliczeniowy do kompilacji.

Tutaj możesz uzyskać przykład z liczbami Fibonacciego

Jeśli używasz go w konstruktorze klasy statycznej i możesz pisać liczby bez konieczności pisania kodu w funkcji main.

Mam nadzieję, że ci to pomoże.

superarce
źródło
3

Podczas inicjalizacji zmiennych globalnych / statycznych może się zdarzyć. Kod zostanie wyzwolony podczas uruchamiania aplikacji.

log0
źródło
3

Wszystkie konstruktory [*] dla obiektów o zasięgu plikowym są wywoływane przed osiągnięciem main, podobnie jak wszystkie wyrażenia inicjalizujące dla zmiennych niebędących obiektami o zasięgu plikowym.

Edycja: Ponadto wszystkie [*] destruktory dla wszystkich obiektów o zasięgu plikowym są wywoływane w odwrotnej kolejności do konstrukcji po zakończeniu main. Teoretycznie można by umieścić swój program Fibonacciego w destruktorze obiektu.

[*] Zauważ, że 'all' ignoruje zachowanie dynamicznego ładowania i usuwania bibliotek, z którymi twój program nie był bezpośrednio powiązany. Jednak technicznie są one poza podstawowym językiem C ++.

Joe Z
źródło
Wszystko ? Nawet te w bibliotekach dll, które są jawnie ładowane po main?
James Kanze
Cóż, C ++ nie definiuje technicznie dynamicznie ładowanych bibliotek, więc w czystym C ++ moje stwierdzenie jest poprawne. Tak więc odcień tego „Wszystko, zapisz dla inicjatorów i obiektów zakresu plików zawartych w bibliotekach DLL / DSO załadowanych po osiągnięciu main”. W tym przypadku mainjest pusty, więc te biblioteki DLL / DSO musiałyby być ładowane przez destruktory, co jest cholernie przewrotne. Ale ponieważ jest to informatyka, przypuszczam, że powinniśmy uważać na słowa takie jak „wszystko”.
Joe Z
Do powyższej odpowiedzi dodałem zastrzeżenie dotyczące „wszystkich”, a także dodałem notatkę o lekarzach.
Joe Z
Tak, ale miejmy nadzieję, że to nadejdzie. Przed C ++ 11 zawierało trochę dziwacznych sformułowań, które miały zezwalać na biblioteki DLL, ale w praktyce oznaczało to tylko, że technicznie gwarancja nie zawsze istniała, mimo że była we wszystkich rzeczywistych implementacjach i zależała od niej duża część kodu. C ++ 11 przynajmniej to naprawił.
James Kanze,