Являются ли «окончательные» подпрограммы Fortran достаточно надежными для практического использования? - PullRequest
6 голосов
/ 30 января 2020

Современный Фортран содержит различные объектно-ориентированные идеи, в том числе понятия «деструкторы» через ключевое слово FINAL.

MODULE mobject
  TYPE :: tobject
    ! Data declarations
  CONTAINS
    FINAL :: finalize
  END TYPE
CONTAINS
  SUBROUTINE finalize(object)
    TYPE(tobject) object
    ...
  END SUBROUTINE
END MODULE

Однако надежна ли эта функция? Примечательно, что я заметил несоответствия относительно того, когда и вообще будет ли он вызываться, с существенными различиями между Intel Fortran 19 и GFortan 7, 8:

  • GFortran не может уничтожить объекты, хранящиеся в массивах.
  • Intel Fortran:
    • выполняет ложные и потенциально лишние разрушения при назначении, потенциально даже в памяти, содержащей ненужные данные, а
    • выполняет ложный вызов деструктору по возвращении из функции .

Я не заметил никакой разницы между gfortran-7.4.0 и gfortran-8.2.1.2.

Эти несоответствия поднимают некоторые вопросы о практической применимости деструкторов для меня. Является ли какое-либо из поведений полностью соответствующим стандарту? Стандарт неясен по этому поводу? Может ли стандарт содержать пункты, которые ведут к неинтуитивному поведению?

Детальный анализ (см. Код ниже)

  • Блок PROGRAM. Gfortran не будет вызовите деструктор для экземпляров, объявленных в блоке PROGRAM, тогда как Ifort сделает это (см. run1 в примере).

  • Скалярные объекты. Для экземпляров, объявленных как скаляры, и Gfortran, и IFort будут вызывать деструктор, если переменная видела какую-либо форму инициализации. Однако Intel Fortran при назначении значения, возвращаемого функцией, также вызовет его

    • для неинициализированного объекта в стеке перед перезаписью его данными из функции, а
    • , по-видимому, в конец функции newObject.

    Однако этого можно избежать, явно проверив, инициализирован ли объект, перед выполнением какой-либо очистки.

Это означает, что программист должен явно проверить, инициализирован ли экземпляр.

  • Объекты в массивах. Если объект содержится в массиве, и массив выходит из области видимости,

    • Gfortran не будет вызывать деструктор.
    • Intel Fortran может вызывать деструктор в зависимости от того, как был инициализирован данный элемент массива.
    • Не имеет значения, объявлен ли массив allocatable.
  • Выделенный массив, инициализированный присваиванием. При использовании модера Функция n, где присвоение выделяемому массиву подразумевает распределение, сохраняется и то же самое, за исключением того, что не существует неинициализированных экземпляров, на которых IntelFortran может вызвать деструктор.

  • Выделенные / указатели из functions.

    • GFortran не вызывает деструктор в конце функции, возвращающей объект allocatable или pointer объекту, а вместо этого вызывает его, когда значение освобождается в клиентском коде явным образом или выходит за рамки allocatable s. Это то, что я ожидал.
    • Intel Fortran вызывает в некоторых дополнительных случаях:
      • Когда объект объявляется allocatable, но не когда это pointer, Intel Fortran вызывает деструктор на локальное значение функции при выходе из функции.
      • При инициализации объекта внутри функции с подразумеваемым распределением (var = newObject(...)) или, в случае варианта pointer, с явным распределением (allocate(var); var = newObject(...)) деструктор вызывается в неинициализированной памяти, видимой в run5MoveAlloc и run6MovePtr из %name, содержащей нежелательные данные. Это можно решить, используя вместо этого шаблон allocate(var); call var%init(...).

Код тестирования

!! -- Makefile ---------------------------------------------------
!! Runs the code with various compilers.

SHELL = bash
FC = NO_COMPILER_SPECIFIED
COMPILERS = gfortran-7 gfortran-8 ifort
PR = @echo$(n)pr -m -t -w 100

define n


endef

all: 
    rm -rf *.mod *.bin
    $(foreach FC, $(COMPILERS), $(n)\
      rm -rf *.mod && \
      $(FC) destructor.f90 -o $(FC).bin && \
      chmod +x $(FC).bin)
    $(PR) $(foreach FC, $(COMPILERS), <(head -1 <($(FC) --version)))
    $(info)
    $(foreach N,0 1 2 3 4 5 6,$(n) \
      $(PR) $(foreach FC, $(COMPILERS), <(./$(FC).bin $(N))))



!! -- destructor.f90 ---------------------------------------------

module mobject
  implicit none
  private
  public tobject, newObject

  type :: tobject
     character(32) :: name = "<undef>"
   contains
     procedure :: init
     final :: finalize
  end type tobject

contains

  subroutine init(object, name)
    class(tobject), intent(inout) :: object
    character(*), intent(in) :: name
    print *, "+ ", name
    object%name = name
  end subroutine init

  function newObject(name)
    type(tobject) :: newObject
    character(*), intent(in) :: name
    call new%init(name)
  end function newObject

  subroutine finalize(object)
    type(tobject) :: object
    print *, "- ", object%name
  end subroutine finalize

end module mobject



module mrun
  use mobject
  implicit none
contains

  subroutine run1()
    type(tobject) :: o1_uninit, o2_field_assigned, o3_tobject, o4_new, o6_init
    type(tobject), allocatable :: o5_new_alloc, o7_init_alloc
    print *, ">>>>> run1"
    o2_field_assigned%name = "o2_field_assigned"
    o3_tobject = tobject("o3_tobject")
    o4_new = newObject("o4_new")
    o5_new_alloc = newObject("o5_new_alloc")
    call o6_init%init("o6_init")
    allocate(o7_init_alloc)
    call o7_init_alloc%init("o7_init_alloc")
    print *, "<<<<< run1"
  end subroutine run1

  subroutine run2Array()
    type(tobject) :: objects(4)
    print *, ">>>>> run2Array"
    objects(1)%name = "objects(1)_uninit"
    objects(2) = tobject("objects(2)_tobject")
    objects(3) = newObject("objects(3)_new")
    call objects(4)%init("objects(4)_init")
    print *, "<<<<< run2Array"
  end subroutine run2Array

  subroutine run3AllocArr()
    type(tobject), allocatable :: objects(:)
    print *, ">>>>> run3AllocArr"
    allocate(objects(4))
    objects(1)%name = "objects(1)_uninit"
    objects(2) = tobject("objects(2)_tobject")
    objects(3) = newObject("objects(3)_new")
    call objects(4)%init("objects(4)_init")
    print *, "<<<<< run3AllocArr"
  end subroutine run3AllocArr

  subroutine run4AllocArrAssgn()
    type(tobject), allocatable :: objects(:)
    print *, ">>>>> run4AllocArrAssgn"
    objects = [ &
         tobject("objects(1)_tobject"), &
         newObject("objects(2)_new") ]
    print *, "<<<<< run4AllocArrAssgn"
  end subroutine run4AllocArrAssgn

  subroutine run5MoveAlloc()
    type(tobject), allocatable :: o_alloc
    print *, ">>>>> run5MoveAlloc"
    o_alloc = getAlloc()
    print *, "<<<<< run5MoveAlloc"
  end subroutine run5MoveAlloc

  function getAlloc() result(object)
    type(tobject), allocatable :: object
    print *, ">>>>> getAlloc"
    allocate(object)
    object = newObject("o_alloc")
    print *, "<<<<< getAlloc"
  end function getAlloc

  subroutine run6MovePtr()
    type(tobject), pointer :: o_pointer
    print *, ">>>>> run6MovePtr"
    o_pointer => getPtr()
    deallocate(o_pointer)
    print *, "<<<<< run6MovePtr"
  end subroutine run6MovePtr

  function getPtr() result(object)
    type(tobject), pointer :: object
    print *, ">>>>> getPtr"
    allocate(object)
    object = newObject("o_pointer")
    print *, "<<<<< getPtr"
  end function getPtr

end module mrun



program main
  use mobject
  use mrun
  implicit none
  type(tobject) :: object
  character(1) :: argument

  print *, ">>>>> main"
  call get_command_argument(1, argument)
  select case (argument)
  case("1")
     call run1()
  case("2")
     call run2Array()
  case("3")
     call run3AllocArr()
  case("4")
     call run4AllocArrAssgn()
  case("5")
     call run5MoveAlloc()
  case("6")
     call run6MovePtr()
  case("0")
     print *, "####################";
     print *, ">>>>> runDirectlyInMain"
     object = newObject("object_in_main")
     print *, "<<<<< runDirectlyInMain"
  case default
     print *, "Incorrect commandline argument"
  end select
  print *, "<<<<< main"
end program main

Вывод код тестирования

>> make
rm -rf *.mod *.bin
rm -rf *.mod && gfortran-7 destructor.f90 -o gfortran-7.bin && chmod +x gfortran-7.bin  
rm -rf *.mod && gfortran-8 destructor.f90 -o gfortran-8.bin && chmod +x gfortran-8.bin  
rm -rf *.mod && ifort destructor.f90 -o ifort.bin && chmod +x ifort.bin

pr -m -t -w 100  <(head -1 <(gfortran-7 --version))  <(head -1 <(gfortran-8 --version))  <(head -1 <(ifort --version))
GNU Fortran (SUSE Linux) 7.4.0   GNU Fortran (SUSE Linux) 8.2.1 2 ifort (IFORT) 19.0.4.243 2019041

pr -m -t -w 100  <(./gfortran-7.bin 0)  <(./gfortran-8.bin 0)  <(./ifort.bin 0) 
 >>>>> main                       >>>>> main                       >>>>> main
 ####################             ####################             ####################
 >>>>> runDirectlyInMain          >>>>> runDirectlyInMain          >>>>> runDirectlyInMain
 + object_in_main                 + object_in_main                 + object_in_main
 <<<<< runDirectlyInMain          <<<<< runDirectlyInMain          - <undef>
 <<<<< main                       <<<<< main                       - object_in_main
                                                                   <<<<< runDirectlyInMain
                                                                   <<<<< main

pr -m -t -w 100  <(./gfortran-7.bin 1)  <(./gfortran-8.bin 1)  <(./ifort.bin 1) 
 >>>>> main                       >>>>> main                       >>>>> main
 >>>>> run1                       >>>>> run1                       >>>>> run1
 + o4_new                         + o4_new                         - <undef>
 + o5_new_alloc                   + o5_new_alloc                   + o4_new
 + o6_init                        + o6_init                        - <undef>
 + o7_init_alloc                  + o7_init_alloc                  - o4_new
 <<<<< run1                       <<<<< run1                       + o5_new_alloc
 - o7_init_alloc                  - o7_init_alloc                  - o5_new_alloc
 - o6_init                        - o6_init                        + o6_init
 - o5_new_alloc                   - o5_new_alloc                   + o7_init_alloc
 - o4_new                         - o4_new                         <<<<< run1
 - o3_tobject                     - o3_tobject                     - <undef>
 - o2_field_assigned              - o2_field_assigned              - o2_field_assigned
 <<<<< main                       <<<<< main                       - o3_tobject
                                                                   - o4_new
                                                                   - o6_init
                                                                   - o5_new_alloc
                                                                   - o7_init_alloc
                                                                   <<<<< main

pr -m -t -w 100  <(./gfortran-7.bin 2)  <(./gfortran-8.bin 2)  <(./ifort.bin 2) 
 >>>>> main                       >>>>> main                       >>>>> main
 >>>>> run2Array                  >>>>> run2Array                  >>>>> run2Array
 + objects(3)_new                 + objects(3)_new                 - <undef>
 + objects(4)_init                + objects(4)_init                + objects(3)_new
 <<<<< run2Array                  <<<<< run2Array                  - <undef>
 <<<<< main                       <<<<< main                       - objects(3)_new
                                                                   + objects(4)_init
                                                                   <<<<< run2Array
                                                                   <<<<< main

pr -m -t -w 100  <(./gfortran-7.bin 3)  <(./gfortran-8.bin 3)  <(./ifort.bin 3) 
 >>>>> main                       >>>>> main                       >>>>> main
 >>>>> run3AllocArr               >>>>> run3AllocArr               >>>>> run3AllocArr
 + objects(3)_new                 + objects(3)_new                 - <undef>
 + objects(4)_init                + objects(4)_init                + objects(3)_new
 <<<<< run3AllocArr               <<<<< run3AllocArr               - <undef>
 <<<<< main                       <<<<< main                       - objects(3)_new
                                                                   + objects(4)_init
                                                                   <<<<< run3AllocArr
                                                                   <<<<< main

pr -m -t -w 100  <(./gfortran-7.bin 4)  <(./gfortran-8.bin 4)  <(./ifort.bin 4) 
 >>>>> main                       >>>>> main                       >>>>> main
 >>>>> run4AllocArrAssgn          >>>>> run4AllocArrAssgn          >>>>> run4AllocArrAssgn
 + objects(2)_new                 + objects(2)_new                 + objects(2)_new
 <<<<< run4AllocArrAssgn          <<<<< run4AllocArrAssgn          - objects(2)_new
 <<<<< main                       <<<<< main                       <<<<< run4AllocArrAssgn
                                                                   <<<<< main

pr -m -t -w 100  <(./gfortran-7.bin 5)  <(./gfortran-8.bin 5)  <(./ifort.bin 5) 
 >>>>> main                       >>>>> main                       >>>>> main
 >>>>> run5MoveAlloc              >>>>> run5MoveAlloc              >>>>> run5MoveAlloc
 >>>>> getAlloc                   >>>>> getAlloc                   >>>>> getAlloc
 + o_alloc                        + o_alloc                        + o_alloc
 <<<<< getAlloc                   <<<<< getAlloc                   - `4�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
 <<<<< run5MoveAlloc              <<<<< run5MoveAlloc              - o_alloc
 - o_alloc                        - o_alloc                        <<<<< getAlloc
 <<<<< main                       <<<<< main                       - o_alloc
                                                                   <<<<< run5MoveAlloc
                                                                   - o_alloc
                                                                   <<<<< main

pr -m -t -w 100  <(./gfortran-7.bin 6)  <(./gfortran-8.bin 6)  <(./ifort.bin 6)
 >>>>> main                       >>>>> main                       >>>>> main
 >>>>> run6MovePtr                >>>>> run6MovePtr                >>>>> run6MovePtr
 >>>>> getPtr                     >>>>> getPtr                     >>>>> getPtr
 + o_pointer                      + o_pointer                      + o_pointer
 <<<<< getPtr                     <<<<< getPtr                     - `��\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
 - o_pointer                      - o_pointer                      - o_pointer
 <<<<< run6MovePtr                <<<<< run6MovePtr                <<<<< getPtr
 <<<<< main                       <<<<< main                       - o_pointer
                                                                   <<<<< run6MovePtr
                                                                   <<<<< main

1 Ответ

3 голосов
/ 30 января 2020

TLDR: в Gfortran есть известные проблемы. Intel заявляет о полной поддержке. Некоторые компиляторы не требуют поддержки.


Вопрос о надежности и удобстве использования в целом довольно субъективен, поскольку нужно учитывать множество уникальных для вас моментов (вам нужно поддерживать несколько компиляторов? Нужно поддерживать их старые версии? Какие именно? Насколько это важно, если какая-то сущность не завершена?).

Вы выдвигаете некоторые претензии, на которые трудно ответить без реальных примеров кода и которые могут быть топи c для отдельных полных вопросов и ответов. Gfortran публикует текущее состояние реализации функций Fortran 2003 и 2008 в этом отчете об ошибках https://gcc.gnu.org/bugzilla/show_bug.cgi?id=37336 (ссылка указывает на мета-ошибку, указывающую на несколько отдельных проблем, отслеживаемых в bugzilla). Известно и то, что функция не закончена и что есть нерешенные проблемы. Что особенно важно (по крайней мере, для меня), результаты функции не завершаются. Обзор состояния других компиляторов (упрощенно Y / N / paritally) находится по адресу http://fortranwiki.org/fortran/show/Fortran+2003+status и раньше периодически обновлялся в статьях форума Fortran.

Я не могу говорить о тех предполагаемых ложных доработках Intel Fortran. Если вы обнаружили ошибку в своем компиляторе, вы должны отправить отчет об ошибке вашему поставщику. Intel, как правило, довольно отзывчива.

Однако можно ответить на некоторые отдельные вопросы. Скорее всего, вы найдете отдельные Q / As о них. Но:

  • Gfortran не будет вызывать деструктор для экземпляров, объявленных в блоке PROGRAM, в то время как Ifort будет (см. Run1 в примере).

    • Переменные, объявленные в основная программа неявно получает атрибут save в соответствии со стандартом. Компилятор не должен генерировать какие-либо автоматические c финализации.
  • Однако Intel Fortran при назначении возвращаемого значения функции вызовет его также

    • Как указано в багзилле Gfortran, gfortran еще не завершает переменные результата функции.
  • Это означает, что программист должен явно проверить, имеет ли экземпляр был инициализирован.

    • Боюсь, что в стандарте Фортрана такого понятия нет. Я понятия не имею, что может означать «если переменная видела какую-либо форму инициализации». Обратите внимание, что функция инициализатора - это функция, аналогичная любой другой .
  • При использовании современной функции, когда назначение выделенному массиву подразумевает распределение, то же самое имеет место, за исключением того, что не существует ни одного неинициализированного экземпляра, на котором IntelFortran может вызвать деструктор.

    • Не уверен, что это на самом деле означает. В Фортране нет такой «инициализации». Возможно, результаты функции снова?
  • Выделяемые / указатели на функции.

    • Как уже несколько раз указывалось, результаты функции не завершаются должным образом в текущем версии Gfortran.

Если вы хотите подробно ответить на любой из пунктов, вам действительно нужно задать конкретный c вопрос . Этот слишком широк для этого. Справка / инструкции для этого сайта содержат "Пожалуйста, отредактируйте вопрос, чтобы ограничить его конкретной проблемой c с достаточным количеством деталей для определения адекватного ответа. Избегайте одновременного задания нескольких отдельных вопросов . См. [попроси] помочь прояснить этот вопрос. "

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...