为何用c_f_pointer在Fortran访问C++ Eigen Tensor会触发段错误?
我有一个作为C程序组成部分的Fortran模块,该模块包含一个全局三维数组,希望将其指向C中定义的三维Eigen Tensor。
代码文件
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
编译链接方式
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)
问题
当我尝试在Fortran中打印新绑定的全局数组时触发了段错误,已知C的Eigen库默认采用与Fortran相同的列主序格式,因此问题与数组值的对齐无关,请问为何无法访问C代码中定义的值?
核心原因:维度顺序不匹配
你在C++中传递的维度顺序完全搞反了。Eigen Tensor和Fortran数组都采用列主序(第一个索引变化最快),但你构造dims数组时,把Eigen的维度顺序反转成了{tensor.dimension(2), tensor.dimension(1), tensor.dimension(0)},导致Fortran拿到的数组形状与实际内存布局完全不匹配,访问时直接越界触发段错误。
举个具体例子:你的Eigen Tensor是(1,2,3),总元素数为6,内存中元素按(0,0,0) → (0,1,0) → (0,0,1) → (0,1,1) → (0,0,2) → (0,1,2)的顺序排列。但你传给Fortran的形状是(3,2,1),Fortran会认为这是一个3×2×1的数组,当你访问my_global_ndarray(1,1,2)时,计算出的内存位置会超出实际的6个元素范围,直接触发段错误。
修正步骤
调整C++中的维度传递顺序
将dims数组改为与Eigen Tensor的维度顺序一致:std::array<int, 3> dims = {tensor.dimension(0), tensor.dimension(1), tensor.dimension(2)};这样Fortran拿到的形状是
(1,2,3),和实际内存布局完全匹配。修正CMake中的目标名错误
你的CMake代码中,target_link_libraries的第一个参数写错了,应该对应可执行文件test,而不是test_cpp_fortran_interface:target_link_libraries(test PUBLIC stub_fortran_interface)验证索引对应关系
修正后,Fortran中的my_global_ndarray(1,1,1)对应Eigen的tensor(0,0,0),my_global_ndarray(1,2,1)对应tensor(0,1,0),my_global_ndarray(1,1,2)对应tensor(0,0,1),以此类推,所有访问都不会越界。
修正后的效果
修改完成后,Fortran可以正常打印数组内容,修改my_global_ndarray的值也会同步到C的Eigen Tensor中,C端能正确输出修改后的结果。
内容的提问来源于stack exchange,提问作者Yes

