View in Telegram
​​Что происходит до main? Рассмотрим простую программу:
#include <iostream>
#include <random>

int a;
int b;

int main() {
  a = rand();
  b = rand();
  std::cout << (a + b);
}
Все очень просто. Объявляем две глобальные переменные, в main() присваиваем им значения и выводим их сумму на экран. Скомпилировав эту программу, мы сможем посмотреть ее ассемблер и увидеть просто набор меток, соответствующих разным сущностям кода(переменным a и b, функции main). Но вы не увидите какого-то "скрипта". Типа как в питоне. Если питонячий код не оборачивать в функции, то мы точно будем знать, что выполнение будет идти сверху вниз. Так вот, такой простыни ассемблера вы не увидите. Код будет организован так, как будто бы им кто-то будет пользоваться. И это действительно так! Убирая сложные детали, можем увидеть вот такое:
a:
  .zero 4

b:
  .zero 4

main:

  push rbp
  mov rbp, rsp
  call rand
  ...
  call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
  mov eax, 0
  pop rbp
  ret
Суть программы состоит из меток. Метки нужны, чтобы обращаться к сущностям программы. Да, они и внутри основного кода используются. Но то, что на главной функции стоит метка, говорит нам о том, что ее кто-то вызывает! Но даже до того, как начнет работу сущность, которая вызывает main, нужно проделать большую работу по подготовке программы к исполнению. Давайте просто перечислю, что должно быть сделано: 💥 Программа загружается в оперативную память. 💥 Аллокация памяти для стека. Для исполнения функций и хранения локальных переменных обязательно нужен стек. 💥 Аллокация памяти для кучи. Для программы нужна дополнительная память, которую она берет из кучи. 💥 Инициализация регистров. Там их большое множество. Например, нужно установить текущий указатель на вершину стека(stack pointer), указатель на инструкции(instruction pointer) и тд. 💥 Замапить виртуальное адресное пространство процесса. Процессы не работают с железной памятью напрямую. Они делают это через абстракцию, называемую виртуальная память. 💥 Положить на стек аргументы argc, argv(мб envp). Это аргументы для функции main. 💥 Загрузка динамических библиотек. Программа всегда линкуется с разными динамическими либами, даже если вы этого явно не делаете) 💥 Вызов всякий преинициализирующих функций. Важная оговорка, что это все суперсильное упрощение. В реале все намного сложнее. Не претендую на полноту изложения и правильность порядка шагов. К тому же я говорю только про эквайромент полноценных ОС типа окон и пингвина. В эмбеде могут быть сильные отличия. Обязательно оставляйте свои дополнения процесса старта программы в комментариях. В этих полноценных осях всю эту грязную работу на себя берет загрузчик программ. После того, как эти шаги выполнены, загрузчик может вызывать ту самую функцию _start(название условное, зависит от реализации). Она уже выполняет более прикладные чтоли вещи: 👉🏿 Статическая инициализация глобальных переменных. Это и недавно обсуждаемая zero-инициализация и константная инициализация(когда объект инициализирован константным выражением). То есть инициализируется все, что можно было узнать на этапе компиляции. 👉🏿 Динамическая инициализация глобальных объектов. Выполняется код конструкторов глобальных объектов. 👉🏿 Инициализация стандартного ввода-вывода. Об этом мы говорили тут. 👉🏿 Инициализация еще бог знает чего. Начальное состояние рандомайзера, malloc'а и прочего. Так-то это часть первых шагов, но привожу отдельно, чтобы вы не думали, что только ваши глобальные переменные инициализируются. И только вот после этого всего, когда состояние программы приведено в соответствие с ожиданиями стандарта С++, функция _start вызывает main. Так что, чтобы вы смогли выполнить свою программу, кому-то нужно очень мощно поднапрячься... See what's underneath. Stay cool. #OS #compiler
Love Center - Dating, Friends & Matches, NY, LA, Dubai, Global
Love Center - Dating, Friends & Matches, NY, LA, Dubai, Global
Find friends or serious relationships easily