Если бы мне нужно было загрузить некоторые символы, используя что-то вроде dlopen
в C++, в то время как другие классы в этой единице перевода имели бы static
переменных-членов, каково именно поведение этих статических переменных-членов. Они инициализируются или нет, потому что библиотека на самом деле не загружает только те символы, которые вы искали (что, я думаю, последнее неверно, потому что, если символ, который вы искали, нуждается в них, они также должны быть загружены)?
Статическая переменная-член для динамически загружаемого класса
- возможный дубликат Когда инициализируются статические члены класса C++? 09.07.2014
- Не дублировать это связано с загрузкой библиотеки во время выполнения и с тем, что делают статические члены. 09.07.2014
- Достаточно справедливо, близкое голосование отозвано - возможно, вам следует пометить свою ОС, потому что у меня есть ощущение, что это будет зависеть от реализации, а не от чего-то, указанного в стандарте. 09.07.2014
Ответы:
Короче говоря, нет никакой гарантии, что статические переменные, которые не могут быть инициализированы во время компиляции, будут инициализированы до того, как будет сделана ссылка на видимую извне функцию или переменную в той же единице трансляции. Это справедливо даже для статических ссылок. Что касается попытки получить статические переменные в динамически загружаемых библиотеках для инициализации при загрузке, мой опыт показывает, что вам часто повезет, особенно для небольших программ, но в основном это неопределенное поведение, и на него не следует полагаться. Возникающие в результате ошибки непредсказуемы, трудно воспроизводимы и сильно зависят от системы.
Во-первых, некоторые стандарты и объяснение того, почему это поведение undefined, а затем некоторые обходные пути.
Слово «статический», к сожалению, перегружено в Стандарте, так что потерпите меня. Стандарт ссылается как на длительность статического хранения, так и на статическую инициализацию. Типы продолжительности хранения, определенные Стандартом, являются статическими, потоковыми, автоматическими и динамическими. Они такие, как звучат. Статическая продолжительность хранения означает, что время жизни такой переменной равно всей продолжительности программы.
Статическая инициализация — это отдельная концепция. Хотя переменная может быть сохранена только один раз за выполнение программы, значение, с которым она будет инициализирована, может быть неизвестно при запуске программы. В начале программы все переменные со статической продолжительностью хранения будут инициализированы нулями, а те, которые могут быть, затем будут инициализированы константами. Тонкие моменты описаны в §3.6.2, но грубо говоря, статическая переменная будет инициализирована константой, если ее инициализация зависит только от константных выражений. Вместе инициализация нуля и инициализация констант называются статической инициализацией. Аналогом является динамическая инициализация. Это интересные, но, к сожалению, нет переносимого способа заставить динамическую инициализацию выполняться до первого выполнения main()
в случае динамической компоновки или до возврата dlopen()
в случае динамической загрузки. C++ просто не требует такого.
Ключевая часть стандарта С++ 11 находится в §3.6.2:
Реализация определяет, выполняется ли динамическая инициализация нелокальной переменной со статической продолжительностью хранения до первого оператора main. Если инициализация отложена до некоторого момента времени после первого оператора main, она должна произойти до первого использования odr (3.2) любой функции или переменной, определенной в той же единице перевода, что и инициализируемая переменная.
Тем не менее, если вы экспериментировали, вы заметили, что иногда это работает. Иногда вы можете получить произвольный код для запуска при загрузке библиотеки, вставив его в конструкторы статических переменных. Произойдет ли это, зависит только от компилятора (а не компоновщика). Man-страница для dlopen объясняет.
Если динамическая библиотека экспортирует подпрограмму с именем _init(), то этот код выполняется после загрузки, прежде чем dlopen() вернется.
Изучая вывод asm небольшого общего объекта, написанного на стандартном C++, я вижу, что clang 3.4 и g++ 4.8 добавляют раздел _init, однако они не обязаны это делать.
Что касается обходных путей, расширение gcc, которое стало обычным явлением, позволяет контролировать это поведение. Добавляя к функциям атрибут конструктора, мы можем настаивать на том, чтобы они запускались при инициализации библиотеки. Связанная справочная страница для dlopen предлагает использовать этот метод.
См. документацию GCC по атрибутам функций и этот вопрос SO, который имеет пример использования. Это расширение поддерживается gcc, clang, IBM XL, и я предполагаю, что icc также его поддерживает. MSVC не поддерживает это, но я понимаю, что есть что-то подобное.
Действительно портативное решение неуловимо. Как говорится в стандарте, если вы можете каким-то образом вызвать использование odr в той же единице перевода, что и статическая переменная, тогда статическая переменная должна быть инициализирована. Вызов функции, даже фиктивной, только для этой цели сработает.