【问题标题】:Intentional type mismatch in FortranFortran 中的故意类型不匹配
【发布时间】:2019-03-27 19:03:40
【问题描述】:

我想将旧版 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,但我认为应该是另一个问题。

我做错了什么,或者我怎样才能让 ifort 做我想做的事?


编辑:在@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)

【问题讨论】:

  • 您可以使用transfer,但您必须以某种方式更改例程的逻辑。我知道没有可移植的方法来通过参数实现这一点。但是,您应该首先尝试了解原作者为什么首先这样做,以及如何正确地做同样的事情(可能是 X/Y 问题),可能没有这样的 hack。
  • “保持代码合规”是什么意思?代码肯定不符合 Fortran 标准。
  • @Steve 我的意思是编译器告诉我的。如果没有cast_to_int,它显然不符合要求。我想要一些行为相同(不仅仅是给出相同结果)并且符合要求的东西,或者至少给出尽可能少的警告。
  • @Jellby 出于好奇,如果您将“双精度,目标 :: a(*)”更改为“双精度,目标 :: a(:)”,ifort 是否给出与gfortran 取消注释 cast_to_int() + 注释掉调用时没有它?
  • @roygvib 不,它与a(*)a(:) 给出相同的结果

标签: pointers fortran legacy-code


【解决方案1】:

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

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

它应该应用于模块过程(或接口块),所以你应该将你的函数和子例程移动到一个模块中。

许多其他编译器都有similar directives


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

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

另外,你的数组a没有target属性,所以cast_to_int(a)内部使用的地址只在函数内部有效,返回后无效。

您应该在主程序中创建b,然后只传递b 而不是a。它将与等价类似。无论如何,查看存储为不同类型的值将不符合标准。不允许使用这种形式的双关语。

【讨论】:

  • 我试图避免它,因为首先,它需要编写接口块或模块,其次是因为不检查与显式执行“转换”不同。
  • @Jellby 这真的不是一个很大的限制,我认为你真的想要太多。你希望发生什么样的转变?自动?您不能仅仅将整数位模式重新解释为实数并期望有意义的数字。
  • 也许您还应该显示原始方法的代码,以便我们可以看到您实际尝试做什么。
  • 当然,我对数字的含义没有任何期望,但原作者认为它会起作用,而且显然已经有几十年了。我手头没有特定的示例,但我在代码中的几个地方看到过它(大而杂乱的代码库),正如英特尔页面所说,通常是内存复制例程或读/写二进制文件,或者将一大块分配为真实的,然后将其中的一些块用作整数。
  • 例如gitlab.com/Molcas/OpenMolcas/blob/master/src/cpf/cpfctl.f#L91 仅使用数组 H 的不同部分作为参数,而子例程在 gitlab.com/Molcas/OpenMolcas/blob/master/src/cpf/cupdate.f#L14 中定义,带有实数和整数参数。
【解决方案2】:

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

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,我不需要传递所有变量,只需传递那些将被“双关”的变量。 c_f_pointer 调用应该在有问题的调用之前完成,因为索引变量(示例中为idx)可能位于公共块中,并在其他调用中发生更改。

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

【讨论】:

  • 这基本上是我写的:“你应该在主程序中创建 b,然后传递 b 而不是 a。”
  • @VladimirF 确实,虽然我一开始并没有完全理解。此外,在虚拟参数中包含target 需要显式接口这一事实让我担心了一段时间。我只是想写下完整的“解决方案”。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-05
  • 2014-09-12
  • 1970-01-01
  • 2012-09-23
  • 2018-07-08
  • 1970-01-01
相关资源
最近更新 更多