在 gfortran 中隐含执行的数组构造函数的奇怪初始化行为
Strange initialization behavior for an array constructor with implied do in gfortran
假设我有 3 个双精度数组,
real*8, dimension(n) :: x, y, z
初始化为
x = 1.
y = (/ (1., i=1,n) /)
z = (/ (1. +0*i, i=1,n) /)
他们应该将所有数组的所有元素初始化为1
。在 ifort
(16.0.0 20150815) 中,这适用于声明精度范围内的任何 n
。也就是说,如果我们将 n
初始化为
integer*4, parameter :: n
那么只要 n < 2147483647
,所有声明的初始化工作都按预期进行。
在gfortran
(4.8.5 20150623 Red Hat 4.8.5-16)中,只要n>65535
,y
(具有常量参数的数组理解)初始化就会失败, 与其精度无关。 AFAIK,65535
是 unsigned short int
的最大值,又名 unsigned int*2
,正好在 integer*4
.
的范围内
下面是一个 MWE:
program test
implicit none
integer*4, parameter :: n = 65536
integer*4, parameter :: m = 65535
real*8, dimension(n) :: x, y, z
real*8, dimension(m) :: a, b, c
integer*4 :: i
print *, huge(n)
x = 1.
y = (/ (1., i=1,n) /)
z = (/ (1.+0*i, i=1,n) /)
print *, x(n), y(n), z(n)
a = 1.
b = (/ (1., i=1,m) /)
c = (/ (1.+0*i, i=1,m) /)
print *, a(m), c(m), c(m)
end program test
用gfortran
编译(gfortran test.f90 -o gfortran_test
),输出:
2147483647
1.0000000000000000 0.0000000000000000 1.0000000000000000
1.0000000000000000 1.0000000000000000 1.0000000000000000
用ifort
编译(ifort test.f90 -o ifort_test
),输出:
2147483647
1.00000000000000 1.00000000000000 1.00000000000000
1.00000000000000 1.00000000000000 1.00000000000000
什么给了?
编译器处理数组构造函数的方式确实存在很大差异。对于 n<=65535
,[1., 1., 1.,...] 的实际数组存储在目标文件中(或某些中间表示形式)。
对于更大的数组,编译器生成一个循环:
(*(real(kind=8)[65536] * restrict) atmp.0.data)[offset.1] = 1.0e+0;
offset.1 = offset.1 + 1;
{
integer(kind=8) S.2;
S.2 = 0;
while (1)
{
if (S.2 > 65535) goto L.1;
y[S.2] = (*(real(kind=8)[65536] * restrict) atmp.0.data)[S.2];
S.2 = S.2 + 1;
}
L.1:;
}
在我看来,首先它只设置临时数组的一个元素,然后将(大部分未定义的)临时数组复制到 y
。这是错误的。 Valgrind 还报告未初始化内存的使用情况。
对于默认的实数,我们有
while (1)
{
if (shadow_loopvar.2 > 65536) goto L.1;
(*(real(kind=4)[65536] * restrict) atmp.0.data)[offset.1] = 1.0e+0;
offset.1 = offset.1 + 1;
shadow_loopvar.2 = shadow_loopvar.2 + 1;
}
L.1:;
{
integer(kind=8) S.3;
S.3 = 0;
while (1)
{
if (S.3 > 65535) goto L.2;
y[S.3] = (*(real(kind=4)[65536] * restrict) atmp.0.data)[S.3];
S.3 = S.3 + 1;
}
L.2:;
}
我们现在有两个循环,一个设置 整个 临时数组,第二个将其复制到 y
,一切正常。
结论:一个编译器错误。
阅读此问题的 GCC 开发人员已解决此问题。该错误在 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84931
处被跟踪
他们还确定问题与类型转换有关。构造函数具有默认精度 1.
并且对于单精度数组没有类型转换,但是对于双精度数组有一些类型转换。这导致了这两种情况的不同。
假设我有 3 个双精度数组,
real*8, dimension(n) :: x, y, z
初始化为
x = 1.
y = (/ (1., i=1,n) /)
z = (/ (1. +0*i, i=1,n) /)
他们应该将所有数组的所有元素初始化为1
。在 ifort
(16.0.0 20150815) 中,这适用于声明精度范围内的任何 n
。也就是说,如果我们将 n
初始化为
integer*4, parameter :: n
那么只要 n < 2147483647
,所有声明的初始化工作都按预期进行。
在gfortran
(4.8.5 20150623 Red Hat 4.8.5-16)中,只要n>65535
,y
(具有常量参数的数组理解)初始化就会失败, 与其精度无关。 AFAIK,65535
是 unsigned short int
的最大值,又名 unsigned int*2
,正好在 integer*4
.
下面是一个 MWE:
program test
implicit none
integer*4, parameter :: n = 65536
integer*4, parameter :: m = 65535
real*8, dimension(n) :: x, y, z
real*8, dimension(m) :: a, b, c
integer*4 :: i
print *, huge(n)
x = 1.
y = (/ (1., i=1,n) /)
z = (/ (1.+0*i, i=1,n) /)
print *, x(n), y(n), z(n)
a = 1.
b = (/ (1., i=1,m) /)
c = (/ (1.+0*i, i=1,m) /)
print *, a(m), c(m), c(m)
end program test
用gfortran
编译(gfortran test.f90 -o gfortran_test
),输出:
2147483647
1.0000000000000000 0.0000000000000000 1.0000000000000000
1.0000000000000000 1.0000000000000000 1.0000000000000000
用ifort
编译(ifort test.f90 -o ifort_test
),输出:
2147483647
1.00000000000000 1.00000000000000 1.00000000000000
1.00000000000000 1.00000000000000 1.00000000000000
什么给了?
编译器处理数组构造函数的方式确实存在很大差异。对于 n<=65535
,[1., 1., 1.,...] 的实际数组存储在目标文件中(或某些中间表示形式)。
对于更大的数组,编译器生成一个循环:
(*(real(kind=8)[65536] * restrict) atmp.0.data)[offset.1] = 1.0e+0;
offset.1 = offset.1 + 1;
{
integer(kind=8) S.2;
S.2 = 0;
while (1)
{
if (S.2 > 65535) goto L.1;
y[S.2] = (*(real(kind=8)[65536] * restrict) atmp.0.data)[S.2];
S.2 = S.2 + 1;
}
L.1:;
}
在我看来,首先它只设置临时数组的一个元素,然后将(大部分未定义的)临时数组复制到 y
。这是错误的。 Valgrind 还报告未初始化内存的使用情况。
对于默认的实数,我们有
while (1)
{
if (shadow_loopvar.2 > 65536) goto L.1;
(*(real(kind=4)[65536] * restrict) atmp.0.data)[offset.1] = 1.0e+0;
offset.1 = offset.1 + 1;
shadow_loopvar.2 = shadow_loopvar.2 + 1;
}
L.1:;
{
integer(kind=8) S.3;
S.3 = 0;
while (1)
{
if (S.3 > 65535) goto L.2;
y[S.3] = (*(real(kind=4)[65536] * restrict) atmp.0.data)[S.3];
S.3 = S.3 + 1;
}
L.2:;
}
我们现在有两个循环,一个设置 整个 临时数组,第二个将其复制到 y
,一切正常。
结论:一个编译器错误。
阅读此问题的 GCC 开发人员已解决此问题。该错误在 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84931
处被跟踪他们还确定问题与类型转换有关。构造函数具有默认精度 1.
并且对于单精度数组没有类型转换,但是对于双精度数组有一些类型转换。这导致了这两种情况的不同。