В конечном итоге это операционная система. Обычно между реальной точкой входа и главной функцией есть некий носитель, его вставляет компилятор компоновщик.
Некоторые детали (связанные с Windows): В PE файле есть заголовок IMAGE_OPTIONAL_HEADER
, который имеет поле AddressOfEntryPoint
, которое в свою очередь является адресом первого байта кода в файле, который будет выполнен.
Обратите внимание, что в дополнение к уже опубликованным ответам вы также можете сами позвонить по main
. Обычно это плохая идея, зарезервированная для обфусцированного кода.
Операционная система вызывает функцию main()
. На самом деле, обычно она вызывает что-то другое с таким странным названием, как _init
. Компилятор C подключает к каждому приложению стандартную библиотеку, которая обеспечивает эту определенную операционной системой точку входа, а затем вызывает main()
.
Редактировать: Очевидно, что для некоторых это было недостаточно подробно и правильно.
Формат Executable and Linkable Format (ELF), который используют многие ОС Unix, определяет адрес точки входа. Это место, где программа начинает выполняться после того, как ОС завершает вызов exec()
. В системе Linux это _init.
Из objdump -d:
Disassembly of section .init:
08049f08 <_init>:
8049f08: 55 push %ebp
8049f09: 89 e5 mov %esp,%ebp
8049f0b: 83 ec 08 sub $0x8,%esp
8049f0e: e8 a1 05 00 00 call 804a4b4 <call_gmon_start>
8049f13: e8 f8 05 00 00 call 804a510 <frame_dummy>
8049f18: e8 d3 50 00 00 call 804eff0 <__do_global_ctors_aux>
8049f1d: c9 leave
8049f1e: c3 ret
Из readelf -d:
0x00000001 (NEEDED) Shared library: [libstdc++.so.6]
0x00000001 (NEEDED) Shared library: [libm.so.6]
0x00000001 (NEEDED) Shared library: [libgcc_s.so.1]
0x00000001 (NEEDED) Shared library: [libpthread.so.0]
0x00000001 (NEEDED) Shared library: [libc.so.6]
0x0000000c (INIT) 0x8049f08
0x0000000d (FINI) 0x804f018
0x00000004 (HASH) 0x8048168
0x00000005 (STRTAB) 0x8048d8c
0x00000006 (SYMTAB) 0x804867c
0x0000000a (STRSZ) 3313 (bytes)
0x0000000b (SYMENT) 16 (bytes)
0x00000015 (DEBUG) 0x0
0x00000003 (PLTGOT) 0x8059114
0x00000002 (PLTRELSZ) 688 (bytes)
0x00000014 (PLTREL) REL
0x00000017 (JMPREL) 0x8049c58
0x00000011 (REL) 0x8049be0
0x00000012 (RELSZ) 120 (bytes)
0x00000013 (RELENT) 8 (bytes)
0x6ffffffe (VERNEED) 0x8049b60
0x6fffffff (VERNEEDNUM) 3
0x6ffffff0 (VERSYM) 0x8049a7e
0x00000000 (NULL) 0x0
Видно, что INIT равен адресу _init.
Код для frame_dummy и __do_global_ctors_aux находится в наборе файлов с именами crtbegin.o и crtend.o (и вариантами этих имен). Они являются частью GCC. Этот код выполняет различные действия, необходимые для программы на языке Си, такие как установка stdin, stdout, глобальных и статических переменных и других вещей.
Следующая статья довольно хорошо описывает, что он делает в Linux (взято из ответа ниже с меньшим количеством голосов): http://dbp-consulting.com/tutorials/debugging/linuxProgramStartup.html
Я считаю, что в чьем-то ответе уже описано, что делает Windows.
Операционная система вызывает функцию, включенную в среду выполнения C (CRT) и связанную с вашим исполняемым файлом. Это называется "CRT main."
CRT main делает несколько вещей, две самые важные из которых, по крайней мере в C++, это пробежаться по массиву глобальных классов C++ и вызвать их конструкторы, а также вызвать вашу функцию main() и передать ее возвращаемое значение оболочке.
Функция main CRT в Visual C++ делает еще несколько вещей, если мне не изменяет память. Он настраивает распределитель памяти, что важно при использовании Debug CRT для поиска утечек памяти или плохих доступов. Он также вызывает main внутри структурированного обработчика исключений, который отлавливает неудачные обращения к памяти и другие сбои и отображает их.
Операционная система вызывает main. В перемещаемом исполняемом файле будет адрес, указывающий на местоположение main (см. Unix ABI для получения дополнительной информации).
Но, кто вызывает операционную систему?
Центральный процессор, по сигналу "RESET" (который также подается при включении питания), начнет искать в ПЗУ по заданному адресу (скажем, 0xffff) свои инструкции.
Как правило, это будет своего рода инструкция перехода в BIOS, которая настраивает микросхемы памяти, загружает основные драйверы жесткого диска и т. д. и т. п. Затем считывается загрузочный сектор жесткого диска и запускается следующий загрузчик, который загружает файл, содержащий основную информацию о том, как читать, скажем, NTFS-раздел и как читать сам файл ядра. Будет настроено окружение ядра, загружено ядро, а затем - а затем! - ядро будет запущено на выполнение.
После того как вся эта тяжелая работа проделана, ядро может приступить к загрузке нашего программного обеспечения.