Startet main () wirklich ein C ++ - Programm?

131

Der Abschnitt $ 3.6.1 / 1 aus dem C ++ Standard lautet:

Ein Programm muss eine globale Funktion namens main enthalten , die den festgelegten Start des Programms darstellt.

Betrachten Sie nun diesen Code,

int square(int i) { return i*i; }
int user_main()
{ 
    for ( int i = 0 ; i < 10 ; ++i )
           std::cout << square(i) << endl;
    return 0;
}
int main_ret= user_main();
int main() 
{
        return main_ret;
}

Dieser Beispielcode macht das, was ich beabsichtige, dh das Quadrat der ganzen Zahlen von 0 bis 9 drucken, bevor er in die main()Funktion eingeht, die der "Start" des Programms sein soll.

Ich habe es auch mit der -pedanticOption GCC 4.5.0 kompiliert. Es gibt keinen Fehler, nicht einmal Warnung!

Meine Frage ist also:

Ist dieser Code wirklich standardkonform?

Wenn es standardkonform ist, macht es dann nicht ungültig, was der Standard sagt? main()ist kein Start dieses Programms! user_main()vor dem ausgeführt main().

Ich verstehe, dass zum Initialisieren der globalen Variablen zuerst main_retdie use_main()Ausführung ausgeführt wird, aber das ist eine ganz andere Sache. der Punkt ist , dass es nicht die zitierte Aussage $ 3.6.1 / 1 von der Norm ungültig, da main()nicht das ist Start des Programms; es ist das in der Tat Ende von diesem Programm!


BEARBEITEN:

Wie definieren Sie das Wort "Start"?

Es läuft auf die Definition des Ausdrucks "Programmstart" hinaus . Wie genau definieren Sie es?

Nawaz
quelle

Antworten:

85

Nein, C ++ unternimmt viele Schritte, um die Umgebung vor dem Aufruf von main festzulegen. Haupt ist jedoch der offizielle Start des "benutzerdefinierten" Teils des C ++ - Programms.

Einige der Umgebungseinstellungen sind nicht steuerbar (wie der ursprüngliche Code zum Einrichten von std :: cout; einige der Umgebungen sind jedoch steuerbar wie statische globale Blöcke (zum Initialisieren statischer globaler Variablen). Beachten Sie, dass Sie nicht voll sind Kontrolle vor main Sie haben keine vollständige Kontrolle über die Reihenfolge, in der die statischen Blöcke initialisiert werden.

Nach main hat Ihr Code konzeptionell "die volle Kontrolle" über das Programm, in dem Sinne, dass Sie sowohl die auszuführenden Anweisungen als auch die Reihenfolge angeben können, in der sie ausgeführt werden sollen. Multithreading kann die Reihenfolge der Codeausführung ändern. Sie haben jedoch weiterhin die Kontrolle über C ++, da Sie angegeben haben, dass Codeabschnitte (möglicherweise) nicht in der richtigen Reihenfolge ausgeführt werden sollen.

Edwin Buck
quelle
9
+1 für dieses "Beachten Sie, dass Sie, da Sie vor main nicht die volle Kontrolle haben, nicht die volle Kontrolle über die Reihenfolge haben, in der die statischen Blöcke initialisiert werden. Nach main hat Ihr Code konzeptionell" volle Kontrolle "über das Programm in dem Sinne, dass Sie sowohl die auszuführenden Anweisungen als auch die Reihenfolge ihrer Ausführung angeben können " . Dies veranlasst mich auch, diese Antwort als akzeptierte Antwort zu markieren ... Ich denke, dies sind sehr wichtige Punkte, die main()als "Start des Programms"
Nawaz
13
@Nawaz: Beachten Sie, dass Sie neben der vollständigen Kontrolle über die Initialisierungsreihenfolge keine Kontrolle über Initialisierungsfehler haben: Sie können keine Ausnahmen in einem globalen Bereich abfangen.
André Caron
@Nawaz: Was sind statische globale Blöcke? Würden Sie es bitte anhand eines einfachen Beispiels erklären? Danke
Zerstörer
@meet: Die Objekte im Namensraum Ebene deklariert haben staticLagerdauer, und als solche, diese Objekte zu verschiedenen Übersetzungseinheiten gehören , können initialisiert werden , in jeder Reihenfolge (weil die Reihenfolge ist nicht spezifiziert in der Norm). Ich bin mir nicht sicher, ob das Ihre Frage beantwortet, obwohl ich das im Zusammenhang mit diesem Thema sagen könnte.
Nawaz
88

Sie lesen den Satz falsch.

Ein Programm muss eine globale Funktion namens main enthalten, die den festgelegten Start des Programms darstellt.

Der Standard definiert das Wort "Start" für den Rest des Standards. Es heißt nicht, dass kein zuvor ausgeführter Code mainaufgerufen wird. Es heißt, dass der Start des Programms als an der Funktion liegend angesehen wird main.

Ihr Programm ist konform. Ihr Programm wurde erst "gestartet", wenn main gestartet wurde. Der Konstruktor wird aufgerufen, bevor Ihr Programm gemäß der Definition von "start" im Standard "startet", aber das spielt kaum eine Rolle. Eine Menge Code wird ausgeführt , bevor mainwird immer in jedem Programm genannt, nicht nur dieses Beispiel.

Zu Diskussionszwecken wird Ihr Konstruktorcode vor dem 'Start' des Programms ausgeführt, und dies entspricht vollständig dem Standard.

Adam Davis
quelle
3
Entschuldigung, aber ich bin mit Ihrer Auslegung dieser Klausel nicht einverstanden.
Leichtigkeitsrennen im Orbit
Ich denke, Adam Davis hat Recht, "main" ist eher eine Art von Codierungsbeschränkungen.
Laike9m
@LightnessRacesinOrbit Ich habe nie nachgearbeitet, aber für mich kann dieser Satz logisch auf "eine globale Funktion namens main ist der festgelegte Start des Programms" reduziert werden (Hervorhebung hinzugefügt). Wie interpretieren Sie diesen Satz?
Adam Davis
1
@AdamDavis: Ich erinnere mich nicht, was mein Anliegen war. Mir fällt jetzt keiner ein.
Leichtigkeitsrennen im Orbit
23

Ihr Programm wird nicht verlinkt und daher nicht ausgeführt, es sei denn, es gibt eine Hauptleitung. Main () bewirkt jedoch nicht den Start der Ausführung des Programms, da Objekte auf Dateiebene Konstruktoren haben, die zuvor ausgeführt werden, und es möglich wäre, ein gesamtes Programm zu schreiben, das seine Lebensdauer ausführt, bevor main () erreicht wird, und main selbst zu lassen ein leerer Körper.

Um dies zu erzwingen, müssten Sie in der Realität ein Objekt haben, das vor main und seinem Konstruktor erstellt wurde, um den gesamten Programmfluss aufzurufen.

Schau dir das an:

class Foo
{
public:
   Foo();

 // other stuff
};

Foo foo;

int main()
{
}

Der Ablauf Ihres Programms würde sich effektiv aus ergeben Foo::Foo()

Goldesel
quelle
13
+1. Beachten Sie jedoch, dass Sie schnell in Schwierigkeiten geraten, wenn Sie mehrere globale Objekte in verschiedenen Übersetzungseinheiten haben, da die Reihenfolge, in der die Konstruktoren aufgerufen werden, undefiniert ist. Sie können mit Singletons und verzögerter Initialisierung davonkommen, aber in einer Multithread-Umgebung werden die Dinge sehr schnell hässlich. Mit einem Wort, tun Sie dies nicht in echtem Code.
Alexandre C.
3
Während Sie main () wahrscheinlich einen geeigneten Textkörper in Ihrem Code geben und ihm erlauben sollten, die Ausführung auszuführen, basiert das Konzept von Objekten außerhalb dieses Starts auf vielen LD_PRELOAD-Bibliotheken.
CashCow
2
@Alex: Der Standard sagt undefiniert, aber aus praktischen Gründen steuert die Linkreihenfolge (normalerweise abhängig vom Compiler) die Initiazierungsreihenfolge.
ThomasMcLeod
1
@Thomas: Ich würde sicherlich nicht einmal aus der Ferne versuchen, mich darauf zu verlassen. Ich würde sicherlich auch nicht versuchen, das Build-System manuell zu steuern.
Alexandre C.
1
@Alex: nicht mehr so ​​wichtig, aber früher verwendeten wir die Linkreihenfolge, um das Build-Image zu steuern und das Paging des physischen Speichers zu verringern. Es gibt andere Nebengründe, warum Sie die Initialisierungsreihenfolge steuern möchten, auch wenn sie die Programmsemantik nicht beeinflusst, z. B. das Testen des Startleistungsvergleichs.
Thomas McLeod
15

Sie haben die Frage ebenfalls mit "C" markiert. Wenn Sie also streng über C sprechen, sollte Ihre Initialisierung gemäß Abschnitt 6.7.8 "Initialisierung" des ISO C99-Standards fehlschlagen.

Das relevanteste in diesem Fall scheint die Einschränkung Nr. 4 zu sein, die besagt:

Alle Ausdrücke in einem Initialisierer für ein Objekt mit statischer Speicherdauer müssen konstante Ausdrücke oder Zeichenfolgenliterale sein.

Die Antwort auf Ihre Frage lautet also, dass der Code nicht dem C-Standard entspricht.

Sie möchten wahrscheinlich das "C" -Tag entfernen, wenn Sie nur am C ++ - Standard interessiert sind.

Remo.D
quelle
4
@ Remo.D können Sie uns sagen, was in diesem Abschnitt ist. Nicht alle von uns haben C-Standard :).
UmmaGumma
2
Da Sie so wählerisch sind: Leider ist ANSI C seit 1989 veraltet. ISO C90 oder C99 sind die relevanten Standards, die zitiert werden müssen.
Lundin
@Lundin: Niemand ist jemals wählerisch genug :) Ich habe ISO C99 gelesen, aber ich bin ziemlich sicher, dass es auch für C90 gilt.
Remo.D
@Ein Schuss. Sie haben Recht, fügte der Satz hinzu, den ich hier für am relevantesten halte.
Remo.D
3
@Remo: +1 für die Bereitstellung der Information, dass es nicht gültig ist C; das wusste ich nicht Sehen Sie, wie Menschen lernen, manchmal nach Plan, manchmal nach Zufall!
Nawaz
10

Abschnitt 3.6 als Ganzes ist sehr klar über das Zusammenspiel mainund die dynamischen Initialisierungen. Der "festgelegte Start des Programms" wird nirgendwo anders verwendet und beschreibt lediglich die allgemeine Absicht von main(). Es macht keinen Sinn, diesen einen Satz normativ zu interpretieren, was den detaillierteren und klareren Anforderungen des Standards widerspricht.

aschepler
quelle
9

Der Compiler muss häufig Code vor main () hinzufügen, um standardkonform zu sein . Weil der Standard vorschreibt, dass die Initialisierung von Globals / Statics erfolgen muss, bevor das Programm ausgeführt wird. Gleiches gilt für Konstruktoren von Objekten, die im Dateibereich (Globals) platziert sind.

Damit wird die ursprüngliche Frage ist relevant für C als auch, weil in einem C - Programm , das Sie noch die Globals / statische Initialisierung zu tun haben würden , bevor das Programm gestartet werden kann.

Die Standards gehen davon aus, dass diese Variablen durch "Magie" initialisiert werden, da sie nicht angeben, wie sie vor der Programminitialisierung festgelegt werden sollen. Ich denke, sie haben das als etwas angesehen, das außerhalb des Geltungsbereichs eines Programmiersprachenstandards liegt.

Bearbeiten: Siehe zum Beispiel ISO 9899: 1999 5.1.2:

Alle Objekte mit statischer Speicherdauer müssen vor dem Programmstart initialisiert (auf ihre Anfangswerte gesetzt) ​​werden. Die Art und der Zeitpunkt einer solchen Initialisierung sind ansonsten nicht spezifiziert.

Die Theorie, wie diese "Magie" ausgeführt werden sollte, reicht bis in die Geburt von C zurück, als es sich um eine Programmiersprache handelte, die nur für das UNIX-Betriebssystem auf RAM-basierten Computern verwendet werden sollte. Theoretisch wäre das Programm in der Lage, alle vorinitialisierten Daten aus der ausführbaren Datei in den RAM zu laden, während das Programm selbst in den RAM hochgeladen wurde.

Seitdem haben sich Computer und Betriebssystem weiterentwickelt, und C wird in einem weitaus größeren Bereich als ursprünglich angenommen verwendet. Ein modernes PC-Betriebssystem verfügt über virtuelle Adressen usw., und alle eingebetteten Systeme führen Code aus dem ROM und nicht aus dem RAM aus. Es gibt also viele Situationen, in denen der RAM nicht "automatisch" eingestellt werden kann.

Außerdem ist der Standard zu abstrakt, um etwas über Stapel und Prozessspeicher usw. zu wissen. Diese Dinge müssen ebenfalls ausgeführt werden, bevor das Programm gestartet wird.

Daher verfügt so ziemlich jedes C / C ++ - Programm über einen Init / "Copy-Down" -Code, der ausgeführt wird, bevor main aufgerufen wird, um den Initialisierungsregeln der Standards zu entsprechen.

Beispielsweise verfügen eingebettete Systeme normalerweise über eine Option namens "Nicht ISO-konformer Start", bei der die gesamte Initialisierungsphase aus Leistungsgründen übersprungen wird und der Code dann direkt von main startet. Solche Systeme entsprechen jedoch nicht den Standards, da Sie sich nicht auf die Init-Werte globaler / statischer Variablen verlassen können.

Lundin
quelle
4

Ihr "Programm" gibt einfach einen Wert aus einer globalen Variablen zurück. Alles andere ist Initialisierungscode. Somit gilt der Standard - Sie haben nur ein sehr triviales Programm und eine komplexere Initialisierung.

Zac Howland
quelle
2

Scheint wie ein englischer Semantik-Streit. Das OP bezeichnet seinen Codeblock zuerst als "Code" und später als "Programm". Der Benutzer schreibt den Code und der Compiler schreibt das Programm.

dSerk
quelle
1

main wird aufgerufen, nachdem alle globalen Variablen initialisiert wurden.

Was der Standard nicht spezifiziert, ist die Reihenfolge der Initialisierung aller globalen Variablen aller Module und statisch verknüpften Bibliotheken.

vz0
quelle
0

Ja, main ist der "Einstiegspunkt" jedes C ++ - Programms, mit Ausnahme implementierungsspezifischer Erweiterungen. Trotzdem passieren einige Dinge vor main, insbesondere die globale Initialisierung, wie zum Beispiel für main_ret.

Fred Nurk
quelle