Search code examples
c++fortraneigen3

Why do you get a segmentation fault when trying to access a ndarray defined in c++ in fortran with c_f_pointer like this?


I have a fortran module, which is part of a c++ program. This module has a global variable, which is a 3d array, and which I would like to have pointed to a 3d Tensor I am defining in c++:

test.cpp:

#include <Eigen/Dense>
#include <unsupported/Eigen/CXX11/Tensor>

extern "C" {
  void f_link_global_variable(double* data, int* dims, int* strides);
}
int main(){
  Eigen::Tensor<double, 3> tensor(1,2,3);
  tensor.setZero();
  tensor(0,0,0) = 1;
  std::array<int, 3> dims = {tensor.dimension(2), tensor.dimension(1), tensor.dimension(0)};
  int n_dims = 3;
  std::cout << "tensor before passing: " << tensor << std::endl; // prints 1 0 0 0 0 0

  f_link_global_variable(tensor.data(), dims.data(), &n_dims);
  std::cout << "tensor after passing: " << tensor << std::endl;
}

stub_fortran_interface.f90:

module fortran_testmodule
  use iso_c_binding
  implicit none
  double precision, pointer :: my_global_ndarray(:,:,:)

contains

subroutine link_global_variable(c_pointer, dims, n_dims) bind(C, name="f_link_global_variable")
  type(c_ptr), intent(in) :: c_pointer
  integer(c_int), intent(in) :: dims(*)
  integer(c_int), intent(in) :: n_dims
  integer, allocatable :: shape_(:)
  integer :: i
  
  allocate(shape_(n_dims))
  do i = 1, n_dims
    shape_(i) = dims(i)
  end do

  call c_f_pointer(c_pointer, my_global_ndarray, shape_)
  ! generates segfault:
  print *, "array in fortran:", my_global_ndarray
  my_global_ndarray(1,1,2) = 3
end subroutine link_global_variable

end module fortran_testmodule

I am linking these files with

enable_language(Fortran)
add_library(stub_fortran_interface SHARED stub_fortran_interface.f90)
target_compile_options(stub_fortran_interface PUBLIC "-fvisibility=default")

add_executable(test test.cpp)
target_link_libraries(test_cpp_fortran_interface
                      PUBLIC
                      stub_fortran_interface)

When I try to print the newly allocated public array in fortran, I get a segmentation fault. Why am I not able to access the values I am defining in my c++ code? Note that the c++ Eigen library uses by default the same col-major format as Fortran, so the issue is not related to the alignment of the array values.

Since this code uses both fortran and c++, I am unable to provide working code in a compiler explorer, so I would be grateful if you could let me know if there is anything that is not working properly.


Solution

  • When you pass a pointer from C/C++, what you get on the Fortran side is the pointed object, not the pointer itself. This is inherent to the way Fortran is passing argument, always by reference (contrary to C that passes by value).

    And you don't need the shape array.

    So on the Fortran side you should have rather:

    subroutine link_global_variable(my_global_1darray, dims, n_dims) bind(C, name="f_link_global_variable")
      real(c_double), intent(in) :: my_global_1darray(*)
      integer(c_int), intent(in) :: dims(n_dims)
      integer(c_int), intent(in) :: n_dims   ! is necessarily 3
    !...
    my_global_ndarray(1:dims(1),1:dims(2),1:dims(3)) => my_global_1darray(1:prod(dims))
    
    

    Note that your approach with type(c_ptr) would work if you were adding the value attribute to the argument, meaning that you want to receive the pointer itself.

    subroutine link_global_variable(c_pointer, dims, n_dims) bind(C, name="f_link_global_variable")
      type(c_ptr), intent(in), value :: c_pointer