gfortran中隐含的数组构造函数的奇怪初始化行为

cak*_*ako 5 arrays constructor fortran intel gfortran

假设我有3个双精度数组,

real*8, dimension(n) :: x, y, z
Run Code Online (Sandbox Code Playgroud)

初始化为

x = 1.
y = (/ (1., i=1,n) /)
z = (/ (1. +0*i, i=1,n) /)
Run Code Online (Sandbox Code Playgroud)

他们应该将所有数组的所有元素初始化为1.在ifort(16.0.0 20150815)中,这适用n于声明的精度范围内的任何范围.也就是说,如果我们初始化n

integer*4, parameter :: n
Run Code Online (Sandbox Code Playgroud)

然后,只要n < 2147483647初始化按预期用于所有声明.

gfortran(4.8.5 20150623红帽4.8.5-16),初始化失败对于y(具有恒定的参数数组理解),只要n>65535,独立的其精度.AFAIK,65535是a的最大值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
Run Code Online (Sandbox Code Playgroud)

gfortran(gfortran test.f90 -o gfortran_test)编译,它输出:

  2147483647
   1.0000000000000000        0.0000000000000000        1.0000000000000000     
   1.0000000000000000        1.0000000000000000        1.0000000000000000
Run Code Online (Sandbox Code Playgroud)

ifort(ifort test.f90 -o ifort_test)编译,它输出:

2147483647
   1.00000000000000        1.00000000000000        1.00000000000000     
   1.00000000000000        1.00000000000000        1.00000000000000     
Run Code Online (Sandbox Code Playgroud)

是什么赋予了?

Vla*_*r F 4

编译器对待数组构造函数的方式确实存在很大差异。因为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:;
    }
Run Code Online (Sandbox Code Playgroud)

在我看来,首先它仅设置临时数组的一个元素,然后将(大部分未定义的)临时数组复制到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:;
    }
Run Code Online (Sandbox Code Playgroud)

我们现在有两个循环,一个循环设置整个临时数组,第二个循环将其复制到y,一切都很好。

结论:编译器错误。

该问题由阅读此问题的 GCC 开发人员解决。该错误可在https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84931进行跟踪

他们还发现该问题与类型转换有关。构造函数具有默认精度1.,对于单精度数组,没有类型转换,但对于双精度数组,存在一些类型转换。这导致了这两种情况的差异。