Who are you?

C与Fortran混合编程

这篇文档主要是对C和Fortran的混合编程实现进行一个大致的学习与归纳。目前在work中主要是利用Fortran调用C函数。

基本概况

由于 GNU 的 Fortran 和 C 语言二者的函数彼此可以直接相互调用,所以混合编程可以非常容易地实现。只要你足够仔细,确保函数调用时传递的参数类型正确,函数就可以在两种语言间来回调用,就像它们是同一种语言一样。

下表中列出了 Fortran 的数据类型和它们在 C 中对应的类型。这张表在大多数平台下是没问题的,但是或许会有例外的情况发生。在你打算传递某种数据类型时,先编写一个简单的例子进行测试将是很明智的。

Paste_Image.png

注意

由于 Fortran 总是以引用的方式传递参数,而 C 则始终以地址方式传递数组,因此数组做参数时不需做任何修改。但是对多维数组来说,混合调用时其下标需要翻转,因为 Fortran 的数组是以列为主序(column-major order)而 C 数组以行为主序(row-major order)。

实例分析

简单案例

Fortran调用C函数

  • 主程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    ! C 表示注释
    C fortran2c.f
    C
    PROGRAM FORTRAN2C
    C
    CHARACTER*32 HELLO
    REAL PI
    C
    HELLO = "Hello C from Fortran"
    HELLO(21:21) = CHAR(0)
    C HELLO(21) = CHAR(0)
    PI = 3.14159
    CALL SHOWHIPI(HELLO,PI)
    END PROGRAM FORTRAN2C

    Tips:

    1. 可存储32个字符的 CHARACTER 型字符串变量 HELLO 中,存有 21 个字符,剩余部分用空格填充。
    2. 要将该字符串格式化为 C 语言标准的字符串,我们必须在实际字符串结束的位置插入一个 ascii 码为“0”字符作为结束标志。
    3. REAL 型浮点数 PI 存储方式和 C 中 float 型变量完全一样,因此可以直接传递给函数。
    4. 理解 Fortran 的参数总是通过引用传递是非常重要的,因此 C 函数接收到的总是被传递的变量的地址而不是变量值本身。
  • 调用程序

    1
    2
    3
    4
    5
    6
    /* showhipi.c */
    #include <stdio.h>
    void showhipi_(char *string,float *pi)
    {
    printf("%s\nPI=%f\n",string,*pi);
    }

    Tips:

    1. 在不同的平台下 Fortran 和 C 语言的命名惯例和数据类型的匹配关系是不同的。如同你在本例中看到的,在函数名后添加一下划线是必须的
  • 编译运行

    1
    2
    3
    4
    5
    $ gfortran -c fortran2c.f -o fortran2c.o
    $ gcc -c showhipi.c -o showhipi.o
    $ gfortran fortran2c.o showhipi.o -o fortran2c
    # OR
    $ gcc fortran2c.o showhipi.o -o fortran2c -lgfortranbegin -lgfortran

    Tips:

    1. gfortran只是gcc的一个前端

C调用Fortran函数

  • 主程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /* c2fortran.c */
    int main(int argc,char *argv[])
    {
    int i;
    float e = 2.71828;
    char hello[32];
    int length = sizeof(hello);
    strcpy(hello,"Hello Fortran from C");
    for(i=strlen(hello); i<length; i++)
    hello[i] = ' ';
    showhie_(hello,&length,&e);
    return(0);
    }

    Tips:

    1. 在 C 中,字符串的长度是由 null 字符的位置决定的,但在 Fortran 中,所有字符串的长度都是固定的。
    2. 由于 Fortran 没有办法确定传递给它字符串的长度,我们必须将字符串长度作为参数同时传递。本例中,字符数组用空格填充,数组大小作为第二个参数传递。
    3. 注意到三个参数都是通过指针传递的──因为 Fortran 总是期待地址而不是真实的值。
    4. 在被调用的子程序名后添加一下划线通常是必须的。
  • 调用程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    C showhie.f
    C
    SUBROUTINE SHOWHIE(HELLO,LENGTH,E)
    CHARACTER*(*) HELLO
    INTEGER LENGTH
    REAL E
    C
    WRITE(*,100) HELLO(1:LENGTH),LENGTH,E
    100 FORMAT(3X,A,2X,I3,4X,F6.4)
    RETURN
    END SUBROUTINE SHOWHIE
  • 编译运行

    1
    2
    3
    4
    5
    $ gfortran -c showhie.f -o showhie.o
    $ gcc -c c2fortran.c -o c2fortran.o
    $ gfortran c2fortran.o showhie.o -o c2fortran
    # OR
    $ gcc fortran2c.o showhipi.o -o fortran2c -lgfortranbegin -lgfortran

重点探讨

混合语言编程要注意的问题主要体现在: 函数调用和数据结构的存储。

注意及范例

注意事项:

  1. 在C与Fortran中数组存放顺序不同,因此,传送后必须进行转置
  2. 在C中数组元素下标从0开始,而在Fortran中则从1开始
  3. 在C与Fortran中传递的数组大小、类型必须完全相同、对应
  4. 在C中,形参中的数组元素不能采用动态数组,即不能用malloc分配内存

数组顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/* Fortran为主程序调用C子程序的例子如下: */
program test
external get3d
dimension u(12,11,10)
imax=10
jmax=11
zmax=12
ii=20
x=200.0
! 传递时需要将ijk转为jki
call get3d(imax,jmax,kmax,delx,bb,ii,x)
write(*,*) 'in F:', imax,jmax,kmax,delx,ii,x
do k=1,kmax
do j=1,jmax
do i=1,imax
write(*,*) i,j,k,u(k,j,i)
enddo
enddo
enddo
end
-------------------------------------------
#include
#include
void get3d_(int *LEN,int *DEP,int *HIG,float *deltaX, \
float u[12][11][10],int *ii,float *x)
{
int i,j,k;
*LEN=12;
*DEP=11;
*HIG=10;
*deltaX=1.0;
for (k = 0; k < *HIG; k++) {
for (j = 0; j < *DEP; j++) {
for (i = 0; i < *LEN; i++) {
u[i][j][k] = (float)(i+j+k);
printf("in C1: %f\n",u[i][j][k]);
}
}
}
printf("%d %f %d %f\n",*LEN,*DEP,*HIG,*deltaX,*ii,*x);
}

元素下标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/* C为主程序调用Fortran子程序的例子如下: */
#include
#include
int main()
{
extern void get3d_(int *LEN,float *deltaX,float bb[10],int *j,float *x);
int i,j,LEN;
float x,deltaX,bb[10];
LEN=325;
deltaX = 1.0;
for(i=0;i<10;i++)
{
bb[i]=i;
printf("in C1: %f\n",bb[i]);
}
printf("in C1: %d %f\n",LEN,deltaX);
get3d_(&LEN,&deltaX,bb,&j,&x);
printf("in C2: %d %f %d %f\n",LEN,deltaX,j,x);
// 下标从0开始,[0:9]
for(i=0;i<10;i++){
printf("in C2: %f\n",bb[i]);
}
}
-------------------------------------------
subroutine get3d(imax,delx,bb,j,x)
dimension bb(10)
j=20
x=200.0
! 下标从1开始,[1:10]
do i=1,10
bb(i)=bb(i)+1
enddo
write(*,*) 'in F:', imax,delx,j,x
write(*,*) 'in F:', bb
end

其他

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/* C调用Fortran: */
#include
void sub_fortran_(int *,float *,double *);
double function_fortran_(double *);
int main() {
int num_int;
float num_float;
double num_double;
double num;
num_int=3;
num_float=5.0;
sub_fortran_(&num_int,&num_float,&num_double);
num=function_fortran_(&num_double);
printf("num_int=%d\nnum_float=%f\nnum_double=%f\nnum=%f", \
num_int,num_float,num_double,num);
return 0; }
-------------------------------------------
/* Fortran90: */
subroutine Sub_Fortran(NumInt,NumFloat,NumDouble)
implicit none
integer :: NumInt
real :: NumFloat
real(8) :: NumDouble
NumDouble=NumFloat**NumInt
end subroutine
real(8) function Function_Fortran(NumDouble)
implicit none
real(8) :: NumDouble
Function_Fortran=sqrt(NumDouble)
end function
-------------------------------------------
/* Fortran2003: */
subroutine Sub_Fortran(NumInt,NumFloat,NumDouble)
use ISO_C_BINDING
implicit none
integer(c_int) :: NumInt
real(c_float) :: NumFloat
real(c_double) :: NumDouble
NumDouble=NumFloat**NumInt
end subroutine
real(c_double) function Function_Fortran(NumDouble)
use ISO_C_BINDING
implicit none
real(c_double) :: NumDouble
Function_Fortran=sqrt(NumDouble)
end function
-------------------------------------------
链接方法:
gcc –o main.o –c main.c
gfortran –o sub.o –c sub.f90
gcc –o main.exe main.o sub.o
或者直接
gcc –o main.exe main.c sub.f90
=========================================================
/* Fortran调用C:
Fortran90: */
! main fortran
program main
implicit none
! 函数接口
interface
subroutine
subroutine sub_c(n1,n2,n3)
integer :: n1
real :: n2
real(8) :: n3
end subroutine
real(8) function func_c(n3)
real(8) :: n3
end function
end interface
integer :: n1
real :: n2
real(8) :: n3,n4
n1=3
n2=5.0
call sub_c(n1,n2,n3)
n4=func_c(n3)
write(*,*) "n1=",n1
write(*,*) "n2=",n2
write(*,*) "n3=",n3
write(*,*) "n4=",n4
end program
-------------------------------------------
/* Fortran2003: */
program main
use ISO_C_BINDING
implicit none
! 函数接口
interface
subroutine sub_c(n1,n2,n3)
use ISO_C_BINDING
integer(c_int) :: n1
real(c_float) :: n2
real(c_double) :: n3
end subroutine
real(c_double) function func_c(n3)
use ISO_C_BINDING
real(c_double) :: n3
end function
end interface
integer(c_int) :: n1
real(c_float) :: n2
real(c_double) :: n3,n4
n1=3
n2=5.0
call sub_c(n1,n2,n3)
n4=func_c(n3)
write(*,*) "n1=",n1
write(*,*) "n2=",n2
write(*,*) "n3=",n3
write(*,*) "n4=",n4
end program
-------------------------------------------
/* 调用C程序 */
#include < math.h >
void sub_c_(int *,float *,double *);
double func_c_(double *);
void sub_c_(int *n1,float *n2,double *n3)
{ *n3=pow(*n2,*n1); }
double func_c_(double *n3)
{ double n4;
n4=sqrt(*n3);
return n4; }
-------------------------------------------
链接方式:
gcc –o sub.o sub.c
gfortran –o main.o main.f90
gfortran –o main.exe main.o sub.o
或是直接
gfortran –o main.exe main.f90 sub.c

函数调用

58c8e80d99d18.png

  1. 对于有返回值调用,二者的返回值数据类型必须一致。
  2. Fortran调用例程时,实参和形参默认是引用传递,而C则是值传递。
  3. 如果二者均采用默认的调用方式,那么Fortran调用C函数 时,C的形参应该声明为指针类型(*);C调用Fortran函数时,C传递实参时须以&形式传递变量地址。
  4. 传递数组时,二者均使用数组首地址作为参数,因此无需转换。如果C的参数本身为指针变量,也不需要转换。

参考: