Wie ist es möglich, in main () in C ++ nichts zu deklarieren und dennoch nach der Kompilierung eine funktionierende Anwendung zu haben?

86

In einem Interview wurde ich mit einer Frage wie dieser konfrontiert:

Ihr Freund hat Ihnen eine einzige Quellcodedatei gegeben, die die Fibonacci-Zahlen auf der Konsole druckt. Beachten Sie, dass der Block main () leer ist und keine Anweisungen enthält.

Erklären Sie, wie dies möglich ist (Hinweis: globale Instanz!)

Ich möchte wirklich wissen, wie so etwas überhaupt möglich sein kann!

Rika
quelle
26
Schau dir den Hinweis an!
R. Martinho Fernandes
14
Weil es etwas ist, von dem 1) ich noch nichts gehört habe, 2) nützliche Trivia sind, weil die Leute es in Interviews fragen, 3) eine interessante Anwendung der Sprache zu wissen, damit 4) ich es erkennen und jedem ins Gesicht stechen kann ein rostiger Scheiß, wenn ich sehe, dass sie ihn tatsächlich im Produktionscode verwenden.
OmnipotentEntity
4
Ein kompetenter, professioneller C ++ - Programmierer kennt die Antwort auf diese Frage. Wenn der Zweck dieser Interviewfrage darin besteht, festzustellen, ob die interviewte Person ein kompetenter, professioneller C ++ - Programmierer ist, sollte die Frage ihnen keine Antwort geben.
John Dibling
1
In einer Intervieweinstellung besteht eine Alternative darin, die Logik in einer beliebigen Funktion im Code zu haben und die Ausgabe mit assertoder #pragma messageusw. zu protokollieren . Dadurch wird die Ausgabe während der Kompilierung an die Konsole umgeleitet. Das Programm wird möglicherweise nie vollständig kompiliert, aber dies ist sicher eine unterhaltsame Möglichkeit, Ihr "out-of-the-box" -Denken während des Interviews zu zeigen. Dies erfüllt die zitierte Frage, da nichts über die Erzeugung von Binärdateien erwähnt wird. Vielmehr handelt es sich nur um eine C-Datei, die "Zeug" auf der Konsole anzeigen kann. ;-)
TheCodeArtist
1
War es ein Interview für IOCC ? :-) Ok, ich gebe zu, dass ich es oft mache, um meine Fabriken zu initialisieren oder einen Testcode auszuführen. Übrigens ist ' Single Source Code File' auch ein Hinweis darauf, dass das Entry-Pint (standardmäßig main) nicht durch Linker ersetzt wird.
Valentin Heinitz

Antworten:

127

Es wird höchstwahrscheinlich implementiert als (oder eine Variante davon):

 void print_fibs() 
 {
       //implementation
 }

 int ignore = (print_fibs(), 0);

 int main() {}

In diesem Code muss die globale Variable ignorevor dem Eintritt in die main()Funktion initialisiert werden. Um nun das Globale zu initialisieren, print_fibs()muss es ausgeführt werden, wo Sie alles tun können - in diesem Fall Fibonacci-Zahlen berechnen und ausdrucken! Eine ähnliche Sache habe ich in der folgenden Frage gezeigt (die ich vor langer Zeit gestellt hatte):

Beachten Sie, dass ein solcher Code nicht sicher ist und generell vermieden werden sollte. Beispielsweise kann das std::coutObjekt nicht initialisiert werden, wennprint_fibs() es ausgeführt wird. Wenn ja, was würde std::coutdann in der Funktion tun? Wenn dies jedoch unter anderen Umständen nicht von einer solchen Initialisierungsreihenfolge abhängt, können Initialisierungsfunktionen sicher aufgerufen werden (was in C und C ++ üblich ist).

Nawaz
quelle
3
@Nawaz Es lohnt sich wahrscheinlich, die genauen Garantien zu zitieren. Objekte innerhalb einer Übersetzungseinheit werden garantiert in der richtigen Reihenfolge initialisiert. Es wird garantiert, dass die Standard-Stream-Objekte vor oder während der ersten Initialisierung eines std::ios_base::InitObjekts initialisiert werden. Und es <iostream>wird garantiert, dass es sich so verhält, als ob es eine Instanz eines std::ios_base_InitObjekts im Namespace-Bereich enthält.
James Kanze
3
@ Steve314: Es wird nichts zurückgegeben, weshalb ich den Komma-Operator verwendet habe, um sicherzustellen, dass der Typ des gesamten Ausdrucks (print_fibs(), 0)ist int. Hier ist die Online-Demo .
Nawaz
1
@Nawaz Eine Alternative zur Void-Funktion und zum Komma-Operator wäre die Rückgabe von a boolund der Variablen bool fibsPrinted. Das ist wahrscheinlich etwas sauberer, wenn die Funktion nur hier dient. (Aber der Unterschied ist wahrscheinlich nicht genug, um sich Sorgen zu machen.)
James Kanze
1
+1, rede über großartig. Musste Stackoverflow beitreten, um diese Frage und diese Antwort zu bewerten.
Fixpunkt
1
@Nawaz Ich bin nicht sicher, was dein Punkt ist. Die Definition von std::coutbefindet sich irgendwo in der Bibliothek. Wie ich bereits erwähnt habe, muss es nach dem Standard initialisiert werden, bevor der erste Konstruktor eines std::ios_base::InitObjekts fertig ist, und es muss sich so <iostream>verhalten, als ob ein std::ios_base::InitObjekt im Namespace-Bereich definiert wäre. Wenn die Übersetzungseinheit <iostream>vor der Definition des zu initialisierenden Objekts enthält, std::coutwird garantiert konstruiert.
James Kanze
18

Hoffe das hilft

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

int main()
{
}

Sobald eine globale Variable der Klasse deklariert ist, wird der Konstruktor aufgerufen und dort fügen Sie die Logik zum Ausdrucken der Fibonacci-Reihe hinzu.

Saksham
quelle
9

Ja, es ist möglich. Sie müssen eine globale Instanz eines Objekts deklarieren, die die Fibonacci-Zahlen im Objektkonstruktor berechnet.

Mr. Beer
quelle
6
Sie müssen eine globale Instanz eines Objekts deklarieren, dessen Initialisierer die Fibonacci-Zahlen berechnet.
James Kanze
4

Ich kenne einige Beispiele, die Sie erzählen. Eine Möglichkeit, dies zu erreichen, ist die Verwendung der Metaprogrammierung für Vorlagen. Mit ihm können Sie einen Rechenprozess in die Kompilierung verschieben.

Hier Sie ein Beispiel mit den Fibonacci-Zahlen

Wenn Sie es in einem statischen Klassenkonstruktor verwenden und die Zahlen schreiben können, ohne Code in die Hauptfunktion schreiben zu müssen.

Hoffe es hilft dir.

Superarce
quelle
3

Während der Initialisierung globaler / statischer Variablen können Dinge passieren. Der Code wird beim Start der Anwendung ausgelöst.

log0
quelle
3

Alle [*] Konstruktoren für Dateibereichsobjekte werden vor dem Erreichen aufgerufen main , ebenso wie alle Initialisiererausdrücke für Nichtobjekt-Dateibereichsvariablen.

Bearbeiten: Außerdem werden alle [*] Destruktoren für alle Dateibereichsobjekte in umgekehrter Reihenfolge aufgerufen main Beenden . Theoretisch könnten Sie Ihr Fibonacci-Programm in den Destruktor eines Objekts einfügen.

[*] Beachten Sie, dass 'all' das Verhalten beim dynamischen Laden und Entladen von Bibliotheken ignoriert, mit denen Ihr Programm nicht direkt verknüpft war. Diese befinden sich jedoch technisch außerhalb der C ++ - Basissprache.

Joe Z.
quelle
Alle ? Sogar diejenigen in DLLs, die explizit nach geladen werden main?
James Kanze
Nun, C ++ definiert technisch gesehen keine dynamisch geladenen Bibliotheken, daher ist meine Aussage in reinem C ++ korrekt. Schattieren Sie es also "Alle, außer für Initialisierer und Dateibereichsobjekte, die in DLLs / DSOs enthalten sind, die nach Erreichen von main geladen wurden." In diesem Fall mainist leer, so dass diese DLLs / DSOs von Destruktoren geladen werden müssten, was verdammt pervers ist. Aber da dies Informatik ist, sollten wir mit Worten wie "alle" vorsichtig sein.
Joe Z
Ich habe meiner obigen Antwort eine Einschränkung für 'alle' hinzugefügt und auch einen Hinweis zu dtors hinzugefügt.
Joe Z
Ja, aber hoffentlich kommt das. Pre C ++ 11 enthielt einige Wieselformulierungen, die DLLs zulassen sollten, was jedoch in der Praxis nur bedeutete, dass die Garantie technisch nicht immer vorhanden war, obwohl sie in allen tatsächlichen Implementierungen vorhanden war und viel Code davon abhing. C ++ 11 hat das zumindest behoben.
James Kanze