Qual è l'uso di _start () in C?


126

Ho imparato dal mio collega che si può scrivere ed eseguire un programma C senza scrivere una main()funzione. Può essere fatto in questo modo:

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

Compilalo con questo comando:

gcc -o my_main my_main.c nostartfiles

Eseguilo con questo comando:

./my_main

Quando sarebbe necessario fare questo genere di cose? Esiste uno scenario del mondo reale in cui ciò sarebbe utile?



7
Articolo classico che dimostra alcuni dei meccanismi interni di come si avviano i programmi: un tutorial vorticoso sulla creazione di eseguibili ELF davvero adolescenti per Linux . Questa è una buona lettura che discute alcuni dei punti più fini _start()e altre cose al di fuori di main().

1
Il linguaggio C stesso non dice nulla su _starto su qualsiasi punto di ingresso diverso da main(tranne che il nome del punto di ingresso è definito dall'implementazione per implementazioni indipendenti (incorporate)).
Keith Thompson

Risposte:


108

Il simbolo _startè il punto di ingresso del tuo programma. Cioè, l'indirizzo di quel simbolo è l'indirizzo a cui si è saltato all'avvio del programma. Normalmente, la funzione con il nome _startviene fornita da un file chiamato crt0.oche contiene il codice di avvio per l'ambiente di runtime C. Imposta alcune cose, popola l'array di argomenti argv, conta quanti argomenti ci sono e quindi chiama main. Dopo i mainritorni, exitviene chiamato.

Se un programma non desidera utilizzare l'ambiente di runtime C, deve fornire il proprio codice per _start. Ad esempio, l'implementazione di riferimento del linguaggio di programmazione Go lo fa perché necessita di un modello di threading non standard che richiede un po 'di magia con lo stack. È anche utile fornire il proprio _startquando si desidera scrivere programmi molto piccoli o programmi che fanno cose non convenzionali.


2
Un altro esempio è il linker / caricatore dinamico di Linux che ha il proprio _start definito.
PP

2
@BlueMoon Ma anche questo _startviene dal file oggetto crt0.o.
fuz

2
@ThomasMatthews Lo standard non specifica _start; infatti, non specifica affatto cosa succede prima di mainessere chiamato, specifica solo quali condizioni devono essere soddisfatte quando mainviene chiamato. È più una convenzione che il punto di ingresso _startrisale ai vecchi tempi.
fuz

1
"l'implementazione di riferimento del linguaggio di programmazione Go lo fa perché necessita di un modello di threading non standard" crt0.o è specifico del C (crt-> C runtime). Non c'è motivo di aspettarsi che venga utilizzato per qualsiasi altra lingua. E il modello di threading di Go è completamente conforme agli standard
Steve Cox

8
@SteveCox Molti linguaggi di programmazione sono costruiti sopra il runtime C perché è più facile implementare i linguaggi in questo modo. Go non utilizza il normale modello di threading. Usano piccoli stack allocati in heap e il proprio scheduler. Questo non è certamente un modello di threading standard.
fuz

45

Sebbene mainsia il punto di ingresso per il tuo programma dal punto di vista dei programmatori, _startè il solito punto di ingresso dal punto di vista del sistema operativo (la prima istruzione che viene eseguita dopo che il programma è stato avviato dal sistema operativo)

In un tipico programma C e soprattutto C ++, è stato fatto molto lavoro prima che l'esecuzione entri in main. Soprattutto cose come l'inizializzazione delle variabili globali. Qui puoi trovare una buona spiegazione di tutto ciò che sta succedendo tra _start()e main()e anche dopo che main è uscito di nuovo (vedi commento sotto).
Il codice necessario per questo è di solito fornito dagli autori del compilatore in un file di avvio, ma con il flag in –nostartfilessostanza dici al compilatore: "Non preoccuparti di darmi il file di avvio standard, dammi il pieno controllo su ciò che sta accadendo direttamente dal inizio".

Questo a volte è necessario e spesso utilizzato su sistemi embedded. Ad esempio, se non hai un sistema operativo e devi abilitare manualmente alcune parti del tuo sistema di memoria (es. Cache) prima dell'inizializzazione degli oggetti globali.


Le variabili globali fanno parte della sezione dati e quindi vengono impostate durante il caricamento del programma (se sono const fanno parte della sezione testo, stessa storia). La funzione _start è completamente estranea a questo.
Cheiron

@Cheiron: Scusa, errore mio In c ++, le variabili globali sono spesso inizializzate da un costruttore che viene eseguito all'interno _start()(o in realtà un'altra funzione chiamata da esso) e in molti programmi Bare-Metal, copi esplicitamente tutti i dati globali da flash a RAM primo, cosa che accade anche in _start(), ma questa domanda non riguardava né il c ++ né il codice bare-metal.
MikeMB

1
Si noti che in un programma che fornisce la propria _start, la libreria C non verrà inizializzata a meno che non si intraprendano passaggi speciali per farlo da soli: potrebbe non essere sicuro utilizzare qualsiasi funzione di sicurezza del segnale non asincrona da un programma del genere. (Non esiste alcuna garanzia ufficiale che qualsiasi funzione di libreria funzioni, ma le funzioni di sicurezza del segnale asincrono non possono fare riferimento a nessun dato globale, quindi dovrebbero fare di tutto per non funzionare correttamente.)
zwol

@zwol è solo parzialmente corretto. Ad esempio, una tale funzione potrebbe allocare memoria. L'allocazione della memoria è problematica quando le strutture dati interne per mallocnon sono inizializzate.
fuz

1
@FUZxxl Detto questo, noto che le funzioni async-signal-safe sono autorizzati per modificare errno(es reade writesono async-signal-safe e può impostare errno) e che potrebbe in teoria essere un problema a seconda esattamente quando il pro filo errnodi posizione viene allocato .
Zwol

2

Ecco una buona panoramica di ciò che accade durante l'avvio del programma prima main . In particolare, mostra che __startè il punto di ingresso effettivo al programma dal punto di vista del sistema operativo.

È il primo indirizzo dal quale il puntatore dell'istruzione inizierà a contare nel programma.

Il codice richiama alcune routine di libreria di runtime C solo per fare un po 'di pulizia, quindi chiama il tuo main, quindi porta giù le cose e chiama exitcon qualsiasi codice di uscita mainrestituito.


Un'immagine vale più di mille parole:

Diagramma di avvio del runtime C.


PS: questa risposta è trapiantata da un'altra domanda che SO ha utilmente chiuso come duplicato di questa.


Cross postato per preservare l'ottima analisi e la bella foto.
ulidtko

1

Quando sarebbe necessario fare questo genere di cose?

Quando vuoi il tuo codice di avvio per il tuo programma.

mainnon è la prima voce per un programma in C, _startè la prima voce dietro le quinte.

Esempio in 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

Esiste uno scenario del mondo reale in cui ciò sarebbe utile?

Se intendi, implementa il nostro _start:

Sì, nella maggior parte dei software embedded commerciali con cui ho lavorato, dobbiamo implementare il nostro _startin base ai nostri requisiti specifici di memoria e prestazioni.

Se intendi, rilascia la mainfunzione e cambiala in qualcos'altro:

No, non vedo alcun vantaggio nel farlo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.