本章论述 Fortran 与 C 的互操作性方面的问题,内容仅适用于 Sun Studio Fortran 95 和 C 编译器的特定情况。
11.9 Fortran 2003 与 C 的互操作性简要说明了 Fortran 2003 标准第 15 部分中提到的 C 绑定功能。(此标准可以从国际 Fortran 标准 Web 站点 http://www.j3-fortran.org 获得)。Fortran 95 编译器实现了标准中所述的这些功能。
如不特别注明,32 位 x86 处理器视为与 32 位 SPARC 处理器等同。对于 64 位 x86 处理器和 64 位 SPARC 处理器也是如此,只是 x86 系统未定义 REAL*16 和 COMPLEX*32 数据类型,这些数据类型只能用于 SPARC。
大多数 C-Fortran 接口必须在以下这些方面全部保持一致:
函数和子例程的定义及调用
数据类型的兼容性
参数传递(按引用或按值)
参数的顺序
过程名(大写、小写或带有结尾下划线 (_))
向链接程序传递正确的库引用
某些 C-Fortran 接口还必须符合:
数组索引及顺序
文件描述符和 stdio
文件权限
函数一词在 C 和 Fortran 中有不同的含义。根据具体情况做出选择很重要:
当 Fortran 例程调用 C 函数时:
如果被调用的 C 函数返回一个值,则将其作为函数从 Fortran 中调用。
如果被调用的 C 函数不返回值,则将其作为子例程调用。
当 C 函数调用 Fortran 子程序时:
如果被调用的 Fortran 子程序是一个函数,则将其作为一个返回兼容数据类型的函数从 C 中调用。
如果被调用的 Fortran 子程序是一个子例程,则将其作为一个返回 int(与 Fortran INTEGER*4 兼容)或 void 值的函数从 C 中调用。如果 Fortran 子例程使用交替返回,则会返回一个值,这种情况下它是 RETURN 语句中的表达式的值。如果 RETURN 语句中没有出现表达式,但在 SUBROUTINE 语句中声明了交替返回,则会返回零。
表 11–2 总结了 Fortran 95(与 C 比较)数据类型的数据大小和缺省对齐。该表假设未应用影响对齐或提升缺省数据大小的编译选项。请注意以下事项:
C 数据类型 int、long int 和 long 在 32 位环境下是等同的(4 字节)。但是,在 64 位环境下 long 和指针为 8 字节。这称为 LP64 数据模型。
在 64 位 SPARC 环境下,当用任意 -m64 选项进行编译时,REAL*16 和 COMPLEX*32 与 16 字节边界对齐。
标有 4/8 的对齐表示缺省情况下与 8 字节边界对齐,但在 COMMON 块中与 4 字节边界对齐。COMMON 中的最大缺省对齐为 4 字节。当用 -m64 选项进行编译时,4/8/16 表示与 16 字节边界对齐。
REAL(KIND=16)、REAL*16、COMPLEX(KIND=16)、COMPLEX*32 只能用于 SPARC 平台。
数组和结构的元素及字段必须兼容。
不能按值传递数组、字符串或结构。
可以在调用点使用 %VAL(arg),按值将参数从 Fortran 95 例程传递到 C 例程。假如 Fortran 例程具有一个显式接口块,该接口块用 VALUE 属性声明了伪参数,则可以按值将参数从 C 传递到 Fortran 95。
数值序列类型的组件的对齐方式与通用块的对齐方式相同,也会受到 -aligncommon 选项的影响。数值序列类型是这样一种序列类型:其中所有组件的类型为缺省整数、缺省实数、双精度实数、缺省复数或缺省逻辑,而不是指针。
在大多数情况下,非数值序列类型的数据类型组件以自然对齐的方式对齐,但 QUAD 变量除外。对于四精度变量,32 位 SPARC 平台和 64 位 SPARC 平台之间的对齐方式不同。
在所有平台上,用 BIND(C) 属性定义的 VAX 结构和数据类型的组件始终与 C 结构具有相同的对齐方式。
Fortran 95 数据类型 |
C 数据类型 |
大小 |
对齐 |
|
---|---|---|---|---|
BYTE x |
char x |
1 |
1 |
|
CHARACTER x |
unsigned char x ; |
1 |
1 |
|
CHARACTER (LEN=n) x |
unsigned char x[n] ; |
n |
1 |
|
COMPLEX x |
struct {float r,i;} x; |
8 |
4 |
|
COMPLEX (KIND=4) x COMPLEX (KIND=8) x COMPLEX (KIND=16) x (SPARC) |
struct {float r,i;} x; struct {double dr,di;} x; struct {long double, dr,di;} x; |
8 16 32 |
4 4/8 4/8/16 |
|
DOUBLE COMPLEX x |
struct {double dr, di;} x; |
16 |
4/8 |
|
DOUBLE PRECISION x |
double x ; |
8 |
4 |
|
REAL x |
float x ; |
4 |
4 |
|
REAL (KIND=4) x REAL (KIND=8) x REAL (KIND=16) x (SPARC) |
float x ; double x ; long double x ; |
4 8 16 |
4 4/8 4/8/16 |
|
INTEGER x |
int x ; |
4 |
4 |
|
INTEGER (KIND=1) x INTEGER (KIND=2) x INTEGER (KIND=4) x INTEGER (KIND=8) x |
signed char x ; short x ; int x ; long long int x; |
1 2 4 8 |
4 4 4 4 |
|
LOGICAL x |
int x ; |
4 |
4 |
|
LOGICAL (KIND=1) x LOGICAL (KIND=2) x LOGICAL (KIND=4) x LOGICAL (KIND=8) x |
signed char x ; short x ; int x ; long long int x; |
1 2 4 8 |
4 4 4 4 |
C 和 Fortran 在区分大小写方面采取截然相反的处理方法:
C 区分大小写-大小写很重要。
Fortran 在缺省情况下忽略大小写。
f95 缺省通过将子程序名转换成小写来忽略大小写。除了字符串常量以外,它会将所有大写字母都转换成小写字母。
对于大/小写问题,有两种常用解决方案:
在 C 子程序中,使 C 函数名全为小写。
用 -U 选项编译 Fortran 程序,该选项会通知编译器保留函数/子程序名称的现有大/小写区别。
只能采用这两种解决方案中的一种,不能同时采用。
本章大多数示例的 C 函数名均采用小写字母,并且没有使用 f95-U 编译器选项。
Fortran 编译器通常会在入口点定义和调用中都出现的子程序名末尾追加一个下划线 (_)。该惯例不同于具有相同的用户指定名称的 C 过程或外部变量。几乎所有 Fortran 库过程名都有两个前导下划线,以减少与用户指定的子例程名的冲突。
对于下划线问题,有三种常用解决方案:
只能使用上述解决方案中的一种。
本章的示例都可以使用 BIND(C) 属性声明来避免下划线。BIND(C) 声明可从 Fortran 调用的 C 外部函数,以及可从 C 中作为参数调用的 Fortran 例程。Fortran 编译器在处理外部名称时通常不追加下划线。BIND(C) 必须出现在每个包含这样的引用的子程序中。惯常用法是:
FUNCTION ABC EXTERNAL XYZ BIND(C) ABC, XYZ |
在此处,用户不仅指定 XYZ 是外部 C 函数,而且还指定 Fortran 调用程序 ABC 应该可以从 C 函数调用。如果使用 BIND(C),C 函数不需要在函数名末尾追加下划线。
通常,Fortran 例程按引用传递参数。在调用中,如果非标准函数 %VAL() 中包含一个参数,则调用例程会按值传递该参数。
Fortran 95 按值传递参数的标准方法是通过 VALUE 属性和 INTERFACE 块。请参见 11.4 按值传递数据参数。
C 通常按值传递参数。如果在参数前加上表示“和”的符号 (&),C 会使用指针按引用传递参数。C 总是按引用传递数组和字符串。
除字符串参数之外,Fortran 和 C 均以相同的顺序传递参数。但对于每个字符型参数,Fortran 例程都会传递一个附加参数,用以指定串长度。这些参数在 C 中是 long int 数量,按值进行传递。
参数顺序为:
与每个参数相应的地址(数据或函数)
与每个字符参数对应的 long int(字符串长度的完整列表位于其他参数的完整列表之后)
示例:
Fortran 代码片段: |
等价的 C 代码片段: |
||
---|---|---|---|
|
|
C 数组总是从 0 开始,而 Fortran 数组在缺省情况下是从 1 开始。有两种常用的索引处理方法。
如上述示例所示,可以使用 Fortran 缺省设置。此时,Fortran 元素 B(2) 等同于 C 元素 b[1]。
可以指定 Fortran 数组 B 以 B(0) 开始,如下所示:
INTEGER B(0:2) |
这样,Fortran 元素 B(1) 就等同于 C 元素 b[1]。
Fortran 数组按列主顺序存储:A(3,2)
A(1,1) A(2,1) A(3,1) A(1,2) A(2,2) A(3,2) |
C 数组按行主顺序存储:A[3][2]
A[0][0] A[0][1] A[1][0] A[1][1] A[2][0] A[2][1] |
这对于一维数组不存在任何问题。但对于多维数组,应注意下标在所有引用和声明中是如何出现和使用的-可能需要做些调整。
例如,在 C 中进行部分矩阵操作,而后在 Fortran 中完成余下部分,这样做可能会产生混淆。最好是将整个数组传递给另一语言中的例程,然后在该例程中执行所有矩阵操作,以避免在 C 和 Fortran 中各执行部分操作的情况。
Fortran I/O 通道采用的是单元号。底层 SunOS 操作系统不处理单元号,而是处理文件描述符。Fortran 运行时系统会不断变换,所以大多数 Fortran 程序没必要识别文件描述符。
许多 C 程序都使用一组称为标准 I/O(即 stdio)的子例程。有许多 Fortran I/O 函数也使用标准 I/O,而后者又使用操作系统 I/O 调用。下表列出了这些 I/O 系统的某些特性。
表 11–2 Fortran 与 C 之间的 I/O 比较
|
Fortran 单元 |
标准 I/O 文件指针 |
文件描述符 |
---|---|---|---|
文件打开 |
为读写打开 |
为读打开、为写打开、为读写打开,或者为追加打开;请参见 open(2) |
为读打开、为写打开或同时为读写打开 |
属性 |
已格式化或未格式化 |
始终未格式化,但可用格式解释例程进行读或写 |
始终未格式化 |
访问 |
直接或顺序 |
直接访问(如果物理文件的表示是直接访问),但总是可以按顺序读取 |
直接访问(如果物理文件的表示是直接访问),但总是可以按顺序读取 |
结构 |
记录 |
字节流 |
字节流 |
形式 |
0-2147483647 间的任意非负整数 |
指向用户地址空间中结构的指针 |
0-1023 间的整数 |
要链接正确的 Fortran 和 C 库,请使用 f95 命令调用链接程序。
示例 1:用编译器进行链接:
demo% cc -c someCroutine.c demo% f95 theF95routine.f someCroutine.o <- 链接步骤 demo% a.out 4.0 4.5 8.0 9.0 demo% |
用 f95 编译的主程序在程序启动时会调用库中的伪初始化例程 f90_init。库中的这些例程是不进行任何操作的伪例程。编译器生成的调用将指针传递到程序的参数和环境。这些调用会提供软件挂钩,您可以在 C 中用软件挂钩提供自己的例程,以便在程序启动之前以任何定制方式初始化程序。
这些初始化例程的一种可能用途是,为国际化 Fortran 程序调用 setlocale。由于 setlocale 在 libc 以静态方式链接时不起作用,因此只有以动态方式链接了 libc 的 Fortran 程序才能进行国际化。
库中 init 例程的源代码如下
void f90_init(int *argc_ptr, char ***argv_ptr, Char ***envp_ptr) {} |
f90_init 由 f95 主程序调用。参数分别被设置为 argc、argv 和 envp 的地址。
在 Fortran 例程和 C 过程之间传递数据的标准方法是按引用传递。对于 C 过程而言,Fortran 子例程或函数调用就像是一个所有参数均用指针表示的过程调用。唯一特殊的是 Fortran 将字符串和函数作为参数及 CHARACTER*n 函数的返回值进行处理的方式。
对于简单数据类型(非 COMPLEX 或 CHARACTER 串),将 C 例程中的每个关联参数作为指针定义或传递:
表 11–3 传递简单数据类型
Fortran 调用 C |
C 调用 Fortran |
||
---|---|---|---|
|
|
将 Fortran COMPLEX 数据项作为指针传递到具有两种浮点或两种双精度数据类型的 C 结构:
表 11–4 传递 COMPLEX 数据类型
Fortran 调用 C |
C 调用 Fortran |
||
---|---|---|---|
|
|
在 64 位 环境下,在寄存器中返回 COMPLEX 值。
由于没有标准接口,因此不推荐在 C 与 Fortran 例程间传递字符串。不过,请注意以下方面:
所有 C 字符串均按引用传递。
Fortran 调用会为参数列表中具有字符类型的每个参数传递一个附加参数。此额外参数给出串长度,它等同于按值传递的 C 长整数。(这要依具体实现而定。)额外的串长度参数出现在调用中的显式参数之后。
下例展示了具有字符串参数的 Fortran 调用及其等同的 C 调用:
表 11–5 传递 CHARACTER 串
Fortran 调用: |
等价的 C 调用: |
||
---|---|---|---|
|
|
如果在被调用例程中不需要串长度,则可以忽略额外的参数。但要注意,Fortran 不会自动以 C 期望的显式空字符来终结字符串。该终结符必须由调用程序添加。
字符数组调用与单个字符变量调用看起来一样。会传递数组的起始地址,所使用的长度是数组中单个元素的长度。
在 C 中数组下标以 0 开始。
表 11–6 传递一维数组
Fortran 调用 C |
C 调用 Fortran |
||
---|---|---|---|
|
|
C 与 Fortran 间的行列转换。
表 11–7 传递二维数组
Fortran 调用 C |
C 调用 Fortran |
||
---|---|---|---|
|
|
只要相应的元素是兼容的,便可以将 C 和 Fortran 95 派生类型传递给彼此的例程。(f95 接受传统的 STRUCTURE 语句。)
表 11–8 传递传统 FORTRAN 77 STRUCTURE 记录
Fortran 调用 C |
C 调用 Fortran |
||
---|---|---|---|
|
|
请注意,在所有平台上 Fortran 77 (VAX) 结构与 C 结构的对齐方式始终相同。但是,平台之间对齐方式会有所变化。
表 11–9 传递 Fortran 95 派生类型
Fortran 95 调用 C |
C 调用 Fortran 95 |
||
---|---|---|---|
|
|
请注意,Fortran 95 标准要求派生类型定义中有 SEQUENCE 语句,以确保编译器保持存储序列的顺序。
在所有平台上,数值序列类型的组件缺省情况下与字(4 字节)边界对齐。这与 x86 平台上 C 结构的对齐方式相匹配,但是不同于 SPARC 平台上 C 结构的对齐方式。使用 -aligncommon 选项可更改数值序列类型的对齐方式,以便与 C 结构相匹配。使用 -aligncommon=8 匹配 32 位 SPARC C 结构,使用-aligncommon=16 匹配 64 位 SPARC。
未使用 SEQUENCE 显式声明的派生类型与 SPARC 平台上的 C 结构对齐方式相同,但与 x86 平台上的对齐方式不同。这种对齐方式不随编译器选项而改变。
由于 Fortran 例程按引用传递参数,因此可将 FORTRAN 77 (Cray) 指针作为指针的指针传递给 C 例程。
表 11–10 传递 FORTRAN 77 (Cray) 指针
Fortran 调用 C |
C 调用 Fortran |
||
---|---|---|---|
|
|
C 指针与 Fortran 95 标量指针兼容,但与数组指针不兼容。
Fortran 95 用标量指针调用 C |
||
---|---|---|
Fortran 95 例程:
C 例程:
|
Cray 与 Fortran 95 指针间的主要区别是 Cray 指针的目标始终是已命名的。在许多上下文中,声明 Fortran 95 指针会自动标识其目标。另外,被调用 C 例程还需要显式 INTERFACE 块。
要将 Fortran 95 指针传递给数组或数组段,需要特定的 INTERFACE 块,如下例所示:
Fortran 95 例程: INTERFACE SUBROUTINE S(P) integer P(*) END SUBROUTINE S END INTERFACE integer, target:: A(0:9) integer, pointer :: P(:) P => A(0:9:2) !! pointer selects every other element of A call S(P) ... C 例程: void s_(int p[]) { /* change middle element */ p[2] = 444; } |
请注意,由于 C 例程 S 不是 Fortran 95 例程,因此不能在接口块中将其定义成假定的形状 (integer P(:))。如果 C 例程需要知道数组的实际大小,必须将其作为参数传递给 C 例程。
另请注意,C 与 Fortran 间的下标编排不同,C 数组以下标 0 开始。
从 C 中调用时,Fortran 95 程序应在伪参数中使用 VALUE 属性,并且应为从 Fortran 95 中调用的 C 例程提供一个 INTERFACE 块。
表 11–11 在 C 与 Fortran 95 之间传递简单数据元素
Fortran 95 调用 C |
C 调用 Fortran 95 |
||
---|---|---|---|
|
|
请注意,如果要以不同的数据类型作为实际参数来调用 C 例程,应该在接口块中包含 !$PRAGMA IGNORE_TKR I,以防止编译器在实际参数和伪参数之间要求类型、类别和等级匹配。
对于传统 Fortran 77,按值调用仅对简单数据可用,并且只能为调用 C 例程的 Fortran 77 例程所用。无法做到让 C 例程调用 Fortran 77 例程并按值传递参数。数组、字符串或结构最好是按引用传递。
要将值从 Fortran 77 例程传递到 C 例程,请使用非标准 Fortran 函数 %VAL(arg) 作为调用中的一个参数。
在以下示例中,Fortran 77 例程按值传递 x,按引用传递 y。C 例程同时增加了 x 和 y,但只有 y 发生了改变。
Fortran 调用 C |
|||
---|---|---|---|
Fortran 例程:
C 例程:
编译并运行会产生以下输出结果:
|
返回 BYTE、INTEGER、REAL、LOGICAL、DOUBLE PRECISION 或 REAL*16 类型值的 Fortran 函数与返回兼容类型的 C 函数是等同的(请参见表 11–1)。字符型函数的返回值存在两个额外参数,复数型函数的返回值存在一个额外参数。
下例返回一个 REAL 或 float 值。BYTE、INTEGER、LOGICAL、DOUBLE PRECISION 和 REAL*16 的处理方式类似:
表 11–12 返回 REAL 或 Float 值的函数
Fortran 调用 C |
C 调用 Fortran |
||
---|---|---|---|
|
|
COMPLEX 数据的互操作性情况在 SPARC 32 位和 64 位实现之间有所不同。
在 32 位平台上,返回 COMPLEX 或 DOUBLE COMPLEX 的 Fortran 函数等同于具有一个指向内存中返回值的附加第一参数的 C 函数。Fortran 函数及其相应的 C 函数的一般样式如下:
Fortran 函数 |
C 函数 |
||
---|---|---|---|
|
|
表 11–13 返回 COMPLEX 数据的函数(32 位 SPARC)
Fortran 调用 C |
C 调用 Fortran |
||
---|---|---|---|
|
|
在 64 位 SPARC 环境下,在浮点寄存器中返回 COMPLEX 值: 在 %f0 和 %f1 中返回 COMPLEX 和 DOUBLE COMPLEX,在 %f0、%f1、%f2 和 %f3 中返回 COMPLEX*32。对于 64 位 SPARC,返回结构(其字段均为浮点型)的 C 函数将在浮点寄存器中返回该结构,但条件是最多需要 4 个这样的寄存器进行此操作。在 64 位 SPARC 平台上,Fortran 函数及其相应的 C 函数的一般样式如下:
Fortran 函数 |
C 函数 |
---|---|
COMPLEX FUNCTION CF(a1, a2, ..., an) |
struct {float r,i;} cf_ (a1, a2, ..., an) |
表 11–14 返回 COMPLEX 数据的函数(64 位 SPARC)
Fortran 调用 C |
|
---|---|
|
|
C 调用 Fortran |
|
|
不鼓励在 C 与 Fortran 例程之间传递字符串。但是,具有字符串值的 Fortran 函数等同于具有两个附加第一参数(数据地址和串长度)的 C 函数。Fortran 函数及其相应的 C 函数的一般样式如下:
Fortran 函数 |
C 函数 |
|
---|---|---|
CHARACTER*n FUNCTION C(a1, ..., an) |
|
以下是一个示例
表 11–15 返回 CHARACTER 串的函数
Fortran 调用 C |
C 调用 Fortran |
||
---|---|---|---|
|
|
在本例中,C 函数和调用 C 例程必须在列表(字符参数的长度)末尾提供两个额外的初始参数(指向结果字符串和串长度的指针)和一个附加参数。请注意,在从 C 中调用的 Fortran 例程中,需要显式添加一个末尾空字符。缺省情况下,Fortran 字符串不以空字符终结。
可以在 C 中使用全局 struct 来模拟 Fortran 带标号的 COMMON。
表 11–16 模拟带标号的 COMMON
Fortran COMMON 定义 |
C“COMMON”定义 |
||
---|---|---|---|
|
|
请注意,C 例程建立的外部名称必须以下划线结束,才能与 Fortran 程序创建的块进行链接。另请注意,可能需要使用 C 指令 #pragma pack 来获得与 Fortran 相同的补白。
缺省情况下,f95 会将通用块中的数据与至多 4 字节边界进行对齐。要获得通用块中所有数据元素的自然对齐并符合缺省结构对齐,请在编译 Fortran 例程时使用 -aligncommon=16。
不推荐混合使用 Fortran I/O 和 C I/O(同时从 C 和 Fortran 例程发出 I/O 调用)。最好是全部执行 Fortran I/O 或全部执行 C I/O,而不是两者同时使用。
Fortran I/O 库大部分是在 C 标准 I/O 库之上实现的。Fortran 程序中的每一个打开单元都有相关联的标准 I/O 文件结构。对于 stdin、stdout 和 stderr 流,不需要显式引用该文件结构,所以可以共享它们。
如果 Fortran 主程序调用 C 来执行 I/O,Fortran I/O 库必须在程序启动时进行初始化,以便将单元 0、5 和 6 分别连接到 stderr、stdin 和 stdout。要对打开的文件描述符执行 I/O,C 函数必须考虑 Fortran I/O 环境。
请记住:即使主程序在 C 中,也应该用 f95 链接。
Fortran 77 的交替返回机制已经过时,如果考虑可移植性,不应再使用它。在 C 中没有与交替返回等同的机制,所以只需关注 C 例程调用具有交替返回的 Fortran 例程的情况。Fortran 95 接受 Fortran 77 的交替返回,但不鼓励使用它。
以下实现返回 RETURN 语句中表达式的 int 值。这依赖于具体实现,应避免使用。
表 11–17 交替返回
C 调用 Fortran |
运行示例 |
||
---|---|---|---|
|
C 例程接受从 Fortran 例程返回的值 2,因为它执行了 RETURN 2 语句。 |
Fortran 2003 标准草案(可从 http://www.j3-fortran.org 获得)提供了一种从 Fortran 95 程序中引用 C 编程语言定义的过程和全局变量的方法。反过来,它又提供了一种定义 Fortran 子程序或全局变量的方法,从而可以从 C 过程中引用它们。
根据设计,采用这些功能实现 Fortran 95 与 C 程序间的互操作性,可确保符合标准的平台间的可移植性。
Fortran 2003 为派生类型提供了 BIND 属性,并且提供了 ISO_C_BINDING 内在模块。利用此模块,可以访问 Fortran 程序的某些支持可互操作对象规范的命名常量、派生类型和过程。可从 Fortran 2003 标准获取详细信息。