gpt4 book ai didi

c - IBM 示例代码,不可重入函数在我的系统中不起作用

转载 作者:行者123 更新时间:2023-12-03 14:48:12 24 4
gpt4 key购买 nike

我正在学习编程的重入性。在此 site IBM 的(非常好)。我已经建立了一个代码,复制在下面。这是第一个在网站上滚动的代码。

该代码尝试通过打印在“危险上下文”中不断变化的两个值来显示涉及在文本程序的非线性开发(异步性)中共享访问变量的问题。

#include <signal.h>
#include <stdio.h>

struct two_int { int a, b; } data;

void signal_handler(int signum){
printf ("%d, %d\n", data.a, data.b);
alarm (1);
}

int main (void){
static struct two_int zeros = { 0, 0 }, ones = { 1, 1 };

signal (SIGALRM, signal_handler);
data = zeros;
alarm (1);
while (1){
data = zeros;
data = ones;
}
}

当我尝试运行代码时出现问题(或者更好,没有出现)。我在默认配置中使用 gcc 版本 6.3.0 20170516 (Debian 6.3.0-18+deb9u1)。不会发生被误导的输出。获得“错误”对值的频率为 0!

到底发生了什么?为什么使用静态全局变量重入没有问题?

最佳答案

这不是真正的重入;你没有在同一个线程中两次运行一个函数 (或在不同的线程中)。您可以通过递归或将当前函数的地址作为回调函数指针 arg 传递给另一个函数来获得它。 (而且它不会不安全,因为它是同步的)。

这只是信号处理程序和主线程之间的普通数据竞争 UB(未定义行为):仅 sig_atomic_t保证安全 .其他人可能碰巧工作,例如在您的情况下,可以在 x86-64 上使用一条指令加载或存储 8 字节对象,而编译器恰好选择了该 asm。 (正如@icarus 的回答所示)。

MCU programming - C++ O2 optimization breaks while loop - 单核微 Controller 上的中断处理程序与单线程程序中的信号处理程序基本相同。在那种情况下,UB 的结果是负载从环路中被提升。

由于数据竞争 UB 实际发生的撕裂测试用例可能是在 32 位模式下开发/测试的,或者使用单独加载结构成员的较旧的笨拙编译器。

在您的情况下,编译器可以从无限循环中优化存储,因为没有 UB-free 程序可以观察它们。 data不是 _Atomicvolatile ,并且循环中没有其他副作用。
因此,任何读者都无法与此作者同步。实际上,如果您在启用优化的情况下进行编译(Godbolt 在 main 的底部显示一个空循环),就会发生这种情况。我还将结构更改为两个 long long , 并且 gcc 使用单个 movdqa循环前的 16 字节存储。 (这不能保证原子性,但实际上在几乎所有 CPU 上都是如此,假设它是对齐的,或者在 Intel 上只是不跨越缓存线边界。Why is integer assignment on a naturally aligned variable atomic on x86?)

因此,在启用优化的情况下进行编译也会破坏您的测试,并每次都显示相同的值。 C 不是可移植的汇编语言。
volatile struct two_int也会强制编译器不优化它们,但不会强制它以原子方式加载/存储整个结构。 (不过,它也不会阻止它这样做。)请注意 volatile不避免数据竞争 UB,但实际上它足以用于线程间通信,并且是人们在 C11/C++11 之前为普通 CPU 架构构建手动原子(以及内联 asm)的方式。它们是缓存一致的,所以 volatilein practice mostly similar to _Atomic with memory_order_relaxed 对于纯加载和纯存储,如果用于足够窄的类型,编译器将使用单个指令,因此您不会撕裂。当然还有 volatile与使用 _Atomic 编译为相同 asm 的代码相比,ISO C 标准没有任何保证。和mo_relaxed。

如果你有一个函数可以做 global_var++;int 上或 long long您从 main 运行并从信号处理程序异步运行,这将是一种使用重入来创建数据竞争 UB 的方法。

根据它的编译方式(到内存目标 inc 或 add,或分离 load/inc/store),对于同一线程中的信号处理程序,它是否是原子的。见 Can num++ be atomic for 'int num'?有关 x86 和 C++ 中原子性的更多信息。 (C11 的 stdatomic.h_Atomic 属性提供与 C++11 的 std::atomic<T> 模板等效的功能)

指令中间不会发生中断或其他异常,因此内存目标添加是原子操作。在单核 CPU 上进行上下文切换。只有(缓存一致的)DMA 写入器才能“踩”来自 add [mem], 1 的增量。没有 lock单核 CPU 上的前缀。没有任何其他内核可以运行另一个线程。

所以它类似于信号的情况:信号处理程序运行而不是处理信号的线程的正常执行,因此它不能在一条指令的中间处理。

关于c - IBM 示例代码,不可重入函数在我的系统中不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59800217/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com