C/C++ 和 C#/Java 之间使用易失性有什么区别?

What's the difference of the usage of volatile between C/C++ and C#/Java?

本文关键字:易失性 什么 区别 之间 C++ Java      更新时间:2023-10-16

我在许多参考资料中发现它提到C/C++中的volatile很弱,可能会导致多处理器上的并发环境中出现问题,但它(volatile)可以用作C#/Java中不同CPU之间的通信机制。似乎这个关键字在 C#/Java 中比在 C/C++ 中更严格,但它们之间有什么区别/影响?

以下是 C/C++ 中volatile的参考。为什么易失性在多线程 C 或 C++ 编程中不被认为是有用的?

对于 C#/Java,"volatile"告诉编译器变量的值绝不能缓存,因为它的值可能会在程序本身的范围之外更改。然后,编译器将避免任何优化,如果变量"超出其控制范围",则可能导致问题。

C/C++中,在开发嵌入式系统或设备驱动程序时需要"volatile",其中需要读取或写入内存映射的硬件设备。特定设备寄存器的内容可能随时更改,因此您需要" volatile "关键字来确保编译器不会优化此类访问。

volatile 关键字对语言及其实现的平台具有高度主观性。虽然 Java 在所有体系结构中提供了一致的易失性行为,但对于直接编译到本机平台中的语言(例如 C/C++),情况并非如此。让我们试着理解为什么会这样。

设 a, b 是一组程序操作 P 的成员,v_{n} (a) 是将波动性要求应用于操作的函数,其中下标 _n 表示应用易失性操作的第 _n 次迭代,\rightarrow 是前面的运算符,如前所述。对于所有程序操作,以下规则适用:

v_n(a) \rightarrow v_{n+1}(a)

a \rightarrow v_n(b) \rightarrow

a \rightarrow v_{n+i}(b) where i \in \mathbb{N}

规则 1 表示所有可变函数强制执行总顺序,其中函数 v_{n} (a) 始终在 v_{n+1} (a) 之前,其中规则 2 说,如果动作 a 在第 _n 次迭代中在动作 b 上的易失性函数之前,则动作 a 必须先于应用于 b 的所有后续挥失函数。

这在Java中是一个非常强大的内存需求,实际上它比C/C++要强得多。C/C++ 语言规范对内存排序没有这样的限制,而是让编译器实现来决定非易失性操作如何围绕易失性操作进行排序。

让我们通过一个简单的代码示例来考虑这些规则如何影响程序执行:

int a = 0;
int b = 0;
volatile int count = 0;
a  = 1;
count = 1;
b = 2;
count = 2;

在 C/C++ 中,volatile 关键字只能保证 count 变量不能相互重新排序,即如果 count == 2,则 count = 1 必须位于它之前。但是,既不能保证 a == 1,也不能保证 b == 2。

在 Java 中,给定上面定义的更强的保证,那么如果 count == 1,那么断言 a == 1 必须为真。类似地,如果计数 == 2,则断言 a == 1 && b == 2 必须为真。这就是Java提供的C/C++所没有的严格内存保证的含义。

然而,这并不意味着 C/C++ 的行为方式与 Java 不同。是否这样做取决于 (1) 编译器是否执行任何可能以令人惊讶的顺序但合法顺序的代码重新排序,以及 (2) 底层计算机体系结构是否遵循相同的严格内存顺序,前提是编译器不执行任何令人惊讶的代码重新排序。

例如,在所有x86平台上设置-O0在gcc上编译代码将符合(并且比Java更严格),但其他架构,如PowerPC,Alpha,Itanium都支持较弱的内存模型,这可能会表现出程序员可能意想不到的令人惊讶的程序行为。警告讲师!

无论如何,如果您对更多内存模型一致性规则感兴趣,您可能需要观看英特尔对 x86 内存模型的解释,其中详细介绍了内存排序的细微差别。享受!

在 C/C++ 中,volatile 没有与多线程相关的特定语义,因此它在该上下文中的行为是特定于平台的。C# 和 Java 为 volatile 提供了特定的多线程语义。所以你知道你得到了什么,可以依靠它。