Jakie jest zastosowanie funkcji _start () w C?


127

Dowiedziałem się od mojego kolegi, że można napisać i wykonać program w języku C bez pisania main()funkcji. Można to zrobić w ten sposób:

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

Skompiluj to za pomocą tego polecenia:

gcc -o my_main my_main.c nostartfiles

Uruchom go za pomocą tego polecenia:

./my_main

Kiedy należałoby robić takie rzeczy? Czy istnieje scenariusz ze świata rzeczywistego, w którym byłoby to przydatne?



7
Klasyczny artykuł, który demonstruje niektóre z wewnętrznych mechanizmów uruchamiania programów: Samouczek Whirlwind na temat tworzenia naprawdę małych plików wykonywalnych ELF dla systemu Linux . To dobra lektura, która omawia niektóre z lepszych punktów _start()i inne rzeczy poza nim main().

1
Sam język C nie mówi nic o _startżadnym punkcie wejścia ani o jakimkolwiek punkcie wejścia poza main(poza tym, że nazwa punktu wejścia jest zdefiniowana przez implementację dla implementacji wolnostojących (osadzonych)).
Keith Thompson

Odpowiedzi:


108

Symbol _start jest punktem wejścia do twojego programu. Oznacza to, że adres tego symbolu jest adresem, na który następuje skok podczas uruchamiania programu. Zwykle funkcja o nazwie _startjest dostarczana przez plik o nazwie, crt0.októry zawiera kod startowy środowiska wykonawczego C. Ustawia pewne rzeczy, zapełnia tablicę argumentów argv, zlicza liczbę argumentów, a następnie wywołuje main. Po mainzwrotach exitjest wywoływana.

Jeśli program nie chce używać środowiska wykonawczego C, musi dostarczyć własny kod dla _start. Na przykład implementacja referencyjna języka programowania Go robi to, ponieważ potrzebuje niestandardowego modelu wątków, który wymaga magii ze stosem. Przydatne jest również dostarczenie własnego, _startgdy chcesz napisać naprawdę małe programy lub programy, które robią niekonwencjonalne rzeczy.


2
Innym przykładem jest dynamiczny linker / loader Linuksa, który ma zdefiniowany własny _start.
PP

2
@BlueMoon Ale to też _startpochodzi z pliku obiektu crt0.o.
fuz

2
@ThomasMatthews Standard nie określa _start; w rzeczywistości nie określa, co dzieje się przed mainwywołaniem, a jedynie określa, jakie warunki muszą być spełnione, gdy mainjest wywoływane. To bardziej konwencja, że ​​punkt wejścia _startsięga dawnych czasów.
fuz

1
„implementacja referencyjna języka programowania Go robi to, ponieważ potrzebuje niestandardowego modelu wątków” crt0.o jest specyficzne dla języka C (crt-> C runtime). Nie ma powodu, by oczekiwać, że będzie używany w jakimkolwiek innym języku. Model gwintowania firmy Go jest całkowicie zgodny ze standardami
Steve Cox

8
@SteveCox Wiele języków programowania jest zbudowanych na bazie środowiska wykonawczego C, ponieważ w ten sposób łatwiej jest implementować języki. Go nie używa normalnego modelu gwintowania. Używają małych, przydzielonych stosów i własnego harmonogramu. Z pewnością nie jest to standardowy model gwintowania.
fuz

45

Chociaż mainz punktu widzenia programistów jest punktem wyjścia do programu,_start jest zwykłym punktem wejścia z perspektywy systemu operacyjnego (pierwsza instrukcja wykonywana po uruchomieniu programu z systemu operacyjnego)

W typowym programie w C, a zwłaszcza w C ++, wykonano dużo pracy, zanim wykonanie przejdzie do main. Zwłaszcza rzeczy takie jak inicjalizacja zmiennych globalnych. Tutaj można znaleźć dobre wyjaśnienie wszystkiego, co się dzieje między _start()i main()a także po wyjściu głównym ponownie (patrz komentarz poniżej).
Niezbędny do tego kod jest zwykle dostarczany przez autorów kompilatora w pliku startowym, ale za pomocą flagi, –nostartfilesktórą zasadniczo przekazujesz kompilatorowi: „Nie przejmuj się podawaniem mi standardowego pliku startowego, daj mi pełną kontrolę nad tym, co się dzieje bezpośrednio z początek".

Czasami jest to konieczne i często używane w systemach wbudowanych. Np. Jeśli nie masz systemu operacyjnego i musisz ręcznie włączyć określone części systemu pamięci (np. Pamięci podręczne) przed inicjalizacją obiektów globalnych.


Zmienne globalne są częścią sekcji danych i dlatego są ustawiane podczas ładowania programu (jeśli są stałymi, są częścią sekcji tekstowej, ta sama historia). Funkcja _start nie ma z tym żadnego związku.
Cheiron

@Cheiron: Przepraszam, mój emistake W c ++ zmienne globalne są często inicjowane przez konstruktora, który jest uruchamiany wewnątrz _start()(lub właściwie inna funkcja przez niego wywołana), aw wielu programach Bare-Metal jawnie kopiujesz wszystkie globalne dane z pamięci flash do pamięci RAM po pierwsze, co również ma miejsce w programie _start(), ale to pytanie nie dotyczyło ani kodu C ++, ani kodu bare-metal.
MikeMB

1
Zauważ, że w programie, który dostarcza swoją własną _start, biblioteka C nie zostanie zainicjowana, chyba że wykonasz specjalne kroki, aby zrobić to sam - użycie jakiejkolwiek funkcji nie-asynchronicznej bezpiecznej dla sygnału z takiego programu może być niebezpieczne. (Nie ma oficjalnej gwarancji, że jakakolwiek funkcja biblioteki będzie działać, ale funkcje asynchroniczne zabezpieczające sygnał nie mogą w ogóle odwoływać się do żadnych danych globalnych, więc musieliby zrobić wszystko, co w ich mocy, aby działać nieprawidłowo.)
zwol

@zwol to tylko częściowo poprawne. Na przykład taka funkcja może przydzielać pamięć. Przydzielanie pamięci jest problematyczne, gdy wewnętrzne struktury danych dla mallocnie zostały zainicjowane.
fuz

1
@FUZxxl Mimo, że widzę, że funkcje async-signal-safe dopuszczone do modyfikacji errno(np readi writesą asynchroniczny sygnał-bezpieczny i można ustawić errno) i że mogłaby być problemem w zależności od dokładnie kiedy per-thread errnolokalizacja jest alokowana .
zwol

2

Tu jest dobry przegląd tego, co dzieje się podczas uruchamiania programu przed main . W szczególności pokazuje, że __startjest to rzeczywisty punkt wejścia do programu z punktu widzenia systemu operacyjnego.

Jest to pierwszy adres, od którego zacznie zliczać wskaźnik instrukcji w twoim programie.

Kod tam wywołuje niektóre procedury biblioteki wykonawczej C tylko po to, aby wykonać pewne czynności porządkowe, a następnie wywołać swój main, a następnie wyłączyć i wywołać exitdowolny mainzwrócony kod zakończenia .


Obraz jest wart tysiąca słów:

Diagram uruchamiania środowiska wykonawczego C.


PS: ta odpowiedź jest przeszczepiona z innego pytania, które SO pożytecznie zamknęło jako duplikat tego.


Opublikowane, aby zachować doskonałą analizę i ładny obraz.
ulidtko

1

Kiedy należałoby robić takie rzeczy?

Gdy chcesz mieć własny kod startowy dla swojego programu.

mainnie jest pierwszym wpisem dla programu w C, _startjest pierwszym wpisem za kurtyną.

Przykład w systemie 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

Czy istnieje scenariusz ze świata rzeczywistego, w którym byłoby to przydatne?

Jeśli masz na myśli, zaimplementuj własne _start:

Tak, w większości komercyjnego oprogramowania wbudowanego, z którym pracowałem, musimy wdrożyć własne, _startuwzględniając nasze specyficzne wymagania dotyczące pamięci i wydajności.

Jeśli masz na myśli, porzuć tę mainfunkcję i zmień ją na coś innego:

Nie, nie widzę w tym żadnej korzyści.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.