Quelle est l'utilisation de _start () en C?

126

J'ai appris de mon collègue que l'on peut écrire et exécuter un programme C sans écrire de main()fonction. Cela peut être fait comme ceci:

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; 
}

Compilez-le avec cette commande:

gcc -o my_main my_main.c nostartfiles

Exécutez-le avec cette commande:

./my_main

Quand faudrait-il faire ce genre de chose? Y a-t-il un scénario du monde réel où cela serait utile?

Gars simple
la source
7
Article classique qui montre quelques-uns des rouages ​​internes du démarrage des programmes: un tutoriel Whirlwind sur la création d'exécutables ELF Really Teensy pour Linux . C'est une bonne lecture qui traite de certains des points les plus fins _start()et d'autres choses en dehors de main().
1
Le langage C lui-même ne dit rien sur _start, ou sur tout point d'entrée autre que main(sauf que le nom du point d'entrée est défini par l'implémentation pour les implémentations autonomes (intégrées)).
Keith Thompson

Réponses:

108

Le symbole _startest le point d'entrée de votre programme. Autrement dit, l'adresse de ce symbole est l'adresse à laquelle saute au démarrage du programme. Normalement, la fonction avec le nom _startest fournie par un fichier appelé crt0.oqui contient le code de démarrage de l'environnement d'exécution C. Il configure certaines choses, remplit le tableau d'arguments argv, compte le nombre d'arguments présents, puis appelle main. Après les mainretours, exitest appelé.

Si un programme ne souhaite pas utiliser l'environnement d'exécution C, il doit fournir son propre code pour _start. Par exemple, l'implémentation de référence du langage de programmation Go le fait car ils ont besoin d'un modèle de thread non standard qui nécessite un peu de magie avec la pile. Il est également utile de fournir le vôtre _startlorsque vous voulez écrire de très petits programmes ou des programmes qui font des choses non conventionnelles.

fuz
la source
2
Un autre exemple est l'éditeur de liens / chargeur dynamique de Linux qui a son propre _start défini.
PP
2
@BlueMoon Mais cela _startvient aussi du fichier objet crt0.o.
fuz le
2
@ThomasMatthews La norme ne spécifie pas _start; en fait, il ne spécifie pas du tout ce qui se passe avant d' mainêtre appelé, il spécifie simplement quelles conditions doivent être remplies lors de l' mainappel. C'est plus une convention pour le point d'entrée _startqui remonte à l'ancien temps.
fuz le
1
"l'implémentation de référence du langage de programmation Go le fait car ils ont besoin d'un modèle de thread non standard" crt0.o est spécifique à C (exécution crt-> C). Il n'y a aucune raison de s'attendre à ce qu'il soit utilisé pour une autre langue. Et le modèle de filetage de Go est entièrement conforme aux normes
Steve Cox
8
@SteveCox De nombreux langages de programmation sont construits au-dessus du runtime C car il est plus facile d'implémenter des langages de cette façon. Go n'utilise pas le modèle de filetage normal. Ils utilisent de petites piles allouées par tas et leur propre planificateur. Ce n'est certainement pas un modèle de filetage standard.
fuz le
45

Alors que mainc'est le point d'entrée de votre programme du point de vue des programmeurs, _startc'est le point d'entrée habituel du point de vue du système d'exploitation (la première instruction exécutée après le démarrage de votre programme à partir du système d'exploitation)

Dans un programme typique en C et en particulier en C ++, beaucoup de travail a été effectué avant que l'exécution entre en main. Surtout des trucs comme l'initialisation des variables globales. Ici vous pouvez trouver une bonne explication de tout ce qui se passe entre _start()et main()aussi après principal est sorti à nouveau (voir commentaire ci - dessous).
Le code nécessaire pour cela est généralement fourni par les rédacteurs du compilateur dans un fichier de démarrage, mais avec le drapeau, –nostartfilesvous dites essentiellement au compilateur: "Ne me donnez pas le fichier de démarrage standard, donnez-moi un contrôle total sur ce qui se passe directement depuis le début".

Ceci est parfois nécessaire et souvent utilisé sur les systèmes embarqués. Par exemple, si vous n'avez pas d'OS et que vous devez activer manuellement certaines parties de votre système de mémoire (par exemple les caches) avant l'initialisation de vos objets globaux.

MikeMB
la source
Les variables globales font partie de la section de données et sont donc configurées lors du chargement du programme (si elles sont const, elles font partie de la section de texte, même histoire). La fonction _start n'a aucun rapport avec cela.
Cheiron
@Cheiron: Désolé, mon erreur En c ++, les variables globales sont souvent initialisées par un constructeur qui est exécuté à l'intérieur _start()(ou en fait une autre fonction appelée par lui) et dans de nombreux programmes Bare Metal, vous copiez explicitement toutes les données globales de la mémoire flash vers la RAM d'abord, ce qui se produit également dans _start(), mais cette question ne concernait ni le c ++ ni le code bare-metal.
MikeMB
1
Notez que dans un programme qui fournit le sien _start, la bibliothèque C ne sera pas initialisée à moins que vous ne preniez des mesures spéciales pour le faire vous-même - il peut être dangereux d'utiliser une fonction non sécurisée pour le signal asynchrone d'un tel programme. (Il n'y a aucune garantie officielle que toute fonction de bibliothèque fonctionne, mais les fonctions de sécurité pour les signaux asynchrones ne peut pas se référer à des données globales du tout, donc ils auraient à sortir de leur chemin à un mauvais fonctionnement.)
Zwol
@zwol ce n'est que partiellement correct. Par exemple, une telle fonction pourrait allouer de la mémoire. L'allocation de mémoire est problématique lorsque les structures de données internes de mallocne sont pas initialisées.
fuz le
1
@FUZxxl Cela dit, je remarque que les fonctions de sécurité du signal asynchrone sont autorisées à modifier errno(par exemple read, elles writesont sûres pour le signal asynchrone et peuvent être définies errno) et cela pourrait éventuellement poser un problème en fonction du moment exact où l' errnoemplacement par thread est alloué .
zwol
2

Voici un bon aperçu de ce qui se passe lors du démarrage du programme avant main . En particulier, cela montre que __startc'est le point d'entrée réel de votre programme du point de vue du système d'exploitation.

C'est la toute première adresse à partir de laquelle le pointeur d'instruction commencera à compter dans votre programme.

Le code là-bas appelle certaines routines de bibliothèque d'exécution C juste pour faire un peu de ménage, puis appelez votre main, puis arrêtez les choses et appelez exitavec le code de sortie mainrenvoyé.


Une image vaut mieux que mille mots:

Diagramme de démarrage de l'exécution C


PS: cette réponse est transplantée à partir d' une autre question que SO a utilement clôturée en double de celle-ci.

ulidtko
la source
Cross-posté pour préserver l'excellente analyse et la belle image.
ulidtko le
1

Quand faudrait-il faire ce genre de chose?

Lorsque vous voulez votre propre code de démarrage pour votre programme.

mainn'est pas la première entrée pour un programme C, _startest la première entrée derrière le rideau.

Exemple sous 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

Y a-t-il un scénario du monde réel où cela serait utile?

Si vous voulez dire, implémentez le nôtre _start:

Oui, dans la plupart des logiciels embarqués commerciaux avec lesquels j'ai travaillé, nous devons implémenter les nôtres _starten fonction de nos besoins spécifiques en matière de mémoire et de performances.

Si vous voulez dire, supprimez la mainfonction et changez-la en autre chose:

Non, je ne vois aucun avantage à faire cela.

Trevor
la source