Fortran中的故意类型不匹配

时间:2019-03-27 19:03:41

标签: pointers fortran legacy-code

我想将遗留的Fortran代码转换为现代的Fortran兼容代码,因此我可以打开编译器警告,接口检查等。在此阶段,我不想更改功能,只需使其正常运行即可尽可能接近原样,并且仍然使编译器感到满意。

我目前的问题是,许多地方的代码都会传递错误类型的数组,例如具有整数哑元参数的子例程的实际数组。这不是代码本身的错误,因为它是有意的,并且可以按预期工作(至少在常规配置中如此)。现在,在保持代码合规性的同时,我该怎么做?考虑以下示例:

program cast
implicit none
double precision :: a(10)

call fill_dble(a,10)
call print_dble(a,10)
call fill_int(a,10)
!call fill_int(cast_to_int(a),10)
call print_dble(a,10)
call print_int(a(1),10)
!call print_int(cast_to_int(a),10)
call print_dble(a(6),5)

contains

function cast_to_int(a) result(b)
use iso_c_binding
implicit none
double precision, target :: a(*)
integer, pointer :: b(:)
call c_f_pointer(c_loc(a(1)), b, [1])
end function

end program

subroutine fill_dble(b,n)
implicit none
integer :: n, i
double precision :: b(n)
do i = 1, n
  b(i) = i
end do
end subroutine

subroutine print_dble(b,n)
implicit none
integer :: n
double precision :: b(n)
write(6,'(10es12.4)') b
end subroutine

subroutine fill_int(b,n)
implicit none
integer :: n, b(n), i
do i = 1, n
  b(i) = i
end do
end subroutine

subroutine print_int(b,n)
implicit none
integer :: n, b(n)
write(6,'(10i4)') b
end subroutine

当我编译并运行它(gfortran 4.8或ifort 18)时,我得到了预期的结果:

  1.0000E+00  2.0000E+00  3.0000E+00  4.0000E+00  5.0000E+00  6.0000E+00  7.0000E+00  8.0000E+00  9.0000E+00  1.0000E+01
  4.2440-314  8.4880-314  1.2732-313  1.6976-313  2.1220-313  6.0000E+00  7.0000E+00  8.0000E+00  9.0000E+00  1.0000E+01
   1   2   3   4   5   6   7   8   9  10
  6.0000E+00  7.0000E+00  8.0000E+00  9.0000E+00  1.0000E+01

实际数组的前半部分被整数破坏(因为整数是大小的一半),但是当打印为整数时,“正确”值在那里。但这是不兼容的代码。当我尝试通过激活cast_to_int函数(并禁用没有它的调用)来修复它时,确实得到了可以毫无警告地进行编译的东西,并且使用gfortran我得到了相同的结果。但是,有了ifort,我得到了:

  1.0000E+00  2.0000E+00  3.0000E+00  4.0000E+00  5.0000E+00  6.0000E+00  7.0000E+00  8.0000E+00  9.0000E+00  1.0000E+01
  1.0000E+00  2.0000E+00  3.0000E+00  4.0000E+00  5.0000E+00  6.0000E+00  7.0000E+00  8.0000E+00  9.0000E+00  1.0000E+01
   0********   0   5   6   7   8   9  10
  6.0000E+00  7.0000E+00  8.0000E+00  9.0000E+00  1.0000E+01

我不明白。此外,带有-O0的ifort会崩溃(并且其他版本没有)。

我知道代码仍然不太正确,因为cast_to_int返回的指针的大小仍然为1,但是我认为应该是另一个问题。

我在做什么错了,或者我怎么能做我想做的事?


编辑:在@VladimirF的回复之后,我在implicit none之后添加:

subroutine fill_int(b,n)
!dec$ attributes no_arg_check :: b
integer :: n, b(n)
end subroutine
subroutine print_int(b,n)
!dec$ attributes no_arg_check :: b
integer :: n, b(n)
end subroutine
end interface

但是带有警告的编译仍然给我一个错误:

$ ifort cast2.f90 -warn all
cast2.f90(17): error #6633: The type of the actual argument differs from the type of the dummy argument.   [A]
call fill_int(a,10)
--------------^
cast2.f90(20): error #6633: The type of the actual argument differs from the type of the dummy argument.   [A]
call print_int(a(1),10)
---------------^
compilation aborted for cast2.f90 (code 1)

2 个答案:

答案 0 :(得分:1)

我找到了可行的通用解决方案。我必须处理的代码如下所示:

subroutine some_subroutine(a,b,c,d,...)
real a(*),b(*),c(*),d(*)
! many more declarations, including common blocks

!...
call other_subroutine(a,b(idx),c,...)
!...

end subroutine some_subroutine

! this typically in another file:
subroutine other_subroutine(x,y,z,...)
real x(*)
integer y(*)
logical z(*)
! other declarations and common blocks

! unreadable code with calls to other procedures
! not clear which which arguments are input and output

end subroutine other_subroutine

我现在将其修改为:

subroutine some_subroutine(a,b,c,d,...)
real a(*),b(*),c(*),d(*)
! many more declarations, including common blocks

call inner_sub(b,c)

contains
subroutine inner_sub(b,c)
use iso_c_binding
real, target :: b(*),c(*)
integer, pointer :: ib(:)
logical, pointer :: lc(:)

!...
call c_f_pointer(c_loc(b(idx)),ib,[1]) ! or use the actual length if I can figure it out
call c_f_pointer(c_loc(c(1)),lc,[1])
call other_subroutine(a,ib,lc,...)
nullify(ib,lc)
!...

end subroutine inner_sub

end subroutine some_subroutine

保持other_subroutine不变。如果直接在外部例程上使用target属性,则必须向调用它的任何对象添加一个显式接口,因此应包装内部代码。通过使用contains,我不需要传递所有变量,只需传递那些将被“ punned”的变量即可。 c_f_pointer调用应该在有问题的调用之前完成,因为索引变量(在示例中为idx)可以位于公共块中,例如在其他调用中可以更改。

除了原始代码中已经存在的陷阱之外,还有其他陷阱吗?

答案 1 :(得分:-1)

Intel Fortran支持!dec$ attributes no_arg_check directive。它指示编译器“将忽略与显式接口相关的类型和形状匹配规则”

“它可以应用于单个虚拟参数名称或例程名称,在这种情况下,该选项将应用于该接口中的所有虚拟参数。”

应将其应用于模块过程(或接口块),因此应将函数和子例程移至模块中。

许多其他编译器都有similar directives


您的代码有什么问题?根据经验,永远不要使用任何返回pointer的Fortran函数。他们是纯粹的邪恶。 Fortran指针与C指针完全不同。

当您执行call fill_int(cast_to_int(a),10)时,会发生表达式cast_to_int(a)的求值,结果是一个数组。现在,根据优化情况,编译器可以选择传递原始指针的地址,但也可以创建结果整数数组的副本并将副本传递给子例程。

另外,您的数组a没有target属性,因此cast_to_int(a)内部使用的地址仅在函数内部有效,并且在返回后无效。

您应该在主程序中放入b,然后仅传递b而不是a。它的工作原理与对等类似。无论如何,查看存储为其他类型的值都不符合标准。不允许这种形式的punning。