fortranfortran-iso-c-binding

How to correctly read a C-string into a Fortran string of unspecified length?


The following function is supposed to convert a C string into a Fortran string and works fine in Release builds, but not in Debug:

! Helper function to generate a Fortran string from a C char pointer
function get_string(c_pointer) result(f_string)
    use, intrinsic :: iso_c_binding
    implicit none
    type(c_ptr), intent(in)         :: c_pointer
    character(len=:), allocatable   :: f_string

    integer(c_size_t)               :: l_str
    character(len=:), pointer       :: f_ptr

    interface
        function c_strlen(str_ptr) bind ( C, name = "strlen" ) result(len)
        use, intrinsic :: iso_c_binding
            type(c_ptr), value      :: str_ptr
            integer(kind=c_size_t)  :: len
        end function c_strlen
    end interface

    l_str = c_strlen(c_pointer)
    call c_f_pointer(c_pointer, f_ptr)

    f_string = f_ptr(1:l_str)
end function get_string

However, it seems that c_f_pointer does not tell the Fortran pointer-to-string, f_ptr, the length of the string it is pointing to. In Debug builds, where bounds-checking is active, this results in

Fortran runtime error: Substring out of bounds: upper bound (35) of 'f_ptr' exceeds string length (0)

I'm using gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0 and set the standard to 2008.

My question: Is there any way to tell the f_ptr its length without changing the declaration or am I doing something fundamentally the wrong way here?


It seems to run correctly if I specify the shape, but for that f_ptr needs to be an array:

character(len=:), allocatable   :: f_string
character(len=1), dimension(:), pointer :: f_ptr
...
call c_f_pointer(c_pointer, f_ptr, [l_str])

However, I cannot find a way to transform that string of rank 1 to the character(len=:), allocatable :: f_string, which apparently has rank 0.

My second question: Is there any way to transfer the f_ptr data into the f_string in this example?


Solution

  • You cannot use c_f_pointer to set the length of the Fortran character pointer (F2018, 18.2.3.3):

    FPTR shall be a pointer, shall not have a deferred type parameter [...]

    A deferred-length character scalar (or array) therefore cannot be used (the length is a type parameter).

    You can indeed use a deferred-shape character array as fptr and then use any number of techniques to copy the elements of that array to the scalar (which is rank-0 as noted).

    For example, with substring assignment (after explicitly allocating the deferred-length scalar):

    allocate (character(l_str) :: f_string)
    do i=1,l_str
      f_string(i:i)=fptr(i)
    end
    

    Or consider whether you can simply use the character array instead of making a copy to a scalar.