передача выделенного символа через два уровня вызовов процедур не удалась - PullRequest
0 голосов
/ 11 декабря 2018

При выделении символьных строк в качестве необязательных аргументов возникает ошибка выделения.Проблема возникает только тогда, когда я вызываю через два уровня процедур.В моем действительном коде вызов get_level1 () (см. Ниже) представляет вызов структуры данных списка, а вызов get_level2 () представляетсписок, вызывающий функцию доступа того же типа для одной из ее записей.Я сократил пример до минимума, который адекватно воспроизводит проблему.

В приведенном ниже коде, когда я вызываю get_level2 , непосредственно ожидаемая строка символов возвращается черезнеобязательный аргумент.Когда я вызываю get_level1 , который, в свою очередь, вызывает get_level2 , выделение необязательного фиктивного аргумента завершается неудачей.Используя gdb, я обнаружил, что попытка выделения для создания символа * 1635 ... когда он возвращается к фактическому аргументу, очевидно, имеет переполнение целых чисел, потому что считает, что выделение является символом * -283635612 ...

MyФактический код имеет много необязательных аргументов, а не только один.В качестве простого примера я добавил необязательный целочисленный аргумент.На этот раз вместо ошибки сегментации я получаю пустую строку.

Во втором примере целочисленный аргумент работает независимо от использования символьного аргумента.(Я ожидаю этого, поскольку динамическое размещение не выполняется). Присутствие целого числа не влияет на символ.Я также попытался изменить intent на (inout).Это не меняет поведение, хотя я этого не ожидал.[Я полагаю, что intent (out) заставляет фактический аргумент сначала освобождаться, а intent (inout) сохраняет фактическое состояние выделения аргумента]

call get_level1( NUM=n )              ! works
call get_level1( NUM=n, TEXT=words )  ! fails
call get_level1( TEXT=words )         ! fails

mycmd компиляции:

gfortran -Wall -g -std=f2008ts stest1.f08 -o stest

Среда

Linux 4.15.0-42-generic #45-Ubuntu SMP x86_64 GNU/Linux
GNU Fortran (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0

Пример с одним необязательным аргументом

module stest1
  implicit none

  character(:), allocatable :: data

contains

  subroutine get_level2( TEXT )
    implicit none
    character(:), optional, allocatable, intent(out) :: TEXT

    if ( PRESENT( TEXT ) ) then
       TEXT = 'Prefix: ' // data // ' :postfix'
    end if

  end subroutine get_level2


  subroutine get_level1( TEXT )
    implicit none
    character(:), optional, allocatable, intent(out) :: TEXT

    call get_level2( TEXT )

  end subroutine get_level1

end module stest1


program main
  use stest1
  implicit none

  character(:), allocatable :: words

  data  = 'Hello Doctor'

  call get_level1( words )

  write(*,100) words

100 format( 'words = [',A,']' )

end program main

Пример с двумя необязательными аргументами

module stest2
  implicit none

  character(:), allocatable :: data
  integer                   :: count

contains

  subroutine get_level2( TEXT, NUM )
    implicit none
    character(:), optional, allocatable, intent(out) :: TEXT
    integer,      optional,              intent(out) :: NUM

    if ( PRESENT( TEXT ) ) then
       TEXT = 'Prefix: ' // data // ' :postfix'
    end if

    if ( PRESENT( NUM ) ) then
       NUM = count
    end if

  end subroutine get_level2


  subroutine get_level1( TEXT, NUM )
    implicit none
    character(:), optional, allocatable, intent(out) :: TEXT
    integer,      optional,              intent(out) :: NUM

    call get_level2( NUM=NUM, TEXT=TEXT )

  end subroutine get_level1

end module stest2


program main
  use stest2
  implicit none

  character(:), allocatable :: words
  integer                   :: n

  count = 42
  data  = 'Hello Doctor'

  call get_level1( TEXT=words )

  write(*,100) words
  write(*,110) n

100 format( 'words = [',A,']' )
110 format( 'N     = [',I0,']' )

end program main

Ответы [ 2 ]

0 голосов
/ 12 декабря 2018

Это ошибка в компиляторе, и она по-прежнему стоит в gfortran v9.0.0 (experimental) в Windows.Вы должны сообщить об этом поставщику .

Я провел несколько тестов, и кажется, что сбой только происходит, когда: передает существующий необязательный аргументкак фактический аргумент, соответствующий фиктивному аргументу, который character(:), allocatable, optional.Кажется, что любое изменение в предыдущем предложении позволяет избежать ошибки и дает правильный результат.

Я сократил ваш пример до минимального контрольного примера:

program main
  implicit none
  character(:), allocatable :: txt

  call sub1(txt)
  print *, "main ", len(txt), txt      ! prints: main 0 (or throws segfault)

contains
  subroutine sub1(txt)
    character(:), allocatable, optional :: txt
    call sub2(txt)
    print *, "sub1 ", len(txt), txt    ! prints: sub1 0 (or throws segfault)
  end
  subroutine sub2(txt)
    character(:), allocatable, optional :: txt
    if(present(txt)) txt = "message"
    print *, "sub2 ", len(txt), txt    ! prints: sub2 7 message
  end
end

Проверка внутри sub2 показываетчто назначение на самом деле работает там.Кажется, проблема возникает, когда этот фиктивный объект сопоставляется с фактическим аргументом внутри sub1.Хм ...

Опять же, любое изменение шаблона character(:), allocatable, optional манекенов дает правильный результат в моих тестах.Итак, я предлагаю вам гибко настроить хотя бы одно из предыдущих условий, чтобы обойти глючную вещь.Есть несколько предложений:


1.неразмещаемый необязательный символ работает , независимо от того, фиксированная или предполагаемая длина;

Вот пример с переменной фиксированной длины и аргументами предполагаемой длины.

Преимущество : Легко реорганизовать, менее разрушительно / навязчиво.

Недостаток : Необходимо заранее оценить длину переменной, хранение отходов.

program option1
  implicit none
  character(10) :: txt

  call sub1(txt)
  print *, "main ", len(txt), txt      ! prints: main 10 message

contains
  subroutine sub1(txt)
    character(*), optional :: txt
    call sub2(txt)
    print *, "sub1 ", len(txt), txt    ! prints: sub1 10 message
  end
  subroutine sub2(txt)
    character(*), optional :: txt
    if(present(txt)) txt = "message"
    print *, "sub2 ", len(txt), txt    ! prints: sub1 10 message
  end
end

2. необязательно для фактического аргумента, переданного из sub1 или для фиктивного аргумента в sub2, также заставляет его работать ;

Конечно, если вы можете реорганизовать свой код, чтобы избежать этой ситуации, это было бы лучшим решением.Например, вы можете использовать общие интерфейсы для достижения аналогичного результата.Или, как вы сказали в комментарии: « с использованием локальных переменных на уровне 1 и передачей всех необязательных аргументов на нижний уровень ».

Недостаток : может потребоватьсяизменить интерфейсы процедур нижнего уровня.

Преимущество : не будет проблем, если они являются процедурами частного модуля;Это деталь реализации.

Рассмотрим следующий подход, который взламывает ошибку и избегает передачи необязательного аргумента, поэтому не меняет сигнатуру процедуры :

program option2
  implicit none
  character(:), allocatable :: txt

  call sub1(txt)
  print *, "main ", len(txt), txt      ! prints: main 7 message

contains
  subroutine sub1(txt)
    character(:), allocatable, optional :: txt
    character(:), allocatable :: txt_

    if(present(txt)) then
      ! txt_ isn't optional, so the bug doesn't fire
      call sub2(txt_)
      txt = txt_
    end if
    print *, "sub1 ", len(txt), txt    ! prints: sub1 7 message
  end
  subroutine sub2(txt)
    character(:), allocatable, optional :: txt
    print *, present(txt)
    if(present(txt)) txt = "message"
    print *, "sub2 ", len(txt), txt    ! prints: sub2 7 message
  end
end

3. с любым другим типом тоже работает , независимо от атрибутов (даже производного типа с выделяемым символьным компонентом).Хотя изменения в ранге или типе не учитываются.

Я покажу вам два варианта, связанных с производными типами: один с выделяемым компонентом длины символа;другой с параметризованным производным типом.

Advantage : Вы можете сохранить свою структуру кода и все дополнительные компоненты.Объем хранения невелик.Вы даже можете расширить свой DT с помощью методов и адаптировать его к вашей проблеме.

Недостаток : Возможно, слишком много хлопот для мало.PDT - это круто, но это новая (и глючная) функция в gfortran.

program option3a
  ! using a derived type with allocatable character length component.
  implicit none

  type :: string
    character(:), allocatable :: chars
  end type

  type(string) :: txt

  call sub1(txt)
  print *, "main ", len(txt%chars), txt%chars   ! prints: main 7 message

contains
  subroutine sub1(txt)
    type(string), optional :: txt
    call sub2(txt)
    print *, "sub1 ", len(txt%chars), txt%chars ! prints: sub1 7 message
  end
  subroutine sub2(txt)
    type(string), optional :: txt
    if(present(txt)) txt = string("message")
    print *, "sub2 ", len(txt%chars), txt%chars ! prints: sub2 7 message
  end
end

program option3b
  ! using a parameterized derived type, you can practically mimic the intrinsic 
  ! character type behavior, with the possibility to add custom behavior.
  ! but its still raw in gfortran.
  implicit none

  type :: string(len)
    integer, len :: len
    character(len) :: chars
  end type

  type(string(:)), allocatable :: txt

  call sub1(txt)
  print *, "main ", txt%len, txt       ! prints: main 7 7 message (a lil bug of gfortran)

contains
  subroutine sub1(txt)
    type(string(:)), allocatable, optional :: txt
    call sub2(txt)
    print *, "sub1 ", txt%len, txt     ! prints: main 7 7 message
  end
  subroutine sub2(txt)
    type(string(:)), allocatable, optional :: txt
    ! the following fails with gfortran, however it's valid syntax
    ! if(present(txt)) txt = string(7)("message")
    allocate(string(7) :: txt)
    if(present(txt)) txt%chars = "message"
    print *, "sub2 ", txt%len, txt     ! prints: main 7 7 message
  end
end

Подводя итог: вы можете изменить свой компилятор или выбрать любой из этих (или других) способов обойти эту ошибкуи продолжайте работать до тех пор, пока поставщик вашего компилятора не решит проблему.

0 голосов
/ 11 декабря 2018

Вы, похоже, попали в ошибку компилятора.Я могу воспроизвести проблему на gfortran 8.2.1:

Operating system error: Cannot allocate memory
Memory allocation failure in xrealloc

Error termination. Backtrace:
#0  0x7f9c0314f107 in write_character
    at ../../../libgfortran/io/write.c:1399
#1  0x7f9c03153e66 in list_formatted_write_scalar
    at ../../../libgfortran/io/write.c:1872
#2  0x400c78 in MAIN__
    at /tmp/test.F90:43
#3  0x400cbe in main
    at /tmp/test.F90:34

, но в 5.1.1 я вижу правильный вывод:

Prefix: Hello Doctor :postfix

Со следующим обходным путем я получил егона работу:

  subroutine get_level1( TEXT )
    implicit none
    character(:), optional, allocatable, intent(out) :: TEXT

    character(:), allocatable :: tmp

    if ( PRESENT( TEXT ) ) then
      call get_level2( tmp )
      TEXT = tmp
    else
      call get_level2( )
    endif

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