Какая польза от _start () в C?

127

Я узнал от своего коллеги, что можно написать и выполнить программу на 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

Когда нужно делать такие вещи? Есть ли какой-нибудь сценарий в реальном мире, где это было бы полезно?

SimpleGuy
источник
1
Связано удаленно: stackoverflow.com/questions/2548486/compiling-without-libc
Mohit Jain
7
Классическая статья, демонстрирующая некоторые внутренние механизмы запуска программ: Учебник Whirlwind по созданию действительно миниатюрных исполняемых файлов ELF для Linux . Это хорошее чтение, в котором обсуждаются некоторые тонкости _start()и другие вещи за пределами main().
1
Сам язык C ничего не говорит ни о _startкакой точке входа, кроме main(за исключением того, что имя точки входа определяется реализацией для автономных (встроенных) реализаций).
Кейт Томпсон

Ответы:

108

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

Если программа не хочет использовать среду выполнения C, она должна предоставить свой собственный код для _start. Например, эталонная реализация языка программирования Go делает это потому, что им нужна нестандартная потоковая модель, которая требует некоторой магии со стеком. Также полезно предоставить свои собственные, _startкогда вы хотите написать действительно крошечные программы или программы, которые делают нетрадиционные вещи.

FUZ
источник
2
Другой пример - динамический компоновщик / загрузчик Linux, для которого определен собственный _start.
PP
2
@BlueMoon Но это тоже _startпроисходит из объектного файла crt0.o.
fuz
2
@ThomasMatthews Стандарт не определяет _start; на самом деле, он вообще не указывает, что происходит перед mainвызовом, он просто указывает, какие условия должны выполняться при mainвызове. Это больше условность для точки входа, _startкоторая восходит к старым временам.
fuz
1
«эталонная реализация языка программирования Go делает это, потому что им нужна нестандартная модель потоков». crt0.o специфичен для C (crt-> C runtime). Нет причин ожидать, что он будет использоваться для любого другого языка. И модель потоковой передачи Go полностью соответствует стандартам
Стив Кокс,
8
@SteveCox Многие языки программирования построены на основе среды выполнения C, потому что так проще реализовать языки. Go не использует обычную модель потоковой передачи. Они используют небольшие стеки с выделенной кучей и собственный планировщик. Это определенно не стандартная модель потоковой передачи.
fuz
45

Хотя mainэто точка входа для вашей программы с точки зрения программистов, _startэто обычная точка входа с точки зрения ОС (первая инструкция, которая выполняется после того, как ваша программа была запущена из ОС)

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

Иногда это необходимо и часто используется во встроенных системах. Например, если у вас нет ОС, и вам нужно вручную включить определенные части вашей системы памяти (например, кеши) перед инициализацией ваших глобальных объектов.

MikeMB
источник
Глобальные переменные являются частью раздела данных и, таким образом, настраиваются во время загрузки программы (если они являются константными, они являются частью текстового раздела, та же история). Функция _start совершенно не связана с этим.
Cheiron
@Cheiron: Извините, моя ошибка. В C ++ глобальные переменные часто инициализируются конструктором, который запускается внутри _start()(или фактически другой функцией, вызываемой им), и во многих программах Bare-Metal вы явно копируете все глобальные данные из флэш-памяти в ОЗУ. во-первых, что также происходит в _start(), но этот вопрос не касался ни C ++, ни кода с нуля.
MikeMB
1
Обратите внимание, что в программе, которая предоставляет свою собственную _start, библиотека C не будет инициализирована, если вы не предпримете специальные шаги, чтобы сделать это самостоятельно - вполне может быть небезопасно использовать какую-либо не асинхронную сигнальную функцию из такой программы. (Нет официальной гарантии, что какая-либо библиотечная функция будет работать, но функции, безопасные для асинхронных сигналов, вообще не могут ссылаться на какие-либо глобальные данные, поэтому им придется изо всех сил
стараться
@zwol это верно только частично. Например, такая функция может выделять память. Выделение памяти проблематично, если внутренние структуры данных для mallocне инициализированы.
fuz
1
@FUZxxl Сказав это, я заметил , что асинхронному сигнал безопасной функции будут разрешено изменять errno(например , readи writeявляюсь асинхронным сигналом безопасными и могу установить errno) , и что , вероятно , может быть проблемой , в зависимости от того, когда именно за нить errnoместа выделяется ,
zwol
2

Вот хороший обзор того, что происходит во время запуска программы раньше main . В частности, это показывает, что __startэто фактическая точка входа в вашу программу с точки зрения ОС.

Это самый первый адрес, с которого указатель команд начнет отсчет в вашей программе.

Код там вызывает некоторые подпрограммы библиотеки времени выполнения C, чтобы выполнить некоторую уборку, затем вызывает ваш main, а затем отключает и вызывает exitлюбой mainвозвращенный код выхода .


Одна картинка стоит тысячи слов:

Диаграмма запуска среды выполнения C


PS: этот ответ перенесен из другого вопроса, который SO услужливо закрыл как дубликат этого.

ulidtko
источник
Перекрестно размещено, чтобы сохранить отличный анализ и красивую картинку.
ulidtko
1

Когда нужно делать такие вещи?

Когда вам нужен собственный код запуска для вашей программы.

mainэто не первая запись для программы C, _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функцию и измените ее на что-нибудь другое:

Нет, я не вижу в этом никакой пользы.

Тревор
источник