Fortran: Fortran und C: Fortran 2003
| <<< zur Fortran-Startseite | |
| << Fortran und C | F >> |
| < Fortran 90/95 und C | Fortran 2003 und C > |
Ab Fortran 2003 ist es viel einfacher auf C zu zugreifen, als in Fortran 95. Es wurde im Fortran 2003-Standard ein intrinsisches Modul namens iso_c_binding vorgesehen, das die zum Zugriff auf C-Programme nötigen Elemente enthält. Fortran 2018 und 2023 brachten einige Erweiterungen zu diesem Thema. Darauf wird aber vorerst hier nicht eingegangen.
Folgende Beispiele wurden unter dem Betriebssystem OpenSUSE Leap 15.6 gestestet. Zum Einsatz kamen der gfortran-Compiler (7.5.0, 2017) und Intels ifx (2025.2.0).
Ein einfaches Beispiel
[Bearbeiten]Fortran 2003-Code: bsp.f90
| Fortran 2003 (oder neuer)-Code |
program bsp
implicit none
interface
function addition( a, b ) bind( c )
use, intrinsic :: iso_c_binding
real( kind = c_float ), value :: a
real( kind = c_float ), value :: b
real( kind = c_float ) :: addition
end function addition
end interface
write (*,*) addition( 2.5, 3.3 )
! Ausgabe: 5.8
end program bsp
|
C-Code: bsp.c
| Programmcode |
float addition(float a, float b)
{
return (a + b);
}
|
Makefile:
| Programmcode |
FC = gfortran # oder ifx CC = gcc bsp: bsp_c.o bsp_f90.o $(FC) -o bsp bsp_c.o bsp_f90.o bsp_c.o: bsp.c $(CC) -c -o bsp_c.o bsp.c bsp_f90.o: bsp.f90 $(FC) -c -o bsp_f90.o bsp.f90 .PHONY: clean clean: rm *.o |
Was ist neu gegenüber Fortran 95?
|
... | bind-Attribut, stellt unter anderem die Interoperabilität mit C bezüglich Prozedurnamenskonventionen nach der Übersetzung sicher |
|
... | Einbindung des intrinsischen Moduls iso_c_binding
|
|
... | C-Datentyp float
|
|
... | call by value |
Datentyp-Zuordnung
[Bearbeiten]Das iso_c_binding-Modul stellt benannte Konstanten zur Verfügung, die bei Fortran-Datentypen als kind-Wert zu verwenden sind, um den jeweiligen C-Datentyp zu charakterisieren. Weist eine solche Konstante einen negativen Wert auf, dann ist keine Entsprechung von Fortran-Datentyp zu C-Datentyp vorhanden.
| Fortran-Datentyp | Benannte iso_c_binding-Konstante (kind-Wert)
|
C-Datentyp |
|---|---|---|
| integer | c_int | int |
| c_short | short int | |
| c_long | long int | |
| c_long_long | long long int | |
| c_signed_char | signed char, unsigned char | |
| c_size_t | size_t | |
| c_int8_t | int8_t | |
| c_int16_t | int16_t | |
| c_int32_t | int32_t | |
| c_int64_t | int64_t | |
| c_int_least8_t | int_least8_t | |
| c_int_least16_t | int_least16_t | |
| c_int_least32_t | int_least32_t | |
| c_int_least64_t | int_least64_t | |
| c_int_fast8_t | int_fast8_t | |
| c_int_fast16_t | int_fast16_t | |
| c_int_fast32_t | int_fast32_t | |
| c_int_fast64_t | int_fast64_t | |
| c_intmax_t | intmax_t | |
| c_intptr_t | intptr_t | |
| real | c_float | float |
| c_double | double | |
| c_long_double | long double | |
| complex | c_float_complex | float _Complex |
| c_double_complex | double _Complex | |
| c_long_double_complex | long double _Complex | |
| logical | c_bool | _Bool |
| character | c_char | char |
| Quelle: J3/04-007 Fortran 2003 Working Draft | ||
Das iso_c_binding-Modul stellt keine speziellen kind-Werte für unsigned-Integer-Datentypen zur Verfügung. Im Bedarfsfall sind die entsprechenden kind-Werte für die signed-Datentypen zu verwendet.
Nicht jeder Fortran-Compiler unterstützt alle genannten C-Datentypen und die unterstützten Datentypen können sich compilerspezifisch in der Byteanzahl unterscheiden.
„call by value“ vs. „call by reference“
[Bearbeiten]Im einführenden Beispiel wurden die Funktionsargumente „call by value“ übergeben. Das Variablenattribut value stellt dieses Verhalten sicher. Wird dieses Attribut nicht gesetzt, so gilt „call by reference“.
Beispiel:
Fortran-Code bsp.f90:
| Fortran 2003 (oder neuer)-Code |
program bsp
implicit none
interface
subroutine zahl( a, b ) bind( c )
use, intrinsic :: iso_c_binding
integer( kind=c_int ), value :: a
integer( kind=c_int ) :: b
end subroutine zahl
end interface
call zahl(5, 7)
! Ausgabe:
! Ergebnis = 35
end program bsp
|
C-Code bsp.c:
| Programmcode |
#include <stdio.h>
void zahl(int a, int *b)
{
printf("%s%d\n", "Ergebnis = ", a * *b);
}
|
Globale C-Variablen
[Bearbeiten]Muss in Fortran auf globale C-Variablen zugegriffen werden, so sind diese im Gültigkeitsbereich eines Fortran-Modul zu spezifizieren.
Felder
[Bearbeiten]Interoperabilität zwischen Fortran und C ist nur mit Feldern definierter Größe gegeben. Allozierbare Felder oder Zeigerfelder sind nicht erlaubt.
Beispiel:
| Fortran 2003 (oder neuer)-Code |
program bsp
implicit none
integer, dimension( 3 ) :: a = (/ 1, 2, 3 /)
interface
subroutine feld1( f ) bind( c )
use, intrinsic :: iso_c_binding
integer( c_int ), dimension(*) :: f
end subroutine feld1
end interface
write (*,*) "Feld a vorher: ", a
call feld1( a )
write (*,*) "Feld a nachher: ", a
! Ausgabe:
! Feld a vorher: 1 2 3
! Feld a nachher: 999 2 3
end program bsp
|
| Programmcode |
void feld1(int f[])
{
f[0] = 999;
}
|
Übergabe von Zeichenketten
[Bearbeiten]Beispiel:
Fortran-Code bsp.f90:
| Fortran 2003 (oder neuer)-Code |
program bsp
use, intrinsic :: iso_c_binding
implicit none
interface
subroutine string1( str_in ) bind( c )
use, intrinsic :: iso_c_binding
character( kind=c_char, len=1 ) :: str_in ! Ungültiges Fortran 2003 da nur len=1 erlaubt ist
end subroutine string1
end interface
call string1( c_char_"Greetings from Fortran" // c_null_char )
! Ausgabe:
! "Greetings from Fortran"
end program bsp
|
C-Code bsp.c:
| Programmcode |
#include <stdio.h>
void string1(char str_in[])
{
printf("%s \n", str_in);
}
|
Für len=1 funktioniert das Beispiel mit allen angeführten Compilern. Das len-Attribut kann im Übrigen auch weggelassen werden; einige unterstützen als compilerspezifische Erweitungen auch andere Längen. Im Fortran 2003-Working-Draft wird in „Note 15.23“ eine andere Möglichkeit für die Übergabe von Zeichenketten angeführt. Diese folgt im wesentlichen der Annahme:
- C-Zeichenketten sind
char-Felder mit einem terminierenden\0-Zeichen und können deshalb in Fortran als Felder vom Datentypcharactermit einem kind-Wertc_charangesprochen werden.
Daher wird im Interface der Dummy-Parameter in Form eines Feldes aus Zeichen des Typs c_char deklariert. Das entspräche auch genau der aufzurufenden C-Funktion. Allerdings ist solcherart verfasster Code dann nicht mit allen Compilern übersetzbar.
Beispiel:
Fortran-Code bsp.f90:
| Fortran 2003 (oder neuer)-Code |
program bsp
use, intrinsic :: iso_c_binding
implicit none
interface
subroutine string1( str_in ) bind( c )
use, intrinsic :: iso_c_binding
character( kind=c_char ), dimension(*) :: str_in
end subroutine string1
end interface
call string1( c_char_"Greetings from Fortran" // c_null_char )
! Ausgabe:
! "Greetings from Fortran"
end program bsp
|
Benannte iso_c_binding-Konstanten für Zeichen mit spezieller Bedeutung in C:
| Benannte Konstante | Wert |
|---|---|
| c_null_char | '\0' |
| c_alert | '\a' |
| c_backspace | '\b' |
| c_form_feed | '\f' |
| c_new_line | '\n' |
| c_carriage_return | '\r' |
| c_horizontal_tab | '\t' |
| c_vertical_tab | '\v' |
Enumerationen
[Bearbeiten]Mit Fortran 2003 sind auch in dieser Programmiersprache Enumerationen (Aufzählungstypen) möglich. Die Werte in einer solchen Enumeration besitzen einen integer-Datentyp. Der kind-Wert ist nicht festgelegt, wird jedoch so gewählt, dass im Rahmen der jeweiligen Möglichkeiten alle Enumeratoren erfasst sind.
Die von C-Enumerationen bekannten Eigenschaften gelten gleichermaßen für Fortran-Enumerationen, z.B.:
- ohne explizite Zuweisung von Werten wird mit dem Wert 0 gestartet.
- ohne explizite Zuweisung von Werten wird in der Anordnungsreihenfolge der Elemente sukzessiv immer um 1 hochgezählt.
- Wurde dem Vorgängerelement eine Ganzzahl zugewiesen, dem Element jedoch nicht, so ist der Wert dieses Elementes die dem Vorgängerelement zugeordnete Ganzzahl + 1.
Beispiel:
Fortran-Code bsp.f90:
| Fortran 2003 (oder neuer)-Code |
program bsp
implicit none
enum, bind(c)
enumerator :: MO=1, DI=2, MI=3, DO=4, FR=5, SA=6, SO=7
end enum
interface
subroutine tag( w ) bind( c )
use, intrinsic :: iso_c_binding
integer( c_int ), value :: w
end subroutine tag
end interface
call tag(MI);
! Ausgabe:
! Mittwoch
end program bsp
|
C-Code bsp.c:
| Programmcode |
#include <stdio.h>
typedef enum {
MO=1, DI=2, MI=3, DO=4, FR=5, SA=6, SO=7
} wochentag;
void tag(wochentag w)
{
switch(w)
{
case MO:
printf("Montag\n");
break;
case DI:
printf("Dienstag\n");
break;
case MI:
printf("Mittwoch\n");
break;
case DO:
printf("Donnerstag\n");
break;
case FR:
printf("Freitag\n");
break;
case SA:
printf("Samstag\n");
break;
case SO:
printf("Sonntag\n");
break;
default:
printf("Kein Tag\n");
}
}
|
Zeiger
[Bearbeiten]Für das C-Zeiger-Handling stellt Fortran 2003 im iso_c_binding-Modul den Datenverbund c_ptr und einige Unterprogramme bereit.
| UP | Beschreibung |
|---|---|
| l = c_associated ( c_ptr1 [, c_ptr2] ) | Prüft den Assoziationsstatus von c_ptr1. Diese Funktion ermittelt also, ob c_ptr1 überhaupt assoziert ist, oder ob c_ptr1 mit c_ptr2 assoziert ist. |
| c_ptr = c_loc ( x ) | Gibt die Adresse von x zurück. |
| c_f_pointer ( c_ptr, fptr [, shape] ) | Wandelt einen C-Zeiger c_ptr in einen Fortran-Zeiger fptr um. Die Vorgabe von shape ist nur dann erforderlich und möglich, wenn fptr ein Feld ist. fptr ist intent( inout ).
|
Beispiel:
Fortran-Code bsp.f90:
| Fortran 2003 (oder neuer)-Code |
module cglob
use, intrinsic :: iso_c_binding
type( c_ptr ), bind( c ) :: ptr1, ptr2, ptr3
real( kind=c_float ), target, bind( c ) :: a, b
end module cglob
program bsp
use cglob
implicit none
real, pointer :: p => null()
! *** Zuordnungsstatus ***
write (*,*) "Ist ptr1 assoziert? ", c_associated( ptr1 )
write (*,*) "Ist ptr2 assoziert? ", c_associated( ptr2 )
write (*,*) "Ist ptr2 mit ptr3 assoziert? ", c_associated( ptr2, ptr3 )
write (*,*) "Ist ptr2 mit &a assoziert? ", c_associated( ptr2, c_loc(a) )
! *** Wert von ptr2? ***
call c_f_pointer( ptr2, p )
write (*,*) "Wert von ptr2? ", p
! *** ptr1 neu setzen ***
ptr1 = c_loc( b )
! *** Wert von ptr1 ***
call c_f_pointer( ptr1, p )
write (*,*) "Wert von ptr1? ", p
! Ausgabe:
! Ist ptr1 assoziert? F
! Ist ptr2 assoziert? T
! Ist ptr2 mit ptr3 assoziert? F
! Ist ptr2 mit &a assoziert? T
! Wert von ptr2 (bzw. p)? 5555.66
! Wert von ptr1 (bzw. p)? -12.3
end program bsp
|
C-Code bsp.c:
| Programmcode |
float a = 5555.66; float b = -12.3; float *ptr1 = 0; float *ptr2 = &a; float *ptr3 = &b; |
Nun ist die Zeiger-Verwendung wie im obigen Beispiel skizziert, eher die Ausnahme als die Regel.
Wesentlich häufiger trifft man Zeiger in C-Bibliotheken im Zusammenhang mit Funktionen an. Dort dienen sie aus Anwendersicht als return-Werte oder der Parameterübergabe „call-by-reference“. Oft sind dabei auch Zeiger auf Strukuren im Spiel. Näheres dazu folgt im nächsten Abschnitt.
Für C-Zeiger auf den Wert NULL bietet das ISO-C-Binding-Modul die Konstante C_NULL_PTR.
Datenverbund
[Bearbeiten]Die einzelnen Datenelemente der Struktur / des Datenverbundes müssen in Fortran und C selbstverständlich äquivalenten Datentyp aufweisen. Die Bezeichnungen der jeweiligen Datenelemente müssen nicht beibehalten werden. Sehr wohl müssen aber die Positionen der Einzelelemente im Fortran-Datenverbund der nachzubildenden C-Struktur entsprechen.
Für die Gewährleistung der Interoperabilität darf ein an C gebundener Datenverbund in Fortran (struct) keine Zeiger mit dem pointer-Attribut oder allozierbaren Felder als Datenelemente enthalten. Sind in der C-Struktur Zeiger vorhanden, so sind diese im entsprechenden Fortran-Datenverbund mittels type( c_ptr ) zu beschreiben.
Für Bitfelder oder Unions gibt es in Fortran keine entsprechenden Gegenstücke.
Beispiel:
| Fortran 2003 (oder neuer)-Code |
module cglob
use, intrinsic :: iso_c_binding
type, bind( c ) :: verbund
integer( kind=c_int ) :: a
real( kind=c_float ) :: b
end type verbund
type(verbund), bind( c ) :: v
end module cglob
program bsp
use cglob
implicit none
interface
subroutine set_v( a_in, b_in) bind( c )
use, intrinsic :: iso_c_binding
integer( c_int ), value :: a_in
real( c_float ), value :: b_in
end subroutine set_v
subroutine print_v() bind( c )
end subroutine print_v
end interface
call set_v( 5, -10.9)
call print_v()
write (*,*) "Fortran-Ausgabe: ", v
v%a = 99
call print_v()
write (*,*) "Fortran-Ausgabe: ", v
! Ausgabe:
! C-Ausgabe: 5 -10.900000
! Fortran-Ausgabe: 5 -10.9
! C-Ausgabe: 99 -10.900000
! Fortran-Ausgabe: 99 -10.9
end program bsp
|
| Programmcode |
#include <stdio.h>
typedef struct {
int a;
float b;
} verbund;
verbund v;
void set_v(int a_in, float b_in)
{
v.a = a_in;
v.b = b_in;
}
void print_v()
{
printf("C-Ausgabe: %i %f\n", v.a, v.b);
}
|
Beispiel: Struktur als Rückgabewert und Argument einer Funktion - Teil 1
Bekannt seien zwei Funktionsprototypen in der Programmiersprache C:
Xyz *get_xyz();
und
void set_xyz(Xyz *x);
Xyz sei ein C-struct, dessen Inhalt hier nicht näher interessiert.
Die Schnittstelle, mit dem in Fortran die C-Anbindung der Funktionen realisiert wird, könnte so aussehen:
interface
function get_xyz() bind( c )
use, intrinsic :: iso_c_binding
type( c_ptr ) :: get_xyz
end function get_xyz
subroutine set_xyz( x ) bind( c )
use, intrinsic :: iso_c_binding
type( c_ptr ), value :: x
end subroutine set_xyz
end interface
Und schon können diese Funktionen auch in Fortranroutinen direkt Anwendung finden, z.B:
type( c_ptr ) :: x56 x56 = get_xyz() call set_xyz( x56 )
Wie man anhand dieses Beispiels erkennt, ist die interne Struktur von Xyz in diesem Fall vollkommen irrelevant. Es ist nicht nötig, in der API-Dokumentation oder in den C-Headerdateien nachzuforschen, wie der C-struct konkret aufgebaut ist. Für den Fortran-Programmierer ist dies immer ein type( c_ptr ) (natürlich nur, solange Zeiger auf Strukturen gefordert sind). Wird ein Zeiger des Typs c_ptr nur via Fortranprogramm zwischen verschiedenen C-Funktionen übergeben oder zurückgeliefert, so ist auch keine Umwandlung in einen Fortran-Zeiger erforderlich und wäre mangels genauer Kenntnis des Aufbaus von Xyz hier auch nicht möglich. Erst dann, wenn von Fortran aus auf einzelne Elemente eines C-struct zugegriffen werden soll oder mit Kopien der Struktur hantiert wird, muss diese Struktur in Fortran nachgebaut werden.
Beispiel: Struktur als Rückgabewert und Argument einer Funktion - Teil 2
| Fortran 2003 (oder neuer)-Code |
module test
use, intrinsic :: iso_c_binding
implicit none
type, bind( c ) :: A
! Das funktioniert nicht mit allen Kompilern; versuchen Sie ansonsten:
! type A
! sequence
integer( c_int ) :: xc, yc
type( c_ptr ) :: str
end type
interface
function get_a() bind( c )
use, intrinsic :: iso_c_binding
type( c_ptr ) :: get_a
end function get_a
subroutine print_a( x ) bind( c )
use, intrinsic :: iso_c_binding
type( c_ptr ), value :: x
end subroutine print_a
end interface
end module test
program main
use test
implicit none
type( c_ptr ) :: x
type( A ), pointer :: fptr
character( len=9 ), pointer :: strptr
x = get_a()
! C-Ausgabe
call print_a( x )
! Ausgabe:
! x = 5
! y = 997
! str = Irgendwas
! Fortran-Ausgabe
call c_f_pointer( x, fptr)
call c_f_pointer( fptr%str, strptr ) ! <--- Fehlerquelle bei gfortran
write(*,*) fptr%xc, fptr%yc, strptr
! Ausgabe
! 5 997 Irgendwas
end program main
|
| Programmcode |
#include <stdlib.h>
#include <stdio.h>
typedef struct
{
int x;
int y;
const char *str;
} A;
A *get_a()
{
A *a = ( A * )malloc( sizeof( A ) );
a->x = 5;
a->y = 997;
a->str = "Irgendwas";
return a;
}
void print_a( A *v )
{
printf("x = %d\n", v->x);
printf("y = %d\n", v->y);
printf("str = %s\n", v->str);
}
|
Bei der Umwandlung des C-String-Zeigers in einen Fortran-Zeiger ist die genaue Stringlänge erforderlich. Ist diese zu klein gewählt, so ist nur ein Teil des C-Strings über den Fortran-Zeiger sichtbar. Wird diese Stringlänge zu groß gewählt, dann wird diese Länge auch voll ausgenutzt und nach dem eigentlich gewünschten String folgen noch ein Menge x-beliebige Zeichen, da in Fortran der C-String-Begrenzer \0 nicht die Bedeutung wie in C besitzt.
Im obigen Beispiel wurde die Speicherplatzreservierung und Wertebelegung für eine Variable des Typs A in einer C-Funktion erledigt. Nun soll diese Aufgabe im Fortran-Programm wahrgenommen werden und dann diese in Fortran mit Werten belegte Variable an die print_a-Funktion übergeben werden. Kein Problem, möchte man im ersten Augenblick meinen. Doch der c_ptr-Typ im Datenverbund für den Zeiger auf eine Zeichenkette macht Probleme. Der g95-Compiler duldet bspw. in der c_loc-Funktion keine Zeichenketten mit einer Länge größer als 1. Der Compiler "castet" auch nicht von selbst im Datenverbundkonstruktor eine Zeichenkette in den Typ c_ptr. Eine Möglichkeit, diese Probleme zu umgehen, besteht darin, einfach einer C-Funktion einen String zu übergeben und die Adresse dieses Strings zurückgeben zu lassen. Diese Variante wird auch im folgenden Beispiel verwendet.
Beispiel: Struktur als Rückgabewert und Argument einer Funktion - Teil 3
| Fortran 2003 (oder neuer)-Code |
module test
use, intrinsic :: iso_c_binding
implicit none
type, bind( C ) :: A
integer( c_int ) :: xc, yc
type( c_ptr ) :: str
end type
type( A ), target :: a1
interface
subroutine print_a( x ) bind( c )
use, intrinsic :: iso_c_binding
type( c_ptr ), value :: x
end subroutine print_a
function c_string_addr( s ) bind( c )
use, intrinsic :: iso_c_binding
character( c_char ) :: s
type( c_ptr ) :: c_string_addr
end function c_string_addr
end interface
end module test
program main
use test
implicit none
character( len = 30 ) :: str
str = "Das ist ein Beispiel" // C_NULL_CHAR
a1 = A( 14, 56, c_string_addr( str ) )
call print_a( c_loc( a1 ) )
! Ausgabe:
! x = 14
! y = 56
! str = Das ist ein Beispiel
end program main
|
| Programmcode |
#include <stdlib.h>
#include <stdio.h>
typedef struct
{
int x;
int y;
const char *str;
} A;
void print_a( A *v )
{
printf("x = %d\n", v->x);
printf("y = %d\n", v->y);
printf("str = %s\n", v->str);
}
char *c_string_addr( char *str )
{
return str;
}
|
Problematische Kamelhöckerschreibweise?
[Bearbeiten]C ist case-sensitive, Fortran nicht. Oft sind Funktionen in C-Bibliotheken in der Kamelhöckerschreibweise vorzufinden, z.B.
void writeHallo();
Die Einbindung dieser C-Funktion in ein Fortran-Programm mit
interface subroutine writeHallo() bind(c) end subroutine writeHallo end interface
könnte beim Linken eine Fehlermeldung der Art
undefined reference to `writehallo'
liefern.
Was ist passiert? Wie schon angedeutet, ist Fortran case-insensitive. Für einen Fortran-Compiler ist writeHallo gleich writehallo oder WRITEHaLLO. Für einen C-Compiler wären das alles unterschiedliche Funktionsbezeichner. Also wandelt der Fortran-Compiler die "unnütze" Groß-/Kleinschreibung in eine einheitliche Schreibweise um und der C-Compiler nicht. Dementsprechend findet der Linker auch keine Funktion writehallo. Es gibt eben nur die nicht-idente C-Funktion writeHallo.
Aber auch für dieses Problem gibt es in Fortran eine Lösung. Mit dem bind-Attribut können zusätzlich auch noch Labels (Benennungen) vergeben werden, z.B.:
bind( c, name="xyz" )
Solche Labels in Stringform sind nicht von der "Kopf ab"-Strategie des Fortran-Compilers betroffen. Das ist keine C-Binding-Spezialität, sondern trifft generell für alle Zeichenketten zu. Ein
interface subroutine writeHallo() bind(c, name="writeHallo") end subroutine writeHallo end interface
sollte das beschriebene Link-Problem lösen.
Sonstiges
[Bearbeiten]- Auch C-Funktionszeiger kennt das
iso_c_binding-Modul. Zu diesem Zwecke gibt es den Datenverbundc_funptr, einige Umwandlungsfunktionen ähnlich jenen im Abschnitt Zeiger behandelten und die KonstanteC_NULL_FUNPTR.
| <<< zur Fortran-Startseite | |
| << Fortran und C | F >> |
| < Fortran 90/95 und C | Fortran 2003 und C > |