编译器是否可以从全局变量中读取两次,而不是存储一个局部变量
Can a compiler read twice from a global variable, instead of storing a local one?
我最近一直在尝试重新熟悉多线程,并找到了这篇论文。其中一个例子说,当使用这样的代码时要小心:
int my_counter = counter; // Read global
int (* my_func) (int);
if (my_counter > my_old_counter) {
... // Consume data
my_func = ...;
... // Do some more consumer work
}
... // Do some other work
if (my_counter > my_old_counter) {
... my_func(...) ...
}
声明:
如果编译器决定需要溢出寄存器在两次测试之间包含我的计数器,它很可能会决定避免存储值(毕竟它只是计数器的副本),并且而是简单地重新读取计数器的值比较涉及我的计数器〔…〕
这样做会将代码变成:
int my_counter = counter; // Read global
int (* my_func) (int);
if (my_counter > my_old_counter) {
... // Consume data
my_func = ...;
... // Do some more consumer work
}
... // Do some other work
my_counter = counter; // Reread global!
if (my_counter > my_old_counter) {
... my_func(...) ...
}
I、 然而,我对此持怀疑态度。我不明白为什么编译器被允许这样做,因为据我所知,只有当试图用任意数量的读取和至少一次写入访问同一内存区时,才会发生数据竞争。作者继续激励:
核心问题源于编译器利用假设变量值不能异步更改显式分配
在我看来,在这种情况下,条件得到了尊重,因为本地变量my_counter从未被访问过两次,其他线程也无法访问。编译器如何知道全局变量不能由另一个线程在另一个翻译单元的其他地方设置?它不能,事实上,我认为第二个if情况实际上会被优化掉。
是作者错了,还是我遗漏了什么?
除非counter
明确为volatile
,否则如果当前执行范围中没有任何内容可以更改它,编译器可能会认为它永远不会更改。这意味着,如果变量上没有别名,或者其间没有函数调用,编译器无法知道其效果,则任何外部修改都是未定义的行为。使用volatile
,您将尽可能地声明外部更改,即使编译器不知道如何声明。
因此,优化是完全有效的。事实上,即使它确实执行了复制,它仍然不会是线程安全的,因为该值可能在读取过程中部分更改,甚至可能完全过时,因为如果没有同步原语或原子,就无法保证缓存一致性。
实际上,在x86上,你不会得到一个整数的中间值,至少只要它是对齐的。这是体系结构的保证之一。过期缓存仍然适用,该值可能已被另一个线程修改。
如果需要此行为,请使用互斥对象或原子。
编译器可以在假设任何"未定义行为"都不可能发生的情况下进行优化:程序员将阻止代码以调用未定义行为的方式执行。
这可能导致相当愚蠢的执行,例如,下面的循环永远不会终止!
int vals[10];
for(int i = 0; i < 11; i++) {
vals[i] = i;
}
这是因为编译器知道vals[10]
将是未定义的行为,因此它假设它不可能发生,并且由于它不可能出现,i
永远不会超过或等于11,因此这个循环永远不会终止。并不是所有的编译器都会以这种方式积极地优化这样的循环,尽管我知道GCC会这样做。
在您正在处理的特定情况下,以这种方式读取全局变量可能是未定义的行为,前提是另一个线程有可能在此期间对其进行修改。因此,编译器假设跨线程修改永远不会发生(因为这是未定义的行为,编译器可以在假设UB没有发生的情况下进行优化),因此重读该值是完全安全的(它知道该值在自己的代码中不会被修改)。
解决方案是使counter
成为原子(std::atomic<int>
),这迫使编译器承认可能存在对变量的某种跨线程操作。
- 当字符串存储在变量中时,如何将字符串转换为wchar_t
- 使用的未初始化局部变量'Quick'
- 修复未初始化的局部变量错误
- 局部变量保留函数中的值
- C++ SSE 内部函数:将结果存储在变量中
- 如何使用 C++ 中的继承函数访问派生类中的局部变量
- 将引用分配给局部变量,如果局部变量超出范围,它会超出范围吗?
- 我们可以将集合的值存储在变量中吗?就像我们可以将数组的值存储在变量中一样
- Gnuplot_i.hpp C++接口绘制局部变量而不是文件
- 如何在函数外部访问函数中局部变量的值?
- C++将字符串数组的元素存储到变量中
- C++ find() 在存储为变量时返回不同的值
- 赋予全局变量而不是局部变量优先级的函数 - (异常行为)
- 调用一个小函数两次(例如在if条件和主体中)比将结果存储在局部变量中更可取
- C++标准对局部变量的存储和分配有什么保证?
- 编译器是否可以从全局变量中读取两次,而不是存储一个局部变量
- 编译时局部变量存储在哪里
- 声明__declspec(裸)的函数如何存储局部变量
- 局部变量和其他变量类型的地址存储在哪里
- 在 c++ 中存储块内局部变量的分配