Преднамеренное несоответствие типов в Фортране - PullRequest
0 голосов
/ 27 марта 2019

Я бы хотел превратить унаследованный код на 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

что я не могу понять. Более того, ifort с -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)

Ответы [ 2 ]

1 голос
/ 29 марта 2019

Я нашел возможное общее решение, которое, кажется, работает. Код, с которым мне приходится иметь дело, выглядит примерно так:

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) могут быть в общих блоках и изменяться, например, в других вызовах.

Есть ли подводные камни, кроме тех, которые уже присутствуют в исходном коде?

0 голосов
/ 27 марта 2019

Intel Fortran поддерживает директиву !dec$ attributes no_arg_check .Он указывает компилятору "что правила соответствия типов и форм, относящиеся к явным интерфейсам, должны игнорироваться" .

". Он может применяться к имени отдельного фиктивного аргумента или кимя подпрограммы, в этом случае параметр применяется ко всем фиктивным аргументам в этом интерфейсе. "

Он должен применяться к процедуре модуля (или блоку интерфейса), поэтому вам следует переместить свои функции иподпрограммы в модуль.

Многие другие компиляторы имеют аналогичные директивы .


Что плохого в вашем коде?Как правило, никогда не использует никакие функции Фортрана, которые возвращают pointer s .Они чистое зло.Указатели на Фортране полностью отличаются от указателей на Си.

Когда вы делаете call fill_int(cast_to_int(a),10), получается, что выражение cast_to_int(a) вычисляется, а результатом является массив.Теперь, в зависимости от оптимизаций, компилятор может выбрать передачу адреса исходного указателя, но он также может создать копию целочисленного массива результата и передать копию в подпрограмму.

Кроме того, ваш массив a не имеет атрибута target, поэтому адрес, используемый внутри cast_to_int(a), действителен только внутри функции и недействителен после ее возврата.

Вы должны сделать b внутри основной программы ипросто передайте b вместо a.Это будет работать аналогично эквивалентности.Просмотр значений, хранящихся в другом типе, в любом случае не будет соответствовать стандартам.Эта форма типа наказания не допускается.

...