Разрешено ли LTO удалять неиспользуемый глобальный объект, если в другом модуле перевода есть код, основанный на побочных эффектах его построения?
Во-первых, просто чтобы избежать проблемы XY: эта проблема возникла по https://github.com/cnjinhao/nana/issues/445. Библиотечный код, вероятно, не должен делать этого (полагаться на конструирование неиспользуемого глобального объекта), но вопрос скорее в том, является ли это действительным поведением LTO, а не проблемами качества кода.
Минимальный код, который демонстрирует ту же проблему (непроверенный, просто чтобы уменьшить пример):
// main.cpp
#include <lib/font.hpp>
int main()
{
lib::font f;
}
// lib/font.hpp
namespace lib
{
struct font
{
font();
int font_id;
};
}
// lib/font.cpp
#include <lib/font.hpp>
#include <lib/font_abstraction.hpp>
namespace lib
{
font::font()
{
font_id = get_default_font_id();
}
}
// lib/font_abstraction.hpp
namespace lib
{
int get_default_font_id();
void initialize_font();
}
// lib/font_abstraction.cpp
#include <lib/font_abstraction.hpp>
namespace lib
{
static int* default_font_id;
int get_default_font_id()
{
return *default_font_id;
}
void initialize_font()
{
default_font_id = new int(1);
}
}
// lib/platform_abstraction.hpp
namespace lib
{
struct platform_abstraction
{
platform_abstraction();
};
}
// lib/platform_abstraction.cpp
#include <lib/platform_abstraction.hpp>
#include <lib/font_abstraction.hpp>
namespace lib
{
platform_abstraction::platform_abstraction()
{
initialize_font();
}
static platform_abstraction object;
}
Построение font
объект в main.cpp
полагается на инициализацию указателя. Единственное, что инициализирует указатель, это глобальный объект object
но это не требуется - в случае связанной проблемы этот объект был удален LTO. Разрешена ли такая оптимизация? (См. Проект C++ 6.6.5.1.2)
Некоторые заметки:
- Библиотека была построена как статическая библиотека и связана с основным файлом, используя
-flto -fno-fat-lto-objects
и динамическая стандартная библиотека C++. - Этот пример может быть собран без компиляции
lib/platform_abstraction.cpp
вообще - в таком случае указатель точно не будет инициализирован.
3 ответа
Ответ VTT дает ответ GCC, но вопрос помечен как язык-юрист.
Причина ISO C++ заключается в том, что объекты, определенные в трансляции, должны быть инициализированы перед первым вызовом функции, определенной в том же модуле трансляции. Это значит platform_abstraction::object
должен быть инициализирован до platform_abstraction::platform_abstraction()
называется. Как правильно определил компоновщик, других нет platform_abstraction
объекты, так platform_abstraction::platform_abstraction
никогда не называется, так object
Инициализация может быть отложена на неопределенный срок. Соответствующая программа не может обнаружить это.
Поскольку вы никогда не ссылаетесь object
из статической библиотеки в главном исполняемом файле она не будет существовать, если вы не свяжете эту статическую библиотеку с -Wl,--whole-archive
, Не стоит полагаться на конструкцию некоторых глобальных объектов для выполнения инициализации в любом случае. Так что вы должны просто призвать initialize_font
явно до использования других функций из этой библиотеки.
Дополнительное объяснение вопроса помечено языком юрист:
static platform_abstraction object;
не может быть устранено ни при каких обстоятельствах в соответствии с
6.6.4.1 Статическая продолжительность хранения [basic.stc.static]
2 Если переменная со статической продолжительностью хранения имеет инициализацию или деструктор с побочными эффектами, она не должна быть удалена, даже если она не используется, за исключением того, что объект класса или его копирование / перемещение могут быть удалены, как указано в 15.8.
и так, что здесь происходит? При компоновке статической библиотеки (архива объектных файлов) по умолчанию компоновщик выбирает только файлы объектов, необходимые для заполнения неопределенных символов, и поскольку platform_abstraction.cpp
не используется где-либо еще, компоновщик полностью пропустит этот блок перевода. --whole-archive
опция изменяет это поведение по умолчанию, заставляя компоновщик связывать все объектные файлы из статической библиотеки.
Не иметь глобальных статических переменных.
Порядок инициализации не определен (в общем случае).
Поместите статические объекты в функции как статические объекты, после чего вы можете быть уверены, что они созданы перед использованием.
namespace lib
{
static int* default_font_id;
int get_default_font_id()
{
return *default_font_id;
}
void initialize_font()
{
default_font_id = new int(1);
}
}
// Изменить это тоже:
namespace lib
{
int get_default_font_id()
{
// This new is guaranteed to only ever be called once.
static std::unique_ptr<int> default_font_id = new int(1);
return *default_font_id;
}
void initialize_font()
{
// Don't need this ever.
}
}