0

I would like to create a fortran binding to a C function that returns an arbitrary length string as a function argument. I have read this similar question, but I simply cannot get the solution there to work. I have simplified the problem as much as I can so I can explain here.

I have a C function like this: c_string.c (I know that usually this kind of function would also have an argument that also returns the length of the string, but I have left it out here for simplicity, since in this example I know the length.)

#include <string.h>

void make_string(char *string) {
    strcpy(string, "sand");
}

It seems to work fine in a C program, for example:

#include <stdlib.h>
#include <stdio.h>
#include "c_string.c"

int main() {
    char *string;
    string = malloc(5*sizeof(char));
    make_string(string);
    printf(string);
}

I want to call this function in a Fortran program: main.f90. So I make a c binding following the solution mentioned above.

program main
    use iso_c_binding
    implicit none
    interface
        subroutine c_make_string(string) bind (C, name='make_string')
            import
            type (c_ptr), value :: string
        end subroutine c_make_string
    end interface

    type (c_ptr) :: c_string
    character, pointer :: local_string(:)

    call c_make_string(c_string)
    call c_f_pointer(c_string, local_string, [4])
    write (*,*) local_string
end program main

But this program results is a segfault at the write statement. Trying to access the resulting local_string pointer by indexing gives a segfault too. Note: after adding the value attribute to the interface, the segfault is now in the C function.

The only solution I came up with is to not use c_ptr and c_f_pointer(), and just use a maximum buffer length like 512.

program main
    use iso_c_binding
    implicit none

    interface
        subroutine c_make_string(string) bind (C, name='make_string')
            import
            character (len=1, kind=c_char) :: string(512)
        end subroutine
    end interface

    character (len=1, kind=c_char) :: local_string(512)

    call c_make_string(local_string)
    write (*,*) local_string
end program main

But that results in a whole bunch of junk after the useful data in the string. Of course, that is easily worked around if you know the length of the intended string, but it feels unsatisfactory to me... if only I knew what was going wrong with the c_f_pointer() call in the original solution. Should I allocate space for the local_string before, as I do in the C program? But how? Why does it seem I need fixed length arrays for the binding? What is the clean solution to passing char * variables of arbitrary length via subroutine/function arguments?

  • Check out https://stackoverflow.com/questions/21174570/holding-a-pointer-to-a-c-function-inside-a-fortran-derived-type/21183315#21183315. You are missing the VALUE attribute on the Fortran side. – IanH Jan 30 '22 at 06:12
  • Thanks for that! I think you're right there. This time I get an invalid memory reference within the C function. – Tam Loughran Jan 30 '22 at 08:25

1 Answers1

0

You do not allocate the memory for the string anywhere. You are just passing an undefined pointer to the C function and then the C function tries to copy some character data to this undefined address.

Either pre-allocate the string in Fortran, pass its length to C and use strncpy or malloc the string in C but then you will have to free it from C.


For example:

#include <string.h>

void copy_string(char *string, int n) {
    strncpy(string, "sand", n);
}
program main
    use iso_c_binding
    implicit none
    interface
        subroutine c_copy_string(string, n) bind (C, name='copy_string')
            import
            type (c_ptr), value :: string
            integer(c_int), value :: n
        end subroutine
    end interface

    type (c_ptr) :: c_string
    character(c_char), pointer :: local_string(:)

    allocate(local_string(10))
    c_string = c_loc(local_string)
    call c_copy_string(c_string, int(size(local_string), c_int))
    
    write (*,*) local_string
    deallocate(local_string)
end program main
> gfortran -fsanitize=address,undefined make_string.c main.f90
> ./a.out 
 sand

If you really want to "make" the string in C, you have to make malloc the space for it. In that case, you need to pass the address by reference. Then then you either also have to pass the length or make a c_strlen function that finds out how large a null-delimited C string is (like here or here or here). There are examples of that already existing on the internet so it is not very useful to write all that from scratch here. The c_interface_module from fortranwiki could be very helpful here and if nothing, it shows good examples how to pass the strings back and forth between C and Fortran.

Remember, the string then has to be deleted from C, if it was malloced there.

  • I tried adding a `malloc` in the `make_string function`, but that didn't work. For the other option, I don't understand how to allocate space for a `type (c_ptr)` in Fortran. – Tam Loughran Jan 30 '22 at 09:48
  • You do not allocate space for `type(c_ptr)`, you allocate `character(c_char), dimenson(:), pointer`. Regarding your C malloc, "it did not work" does not tell anything useful. If you want to pursue this path, you need to show your code and your error messages. Consider enabling address sanitizations. – Vladimir F Героям слава Jan 30 '22 at 10:03
  • But you really cannot just copy some data to some random address where some undefined pointer points. You have to allocate the memory *somehow*. There are many ways how that can be done. You could also use a static string. – Vladimir F Героям слава Jan 30 '22 at 10:04
  • C malloc won't work as you are passing the c_ptr by value - assigning a new value to it won't be reflected in the calling function. – Ian Bush Jan 30 '22 at 10:41
  • Yes, thats in the other option that I am just editing in. – Vladimir F Героям слава Jan 30 '22 at 10:43
  • I see now. `c_string = c_loc(local_string)` was the gap in my knowledge that I needed. Many thanks! – Tam Loughran Jan 30 '22 at 11:35