Яке використання _start () в C?


125

Я дізнався від свого колеги, що можна писати та виконувати програму C без написання main()функції. Це можна зробити так:

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

Складіть його за допомогою цієї команди:

gcc -o my_main my_main.c nostartfiles

Запустіть його за допомогою цієї команди:

./my_main

Коли потрібно було б робити подібні речі? Чи є сценарій реального світу, де це було б корисно?



7
Класична стаття, яка демонструє деякі внутрішні дії щодо запуску програм: Навчальний посібник щодо створення справжніх виконавців ELF для підлітків для Linux . Це хороше прочитання, в якому обговорюються деякі точніші пункти _start()та інші речі поза межами main().

1
Сама мова C нічого не говорить про _startабо про будь-яку точку входу, окрім main(за винятком того, що ім'я точки входу визначено реалізацією для самостійно (вбудованих) реалізацій).
Кіт Томпсон

Відповіді:


107

Символ _startє точкою входу вашої програми. Тобто адреса цього символу - це адреса, перескочена до запуску програми. Зазвичай функція з назвою _startнадається файлом, crt0.oякий називається, який містить код запуску для середовища виконання C. Він встановлює деякі речі, заповнює масив аргументів argv, підраховує кількість аргументів, а потім викликає main. Після mainповернення exitвикликається.

Якщо програма не хоче використовувати середовище виконання C, їй потрібно подати власний код для _start. Наприклад, реалізація опорної мови програмування Go робить це тому, що їм потрібна нестандартна модель різьблення, яка вимагає певної магії зі стеком. Також корисно подавати свої, _startколи ви хочете писати дійсно крихітні програми чи програми, які роблять нетрадиційні речі.


2
Іншим прикладом є динамічний лінкер / завантажувач Linux, який має власний _start.
ПП

2
@BlueMoon Але це також _startпоходить з файлу об'єктів crt0.o.
fuz

2
@ThomasMatthews Стандарт не вказує _start; насправді, він не визначає, що відбувається раніше, mainвзагалі викликається, він просто вказує, які умови повинні бути виконані, коли mainвикликається. Це більше умова для точки входу, _startяка датується давніми часами.
fuz

1
"Референтна реалізація мови програмування Go робить це тому, що їм потрібна нестандартна модель різьблення". Немає підстав очікувати, що він буде використаний для будь-якої іншої мови. Модель різьби Go повністю відповідає стандарту
Стів Кокс

8
@SteveCox Багато мов програмування побудовано на версії C, оскільки це легше реалізувати мови таким чином. Go не використовує звичайну модель різьблення. Вони використовують невеликі стеки, виділені купою, та власний планувальник. Це, звичайно, не стандартна модель різьблення.
fuz

45

Хоча mainточка входу для вашої програми з точки зору програмістів, _startє звичайною точкою входу з точки зору ОС (перша інструкція, яка виконується після запуску вашої програми з ОС)

У типовій програмі C та особливо на C ++ було зроблено багато роботи до того, як виконання стане головним. Особливо такі речі, як ініціалізація глобальних змінних. Тут ви можете знайти гарне пояснення всьому , що відбувається між _start()і , main()а також після того, як основний знову вийшов (див коментар нижче).
Код, необхідний для цього, зазвичай надається авторами-компіляторами у файлі запуску, але за допомогою прапора –nostartfilesви, по суті, говорите компілятору: "Не турбуйтеся, даючи мені стандартний файл запуску, дайте мені повний контроль над тим, що відбувається прямо з початок ".

Це іноді необхідно і часто використовується у вбудованих системах. Наприклад, якщо у вас немає ОС і вам потрібно вручну ввімкнути певні частини системи пам'яті (наприклад, кеші) перед ініціалізацією ваших глобальних об'єктів.


Global vars є частиною розділу даних і, таким чином, встановлюється під час завантаження програми (якщо вони є const, вони є частиною текстового розділу, тієї ж історії). Функція _start абсолютно не пов'язана з цим.
Чейрон

@Cheiron: Вибачте, моя помилка У c ++ глобальні змінні часто ініціалізуються конструктором, який запускається всередині _start()(або насправді іншою функцією, що називається ним). по-перше, що теж трапляється в _start(), але це питання не стосувалося ні c ++, ні коду голого металу.
MikeMB

1
Зауважте, що в програмі, що постачає власну _start, бібліотека C не буде ініціалізована, якщо ви не зробите спеціальних заходів, щоб зробити це самостійно - цілком може бути небезпечним використання будь-якої функції, не захищеної сигналом від асинхронізації, від такої програми. (Немає офіційної гарантії, що будь- яка функція бібліотеки буде працювати, але функції, захищені від асинхронного сигналу, взагалі не можуть посилатися на будь-які глобальні дані, тому їм доведеться вийти зі шляху для несправності.)
zwol

@zwol це лише частково правильно. Наприклад, така функція може виділяти пам'ять. Виділення пам'яті є проблематичним, коли внутрішні структури даних для mallocне ініціалізовані.
fuz

1
@FUZxxl Сказавши це, я помітив , що асинхронного сигнал безпечної функції будуть дозволено змінювати errno(наприклад , readі writeє асинхронним сигналом безпечними і можу встановити errno) , і що , ймовірно , може бути проблемою , в залежності від того, коли саме за нитку errnoмісця виділяється .
zwol

2

Ось хороший огляд того, що відбувається під час запуску програми раніше main . Зокрема, з нього видно, що __startце фактична точка входу до вашої програми з точки зору ОС.

Це найперша адреса, з якої покажчик інструкцій почне рахувати у вашій програмі.

Код там викликає деякі підпрограми бібліотеки С, щоб виконати домашнє господарство, потім зателефонуйте до свого main, а потім збийте всі речі та зателефонуйте exitз тим, що mainповертається код виходу .


Малюнок вартує тисячі слів:

C діаграма запуску програми C


PS: ця відповідь перенесена з іншого питання, яке SO корисно закрило як дублікат цього.


Перекреслено, щоб зберегти відмінний аналіз та гарну картину.
улідко

1

Коли потрібно було б робити подібні речі?

Коли ви хочете свій власний стартовий код для вашої програми.

mainне перший запис для програми С, _startце перший запис за завісою.

Приклад в 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

Чи є сценарій реального світу, де це було б корисно?

Якщо ви хочете сказати, реалізуйте наші власні _start:

Так, у більшості комерційних вбудованих програм, з якими я працював, нам потрібно реалізувати власні _startвимоги до наших специфічних вимог до пам'яті та продуктивності.

Якщо ви хочете сказати, киньте mainфункцію та змініть її на щось інше:

Ні, я не бачу жодної користі для цього.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.