我有以下有趣的问题:
#include <iostream>
#include <omp.h>
using namespace std;
int main(const int, const char **) {
const size_t n = 13; // triggers the issue
// const uint32_t n = 13; // fixes the issue
#pragma omp parallel for
for (uint32_t i = 0; i < n; i += 2) { // `+= 1` fixes the issue
cerr << "i = " << i << ", n = " << n << endl;
}
}
尽管我的
i < n
,n = 13
这会在clang++ -fopenmp
上打印无限系列的数字:
i = 0, n = 13
i = 2, n = 13
i = 4, n = 13
i = 6, n = 13
i = 8, n = 13
i = 10, n = 13
i = 12, n = 13 <- this should be impossible
i = 14, n = 13
...
怎么会这样?
clang++ 11、14 和 16 会发生这种情况,但 GCC 不会。如果删除
-fopenmp
也可以修复。
如果我这样做
+= 1
而不是 += 2
,问题就会消失。
根据OPenMP循环增量,“循环增量大于1”是合法的。
如果我将
n
的类型从 size_t
更改为 uint32_t
,问题也会消失。
使用
-Wall
和 += 2
,clang++
打印这些警告:
clang-openmp-for-loop-test.cpp:49:3: warning: implicit conversion from 'unsigned long' to 'unsigned int' changes value from 9223372034707292167 to 2147483655 [-Wconstant-conversion]
for (uint32_t i = 0; i < n; i += 2) {
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
clang-openmp-for-loop-test.cpp:49:3: warning: implicit conversion from 'unsigned long' to 'unsigned int' changes value from 9223372034707292167 to 2147483655 [-Wconstant-conversion]
for (uint32_t i = 0; i < n; i += 2) {
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
clang-openmp-for-loop-test.cpp:49:3: warning: implicit conversion from 'unsigned long' to 'unsigned int' changes value from 9223372034707292167 to 2147483655 [-Wconstant-conversion]
for (uint32_t i = 0; i < n; i += 2) {
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3 warnings generated.
关于这些警告,我有很多不明白的地方:
9223372034707292167
从何而来?这是一个-Wconstant-conversion
警告,当然它应该警告我实际使用的一些常量。值 2147483655
是 maximum 32-bit-signed integer + 8
。+= 2
更改为 += 1
时,与类型相关的警告如何消失?这两个文字的 type 肯定是相同的吗?Ubuntu 22.04。
sudo apt install clang14 libomp-14-dev gcc+12 libstdc++-12-dev
(需要
gcc+12 libstdc++-12-dev
才能使 #include <iostream>
工作,因为 clang 使用 GCC 的 stdlib 标头。)
编译:
clang++ myprogram.cpp -o myprogram -fopenmp -O2 -Wall -Wextra
运行:
OMP_NUM_THREADS=1 ./myprogram 2>&1 | head -n20
编译器错误,或者我遗漏了什么?
将测试更改为显式转换
i < (uint32_t)n
修复了 clang 的问题 (17)。
但是显式转换
(size_t)i < n
会导致编译错误:
condition of OpenMP for loop must be a relational comparison ('<', '<=', '>', '>=', or '!=') of loop variable 'i'
.
我不太清楚,但我对 OpenMP 5.2 规范(最新的,但我不知道 clang 使用哪一个)的理解是,C/C++ OpenMP 循环的测试表达式应涉及变量
i
单独在左侧或右侧(规范第88页):
因为
i
和 n
是不同的类型,编译器必须隐式转换其中之一,并且因为 n
具有更广泛的范围,所以转换的是 i
。那么测试不再单独涉及i
,而是涉及i
的函数。
所以,最初的问题可能是这是一个非法的 OpenMP 代码,即使 gcc 将其作为 OpenMP 规范的扩展来处理。尽管如此,这也是一个 clang 编译器错误:clang 最初进入隐式转换路由,然后弄乱循环(它应该因为非法代码而产生编译错误,或者像 gcc 那样将其作为扩展处理)。