C 代码调用 Fortran 子程序时,子程序顶部出现分段错误

2022-01-12 00:00:00 gcc fortran gfortran segmentation-fault c++

我在文件 test-Q.cpp 中有 C++ 代码,它调用文件 getqpf.F 中的 Fortran 子例程.在文件 test-Q.cpp 中,我已将 Fortran 代码声明为外部代码,并使用 getqpf_() 名称修改约定调用该函数.gccgfortran 编译器正在 GNU/Linux 上使用.

I have C++ code in file test-Q.cpp that calls a Fortran subroutine in file getqpf.F. In file test-Q.cpp, I've declared the Fortran code as external, and I am calling the function using the getqpf_() name-mangling convention. The gcc and gfortran compilers are being used on GNU/Linux.

这是 C++ 文件顶部的片段:

Here is a snippet from the top of the C++ file:

extern "C" {
            void  getqpf_  (double *tri, 
                    int nsamp, 
                    int lwin,
                    int nfreqfit, 
                    double dt, 
                    float null, 
                    int L2,
                    double df,
                    double *qq, 
                    double *pf, 
                    double *ampls, 
                    double *work1, 
                    double *work2, 
                    double *work3, 
                    double *work4,
                    int mem, 
                    int morder, 
                    int nfs, 
                    double *xReal, 
                    double *xImag, 
                    double *xAbs,
                    double *x1,
                    int cen,
                    int top,
                    int bot, 
                    float cut,
                    int nfst,
                    int raw);  

        } // end

这是来自 Fortran 文件的相应片段:

Here is a corresponding snippet from the Fortran file:

   subroutine getqpf (tri, nsamp, lwin, nfreqfit, dt, null, L2, df,
     1                   qq, pf, ampls, work1, work2, work3, work4,
     2                   mem, morder, nfs, xReal, xImag, xAbs, x1,
     3                   cen,top,bot, cut,nfst,raw)



      integer  morder, lwin, nsamp, nfreqfit, delay, nfs

      real     tri(*)
      real     qq(*), pf(*), ampls(*)

      real * 8 work1(*), work2(*), work3(*), work4(*)
      real * 8 xReal(*), xImag(*), xabs(*), x1(*)

      real * 8 dt8, cut8, df8
      real     null, cut
      integer  nfst
      logical  mem, L2, cen, top, bot, raw


      integer nf

C program logic code starts here
          nf = nfreqfit
          delay = 0
          dt8  = dt
          cut8 = cut

Fortran 代码调用其他 C 代码函数.在使用 gfortrangcc 编译器的 GNU/Linux 上,我按以下方式编译并链接了所有文件:

The Fortran code calls other C-code functions. On GNU/Linux using the gfortran and gcc compilers I've compiled and linked all of the files in the following manner:

 g++ -c test-Q.cpp -I./boost/boost_1_52_0/ -g
 gcc -c paul2.c -g
 gcc -c paul2_L1.c -g
 gcc -c paul6.c -g
 gcc -c paul6_L1.c -g 
 gcc -c fit_slope.c -g
 gfortran -c getqpf.F -g
 g++ -o test-Q test-Q.o paul2.o paul2_L1.o paul6.o paul6_L1.o fit_slope.o getqpf.o -g

虽然我能够成功构建二进制文件,但在 nf = nfreqfit 行出现了段错误.它位于 Fortran 文件的最顶部.在二进制文件上运行 gdb 会产生以下输出:

Although I am able to build the binary successfully, there is a segfault that occurs at the line nf = nfreqfit. This is situated at the very top of the Fortran file. Running gdb on the binary produces the following output:

Program received signal SIGSEGV, Segmentation fault.
0x0000000000406fd3 in getqpf (tri=..., nsamp=Cannot access memory at address 0x3e9
) at getqpf.F:44
44        nf = nfreqfit

这里发生了什么,为什么会出现段错误?似乎没有在 C++ 代码和 Fortran 代码之间正确传递内存.

What is happening here, and why is there a segfault? It appears that memory is not being properly passed between the C++ code and the Fortran code.

更新

正如 IanH 在下面的答案中提到的,问题是由于没有通过引用传递参数.使用 C++,函数必须声明为:

As IanH mentions in the answer below, the problem is due to not passing arguments by reference. Using C++, the function must be declared as:

 extern"C" {
            void  getqpf_  (float *tri, 
                    int &nsamp, 
                    int &lwin,
                    int &nfreqfit, 
                    float &dt, 
                    float &null, 
                    int &L2,
                    float &df,
                    float *qq, 
                    float *pf, 
                    float *ampls, 
                    double *work1, 
                    double *work2, 
                    double *work3, 
                    double *work4,
                    int &mem, 
                    int &morder, 
                    int &nfs, 
                    double *xReal, 
                    double *xImag, 
                    double *xAbs,
                    double *x1,
                    int &cen,
                    int &top,
                    int &bot, 
                    float &cut,
                    int &nfst,
                    int &raw);  

        } // end 

注意与号的存在.然后,该函数可以在代码中调用为:

Note the presence of the ampersands. Then, the function can be called in the code as:

getqpf_ (tri,       
    nsamp, 
    lwin,
    nfreqfit, 
    dt, 
    null, 
    L2,
    df,
    qq, 
    pf, 
    ampls, 
    work1, 
    work2, 
    work3, 
    work4,
    mem, 
    morder, 
    nfs, 
    xReal, 
    xImag, 
    xAbs,
    x1,
    cen,
    top,
    bot, 
    cut,
    nfst,
    raw); 

注意nsamp等变量被声明为int nsamp = 1001.

推荐答案

在支持 MSB 关于使用 F2003 的 C 互操作性的建议时,请注意您的具体问题是通过引用传递/通过值传递不匹配(这仍然是即使在使用 C 互操作性时也必须考虑).典型的 Fortran 实现通过引用传递所有参数,而在 C(++) 中默认是按值传递.在 C++ 方面,请注意所有 int 和 float 参数以及一些 double 参数都缺少指针说明符 (*).这些参数是按值传递的――但 Fortran 方面没有任何东西可以表明这一点.在 F2003 之前,这通常是使用 Fortran 代码中的编译器特定指令来完成的.

While seconding M.S.B.'s recommendation about using F2003's C interoperability, note that your specific issue is a pass by reference/pass by value mismatch (which is still something that you have to consider even when using C interoperability). Typical Fortran implementations pass all arguments by reference, while in C(++) the default is by value. On the C++ side, note that all of the int and float arguments and some of the double arguments lack the pointer specifier (*). These arguments are passed by value - but there is nothing on the Fortran side to indicate that. Before F2003 this was usually done using compiler specific directives in the Fortran code.

使用 F2003 的 C 互操作,将参数传递给具有 BIND(C) 属性的过程的默认约定是通过引用.按值传递的参数需要在其声明中具有 VALUE 属性.

Using F2003's C interop, the default passing convention for arguments to procedures with the BIND(C) attribute is by reference. Arguments that are passed by value need to have the VALUE attribute in their declaration.

相关文章