Atribut bind(c) menandai sebuah antarmuka atau prosedur agar mengikuti konvensi pemanggilan C, yaitu cara kompiler menata argumen dan nama simbol pada berkas hasil kompilasi. Topik ini membahas pemanggilan fungsi C dari Fortran melalui blok interface beratribut bind(c), kompilasi campuran dua bahasa menjadi satu program, serta pemanggilan balik (callback) ketika kode C menerima fungsi Fortran sebagai argumennya. Satu program campuran dan satu program pemanggilan balik disertakan, keduanya dikompilasi dan dijalankan, sehingga kontrak antar bahasa terlihat dari keluaran aslinya.
Deklarasi fungsi C dalam antarmuka Fortran#
Tanpa perlakuan khusus, gfortran menerjemahkan nama prosedur Fortran menjadi simbol C yang diberi tambahan garis bawah dan menata argumennya menurut konvensi Fortran, sehingga tanda tangan kedua bahasa tidak pernah bertemu. Blok interface dengan atribut bind(c, name="...") memutus kebuntuan itu. Kompiler memanggil simbol C persis dengan nama yang diberikan pada atribut name, dan setiap argumen dinyatakan dengan tipe dari iso_c_binding beserta arah datanya. Atribut value membuat argumen scalar diteruskan C secara nilai, sedangkan array dinyatakan sebagai bentuk diasumsikan (assumed-size) seperti v(*) karena C hanya menerima alamatnya.
Program berikut memanggil dua fungsi C, yaitu rata_rata yang mengembalikan rata-rata elemen array dan jumlah_vektor yang menjumlahkan dua array ke array ketiga.
program panggil
use, intrinsic :: iso_c_binding
implicit none
interface
function rata_rata(n, v) bind(c, name="rata_rata") result(r)
import :: c_int, c_double
integer(c_int), value :: n
real(c_double), intent(in) :: v(*)
real(c_double) :: r
end function rata_rata
subroutine jumlah_vektor(n, a, b, c) bind(c, name="jumlah_vektor")
import :: c_int, c_double
integer(c_int), value :: n
real(c_double), intent(in) :: a(*)
real(c_double), intent(in) :: b(*)
real(c_double), intent(out) :: c(*)
end subroutine jumlah_vektor
end interface
real(c_double) :: u(3) = [2.0_c_double, 4.0_c_double, 6.0_c_double]
real(c_double) :: v(3) = [1.0_c_double, 1.0_c_double, 1.0_c_double]
real(c_double) :: w(3)
call jumlah_vektor(3_c_int, u, v, w)
print *, "rata-rata u :", rata_rata(3_c_int, u)
print *, "jumlah u + v:", w
end program panggilPihak C-nya berupa berkas cek.c yang mendefinisikan kedua fungsi tersebut dengan tanda tangan yang cocok.
// tidak-dijalankan
#include <stddef.h>
double rata_rata(int n, const double *v) {
double total = 0.0;
for (int i = 0; i < n; i++) {
total += v[i];
}
return total / n;
}
void jumlah_vektor(int n, const double *a, const double *b, double *c) {
for (int i = 0; i < n; i++) {
c[i] = a[i] + b[i];
}
}Karena C tidak mengenal intent, arah aliran data disepakati melalui kualifikasi C dan atribut Fortran secara bersamaan. Tabel berikut merangkum padanan deklarasi yang dipakai pada program di atas.
| Deklarasi Fortran | Prototipe C yang cocok |
|---|---|
integer(c_int), value :: n | int n |
real(c_double), intent(in) :: v(*) | const double *v |
real(c_double), intent(out) :: c(*) | double *c |
real(c_double) :: r sebagai nilai balik | double sebagai nilai balik |
Penanda const pada pihak C dan intent(in) pada pihak Fortran mendokumentasikan kontrak yang sama dari dua sisi. Kompiler tidak mencocokkan keduanya, tetapi kehadiran keduanya membuat perubahan pada satu sisi mudah diperhatikan dari sisi yang lain, dan tabel semacam ini menjadi rujukan tercepat saat antarmuka baru ditulis.
Kompilasi campuran dan pemanggilan#
Kedua bahasa dikompilasi terpisah menjadi berkas objek, lalu disambungkan (link) menjadi satu program. Kompilasi C dilakukan gcc dengan opsi -c yang menghasilkan cek.o tanpa penyambungan, sedangkan gfortran dipilih sebagai penggerak tahap penyambungan karena pustaka jalannya (runtime) Fortran harus dilibatkan. Perintah berikut dijalankan dari direktori topik.
# kompilasi berkas C menjadi berkas objek
gcc -c cek.c -o cek.o
# kompilasi Fortran dan sambungkan dengan berkas objek C
gfortran panggil.f90 cek.o -o app
# jalankan program campuran hasil kompilasi
./app rata-rata u : 4.0000000000000000
jumlah u + v: 3.0000000000000000 5.0000000000000000 7.0000000000000000 Baris pertama keluaran berasal dari pemanggilan rata_rata(3_c_int, u), yang menghitung (2 + 4 + 6) / 3 dan mengembalikan 4.0 melalui nilai balik fungsi C. Baris kedua berasal dari jumlah_vektor, yang mengisi w melalui pointer karena argumennya dinyatakan intent(out). Kedua fungsi berjalan tanpa konversi data apa pun, karena real(c_double) dan double menempati memori yang sama persis.
Meneruskan fungsi Fortran ke C#
Arah sebaliknya juga diperlukan ketika kode C meminta pemberi kerja (caller) menyediakan fungsinya sendiri, misalnya fungsi matematika yang diterapkan pada setiap elemen. C menerima fungsi melalui pointer fungsi (function pointer), dan padanannya di Fortran adalah tipe type(c_funptr). Prosedur Fortran yang akan diteruskan wajib beratribut bind(c) agar konvensi pemanggilannya cocok, dan alamatnya diambil dengan c_funloc. Bentuk fungsi yang diharapkan C didokumentasikan pada abstract interface, sehingga kesalahan tanda tangan terdeteksi saat kompilasi.
module mod_callback
use, intrinsic :: iso_c_binding
implicit none
abstract interface
function formula(x) bind(c)
import :: c_double
real(c_double), value :: x
real(c_double) :: formula
end function formula
end interface
contains
function kuadrat(x) bind(c) result(y)
real(c_double), value :: x
real(c_double) :: y
y = x * x
end function kuadrat
end module mod_callback
program callback
use, intrinsic :: iso_c_binding
use mod_callback, only: formula, kuadrat
implicit none
interface
subroutine terapkan(n, v, f) bind(c, name="terapkan")
import :: c_int, c_double, c_funptr
integer(c_int), value :: n
real(c_double), intent(inout) :: v(*)
type(c_funptr), value :: f
end subroutine terapkan
end interface
real(c_double) :: data(4) = [1.0_c_double, 2.0_c_double, 3.0_c_double, 4.0_c_double]
call terapkan(4_c_int, data, c_funloc(kuadrat))
print *, "kuadrat tiap elemen:", data
end program callbackPihak C-nya berupa prosedur terapkan yang memanggil f pada setiap elemen array.
// tidak-dijalankan
void terapkan(int n, double *v, double (*f)(double)) {
for (int i = 0; i < n; i++) {
v[i] = f(v[i]);
}
}# kompilasi berkas C yang memuat terapkan
gcc -c callback.c -o callback.o
# sambungkan modul dan program Fortran dengan berkas objek C
gfortran callback.f90 callback.o -o terapkan
# jalankan program pemanggilan balik hasil kompilasi
./terapkan kuadrat tiap elemen: 1.0000000000000000 4.0000000000000000 9.0000000000000000 16.000000000000000 Prosedur C terapkan tidak mengetahui apa pun tentang Fortran, tetapi berhasil memanggil kuadrat empat kali karena alamat yang diterimanya dari c_funloc mengikuti konvensi pemanggilan C. Array data berubah di tempat (in place) karena argumennya dinyatakan intent(inout), sehingga nilai 1, 2, 3, 4 menjadi 1, 4, 9, 16 pada keluaran.
Arah kebalikannya juga tersedia apabila C menyimpan pointer fungsi dan mengembalikannya kepada Fortran. Prosedur c_f_procpointer menghubungkan type(c_funptr) dengan prosedur pointer Fortran, sehingga fungsi yang dipilih oleh C pada waktu berjalan tetap dapat dipanggil dengan sintaks Fortran biasa. Pola ini lazim pada pustaka yang menawarkan beberapa implementasi algoritma untuk satu kegunaan, misalnya pilihan antara versi cepat dan versi hemat memori, dan pemilihannya baru ditetapkan setelah pustaka dimuat.
Kesalahan umum#
- Ketidakcocokan cara penerusan argumen. Argumen scalar C diteruskan secara nilai, sedangkan tanpa atribut
valueFortran meneruskannya sebagai alamat. Deklarasiinteger(c_int) :: ntanpavalueuntuk fungsi Cint nmembuat C membaca angka dari alamat yang salah, dan akibatnya nilai acak atau kerusakan memori. - Nama simbol yang tidak cocok. Atribut
namepadabind(c)dipakai apa adanya tanpa tambahan apa pun, sehingga untuk pustaka yang mengikuti konvensi garis bawah seperti OpenBLAS, atributnya harusname="dgemm_"beserta garis bawahnya. Kekeliruan ini muncul sebagai errorundefined referencepada tahap penyambungan. - Melupakan
importpada badan antarmuka. Badaninterfacetidak mewarisi deklarasi modul atau program secara otomatis, sehinggac_intyang tidak diimpor menyebabkan error kompilasi, bukan error saat program berjalan.
Ringkasan#
- Blok
interfacedenganbind(c, name="...")mengikat deklarasi Fortran pada simbol C yang tepat, termasuk arah dan cara penerusan setiap argumen. - Kompilasi campuran dilakukan terpisah dengan
gcc -cuntuk berkas C, lalugfortranmenyambungkan berkas objeknya bersama pustaka jalannya Fortran. - Fungsi Fortran dapat diteruskan ke C sebagai
type(c_funptr)melaluic_funloc, dengan syarat prosedurnya beratributbind(c).
Referensi#
- Learn: tutorial resmi Fortran — fortran-lang — Tutorial resmi komunitas Fortran yang memuat bab interoperabilitas dengan C.
- The GNU Fortran Compiler — GNU Project — Manual gfortran yang menerangkan atribut bind(c), konvensi penerusan argumen, dan tahap penyambungan.