使用来自信号处理程序的静态对象
Using static objects from signal handler
根据cppreference.com
(我没有在标准中搜索),使用来自信号处理程序的静态对象是UB。
UB为什么要做这样的事情?那有什么潜在的问题?
If the signal handler is called NOT as a result of std::abort
or std::raise
(asynchronous signal), the behavior is undefined if [...] the signal handler refers to any object with static storage duration that is not std::atomic
(since C++11) or volatile std::sig_atomic_t
.
C++ 标准在 [intro.execution] 中有这样的说法:
19 If a signal handler is executed as a result of a call to the std::raise
function, then the execution of the handler is sequenced after the invocation of the std::raise
function and before its return. [ Note: When a signal is received for another reason, the execution of the signal handler is usually unsequenced with respect to the rest of the program. — end note ]
"unsequenced"的意思前面说清楚了:
15 ...SNIP... [ Note: The execution of unsequenced evaluations can overlap. — end note ]
然后在 [intro.races] 中:
20 Two actions are potentially concurrent if
(20.1) — they are performed by different threads, or
(20.2) — they are unsequenced, at least one is performed by a signal handler, and they are not both performed by the same signal handler invocation.
The execution of a program contains a data race if it contains two potentially concurrent conflicting actions, at least one of which is not atomic, and neither happens before the other, except for the special case for signal handlers described below. Any such data race results in undefined behavior.
特例是指:
21 Two accesses to the same object of type volatile std::sig_atomic_t
do not result in a data race if both occur in the same thread, even if one or more occurs in a signal handler.
总而言之:当信号处理程序(为异步信号调用)访问静态存储持续时间不是原子的对象时,该访问是无序的,并且当它与冲突访问同时发生时(到相同的对象,例如),然后就会出现数据竞争,导致未定义的行为。
请注意,这在单线程应用程序和多线程应用程序中都可能发生。示例(如果需要,将 int
替换为任何其他更明显非原子的类型):
#include <csignal>
int global = 0;
void signal_handler(int signal) {
global = 0; // OOPS : this access is (typically) unsequenced
// and might happen concurrently with the access
// in main, when the interrupt happens right in
// the middle of that access
}
int main(void) {
std::signal(SIGINT, signal_handler);
while (true) {
++global; // potentially concurrent access
}
return 0;
}
这个问题可以追溯到 C 标准,它使用术语 UB 来描述一般情况,对于实现以顺序一致的方式处理代码有时可能代价高昂,即使大多数实现应该有意义地处理这种情况实际的。考虑这样一个函数:
extern int x,y,z;
void test(int a)
{
int i;
for (i=0; i<a; i++)
{
x=a*i;
y=a*a;
z=a*i;
}
}
是否应该要求编译器在写入 x
和 z
之间将值 a*a
存储到 y
,还是应该允许它在空闲时存储要么在循环之前将赋值提升到 y
,要么将其推迟到循环完成之后。如果允许编译器提升或延迟对 y
的赋值,如果在循环执行期间恰好发生信号并且信号处理程序读取值,是否有任何简单明了的方法来描述程序行为x、y 和 z 的?在这种情况下提供各种行为保证的成本和价值将取决于标准作者无法知道的各种因素。该标准的作者并没有试图为应该保证的内容编写规则,而是希望编译器编写者比委员会能够更好地判断其客户的需求。
根据cppreference.com
(我没有在标准中搜索),使用来自信号处理程序的静态对象是UB。
UB为什么要做这样的事情?那有什么潜在的问题?
If the signal handler is called NOT as a result of
std::abort
orstd::raise
(asynchronous signal), the behavior is undefined if [...] the signal handler refers to any object with static storage duration that is notstd::atomic
(since C++11) orvolatile std::sig_atomic_t
.
C++ 标准在 [intro.execution] 中有这样的说法:
19 If a signal handler is executed as a result of a call to the
std::raise
function, then the execution of the handler is sequenced after the invocation of thestd::raise
function and before its return. [ Note: When a signal is received for another reason, the execution of the signal handler is usually unsequenced with respect to the rest of the program. — end note ]
"unsequenced"的意思前面说清楚了:
15 ...SNIP... [ Note: The execution of unsequenced evaluations can overlap. — end note ]
然后在 [intro.races] 中:
20 Two actions are potentially concurrent if
(20.1) — they are performed by different threads, or
(20.2) — they are unsequenced, at least one is performed by a signal handler, and they are not both performed by the same signal handler invocation.
The execution of a program contains a data race if it contains two potentially concurrent conflicting actions, at least one of which is not atomic, and neither happens before the other, except for the special case for signal handlers described below. Any such data race results in undefined behavior.
特例是指:
21 Two accesses to the same object of type
volatile std::sig_atomic_t
do not result in a data race if both occur in the same thread, even if one or more occurs in a signal handler.
总而言之:当信号处理程序(为异步信号调用)访问静态存储持续时间不是原子的对象时,该访问是无序的,并且当它与冲突访问同时发生时(到相同的对象,例如),然后就会出现数据竞争,导致未定义的行为。
请注意,这在单线程应用程序和多线程应用程序中都可能发生。示例(如果需要,将 int
替换为任何其他更明显非原子的类型):
#include <csignal>
int global = 0;
void signal_handler(int signal) {
global = 0; // OOPS : this access is (typically) unsequenced
// and might happen concurrently with the access
// in main, when the interrupt happens right in
// the middle of that access
}
int main(void) {
std::signal(SIGINT, signal_handler);
while (true) {
++global; // potentially concurrent access
}
return 0;
}
这个问题可以追溯到 C 标准,它使用术语 UB 来描述一般情况,对于实现以顺序一致的方式处理代码有时可能代价高昂,即使大多数实现应该有意义地处理这种情况实际的。考虑这样一个函数:
extern int x,y,z;
void test(int a)
{
int i;
for (i=0; i<a; i++)
{
x=a*i;
y=a*a;
z=a*i;
}
}
是否应该要求编译器在写入 x
和 z
之间将值 a*a
存储到 y
,还是应该允许它在空闲时存储要么在循环之前将赋值提升到 y
,要么将其推迟到循环完成之后。如果允许编译器提升或延迟对 y
的赋值,如果在循环执行期间恰好发生信号并且信号处理程序读取值,是否有任何简单明了的方法来描述程序行为x、y 和 z 的?在这种情况下提供各种行为保证的成本和价值将取决于标准作者无法知道的各种因素。该标准的作者并没有试图为应该保证的内容编写规则,而是希望编译器编写者比委员会能够更好地判断其客户的需求。