Подтвердить что ты не робот

С++: Когда (и как) называются глобальные статические конструкторы С++?

Я работаю над некоторым кодом на С++, и я столкнулся с вопросом, который навязывал мне какое-то время... Предполагая, что я собираюсь с GCC на узле Linux для цели ELF, где находятся глобальные статические конструкторы и деструкторы, называемые?

Я слышал там функцию _init в crtbegin.o и функцию _fini в crtend.o. Вызывается ли их crt0.o? Или динамический компоновщик фактически обнаруживает свое присутствие в загруженном двоичном файле и вызывает их? Если да, когда это действительно называется?

Мне в основном интересно знать, поэтому я могу понять, что происходит за кулисами, когда мой код загружается, выполняется, а затем выгружается во время выполнения.

Спасибо заранее!

Обновление: я в основном пытаюсь выяснить общее время, в которое вызываются конструкторы. Я не хочу делать предположения в своем коде на основе этой информации, это более или менее, чтобы лучше понять, что происходит на более низких уровнях, когда загружается моя программа. Я понимаю, что это вполне специфично для ОС, но я попытался немного сузить его в этом вопросе.

4b9b3361

Ответ 1

Говоря о нелокальных статических объектах, не так много гарантий. Как вы уже знаете (и это также упоминалось здесь), он не должен писать код, который зависит от этого. Фиаско порядка статического инициализации...

Статические объекты проходят через двухфазную инициализацию: статическая инициализация и динамическая инициализация. Первое происходит первым и выполняет нулевую инициализацию или инициализацию постоянными выражениями. Последнее происходит после завершения статической инициализации. Это когда конструкторы называются, например.

В общем, эта инициализация происходит через некоторое время перед main(). Однако, в отличие от того, что многие люди думают, даже это не гарантируется стандартом С++. На самом деле гарантируется, что инициализация выполняется до использования какой-либо функции или объекта, определенного в той же самой единицы перевода, что и инициализированный объект. Обратите внимание, что это не зависит от ОС. Это правила С++. Вот цитата из Стандарта:

Определяется ли реализация, является ли динамическая инициализация (8.5, 9.4, 12.1, 12.6.1) объекта область пространства имен выполняется до первого оператора main. Если инициализация отложена до некоторой точки во времени после первого утверждения main, это должно произойти до первого использования любой функции или объекта, определенных в той же единице перевода, что и объект, подлежащий инициализации

Ответ 2

Это зависит от компилятора и времени выполнения. Не рекомендуется делать какие-либо предположения о создании глобальных объектов.

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

Это называется " статический идентификатор порядка инициализации". Даже если это не так в вашем коде, статьи с часто задаваемыми вопросами С++ Lite по этой теме заслуживают того, чтобы их прочитали.

Ответ 3

Это не специфичная для ОС, а скорее специфическая для компилятора.

Вы дали ответ, инициализация выполняется в __init.

Для второй части в gcc вы можете гарантировать порядок инициализации с помощью ____attribute____((init_priority(PRIORITY))), привязанного к определению переменной, где PRIORITY - некоторое относительное значение, причем младшие числа сначала инициализируются.

Ответ 4

Получатели:

  • Все статические нелокальные объекты в глобальном пространстве имен создаются до main()
  • Все статические нелокальные объекты в другом пространстве имен создаются до того, как будут использованы какие-либо функции/методы в этом пространстве имен (таким образом, позволяя компилятору потенциально лениться оценить их [но не рассчитывать на это поведение]).
  • Все статические нелокальные объекты в блоке трансляции строятся в порядке объявления.
  • Ничего не определено о порядке между единицами перевода.
  • Все статические нелокальные объекты уничтожаются в обратном порядке создания. (Это включает в себя статические функциональные переменные (которые лениво создаются при первом использовании).

Если у вас есть глобальные переменные, у которых есть зависимости друг от друга, у вас есть два варианта:

  • Поместите их в одну единицу перевода.
  • Преобразование их в статические функциональные переменные, полученные и созданные при первом использовании.

Пример 1: Глобальный конструктор использует глобальный журнал

class AType
{    AType()  { log.report("A Constructed");}};

LogType    log;
AType      A;

// Or 
Class AType() 
{    AType()  { getLog().report("A Constructed");}};
LogType& getLog()
{
    static LogType  log;
    return log;
}
// Define A anywhere;

Пример Глобальный деструктор B использует глобальный журнал

Здесь вы должны предоставить, чтобы журнал объекта не был уничтожен до объекта B. Это означает, что журнал должен быть полностью сконструирован до B (так как тогда будет применен порядок обратного порядка уничтожения). Опять же можно использовать те же самые методы. Либо поставьте их в одну единицу перевода или используйте функцию для получения журнала.

class BType
{    ~BType()  { log.report("B Destroyed");}};

LogType    log;
BType      B;   // B constructed after log (so B will be destroyed first)

// Or 
Class BType() 
{    BType()    { getLog();}
     /*
      * If log is used in the destructor then it must not be destroyed before B
      * This means it must be constructed before B 
      * (reverse order destruction guarantees that it will then be destroyed after B)
      *
      * To achieve this just call the getLog() function in the constructor.
      * This means that 'log' will be fully constructed before this object.
      * This means it will be destroyed after and thus safe to use in the destructor.
      */
    ~BType()    { getLog().report("B Destroyed");}
};
LogType& getLog()
{
    static LogType  log;
    return log;
}
// Define B anywhere;

Ответ 5

В соответствии со стандартом С++ они вызываются до того, как используется какая-либо функция или объект их единицы перевода. Обратите внимание, что для объектов в глобальном пространстве имен это означает, что они инициализируются до вызова main(). (См. ltcmelo и ответы Мартина для обсуждения и обсуждения этого вопроса.)