我想将传统的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
我无法理解。此外,ifaz与-O0
崩溃(并没有与其他版本)。
我知道代码仍然不太正确,因为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)
我找到了一个似乎有效的通用解决方案。我必须处理的代码看起来像这样:
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
)可以在公共块中,并在其他调用中更改,例如。
除了原始代码中已有的陷阱之外,还有任何陷阱吗?
英特尔Fortran支持!dec$ attributes no_arg_check
directive。它指示编译器“忽略与显式接口相关的类型和形状匹配规则”。
“它可以应用于单个伪参数名称或例程名称,在这种情况下,该选项将应用于该接口中的所有伪参数。”
它应该应用于模块过程(或接口块),因此您应该将函数和子例程移动到模块中。
许多其他编译器都有similar directives。
您的代码有什么问题?根据经验,不要使用任何返回pointer
s的Fortran函数。他们是纯粹的邪恶。 Fortran指针与C指针完全不同。
当你做call fill_int(cast_to_int(a),10)
时会发生什么,表达式cast_to_int(a)
被评估,结果是一个数组。现在,根据优化,编译器可以选择传递原始指针的地址,但它也可以创建结果整数数组的副本并将副本传递给子例程。
此外,您的数组a
没有target
属性,因此cast_to_int(a)
中使用的地址仅在函数内有效,并且在返回后无效。
你应该在主程序中制作b
,然后通过b
而不是a
。它的工作方式类似于等价。查看存储为不同类型的值无论如何都不符合标准。这种形式的双关语是不允许的。