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
否则编译会有问题
查看全文
相关阅读:
理解WebKit和Chromium: Web应用和Web运行环境
理解WebKit和Chromium: 网页渲染的基本过程
【闲谈】我的大学
使用GDAL将下载的Google卫星图像转为带坐标的tif
Linux下使用GDAL进行开发(automake使用)
Linux下编译GDAL
【Unity技巧】统一管理回调函数——观察者模式
【Unity技巧】使用单例模式Singleton
【Unity插件】LitJson杂谈
理解WebKit和Chromium:Chromium资源磁盘缓存
原文地址:https://www.cnblogs.com/vspiders/p/7399123.html
最新文章
统计节点个数
寻找宝藏
翻转数据
树的查找
打牌
最近公共祖先
最长连续等差子数列
IP地址
数制转换(王道)
找犯人——蛮力法(算法)
热门文章
【英文文档】 Installing Go from source Go语言官方编译指南 2019.02.27
Ubuntu 18.04 设置开机启动脚本 rc.local systemd
Ubuntu vim下 实现函数跳转功能
【转】Linux C下非特定波特率的配置和使用
Linux 内核源代码根目录
转: Linux 系统调用sysconf 获取系统配置信息
linux中ldconfig的使用介绍
解决ubuntu中arm-linux-gcc not found
解决 Ubuntu 经常 卡死
使用 boot-repair 对 Windows + Ubuntu 双系统引导修复
Copyright © 2011-2022 走看看