c和fortran混编(基于GNULinux,转⾃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\n”);
}
这两个⽂件都可以编译通过
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 co llection嘛:)
不好意思,绕了⼀⼤圈。上⾯说到这两个⽂件都能独⽴编译(因为语法没错嘛),并且将编译出的⽬
标⽂件放在⼀起链接就可以了。这正是因为链接器(链接器其实是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,这个到哪⾥去?g cc总是会有很多默认链接的库和链接选项,这其中包括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.
成的,从⽽调⽤相应的编译器来编译。.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)这样才⼈性化。再说如果 fort ran需要⽤到⼀个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 -os
ample 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++。这也就是为什么时常有⼈问会出错的问题了,如果⽤到了c++库中的函数,那么当然要使⽤gcc -lstdc++ 才⾏了)
如果我们保持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
gnu编译器链接
gcc -lfrtbegin -lg2c main.o foo.o -osample
成了。(其实,当fortran不为主程序时,可以不⽤链接libfrtbegin,起码这个⼩程序不⽤)
这⾥讨论了混编的基本原理,就是让链接器到符号所在。从这点出发,⼀些混编问题都应该有了解决的思路。⾄于代参数的函数我没有涉及到,但我想都得从这个基本出发吧。还有些程序会使⽤动态链接库.so,那么应该使⽤nm的-D选项查看这些动态符号。(objdum的功能⽐nm更强⼤)
有很多东西很基础我还罗嗦了很久,让⼤家见笑了:)
还有⼀件事,那就是我这⾥链接采⽤了gcc -l的⽅式,更基本的是ld的⽅式,只要你知道链接哪些库,链接的顺序如何即可。但是为了简单安全⽅便,还是建议直接⽤相应的编译器完成链接⼯作(⽐如 for
tran就⽤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
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。关于这两个选项,我也着实搜了⼀阵,不是很好搜。当时看了眼原因,可能是有关编译器版本和字符⽅⾯的
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论