jac*_*k X 14 c++ language-lawyer
考虑basic.start.dynamic部分中的示例,即:
// - File 1 -
#include "a.h"
#include "b.h"
B b;
A::A(){
b.Use(); //#1
}
// - File 2 -
#include "a.h"
A a;
// - File 3 -
#include "a.h"
#include "b.h"
extern A a;
extern B b;
int main() {
a.Use(); //#2
b.Use();
}
Run Code Online (Sandbox Code Playgroud)
示例后的注释是:
但是,如果 a 在 main 的第一条语句之后的某个时刻被初始化,那么 b 将在它在 A?::?A 中使用之前被初始化。
我不明白为什么b在 A?::?A 中使用 a?::?A 之前保证初始化,当 a 在 main 的第一条语句之后的某个时间点初始化时。根据规则说:
具有静态存储持续时间的非局部非内联变量的动态初始化是在 main 的第一条语句之前排序还是延迟,由实现定义。如果它被推迟,它强烈地发生在任何非内联函数或在与要初始化的变量相同的翻译单元中定义的非内联变量的任何非初始化 odr 使用之前。
非初始化 odr-use 是 odr-use ([basic.def.odr])不是由非局部静态或线程存储持续时间变量的初始化直接或间接引起的。
我能理解的是,当初始化被推迟时,变量a应该a在标有#2. 但是我不明白的是,评论说b 将在 A?::?A 中使用之前进行初始化。IIUC,函数的调用A::A是变量初始化的一部分a,因此变量bat的odr-use#1不是非初始化odr-use,因为它是由非局部初始化直接或间接引起的静态或线程存储持续时间变量。我认为只能说变量b保证在之前被初始化#2,为什么评论说 b 将在它用于 A?::?A 之前被初始化?如何解释这个例子?
有问题的(非规范性)示例可以追溯到标准的 C++98 版本,但宿主条款中的(规范性)语言在 C++17 中发生了变化。
C++98:
3.6.2 非本地对象的初始化【basic.start.init】
3 - 命名空间范围的对象的动态初始化([交叉引用])是否在 main 的第一条语句之前完成由实现定义。如果初始化推迟到 main 的第一条语句之后的某个时间点,则它应在第一次使用与要初始化的对象在同一翻译单元中定义的任何函数或对象之前发生。[关于副作用的脚注] [示例如下]
C++03 具有相同的文本。C++11 删除了交叉引用并将“命名空间范围的对象”替换为“具有静态存储持续时间的非局部变量”,将“对象”替换为“变量”,将“使用”替换为“odr-use”,但我将提交该条款的含义未改变。C++14 没有改变。
该语言随后由P0250R3更改,并于 2017 年 3 月发布并转录为标准草案,及时将其纳入 C++17。P0250R3 增加了非初始化 odr-use的定义,并修改了子句以引用该定义,同时也用线程感知术语表达了事件之间的关系(排序在前,强发生在前等),并添加了关于避免死锁的注释.
从那时起,关于避免死锁的说明已修改为推荐做法。
幸运的是,P0250R3 包括动机的讨论。在顺序程序的并行初始化部分,我们读到:
目前,我们非常明确地允许静态构造函数在 main 启动后运行,无论其他线程是否启动。这似乎是出于支持例如在引用函数符号时延迟加载动态库的意图,如 Posix 系统上的 RTLD_LAZY。即使在库加载中立即运行静态命名空间范围构造函数,库也可能在 main 开始后隐式加载。
并且:
SG1 普遍认为应该避免使用静态命名空间范围的构造函数 [...] 我们决定将此类构造函数限制在现有线程中,这似乎与已知的实现一致。
我认为这个例子一直是不正确的。
在 C++98 中,该示例是不正确的,因为该版本标准中的规范措辞导致循环。假设我们扩充示例以B::B在与 的定义相同的 TU 中定义构造函数a:
// - File 2 -
#include "a.h"
A a;
B::B() {
a.Use();
}
Run Code Online (Sandbox Code Playgroud)
现在,根据 C++98, 的(动态)初始化a发生在第一次调用 之前B::B,并且 的初始化b发生在第一次调用之前A::A。但是 的初始化a需要调用A::A,而 的初始化b需要调用B::B。所以我们有一个循环回归。
P0250R3 中的措辞更改(将odr-use更改为非初始化 odr-use)打破了这种循环,代价是使示例变得毫无意义。但后来它总是坏了。这就是SIOF,可以通过 Construct on First Use 习惯用法或通过使用诸如ios_base::Init.
我将示例(具有循环性)编译为(Linux,ELF;CentOS 7.8)共享对象,在使用dlopen. 正是其中之一a和b在未初始化状态下使用 odr,这取决于链接顺序。
这表明非初始化 odr-use 的措辞变化反映了实施实践。不幸的是,标准现在包含一个明显不正确的例子,但由于例子和注释是非规范的,这有问题但不是致命的。
| 归档时间: |
|
| 查看次数: |
280 次 |
| 最近记录: |