Problem with getting c_loc pointers from character arrays

Hi,

We’ve run into some problems trying to get addresses of elements in character arrays. The below program is the driver

program repro1_dangling
  use fms_string_utils_repro_mod, only: fms_array_to_pointer
  use iso_c_binding, only: c_ptr, c_loc, c_null_char, c_intptr_t, c_f_pointer, c_char
  implicit none
  character(len=255), allocatable, target :: names(:)
  type(c_ptr),        allocatable         :: ptrs(:)
  integer(c_intptr_t) :: got, expect
  integer :: i
  logical :: dangling
  character(kind=c_char), pointer :: cptr_char

  ! initialize strings
  allocate(names(3))
  names(1) = "alpha"//c_null_char
  names(2) = "beta"//c_null_char
  names(3) = "gamma"//c_null_char

  ! problematic helper returns wrong pointers
  ptrs = fms_array_to_pointer(names)

  ! check collected addresses with expected addresses
  dangling = .false.
  do i = 1, size(names)
    got    = transfer(ptrs(i),         0_c_intptr_t)
    expect = transfer(c_loc(names(i)), 0_c_intptr_t)
    print *, "i =", i, "  returned ptr =", got, "  address of names(i) =", expect
    if (got /= expect) dangling = .true.
  end do

  if (dangling) then
    print *, "BUG: fms_array_to_pointer pointers do not alias names(:) -> dangling"
  else
    print *, "OK: pointers alias names(:)"
  end if
  flush(6)

  ! Dereference the (bad) pointers -> reads through mis-strided/dangling addresses.
  do i = 1, size(names)
    call c_f_pointer(ptrs(i), cptr_char)
    print *, "first char via returned ptr, i =", i, " -> ", cptr_char
  end do
end program repro1_dangling

and the following is the corresponding module.

module fms_string_utils_repro_mod
  use, intrinsic :: iso_c_binding, only: c_ptr, c_loc
  implicit none
  private
  public :: fms_array_to_pointer
contains
  !> Verbatim copy of FMS string_utils/fms_string_utils.F90::fms_array_to_pointer
  function fms_array_to_pointer(my_array) result(my_pointer)
    character(len=*), target :: my_array(:) !< Array of strings to convert
    type(c_ptr), allocatable :: my_pointer(:)
    integer :: i

    if (allocated(my_pointer)) stop "my_pointer already allocated"
    allocate(my_pointer(size(my_array)))

    do i = 1, size(my_array)
      my_pointer(i) = c_loc(my_array(i))
    enddo
  end function fms_array_to_pointer
end module fms_string_utils_repro_mod

Running it gives me output like:

 i =            1   returned ptr =                367289024
   address of names(i) =                367289024
 i =            2   returned ptr =                367321792
   address of names(i) =                367289279
 i =            3   returned ptr =                367354560
   address of names(i) =                367289534
 BUG: fms_array_to_pointer pointers do not alias names(:) -> dangling
 first char via returned ptr, i =            1  -> a
 first char via returned ptr, i =            2  ->
 first char via returned ptr, i =            3  ->

The problem being that the addresses produced by the helper function don’t match what is collected outside the helper.

Notably, compiling the module inside the same file as the driver gives the correct result. So to reproduce the wrong result, ensure that the module and the driver program are in separate files.

This code seems to be ok for intel + gnu compilers.

I don’t know much about C-Fortran interop, so not sure if this is well-defined behaviour.

Thanks for the report Edward. I was able to reproduce the error here and have filed a problem report, TPR #38727. I’ve added engineering to investigate.

It appears we’re using the incorrect byte-length for the assumed size character array. You can work around it by using an explicit length, i.e. change “len=*” to “len=255”.

-Mat

All the uses of this problematic function seem to use the same char length, so fixing the size to 255 is probably fine. Thanks for the suggestion!

Hi Edward,

Engineering let me know that this feature, target attributed dummy arguments, is a known limitation and not something we’ll be fixing in the current nvfortran. It will be available once our LLVM based nvfortran is released.

Though they offered a few other work arounds:

  1. Use the flag “-Mnotarget_temps” to disable using a temp array when passing for a callee assumed-shape variable with the target attribute.
  2. Add the “asynchronous” attribute which has the side effect of not using the temp array.
  function fms_array_to_pointer(my_array) result(my_pointer)
    character(len=*), target, asynchronous :: my_array(:) !< Array of strings to convert
    type(c_ptr), allocatable :: my_pointer(:)
    integer :: i

I’ve closed TPR #38727.

-Mat