那不是真正的可入性。您不会在同一线程(或不同线程)中两次运行一个函数。您可以通过递归或将当前函数的地址作为回调函数指针arg传递给另一个函数来获得。(并且它不会不安全,因为它将是同步的)。
这只是信号处理程序和主线程之间的普通香草数据争用UB(未定义行为):仅sig_atomic_t
对此保证安全。其他的可能会起作用,例如在x86-64上可以用一条指令加载或存储8字节对象的情况下,编译器恰好选择了该asm。(如@icarus的答案所示)。
请参阅MCU编程-C ++ O2优化在循环时中断-单核微控制器上的中断处理程序与单线程程序中的信号处理程序基本相同。在这种情况下,UB的结果是将负载从环路中吊起。
实际上,由于数据争用UB而导致的撕裂测试用例实际上是在32位模式下开发的,或者是通过较旧的dudu编译器(分别加载结构成员)开发/测试的。
在您的情况下,编译器可以从无限循环中优化存储,因为没有UB-free程序无法观察到它们。 data
不是_Atomic
或volatile
,并且循环中没有其他副作用。因此,任何读者都无法与该作家进行同步。实际上,如果启用了优化功能进行编译,就会发生这种情况(Godbolt在main底部显示一个空循环)。我还将结构更改为2 long long
,并且gcc movdqa
在循环之前使用了一个16字节的存储区。(这不能保证是原子的,但是实际上几乎所有的CPU都假设它是对齐的,或者在Intel上它根本没有越过缓存行边界。 为什么在x86的自然对齐的变量原子上进行整数赋值?)
因此在启用优化的情况下进行编译也会破坏您的测试,并每次都显示相同的值。C不是可移植的汇编语言。
volatile struct two_int
这也将迫使编译器不对其进行优化,但不会迫使其以原子方式加载/存储整个结构。(不过,这也不会阻止它这样做。)请注意,这volatile
并不能避免数据争用UB,但实际上,它足以进行线程间通信,这也是人们构建手动原子的方式(以及内联asm)在C11 / C ++ 11之前,适用于常规CPU体系结构。他们在高速缓存相干所以volatile
是在实践中大多是类似_Atomic
与memory_order_relaxed
纯负载和纯店,如果用于类型足够窄,编译器将使用一个单一的指令,这样你就不会得到撕裂。而且当然volatile
与编写使用_Atomic
和mo_relaxed 编译为相同asm的代码相比,ISO C标准没有任何保证。
如果您有一个在global_var++;
上执行的功能,int
或者long long
您是从main上运行的,并且是从信号处理程序异步运行的,那么这将是使用重入方式创建数据争用UB的一种方式。
取决于它是如何编译的(到内存目标inc或add,或分开的load / inc / store),对于同一线程中的信号处理程序,它是否是原子的。请参阅num ++是否可以为'int num'原子?有关x86和C ++中原子性的更多信息。(C11的stdatomic.h
和_Atomic
属性提供与C ++ 11 std::atomic<T>
模板等效的功能)
指令中间不会发生中断或其他异常,因此内存目标地址是原子wrt。上下文在单核CPU上切换。在单核CPU上,只有(高速缓存相关的)DMA写入器可以从“ add [mem], 1
无lock
前缀”的“增量”开始。没有其他线程可以在其上运行的其他内核。
因此,它与信号的情况类似:信号处理程序运行而不是正常处理信号的线程执行,因此不能在一条指令的中间进行处理。