2

首先,只是为了避免XY问题:这个问题来自https://github.com/cnjinhao/nana/issues/445#issuecomment-502080177。库代码可能不应该做这样的事情(依赖于未使用的全局对象的构造),但问题更多的是它是否是有效的 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——在这种情况下,指针肯定不会被初始化。
4

3 回答 3

5

VTT 的答案给出了 GCC 的答案,但该问题被标记为 language-lawyer。

ISO C++ 的原因是 Translation 中定义的对象必须在第一次调用同一个 Translation Unit 中定义的函数之前进行初始化。这意味着platform_abstraction::object必须在platform_abstraction::platform_abstraction()调用之前进行初始化。正如链接器正确计算的那样,没有其他platform_abstraction对象,所以platform_abstraction::platform_abstraction永远不会被调用,所以object可以无限期地推迟初始化。符合标准的程序无法检测到这一点。

于 2019-06-17T15:02:16.423 回答
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选项通过强制链接器链接静态库中的所有目标文件来更改此默认行为。

于 2019-06-17T09:39:59.190 回答
0

没有全局静态变量。

初始化的顺序是未定义的(在一般情况下)。

将静态对象作为静态对象放入函数中,然后您可以保证它们在使用前被创建。

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.
}
}
于 2019-07-27T20:44:34.840 回答