块范围静态变量或线程存储持续时间变量初始化失败的原因是什么?
在回答这个问题并且在标准论文中没有找到令人满意的答案后,我开始思考。该标准规定了上述变量的以下 wrt 初始化:
§6.7 [stmt.dcl] p4
[...] 否则,此类变量将在控制第一次通过其声明时被初始化; 此类变量在初始化完成后即被视为已初始化。如果初始化通过抛出异常退出,则初始化尚未完成,因此下次控件进入声明时将再次尝试。
没有提及如果初始化因抛出异常(longjmp()
、thead exit、信号等等)之外的其他原因而失败,可能会导致重试。
我是否忽略了标准中的任何内容?我一遍又一遍地查看初始化、声明和异常条款,甚至查阅了 CWG 缺陷目录,快速搜索“静态”,但找不到任何相关内容。
这是否是标准中的规格不足(以及缺陷)?
After answering this question and not finding a satisfying answer in the standard paper, I started wondering. The standard states the following w.r.t. initialization of mentioned variables:
§6.7 [stmt.dcl] p4
[...] Otherwise such a variable is initialized the first time control passes through its declaration; such a variable is considered initialized upon the completion of its initialization. If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration.
There is no mentioning of what might cause the initialization to be retried if it failed by anything else than throwing an exception (longjmp()
, thead exit, signals to name a few).
Did I overlook anything in the standard? I looked through initialization, declaration and exception clauses over and over and even consulted the CWG defects table of content with a quick search for "static", but couldn't find anything related.
Is this an underspecification (and as such a defect) in the standard?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
C++ 规范只能定义 C++ 规范中包含的内容。请记住:C++ 规范定义了它所定义的虚拟机的行为。如果它没有定义某些事情可能发生,那么它当然也没有定义 C++ 围绕它没有说可能发生的事情的行为。
根据 C++ 规范,线程可以通过三种方式退出:从主函数返回、通过主函数抛出异常以及直接进程退出(与 std::terminate 或类似方法一样)功能)。简而言之,C++ 线程不能以任何其他方式退出。标准 C++ 中没有
ExitThread
函数。同样,std::thread 无法在外部或内部终止线程。因此,根据定义,任何导致 C++ 认为不可能发生的事情的事情都是未定义的。我想这甚至不会是“未定义的行为”;在 C++11 真正规定线程交互如何工作之前,线程就处于这个模糊的空间中。
对于“信号”来说也是如此,无论它们是什么。 C++ 规范并没有说这些会导致函数退出。这里有龙。
至于
longjmp
,它由longjmp
的行为涵盖。当您使用 longjmp 退出函数时,该函数永远不会完成,就像您使用 throw 和 catch 一样。在 C++ 中,对象仅在其构造函数完成后才被构造。因此,该对象的初始化从未完成,并且未初始化。我没有 ISO C 规范(C++ 引用了
longjmp
的行为),但 C++11 强烈建议您可以将throw
/catch
与longjmp
/setjmp
,只要你得到未定义的行为:所以我认为这并没有明确说明。它可能布置得不那么漂亮和整齐,但所有的部分都在那里。
The C++ specification can only define things that are contained within the C++ specification. Remember: the C++ specification defines the behavior of a virtual machine it defines. And if it doesn't define that something can happen, it certainly doesn't define the behavior of C++ around that something that it doesn't say can happen.
According to the C++ specification, a thread can exit in exactly three ways: by returning from its main function, throwing an exception through its main function, and direct process exiting (as with
std::terminate
or similar functions). In short, a C++ thread cannot exit in any other way. There is noExitThread
function in standard C++. Similarly,std::thread
cannot kill a thread, either externally or internally.Therefore, anything that does cause this thing that C++ says can't happen is, by definition undefined. I guess it wouldn't even be "undefined behavior"; it would be in that nebulous space that threading was in before C++11 actually laid down how thread interactions worked.
The same goes for "signals", whatever those are. The C++ spec doesn't say that those can cause a function to exit. Here be dragons.
As for
longjmp
, that's covered by the behavior oflongjmp
. When you uselongjmp
to exit a function, that function never finished, just as if you usedthrow
andcatch
. And in C++, an object is only constructed when its constructor has completed. Therefore the object's initialization was never completed, and it is uninitialized.I don't have the ISO C specification (which C++ references for the behavior of
longjmp
), but C++11 does strongly suggest that you can equatethrow
/catch
withlongjmp
/setjmp
, as far as when you get undefined behavior:So I don't think this is underspecified. It may not be nicely and neatly laid out, but all of the pieces are there.
仅仅因为文本提到了一种特定情况,并不意味着忽略其他情况也会有所不同。如果有其他方法阻止初始化完成,则该实现必须在下次执行时重试。
我认为尼科尔的答案大部分是正确的,但非平凡的构造函数并不意味着非平凡的析构函数。因此,
longjmp
可能会中断初始化,从而必须重试。仅在多线程环境中这才很棘手,在多线程环境中需要互斥体来防止线程之间争夺第一个执行初始化的竞争条件。即使初始化的对象没有析构函数,幻影互斥对象也需要一个重要的析构函数。可能的结果是陷入僵局。这对于 DR 来说可能是很好的材料。Just because the text mentions one particular case doesn't imply by omission that others would be any different. If there are other ways to prevent the initialization from completing, the implementation must retry at the next execution.
I think Nicol's answer is mostly correct, but a non-trivial constructor does not imply a non-trivial destructor.
longjmp
may therefore interrupt initialization such that it must be retried. This is tricky only in a multithreaded environment, where a mutex is needed to prevent a race condition between threads vying to be the first to execute the initialization. The phantom mutex object needs a non-trivial destructor even if the initialized object does not have one. The likely result is deadlock. This is probably good material for a DR.