如果不同翻译单元中有代码依赖于其构造的副作用,是否允许LTO删除未使用的全局对象?

Xev*_*ous 2 c++ language-lawyer link-time-optimization

首先,只是为了避免XY问题:此问题来自https://github.com/cnjinhao/nana/issues/445#issuecomment-502080177。库代码可能不应该这样做(依赖于未使用的全局对象的构造),但是问题更多的是关于它是否是有效的LTO行为,而不是代码质量问题。


展示相同问题的最小代码(未经试验,只是为了使示例更小):

// main.cpp
#include <lib/font.hpp>

int main()
{
    lib::font f;
}
Run Code Online (Sandbox Code Playgroud)
// lib/font.hpp
namespace lib
{
struct font
{
    font();

    int font_id;
};
}
Run Code Online (Sandbox Code Playgroud)
// lib/font.cpp
#include <lib/font.hpp>
#include <lib/font_abstraction.hpp>

namespace lib
{
font::font()
{
    font_id = get_default_font_id();
}
}
Run Code Online (Sandbox Code Playgroud)
// lib/font_abstraction.hpp
namespace lib
{
int get_default_font_id();

void initialize_font();
}
Run Code Online (Sandbox Code Playgroud)
// 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);
}
}
Run Code Online (Sandbox Code Playgroud)
// lib/platform_abstraction.hpp
namespace lib
{
struct platform_abstraction
{
    platform_abstraction();
};
}
Run Code Online (Sandbox Code Playgroud)
// 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;
}
Run Code Online (Sandbox Code Playgroud)

font对象的构造main.cpp依赖于指针的初始化。初始化指针的唯一一件事是全局对象,object但是它没有使用-在链接问题的情况下,LTO删除了该对象。是否允许这种优化?(请参阅C ++草稿6.6.5.1.2

一些注意事项:

  • 该库是作为静态库构建的,并使用-flto -fno-fat-lto-objects动态C ++标准库与主文件链接。
  • 此示例可以完全不编译而构建lib/platform_abstraction.cpp-在这种情况下,指针肯定不会初始化。

use*_*670 5

由于您从不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选项通过强制链接器链接静态库中的所有对象文件来更改此默认行为。


MSa*_*ers 5

VTT的答案给出了GCC答案,但该问题被标记为语言律师。

ISO C ++的原因是,在首次调用同一转换单元中定义的函数之前,必须初始化转换中定义的对象。这意味着platform_abstraction::object必须在platform_abstraction::platform_abstraction()调用之前进行初始化。正如链接器正确指出的那样,没有其他platform_abstraction对象,因此platform_abstraction::platform_abstraction从不调用它,因此object可以无限期推迟的初始化。合格程序无法检测到此情况。

  • http://eel.is/c++draft/basic.start.dynamic“由实现定义,是否对具有静态存储持续时间的非本地非内联变量的动态初始化在main或第一条语句之前进行排序推迟。” 海湾合作委员会显然推迟了。 (3认同)