Ich habe von meinem Kollegen gelernt, dass man ein C-Programm schreiben und ausführen kann, ohne a zu schreiben main() Funktion. Es kann so gemacht werden:
my_main.c
/* Compile this with gcc -nostartfiles */
#include <stdlib.h>
void _start() {
int ret = my_main();
exit(ret);
}
int my_main() {
puts("This is a program without a main() function!");
return 0;
}
Kompilieren Sie es mit diesem Befehl:
gcc -o my_main my_main.c –nostartfiles
Führen Sie es mit diesem Befehl aus:
./my_main
Wann sollte man so etwas tun? Gibt es ein reales Szenario, in dem dies nützlich wäre?
Die C-Sprache selbst sagt nichts darüber aus _startoder über jeden Einstiegspunkt außer main (mit der Ausnahme, dass der Name des Einstiegspunkts für freistehende (eingebettete) Implementierungen implementierungsdefiniert ist).
– Keith Thompson
1. Juni 2015 um 3:17 Uhr
Beachten Sie, dass dies _start ist unsicher und verletzt die ABI, wenn es anruft my_main; Sie sagen dem Compiler, dass es sich um eine normale Funktion handelt, aber tatsächlich wird sie mit bereits ausgerichtetem Stapelzeiger eingegeben (z. B. auf x86-64, RSP % 16 == 0), nicht mit RSP % 16 == 8 wie beim Eintritt in eine normale Funktion danach a call das drückt eine 8-Byte-Rückgabeadresse. Das kannst du mit beheben __attribute__((force_align_arg_pointer)) zum _start um GCC mitzuteilen, dass der Stack-Zeiger beim Eintritt in diese eine “Funktion” möglicherweise “falsch ausgerichtet” ist, wie in Get arg values with inline asm without Glibc?
– Peter Cordes
10. Dezember 2020 um 0:38 Uhr
Auf einer modernen Linux-Distribution würde dies zu einem Absturz führen, wenn my_main verwendet scanf überhaupt oder printf (oder eine andere variadische Funktion) mit a floar oder double FP-arg. glibc scanf Segmentierungsfehler, wenn sie von einer Funktion aufgerufen werden, die RSP nicht ausrichtet
– Peter Cordes
10. Dezember 2020 um 0:40 Uhr
Das Symbol _start ist der Einstiegspunkt Ihres Programms. Das heißt, die Adresse dieses Symbols ist die Adresse, zu der beim Programmstart gesprungen wird. Normalerweise die Funktion mit dem Namen _start wird von einer Datei namens bereitgestellt crt0.o die den Startcode für die C-Laufzeitumgebung enthält. Es richtet einige Dinge ein und füllt das Argument-Array argvzählt, wie viele Argumente vorhanden sind, und ruft dann auf main. Nach main kehrt zurück, exit wird genannt.
Wenn ein Programm die C-Laufzeitumgebung nicht verwenden möchte, muss es dafür eigenen Code bereitstellen _start. Beispielsweise tut dies die Referenzimplementierung der Programmiersprache Go, weil sie ein nicht standardmäßiges Threading-Modell benötigt, das etwas Magie mit dem Stack erfordert. Es ist auch nützlich, Ihre eigenen zu liefern _start wenn Sie wirklich kleine Programme oder Programme schreiben wollen, die unkonventionelle Dinge tun.
Ein weiteres Beispiel ist der dynamische Linker/Loader von Linux, für den ein eigenes _start definiert ist.
– PP
17. April 2015 um 10:52 Uhr
@BlueMoon Aber das_start stammt aus der Objektdatei crt0.ozu.
– fuz
17. April 2015 um 10:56 Uhr
@ThomasMatthews Der Standard gibt nicht an _start; Tatsächlich wird nicht angegeben, was vorher passiert main überhaupt aufgerufen wird, es gibt nur an, welche Bedingungen wann erfüllt sein müssen main wird genannt. Es ist eher eine Konvention für den Einstiegspunkt zu sein _start die aus alten Zeiten stammt.
– fuz
17. April 2015 um 19:18 Uhr
“Die Referenzimplementierung der Go-Programmiersprache tut dies, weil sie ein nicht standardmäßiges Threading-Modell benötigen” crt0.o ist C-spezifisch (crt->C-Laufzeit). Es gibt keinen Grund zu erwarten, dass es für andere Sprachen verwendet wird. Und das Threading-Modell von Go ist vollständig standardkonform
– Steve Cox
17. April 2015 um 20:19 Uhr
@SteveCox Viele Programmiersprachen bauen auf der C-Laufzeit auf, da es einfacher ist, Sprachen auf diese Weise zu implementieren. Go verwendet nicht das normale Threading-Modell. Sie verwenden kleine, Heap-zugewiesene Stacks und ihren eigenen Scheduler. Dies ist sicherlich kein Standard-Threading-Modell.
– fuz
17. April 2015 um 20:34 Uhr
MikeMB
Während main ist der Einstiegspunkt für Ihr Programm aus Programmierer-Perspektive, _start ist der übliche Einstiegspunkt aus Sicht des Betriebssystems (die erste Anweisung, die ausgeführt wird, nachdem Ihr Programm vom Betriebssystem gestartet wurde)
In einem typischen C- und insbesondere C++-Programm wurde viel Arbeit geleistet, bevor die Ausführung in main eintritt. Besonders Dinge wie die Initialisierung globaler Variablen.Hier finden Sie eine gute Erklärung für alles, was dazwischen vor sich geht _start() und main() und auch nachdem main wieder beendet wurde (siehe Kommentar unten).
Der notwendige Code dafür wird normalerweise von den Compiler-Autoren in einer Startdatei bereitgestellt, jedoch mit dem Flag –nostartfiles Sie sagen dem Compiler im Wesentlichen: “Geben Sie mir nicht die Standard-Startdatei, geben Sie mir von Anfang an die volle Kontrolle darüber, was passiert.”
Dies ist manchmal notwendig und wird häufig auf eingebetteten Systemen verwendet. ZB wenn Sie kein Betriebssystem haben und bestimmte Teile Ihres Speichersystems (zB Caches) vor der Initialisierung Ihrer globalen Objekte manuell aktivieren müssen.
Die globalen Variablen sind Teil des Datenabschnitts und werden daher während des Ladens des Programms eingerichtet (wenn sie konstant sind, sind sie Teil des Textabschnitts, gleiche Geschichte). Die _start-Funktion ist davon völlig unabhängig.
– Cheiron
17. April 2015 um 13:33 Uhr
@Cheiron: Entschuldigung, mein Fehler In c++ werden globale Variablen oft von einem Konstruktor initialisiert, der intern ausgeführt wird _start() (oder eigentlich eine andere von ihr aufgerufene Funktion) und in vielen Bare-Metal-Programmen kopiert man explizit zuerst alle globalen Daten vom Flash ins RAM, was auch in geschieht _start()aber bei dieser Frage ging es weder um c++ noch um Bare-Metal-Code.
– MikeMB
17. April 2015 um 15:04 Uhr
Beachten Sie, dass in einem Programm, das seine eigenen liefert _start, wird die C-Bibliothek nicht initialisiert, es sei denn, Sie unternehmen spezielle Schritte, um dies selbst zu tun – es kann durchaus unsicher sein, eine nicht asynchronsignalsichere Funktion aus einem solchen Programm zu verwenden. (Dafür gibt es keine offizielle Garantie irgendein Die Bibliotheksfunktion wird funktionieren, aber asynchronsignalsichere Funktionen können überhaupt nicht auf globale Daten verweisen, sodass sie sich um eine Fehlfunktion bemühen müssen.)
– zol
17. April 2015 um 17:59 Uhr
@FUZxxl Trotzdem stelle ich fest, dass async-signalsichere Funktionen funktionieren sind modifizieren dürfen errno (z.B read und write sind async-signalsicher und können gesetzt werden errno) und das könnte möglicherweise ein Problem sein, je nachdem, wann genau der pro-thread errno Standort zugeordnet ist.
– zol
17. April 2015 um 19:39 Uhr
@FUZxxl Ich weiß genau, dass glibc’s malloc ist nicht async-signal-safe, aber ich akzeptiere die Korrektur für den abstrakten Fall.
– zol
17. April 2015 um 19:42 Uhr
Hier ist ein guter Überblick darüber, was während des Programmstarts passiert Vormain. Das zeigt sich vor allem __start ist der eigentliche Einstiegspunkt zu Ihrem Programm aus Sicht des Betriebssystems.
Es ist die allererste Adresse, von der aus die Befehlszeiger beginnt in Ihrem Programm zu zählen.
Der Code dort ruft einige Routinen der C-Laufzeitbibliothek auf, nur um etwas Ordnung zu schaffen, und ruft dann Ihre auf mainund dann bringen Sie die Dinge herunter und rufen Sie an exit mit welchem Exit-Code auch immer main ist zurückgekommen.
Ein Bild sagt mehr als tausend Worte:
PS: Diese Antwort stammt aus einer anderen Frage, die SO hilfreicherweise als Duplikat dieser Frage geschlossen hat.
Quer gepostet, um das Ausgezeichnete zu bewahren Analyse und das schöne Bild.
– ulidtko
20. Januar 2020 um 9:14 Uhr
Wann sollte man so etwas tun?
Wenn Sie Ihren eigenen Startcode für Ihr Programm wünschen.
main ist nicht der erste Eintrag für ein C-Programm, _start ist der erste Eintrag hinter dem Vorhang.
Beispiel unter Linux:
_start: # _start is the entry point known to the linker
xor %ebp, %ebp # effectively RBP := 0, mark the end of stack frames
mov (%rsp), %edi # get argc from the stack (implicitly zero-extended to 64-bit)
lea 8(%rsp), %rsi # take the address of argv from the stack
lea 16(%rsp,%rdi,8), %rdx # take the address of envp from the stack
xor %eax, %eax # per ABI and compatibility with icc
call main # %edi, %rsi, %rdx are the three args (of which first two are C standard) to main
mov %eax, %edi # transfer the return of main to the first argument of _exit
xor %eax, %eax # per ABI and compatibility with icc
call _exit # terminate the program
Gibt es ein reales Szenario, in dem dies nützlich wäre?
Wenn Sie meinen, implementieren Sie unsere eigenen _start:
Ja, in den meisten kommerziellen eingebetteten Softwareprogrammen, mit denen ich gearbeitet habe, müssen wir unsere eigene implementieren _start in Bezug auf unsere spezifischen Speicher- und Leistungsanforderungen.
Wenn du meinst, lass die fallen main Funktion und ändern Sie es in etwas anderes:
Nein, ich sehe keinen Vorteil darin.
14241500cookie-checkWas ist die Verwendung von _start() in C?yes
Entfernt verwandt: stackoverflow.com/questions/2548486/compiling-without-libc
– Mohit Jain
17. April 2015 um 9:09 Uhr
Klassischer Artikel, der einige der inneren Abläufe beim Starten von Programmen demonstriert: Ein Wirbelwind-Tutorial zum Erstellen wirklich winziger ausführbarer ELF-Dateien für Linux. Dies ist eine gute Lektüre, die einige der Feinheiten von bespricht
_start()
und andere Sachen außerhalbmain()
.– Benutzer439793
17. April 2015 um 14:44 Uhr
Die C-Sprache selbst sagt nichts darüber aus
_start
oder über jeden Einstiegspunkt außermain
(mit der Ausnahme, dass der Name des Einstiegspunkts für freistehende (eingebettete) Implementierungen implementierungsdefiniert ist).– Keith Thompson
1. Juni 2015 um 3:17 Uhr
Beachten Sie, dass dies
_start
ist unsicher und verletzt die ABI, wenn es anruftmy_main
; Sie sagen dem Compiler, dass es sich um eine normale Funktion handelt, aber tatsächlich wird sie mit bereits ausgerichtetem Stapelzeiger eingegeben (z. B. auf x86-64, RSP % 16 == 0), nicht mit RSP % 16 == 8 wie beim Eintritt in eine normale Funktion danach acall
das drückt eine 8-Byte-Rückgabeadresse. Das kannst du mit beheben__attribute__((force_align_arg_pointer))
zum_start
um GCC mitzuteilen, dass der Stack-Zeiger beim Eintritt in diese eine “Funktion” möglicherweise “falsch ausgerichtet” ist, wie in Get arg values with inline asm without Glibc?– Peter Cordes
10. Dezember 2020 um 0:38 Uhr
Auf einer modernen Linux-Distribution würde dies zu einem Absturz führen, wenn
my_main
verwendet scanf überhaupt oder printf (oder eine andere variadische Funktion) mit afloar
oderdouble
FP-arg. glibc scanf Segmentierungsfehler, wenn sie von einer Funktion aufgerufen werden, die RSP nicht ausrichtet– Peter Cordes
10. Dezember 2020 um 0:40 Uhr