zoukankan      html  css  js  c++  java
  • c和fortran混编(基于GNU/Linux,转自 linzch)

    网 上说要分c为主程序和fortran为主程序两种情况讨论,其实我觉得不用,只要你了解生成可执行文件的机制。这个机制就是:不论是单一语言模块之间的 链接还是不同语言之间的混合链接,本质目的都是要链接器能找到定义于其他模块中的符号,如果全部找到,则链接成功,生成可执行的二进制文件。
    下面的内容比较基础,看烦了就跳过。
    比如简单的一个c程序:
    /* main.c */
    void FOO (); /* 其实按c语言的规定,调用程序之前不需声明,
    * c编译器会猜测函数的原型。需要在调用之前
    * 定义或者声明那是c++的风格
    */
    int main ()
    {
    FOO ();
    return 0;
    }
    /* foo.c */
    #include <stdio.h>
    void FOO ()
    {
    printf (“hello world ”);
    }
    这两个文件都可以编译通过
    gcc -c main.c
    gcc -c foo.c
    这里gcc只用来编译并不链接,因为单独链接其中一个文件都是不完整的。必须将两个目标文件(上面会生成main.o和foo.o)链接在一起。
    gcc -osample main.o foo.o
    这里gcc 因为看到文件后缀名为.o所以直接进行链接而不再进行编译。(gcc在编译时对文件的识别主要靠起后缀名。如果是gcc -osample main.c foo.c那么因为后缀名为.c所以会先编译,又因为没有-c选项所以会再链接。多说一点如果是gcc -osample main.c foo.o那么只编译main.c文件,再将编译后的临时目标文件与foo.o链接。gcc还会因为不同的后缀名采用不同的编译器进行编译具体参见man 或者info,因为他是compiler collection嘛:)
    不好意思,绕了一大圈。上面说到这两个文件都能独立编译(因为语法没错嘛),并且将编译出的目标文件放在一起链接就可以了。这正是因为链接器(链接器其实 是ld,gcc调用了它)在foo.o中找到了main.o中需要的foo的定义,并且在main.o中找到了main的定义。
    这里还牵扯到一个问题,那就是c是很纯朴的语言,c的函数在文本文件中是什么名字,那么编译出来在目标文件(一般是.o文件)中相应的那个函数还是那个名字(不会像c++一样为了重载在函数名前后加一大堆区分符)。
    比如用nm查看main.o和foo.o
    nm main.o
    U FOO
    00000000 T main
    U表示在main.o中符号foo是未定义的,需要从外部链接进来。T表示该符号存在于这个目标文件对应的文本文件中,说白了就是有定义的符号。
    nm foo.o
    00000000 T FOO
    U printf
    这样就可以看出,编译出的foo.o与原foo.c中的函数名都是FOO。main.c中调用了函数FOO,那么他编译出的这个函数的符号也还是FOO, 这样对函数FOO的供与求才能对的上,链接器能找到对得上的符号才能链接成功。有人说foo.o里还有一个未定义符号printf,这个到哪里去 找?gcc总是会有很多默认链接的库和链接选项,这其中包括c的标准库,而printf就在c标准库中。加上-v选项就可以看出来,gcc在编译和链接时 到底做了哪些事。
    又多说一点,如果一个函数有定义或者被调用,那么编译后在目标文件中就会有其相应的符号,因为要告诉链接器有这个供给,或者有这个需求嘛。如果一个函数仅仅有什么声明,那么是不会编译出它的符号的,因为它既不会给别人用,也不会用别人。
    那么,说了这么多其实还是为了明确一点:要让链接器找到在一个文件中需要的符号定义,那么链接就能成功,就能生成可执行文件了。这也是混编的关键!
    现在开始真真儿的了。
    将主程序main.c换成fortran的
    c main.f
    program test
    external FOO
    call FOO ()
    end
    还是原来的foo.c,也就是说由fortran调用c,仍旧是:
    gcc -c foo.c
    gcc -c main.f
    注意这里用的是gcc编译main.f(fortran程序),这是完全可以的。前面说了gcc是compiler collection,它会根据文件后缀来判断是什么语言写成的,从而调用相应的编译器来编译。.f的文件它自然会用g77或者f77之类的来编译。与 g77 -c main.f完全一样。为了链接成功,先来看看foo.o和main.o中都有什么符号
    nm main.o
    U foo_
    00000000 T MAIN__
    U s_stop
    nm foo.o
    00000000 T FOO
    U printf
    可以看出,main.o里需要用到符号名为foo_但foo.o里提供的是FOO——不匹配。一个办法就是依据上面说的c的纯朴性——写的什么名儿,编译 出就是什么名儿,从而直接改变foo.c中的函数名,改为void foo_ ()即可。这样链接时,main.o需要的foo_符号就能在foo.o中找到。
    但是把c的函数名改成这个样子,感觉总是别扭。应该看到是什么(.c中看到foo_)就用什么(而.f中用的是FOO)这样才人性化。再说如果 fortran需要用到一个c库,这个库里的函数不一定都是小写并且最后还带下划线。就像c++要用c库,也需要在声明这个库中的函数时使用extern “C”,使c++编译器在编译这个函数时生成的符号名是C风格而不是C++风格。所以我们也需要类似c++的做法改变fortran程序编译出来的符号 名。
    我不知道fortran是否有extern “C”之类的东东。但是编译fortran程序是有选项可选的。比如:
    gcc -fno-underscoring -fcase-preserve -c main.f
    这里加了两个选项,顾名思义前者用来去掉下划线,后者用来保持大小写。这下再
    nm main.o
    U FOO
    00000000 T MAIN__
    U s_stop
    这样就解决了c函数被fortran调用的问题了。
    但是因为main.o中还有一个未定义符号s_stop,而gcc默认只链接和c相关的库,所以这时使用gcc -osample main.o foo.o会报错,大概就是说s_stop未定义(unreferenced 或者 undefined)。一个简单的解决办法——使用g77链接main.o和foo.o。就好像gcc默认会链接c库一样,g77默认会链接 fortran的一些基本的,标准的库;另一个办法就是查明g77会链接哪些基本的,标准的fortran库,这也很简单在编译链接fortran程序时 加上-v选项。我看到的g77的比gcc多了这几个选项 -lfrtbegin -lg2c -lm,那么就是说g77链接了libfrtbegin,libg2c,libm,最后一个是数学库,前两个应该就是g77专用的了。所以
    gcc -lfrtbegin -lg2c -osample main.o foo.o
    (同样g++使用了-lstdc++,也就是libstdc++。这也就是为什么时常有人问gcc main.cc会出错的问题了,如果main.cc用到了c++库中的函数,那么当然要使用gcc -lstdc++ main.cc才行了)
    如果我们保持main.c不变,而将foo.c变为foo.f。也就是c调用fortran
    c foo.f
    SUBROUTINE FOO()
    print *,"hello world"
    END
    编译foo.f和main.c
    gcc -fno-underscoring -fcase-preserve -c foo.f
    gcc -c main.c
    链接
    gcc -lfrtbegin -lg2c main.o foo.o -osample
    (use gfortran main.o foo.o -osample)
    成了。(其实,当fortran不为主程序时,可以不用链接libfrtbegin,起码这个小程序不用)
    这里讨论了混编的基本原理,就是让链接器找到符号所在。从这点出发,一些混编问题都应该有了解决的思路。至于代参数的函数我没有涉及到,但我想都得从这个 基本出发吧。还有些程序会使用动态链接库.so,那么应该使用nm的-D选项查看这些动态符号。(objdum的功能比nm更强大)
    有很多东西很基础我还罗嗦了很久,让大家见笑了:)
    还有一件事,那就是我这里链接采用了gcc -l的方式,更基本的是ld的方式,只要你知道链接哪些库,链接的顺序如何即可。但是为了简单安全方便,还是建议直接用相应的编译器完成链接工作(比如 fortran就用g77),因为它们的链接顺序已经理好了(并且它们除了链接自己的库还链接c库,而gcc只链接c库,所以用它们不用担心链不到c库, 而用gcc会担心链不到专有的库)。像上面的例子最后的链接的使用g77最好,因为我的例子很简单,而你的有可能很复杂。
    还想起来一件事,关于网上使用的例子有__stdcall的,那都是关于win api的。
    我 对fortran一无所知,只知道其优势在于科学计算还比较方便,再有就是某领域的早期程序大多由fortran编写。出于重用方便的考虑,我们 现在要把fortran写的代码编译成动态链接库,然后通过C来调用。Windows下动态链接库是很常见的东西,linux下也有,换了一个名字,叫 standard object,大多形如lib*.so。SO文件可以通过编译器的-shared选项得到。
    比如,我们有一个fortran程序名为subf1.f,如下:
    subroutine sUbF1()
    print*,'hello world.'
    return
    end
    如果有一个C程序希望调用sUbF1(),就可以将 subf1.f 编译成为动态链接库。
    gcc -shared -o libf1.so subf1.f
    这个命令将产生libf1.so这个文件,此文件即是一个linux下的动态链接库。gcc会根据文件的扩展名来调用相应的编译器,不用你操心。此例中事实上实际的编译器是f77,我机器上没有f90。
    在main.c里面调用sUbF1(),如下:
    int main(){
    subf1_();
    return 0;
    }
    注意到我们调用的时候,名字变成了“ subf1_ ”。这是编译器(f77)的一个命名规则,没有为什么,它就是把你在fortran中的函数名字全转换成小写,然后在最后加一个下划线。 我昨天搜了很多版本,头昏脑胀,怎么调都说找不到,也没有想到要自己看看。今天一早突然想到用hex编辑器看一下就是了,于是一看,里面果然有真正的函数 名。后来看program版kb也给了正确的解答,很钦佩;伟大的康神还教导我抛弃hex编辑器,用nm,热泪盈眶……
    找到正确的函数名,直接调用就可以,好像你已经在你的C文件里实现了这个函数一样,不需要include任何东西,只需要在编译时告诉编译器你用了哪个动态链接库就可以了,如下:
    gcc -o out main.c libf1.so
    这时候编译器有可能会报告如下错误:
    libf1.so: undefined reference to 'do_lio'
    libf1.so: undefined reference to 'e_wsle'
    libf1.so: undefined reference to 's_wsle'
    这时在后面加上-lg2c -B108的选项应该就可以了,也就是
    gcc -o out main.c libf1.so -lg2c -B108
    这时会得到out的可执行文件。运行out,屏幕会输出hello world。关于这两个选项,我也着实搜了一阵,不是很好搜。当时看了眼原因,可能是有关编译器版本和字符方面的
    注意:
    用C++和fortran混合编程需要用一下命令连接.o文件
    g++ svm-train.o svm.o maintrain.o -oout1 -lgfortran
    否则编译会有问题
  • 相关阅读:
    结对开发:电梯调度(2)
    小组站立会议
    小组站立会议
    小组站立会议
    小组站立会议4月15日
    小组站立会议
    小组站立会议
    团队个人每天详细计划汇总
    Spring冲刺计划会议
    团队项目开发
  • 原文地址:https://www.cnblogs.com/vspiders/p/7399123.html
Copyright © 2011-2022 走看看