C中的两个顺序赋值语句可以在硬件上乱序执行吗?
Can two sequential assignment statements in C be executed on hardware out of order?
给定以下 C 程序:
static char vals[ 2 ] = {0, 0};
int main() {
char *a = &vals[0];
char *b = &vals[1];
while( 1 ) {
SOME_STUFF()
// non-atomic operations in critical section
if( SOME_CONDITION() )
{
*a = 1;
*b = 2;
}
else
{
*a = 0;
*b = 0;
}
SOME_OTHER_STUFF()
}
return 0;
}
int async_interrupt( void ) {
PRINT( a );
PRINT( b );
}
硬件是否有可能首先将值 2
实际加载到内存位置 &vals[1]
,以便中断例程可以执行并查看 vals[1] == 2
和 vals[0] == 0
?
如果可能的话,我们将不胜感激对导致这种情况的 load/store 操作的任何描述。
编辑 1:向代码部分添加了更多上下文。不幸的是,我没有来自编译源的机器代码。
C 不直接在硬件上 运行。必须先编译。
未定义行为的细节(如非原子变量的非同步读取)完全取决于实现(包括编译器中的编译时重新排序,并且取决于目标 CPU 架构,运行那个ISA的时间重排序规则)。
Reads/writes 的非原子变量在 C 或 C++ 中不被视为可观察到的副作用,因此它们可以被优化掉并重新排序,以达到保留程序整体行为的极限(除非程序有未定义的行为——优化可以在这种情况下做任何事情,即使编译器不能"see"编译时会有 UB。)
另见 https://preshing.com/20120625/memory-ordering-at-compile-time/
是的,这是可能的,因为编译器可能会按照 中的描述重新排序这些语句。
但是,您可能仍然想知道另一半:硬件 可以做什么。假设您的商店最终按照您在源 1 中显示的顺序出现在程序集中,如果中断发生在相同的 CPU 上,即 运行宁此代码,从中断中你会看到一切都以一致的顺序。也就是说,在中断处理程序中,您永远不会看到第二个存储已完成,但第一个不会。您将看到的唯一情况是都未完成、都已完成或第一个已完成而第二个未完成。
如果涉及到多核,中断可能运行在不同的核上,那你就简单经典的跨线程共享场景,不管是不是中断——其他核能干什么observe 取决于硬件内存模型。例如,在相对强顺序的 x86 上,您总是会观察到存储是有序的,而在顺序较弱的 ARM 或 POWER 内存模型上,您可能会看到存储是乱序的。
然而,一般来说,CPU 可能会进行各种重新排序:您在中断处理程序中看到的排序是一种特殊情况,其中 CPU 将恢复顺序执行的外观在处理中断的时候。对于线程观察其自己的存储的任何情况也是如此。但是,当存储被 不同的 线程观察时 - 发生的情况取决于硬件内存模型,这在架构之间有很大差异。
1 还假设它们 分别出现 - 没有什么可以阻止智能编译器注意到您正在分配给相邻的值内存,从而将两个商店变成一个更宽的商店。 Most compilers 至少在某些情况下可以做到这一点。
给定以下 C 程序:
static char vals[ 2 ] = {0, 0};
int main() {
char *a = &vals[0];
char *b = &vals[1];
while( 1 ) {
SOME_STUFF()
// non-atomic operations in critical section
if( SOME_CONDITION() )
{
*a = 1;
*b = 2;
}
else
{
*a = 0;
*b = 0;
}
SOME_OTHER_STUFF()
}
return 0;
}
int async_interrupt( void ) {
PRINT( a );
PRINT( b );
}
硬件是否有可能首先将值 2
实际加载到内存位置 &vals[1]
,以便中断例程可以执行并查看 vals[1] == 2
和 vals[0] == 0
?
如果可能的话,我们将不胜感激对导致这种情况的 load/store 操作的任何描述。
编辑 1:向代码部分添加了更多上下文。不幸的是,我没有来自编译源的机器代码。
C 不直接在硬件上 运行。必须先编译。
未定义行为的细节(如非原子变量的非同步读取)完全取决于实现(包括编译器中的编译时重新排序,并且取决于目标 CPU 架构,运行那个ISA的时间重排序规则)。
Reads/writes 的非原子变量在 C 或 C++ 中不被视为可观察到的副作用,因此它们可以被优化掉并重新排序,以达到保留程序整体行为的极限(除非程序有未定义的行为——优化可以在这种情况下做任何事情,即使编译器不能"see"编译时会有 UB。)
另见 https://preshing.com/20120625/memory-ordering-at-compile-time/
是的,这是可能的,因为编译器可能会按照
但是,您可能仍然想知道另一半:硬件 可以做什么。假设您的商店最终按照您在源 1 中显示的顺序出现在程序集中,如果中断发生在相同的 CPU 上,即 运行宁此代码,从中断中你会看到一切都以一致的顺序。也就是说,在中断处理程序中,您永远不会看到第二个存储已完成,但第一个不会。您将看到的唯一情况是都未完成、都已完成或第一个已完成而第二个未完成。
如果涉及到多核,中断可能运行在不同的核上,那你就简单经典的跨线程共享场景,不管是不是中断——其他核能干什么observe 取决于硬件内存模型。例如,在相对强顺序的 x86 上,您总是会观察到存储是有序的,而在顺序较弱的 ARM 或 POWER 内存模型上,您可能会看到存储是乱序的。
然而,一般来说,CPU 可能会进行各种重新排序:您在中断处理程序中看到的排序是一种特殊情况,其中 CPU 将恢复顺序执行的外观在处理中断的时候。对于线程观察其自己的存储的任何情况也是如此。但是,当存储被 不同的 线程观察时 - 发生的情况取决于硬件内存模型,这在架构之间有很大差异。
1 还假设它们 分别出现 - 没有什么可以阻止智能编译器注意到您正在分配给相邻的值内存,从而将两个商店变成一个更宽的商店。 Most compilers 至少在某些情况下可以做到这一点。