tgoop.com/cxx95/109
Create:
Last Update:
Last Update:
#madskillz
Как правильно выполнить любой код до и после main()
Функция int main()
не является настоящей "точкой входа" в программе. До того, как зайти туда (и после него), программа выполняет много другого кода, в том числе юзерского.
Почти всегда этот код - инициализация статических объектов, которая выполняется до main()
. Программа:
std::vector<int> MakeVector() {Выведет такое:
std::cout << "do MakeVector" << std::endl;
return {1, 2, 3};
}
std::vector<int> VECTOR = MakeVector();
int main() {
std::cout << "do main" << std::endl;
}
do MakeVectorПример на godbolt
do main
В этом коде можно делать что угодно (читать файлы, записывать в поток и т.д.), окружение там полностью готово. Я часто использую лямбды:
Пример на godbolt 1, godbolt 2.
main()
: Linux x86 Program Start Up.Но все описание там "примерное", потому что поведение зависит от компилятора и его версии
В моем случае некоторые действия находятся в другом положении схемы (увидел по
gdb
), и еще есть дополнительная вложенность - есть функции для отдельных .cpp-файлов, а там внутри уже вызовы для инициализации переменных.main()
", можно пометить ее специальным атрибутом.А именно
__attribute__((constructor))
или менее вырвиглазно [[gnu::constructor]]
.Здесь слова "constructor" и "destructor" не относятся к классам в C++, это исторические названия еще до C++.
Пример на godbolt с ctor-ами и dtor-ами
Однако здесь есть типичный прикол с
std::cout
x86-64 clang trunk
по состоянию на 13.06.2023
работает нормально.x86-64 clang 16.0.0
дает сегфолт в кторах и будет работать только если поменять там на printf
- ссылка на godbolt.std::cout
и подобные объекты инициализируются в конструкторе статического объекта std::ios_base::Init (объявление этого объекта находится в хедере <iostream>
).А компилятор данной версии так скомпилировал, что кторы выполняются ДО НЕГО и пытаются обратиться к неинициализированному объекту.
Функция
printf
таких спецэффектов не имеет, там просто перенаправление в нужный системный вызов (syscall) без регистрации и СМС.Мораль думайте сами
Вопрос: В чем принципиальное отличие этого кода:
[[gnu::constructor]] void foo() {от этого:
// приколы
}
struct Dummy {или более укуренного этого:
Dummy() { /* приколы */ }
};
Dummy dummy;
const auto dummy = []{Ответ: Никакого, кроме более удобной записи.
// приколы
return nullptr;
}();
Вопрос: Зачем это все надо, почему нельзя этот кусок кода поместить в
int main()
???Ответ: Иногда нужно выполнить кусок кода, только если мы линкуем какую-то библиотеку или подключаем хедер. Например, если в библиотеке хэширования есть расчет хитрых данных для хэшей на старте программы в
[[gnu::constructor]]
, то не надо париться с тем, как и что вызвать в main()
, можно просто линковать библиотеку и заработает.Да точно это же и происходит для
std::cout
, как мы только что увидели выше!Вопрос: Какие жесткие проблемы решает эта запись?
Ответ: В кторах и дторах можно указывать приоритет
Пример на godbolt.
Это решает артиллерийский выстрел в ногу под названием Static Initialization Order Fiasco. Очередность выполнения инициализаций обычно зависит от порядка линковки, а с приоритетом стало можно это нормально разруливать!
В остальном проблемы линковки все еще остаются легендарными - можно почитать пример особо опасной проблемы, о которой писал раньше: https://www.tgoop.com/cxx95/76