简体   繁体   中英

Intentional type mismatch in Fortran

I'd like to turn a legacy Fortran code into modern Fortran compliant code, so I can turn on compiler warnings, interface checking, etc. At this stage I don't want to change the functionality, just make it work as close as possible to what it was, and still keep compilers happy.

My current problem is that the code at many places passes arrays of the wrong types, eg a real array to a subroutine that has an integer dummy argument. This is not a bug per se in the code, since it is intentional and it works as intended (at least in common configurations). Now, how could I do the same and while keeping the code compliant? Consider the following example:

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

When I compile it and run it (gfortran 4.8 or ifort 18), I get, as expected:

  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

The first half of the real array is corrupted with integers (because integers are half the size), but when printed as integers the "right" values are there. But this is non-compliant code. When I try to fix it by activating the cast_to_int function (and disabling the calls without it) I get indeed something that compiles without warning, and with gfortran I get the same result. With ifort, however, I get:

  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

which I can't understand. Moreover, ifort with -O0 crashes (and it doesn't with the other version).

I know the code is still not quite correct, because the pointer returned by cast_to_int is still of size 1, but I believe that should be a different problem.

What am I doing wrong, or how can I get ifort do what I want?


EDIT: Following @VladimirF's reply, I add, after 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

but compiling with warnings on still gives me an error:

$ 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)

I found a possible general solution that seems to work. The code I have to deal with looks something like this:

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

I now modify it to be:

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

leaving other_subroutine untouched. If I use directly the target attribute on the outer routine, I have to add an explicit interface to anything calling it, so instead I wrap the inner code. By using contains I don't need to pass all variables, just those that will be "punned". The c_f_pointer call should be done right before the problematic call, since index variables ( idx in the example) could be in common blocks and changed in other calls, for example.

Any pitfalls, apart from those already present in the original code?

Intel Fortran supports the !dec$ attributes no_arg_check directive . It instructs the compiler "that type and shape matching rules related to explicit interfaces are to be ignored" .

"It can be applied to an individual dummy argument name or to the routine name, in which case the option is applied to all dummy arguments in that interface."

It should be applied to a module procedure (or an interface block), so you should move your functions and subroutines into a module.

Many other compilers have similar directives .


What is wrong about your code? As a rule of thumb, do not ever use any Fortran functions that return pointer s . They are pure evil. Fortran pointers are completely different from C pointers.

When you do call fill_int(cast_to_int(a),10) what happens is that the expression cast_to_int(a) is evaluated and the result is an array. Now depending on the optimizations the compiler may choose to pass the address of the original pointer, but it may also create a copy of the result integer array and pass a copy to the subroutine.

Also, your array a does not have the target attribute, so the address used inside cast_to_int(a) is only valid inside the function and is not valid after it returns.

You should make the b inside the main program and just pass b instead of a . It will work similar to equivalence. Looking at the values stored as a different type will be not standard-conforming anyway. This form of type punning is not allowed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM