C++Guns – RoboBlog blogging the bot

17.02.2016

Fortran: Detect 64bit 32bit

Filed under: Allgemein — Tags: — Thomas @ 08:02

Ein möglicher Weg zu erkennen ob auf einer 64bit oder 32bit Maschine compiliert wird ist mit Hilfe
von macros. Die üblichen macros wie __amd64, __amd64__, __x86_64 __x86_64__ funktionieren unter gfortran (nicht) mehr, da sie nur zeigen ob eine 64Bit Prozessor zur Verfügung steht. Und nicht, ob man
auch wirklich für 64bit compiliert. [1]
Es ist natürlich möglich untr 64bit OS für 32bit zu compilieren, wenn zum Beispiel nur ein 32bit Compiler zur Verfügung steht. Oder zu Testzwecken

Laut doku [2] steht zu diesem Zwecke das macro __LP64__ zur Verfügung.

__LP64__
_LP64
These macros are defined, with value 1, if (and only if) the compilation is for a target where long int and pointer both use 64-bits and int uses 32-bit.

Pointer ist 64bit und integer ist 32bit. So ist es richtig. Alle anderen (M$) machen es falsch.

Nun gibt es leider Compiler, die kein Preprocessing beherrschen. Zum Glück gibt es Fortran 2003
mit dem neuen iso_c_binding Modul. Das Symbol C_INTPTR_T repräsentiert die Größe eines
Pointers und kann als KIND parameter benutzt werden.


program main
  use, intrinsic :: iso_c_binding
  implicit none
  integer(kind=C_INTPTR_T) :: sizeofapointer
  
!__LP64__
!_LP64
! These macros are defined, with value 1, if (and only if) the compilation is for a target where long int and pointer both use 64-bits and int uses 32-bit. 
#ifdef __LP64__
  write(*,*) "64bit detected by macro"
#else
  write(*,*) "32bit detected by macro"
#endif

  write(*,*) "C_INTPTR_T",  C_INTPTR_T
end program

PS: Es ist möglich mit gfortran für 32bit zu compilieren. Einfach gfortran-multilib installieren
und dem Compiler -m32 geben.

[1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47175
[2] https://gcc.gnu.org/onlinedocs/gcc-4.2.1/cpp/Common-Predefined-Macros.html

12.02.2016

/proc status

Filed under: Allgemein — Tags: — Thomas @ 20:02

Code snipped to read /proc status with fortran.
E.g. used memory and so...


! original: http://hiliev.eu/posts/recipe-obtaining-peak-vm-memory-size-in-pure-fortran.html

module ProcStatus
  contains
    !---------------------------------------------------------------!
    ! Returns current process virtual memory size             !
    ! Requires Linux procfs mounted at /proc                        !
    !---------------------------------------------------------------!
    ! Output: vmsize - vmsize VM size in kB                             !
    !---------------------------------------------------------------!
    function getVmSize() result(vmsize)
      implicit none
      integer :: vmsize
      character(len=80) :: stat_key, stat_value
      !
      vmsize = 0
      open(unit=1000, file="/proc/self/status", status='old', err=99)
      do while (.true.)
        read(unit=1000, fmt=*, err=88) stat_key, stat_value
        if (stat_key == 'VmSize:') then
          read(stat_value, *) vmsize
          exit
        end if
      end do
    88 close(unit=1000)
      if (vmsize == 0) goto 99
      return
      !
    99 print *, 'ERROR: procfs not mounted or not compatible'
      vmsize = -1
    end function getVmSize

end module ProcStatus

program test
  use ProcStatus
  implicit none
  integer :: vmsize
  integer, allocatable :: arr(:)

  allocate(arr(10000000)) ! ~ 40MB
  arr(:) = 0

  vmsize = getVmSize()
  print *, 'VM size: ', vmsize, ' kB'
end program test

26.01.2016

Evaluation of logical operations

Filed under: Allgemein — Tags: , , — Thomas @ 21:01

Spass mit dem Standard.
Aus dem Fortran 90/95 Standard [1] Kapitel 7.1.7.6.

Evaluation of logical intrinsic operations
The rules given in 7.2.4 specify the interpretation of logical intrinsic operations. Once the
interpretation of an expression has been established in accordance with those rules, the processor
may evaluate any other expression that is logically equivalent, provided that the integrity of
parentheses in any expression is not violated.
NOTE 7.29
For example, for the variables L1, L2, and L3 of type logical, the processor may choose to
evaluate the expression
L1 .AND. L2 .AND. L3
as
L1 .AND. (L2 .AND. L3)
Two expressions of type logical are logically equivalent if their values are equal for all possible
values of their primaries.

Aus dem c++11 Standard [2] Kapitel 5.14

logical-and-expression
The && operator groups left-to-right.

Und die andern Operatoren auch left-to-right.

Aus dem C89 Standard [3] Kapitel 3.3

Except as indicated by the syntax27 or otherwise specified later (for the function-call operator () , && , || , ?: , and comma operators), the order of evaluation of subexpressions and the order in which side effects take place are both unspecified.

hf

[1] http://j3-fortran.org/doc/standing/archive/007/97-007r2/pdf/97-007r2.pdf
[2] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf
[3] https://web.archive.org/web/20050207005628/http://dev.unicals.com/papers/c89-draft.html#3.3

04.05.2015

storing into constants (Bug of the day 4)

Filed under: Allgemein — Tags: — Thomas @ 17:05

From the Layhey Fortran 95 compiler user guide.

-[N]PCA
Protext Constant Arguments
Compile only. Default npca
Specify -pca to prevent invoked subprograms from storing into constants.

Example
call sub(5)
print *, 5
end
subroutine sub(i)
i = i + 1
end

This example would print 5 using -pca and 6 using -npca.

Greatest bullshit I've ever seen!
They create a compiler who can change compile time constants and make it the default behaviour.

11.04.2015

Local variable will not retain values between function invocations (Bug of the day 2)

Filed under: Allgemein — Tags: — Thomas @ 07:04

Lokale Variablen behalten ihren Wert NICHT zwischen Funktionsaufrufe!

Es gibt in diesem Beispiel keinen Compilerfehler und auch keinen von Fortran erkannten Laufzeitfehler.
Es gibt Compiler Warnings die diesen Fehler zeigen, aber dazu bedarf es mehr Code. Ich habe dieses Beispiel möglichst klein und übersichtlich gehalten.

Ausgabe ohne Optimierung und ohne Fehler

 kater@ktux:~$ gfortran -Wall -Wextra -fcheck=all  bugoftheday_openreadclose.f95 
kater@ktux:~$ ./a.out 
first round
DEBUG address of fhdl         BFC4D8FC
 open. set fhdl to         100
 read
 all file

 second round
DEBUG address of fhdl         BFC4D8FC
 open. set fhdl to         100
DEBUG address of test         BFC4D90C
 set test variabel to        1000
 read
 all file

Man beachte die unterschiedlichen Adressen der lokalen Variablen. Daher wird der Fehler nicht getriggert.

Ausgabe mit Optimierung und mit Fehler

kater@ktux:~$ gfortran -Wall -Wextra -fcheck=all  bugoftheday_openreadclose.f95 -O1
kater@ktux:~$ ./a.out  
 first round
DEBUG address of fhdl         BFA7DE38
 open. set fhdl to         100
 read
 all file

 second round
DEBUG address of fhdl         BFA7DE38
 open. set fhdl to         100
DEBUG address of test         BFA7DE38
 set test variabel to        1000
 read
 ERROR expect fhdl to be         100 but is        1000

Diesmal sind die Adressen der lokalen Variablen gleich! Die Variable fhdl wird ungeplant ueberschrieben!!
Welche Adresse die lokalen Variablen bekommen und ob so der Fehler auftritt oder nicht, ist zufällig und nicht beeinflussbar!!!


! Local variable will not retain values between function invocations
! compile: gfortran -Wall -Wextra -fcheck=all  bugoftheday_openreadclose.f95
! turn on any optimization to trigger the error


subroutine openread(action)
  use testmodule
  implicit none
  character(len=*), intent(in) :: action
  integer :: fhdl

  if(action == 'open') then
    fhdl = 100
    write(*,'(A,Z16)') "DEBUG address of fhdl ", loc(fhdl)
    write(*,*) "open. set fhdl to", fhdl
  else if(action == 'read') then
    write(*,*) "read"
    if(fhdl /= 100) then
      write(*,*) "ERROR expect fhdl to be", 100, "but is", fhdl
    else
      write(*,*) "all file"
    endif
  else if(action == 'close') then
      write(*,*) "close fhdl", fhdl
  endif
end subroutine


subroutine overwriteLocalValue()
  implicit none
  integer :: test

  test = 1000
  write(*,'(A,Z16)') "DEBUG address of test ", loc(test)
  write(*,*) "set test variabel to", test
end subroutine

program bugoftheday
  use testmodule
  implicit none

  write(*,*) "first round"
  call openread("open")
  call openread("read")

  write(*,*)
  write(*,*) "second round"
  call openread("open")
  call overwriteLocalValue
  call openread("read")
end program

10.04.2015

Bug of the day 1

Filed under: Allgemein — Tags: — Thomas @ 08:04

Der bug of the day kommt diesmal von mir!

Ich hab mir von eine Hand voll 3D Punkten neue Z Werte berechnet.
Aber hab den Z Wert im Punkt nie aktualisiert.
Und anstatt in der Ausgabedatei nun Bloedsinn steht oder lauter 0 Eintraege,
finden sich dort Werte die mit einen berechneten immer ein bis zwei Stellen am Anfang überein stimmen.
Fortran ist seltsam.
Ghosts updates mit einem leichten Fuzzy Faktor.
Da schwappen die Bits wohl von einen ins andere CPU Register über ;)

09.02.2015

gfortran bug

Filed under: Allgemein — Tags: — Thomas @ 18:02

Jo en Bug. So assigment subroutine und gfort 4.8 4.9. Vllt geht es in 5.0 ja wieder.

gfortramnbug.F90

26.01.2015

Fortran save, data, static, global, parameter WTF?

Filed under: Allgemein — Tags: — Thomas @ 23:01

Whats the difference between between save, static, global, local and parameter variables?

Save and static are two words for the same thing.
I found a nice definition of what save do in the Fortran 77 Standard [1]

Within a function or subroutine subprogram, an entity specified by a SAVE statement does not become undefined as a result of the execution of a RETURN or END statement in the subprogram


subroutine func()
  implicit none
  real, save :: time
  time = time + 1
  write(*,*) time
end subroutine

program test
  implicit none
  integer :: i
  do i = 1, 10
    call func()
  end do
end

1.00000000
2.00000000
3.00000000
4.00000000
5.00000000
6.00000000
7.00000000
8.00000000
9.00000000
10.0000000

You can count how often a subroutine is called. If you ever want to do this... I can not recommend to do it in this way.
As you may have noticed, I have not initialized the time variable with an start value.
You can do this with the data instruction.


subroutine func()
  implicit none
  real :: time
  data time/100/  
  time = time + 1
  write(*,*) time
end subroutine

101.000000
102.000000
103.000000
104.000000
105.000000
106.000000
107.000000
108.000000
109.000000
110.000000

The save specifier is not needed by using the data keyword.
This may result in confusion, because not everybody knows exactly what the data specifier does.
Again: do not use it.

The greatest pitfall with static variables is this:


real :: time = 0

If you initialize a variable by the declaration, it become a static variable! That really really hurts. You want be a good programer and always zero your variables and then you crash the hole program :D

Lets talk about parameters. Many programmers declare they constans with the save attribute.
I can not recommend this.
First: Constants does not change. Variables with save attribute can change.


real, save :: PI = 3.14...
PI = 4

All fine ;)

If you use parameter instead of save the compiler can detect this issue.


real, parameter :: PI = 3.14...
PI = 4

PI = 4
1
Error: Named constant 'pi' in variable definition context (assignment) at (1)

Second: Fortran is a high performance language. You can easily vectorize assigments using the forall constrcut.
All functions called from the forall constrcut must be declared as pure. to ensure that they have no side effects.


  function func() result(val)
    implicit none
    real, save :: PI = 3.14
    real :: val
    val = PI
  end function

forall( i = 1:100 ) A(i) = func()

forall( i = 1:100 ) A(i) = func()
1
Error: Reference to non-PURE function 'func' at (1) inside a FORALL block

And a pure function can not have static variables


  pure function func() result(val)
    implicit none
    real, save :: PI = 3.14
    real :: val
    val = PI
  end function

real, save :: PI = 3.14
1
Error: SAVE attribute at (1) cannot be specified in a PURE procedure

Using parameter solves the issue


  pure function func() result(val)
    implicit none
    real, parameter :: PI = 3.14
    real :: val
    val = PI
  end function

forall( i = 1:100 ) A(i) = func()

$ ifort -O2 -c save.f90 -vec-report3
save.f90(19): (col. 25) remark: LOOP WAS VECTORIZED

The last thing I talk about are global variables.
They can be accessed and overwritten from everywhere by everyone anytime. Happy bug chasing...
One way to create global variables in Fortran is using the COMMON keyword. I allready wrote about that..

[1] http://www.fortran.com/fortran/F77_std/rjcnf-8.html#sh-8.9

17.01.2015

suppress "unused dummy argument" warning in fortran

Filed under: Allgemein — Tags: — Thomas @ 18:01

Here is my way to supress the "unused dummy argument" warning. I'm searched a long time but did not find a solution which works on any compiler and newer fortran standard.

In C/C++ it is very easy. Just write (void)variable; But this just trigger an "Unclassifiable statement" error.

#define UNUSED(x) if(size( (/(x)/) ) < 0) continue

program main
  implicit none

  type testtype
    integer :: x
  end type

  type(testtype) :: x
  type(testtype), pointer :: y => null()

  integer :: z
  
  UNUSED(x)
  UNUSED(y)
  UNUSED(z)
end program
$ gfortran -Wall -Wextra -fcheck=all -g test.F90
$ ./a.out
$

hf

20.12.2014

Fortran COMMON is deadly

Filed under: Allgemein — Tags: — Thomas @ 22:12

In Fortran 77 gibt es den COMMON Block. Das sind sowas wie globale Variablen die man umbenennen kann. Gleich im nächsten Standard (1990) hat man das wieder rausgenommen, weil es eine unheimliche Scheisse ist. (Die Erkenntnis hat ja nur 13 Jahre gedauert).

Folgenden Beispiel zeigt warum.


      SUBROUTINE PREVENTSTART
        COMMON /ROCKETCONTROL/ ISTARTCOUNTDOWN, ITIMELEFT

        ! stop the countdown and reset time
        ISTARTCOUNTDOWN = 0
        ITIMELEFT = 1000
      END SUBROUTINE

      PROGRAM ROCKET

        COMMON /ROCKETCONTROL/ ITIMELEFT, ISTARTCOUNTDOWN

        ! initital values
        ITIMELEFT = 1000
        ISTARTCOUNTDOWN = 0

        ! now we start the countdown
        ISTARTCOUNTDOWN = 1

        ! some countdown loop here
        ! 999
        ! 998
        ! 997
        ! ...
        
        ! Oh no! Stop the countdown
        CALL PREVENTSTART

        IF(ITIMELEFT == 0) THEN
          WRITE(*,*) "ROCKET STARTED, YOU ARE DEAD!"
        ELSE
          WRITE(*,*) "COUNTDOWN STOPPED!"
        ENDIF
      END PROGRAM
$ gfortran -Wall rocket.for -o rocket
$ ./rocket 
 ROCKET STARTED, YOU ARE DEAD!

Shit ;) Ok die Erkenntnis hat gefruchtet. Man hat den Fortran Standard nicht umsonst schon 4 mal geändert. Damit ihr auch versteht wie es richtig geht, hier ein Beispiel im Fortran 2003 Standard.


module RocketControlClass
  implicit none
  private

  type, public :: RocketControl
    integer, private :: timeLeft
    integer, private :: countdownIsRunning

    contains

    procedure :: startCountdown
    procedure :: preventStart
    procedure :: getTimeLeft
  end type

  interface RocketControl
    module procedure createRocketControl
  end interface

  contains

  function createRocketControl() result(this)
    implicit none
    type(RocketControl) :: this

    this%timeLeft = 1000
    this%countdownIsRunning = 0
  end function

  subroutine startCountdown(this)
    implicit none
    class(RocketControl), intent(inout) :: this

    this%countdownIsRunning = 1
  end subroutine

  subroutine preventStart(this)
    implicit none
    class(RocketControl), intent(inout) :: this

    this%countdownIsRunning = 0
  end subroutine

  pure function getTimeLeft(this) result(timeLeft)
    implicit none
    class(RocketControl), intent(in) :: this
    integer :: timeLeft

    timeLeft = this%timeleft
  end function
end module

program rocket
  use RocketControlClass
  implicit none

  type(RocketControl) :: control

  ! inititalise values
  control = RocketControl()

  ! now we start the countdown
  call control%startCountdown

  ! some countdown loop here
  ! 999
  ! 998
  ! 997
  ! ...

  ! Oh no! Stop the countdown
  call control%preventStart

  if(control%getTimeLeft() == 0) then
    write(*,*) "rocket started, you are dead!"
  else
    write(*,*) "countdown stopped!"
  endif
end
$ gfortran -std=f2003 -Wall rocket.f03 -o rocket2003
$ ./rocket2003 
 countdown stopped!

Gott sei Dank. Diesmal hat es funktioniert, wir leben noch.
Für alle die immernoch nicht kapiert haben warum der 77er Code nicht funktioniert: Ich habe die Variablennamen im COMMON Block vertauscht. Einmal ISTARTCOUNTDOWN, ITIMELEFT und einmal ITIMELEFT, ISTARTCOUNTDOWN. Das ist kein Fehler, das ist im Standard so vorgesehen. Kein Compiler und kein forcheck wird diesen Fehler jemals erkennen können. Scheisse, ich sag es ja.

Kommen wir nun zum 2003er Code. Der ist erstmal deutlich länger. Aber das ist Absicht um einige Konzepte zu zeigen die man für ein ordentlichen Programm so braucht.

Kommen wir gleich zum wichtigsten: Statt COMMON nutze ich TYPE. Ein type kann wie COMMON Variablen zusammen fassen. Aber er existiert nur einer einzigen Stelle im Code und die Variablennamen kann man nicht mehr umbenennen. Das alleine würde schon langen um den 77er Fehler zu erkennen.

Aber die Welt dreht sich weiter und seit 1995 gehts erst so richtig rund.

Ich habe den RocketControl type in ein RocketControlClass - Module ausgelagert. Da Fortran case-insensitive ist, also nicht zwischen GROSS und kleinschreibung! unterschieden wird, muss man selbst seine Variablennamen anpassen. Module bekommen den Suffix "Module" um deutliche zu machen, dass es ein Module ist. Habe ich ein Module welches Objektorientierten Code enthält, bekommt es stattdessen den Suffix "Class" in Anlehung an das class Keyword in C++. *uff* Fortran ist anstrengend.

Okay zurück zu unserem Programm. Ich habe den type in ein module ausgelagert, und normal lagert man jedes Module in eine eingene Datei aus. Dann bleibt die Übersicht erhalten und auch das Versionskontrollprogramm freut sich.

Durch das Modul ist es möglich den type RocketControl überall im Programm zu nutzen, ohne die Definition mit allen Variabeln erneut hinschreiben zu müssen. Damit verhindert man nicht nur etliche copy&paste Fehler. Wenn man den type RocketControl um einige Variablen ergänzt, muss nur eine Stelle im Code geändert werden. Mit COMMON müssten alle Stellen im Programm geändert werden, wo RocketControl genutzt wird. Und jedesmal läuft man Gefahr einen Fehler zu machen. Variablen vergessen oder zu vertauschen. Und wie gesagt, mit COMMON kann der Compiler uns nicht vor Fehler beschützen. Werden types benutzt, kommt man erst garnicht in eine Situaton um die erwähnten Fehler zu machen. Der Compiler erledigt den Rest für uns. Und er kann noch mehr!

Für alle die es nicht wissen: Mit dem use-keyword können Module benutzt werden. Z.B. "use RocketControlModule"

Das Benutzen von types in Modulen bietet noch weitere Vorteile. Angenommen wird würden unseren type RocketControl um die Subroutine setCountDown(value) erweitern, um anzugeben wie lange der Countdown läuft bis die Rakete startet. Die Subroutine nimmt genau ein Argument "value" entgegen der vom Type Integer sein soll.

Wenn wir nun einen Fehler machen, und wir sind Menschen, wir machen etliche Fehler... Wenn wir also der Subroutine setCountDown zwei Variablen übergeben, statt nur einer, was dann? Ohne Module sagt der Compiler garnichts. Im günstigsten Falle gibt es ein Warning.
Das Programm würde laufen, aber könnte auch Abstürzen.

Das ist doch schön. Wir haben ein offensichtlich falsches Programm, welches keine Compilier und auch keine Laufzeitfehler zeigt. Das ist eine tickende Zeitbombe! Irgendwann wechseln wir den Compiler, oder wecheln den Rechner, oder schalten eine Compileroptimierung dazu und BUMM Segfault beim Funktionsaufruf. Na Geil. Das kann doch kein normaler Mensch debuggen. Vorallem nicht nach 10 20 Jahren.

Also? Genau, seine Funktionen in Module packen. Ich darf das mal demonstieren:

rocket.f03:71.32:

  call control%setCountDown(1,2)
                                1
Error: More actual than formal arguments in procedure call at (1)

Ja ist es denn Wahr, wir haben schon wieder die Welt gerettet.
Zur Erklärung warum Module die Welt retten: Sieht der Compieler ein Modul, legt er eine .mod Datei an. In unserem Fall würde sie rocketcontrolclass.mod heissen. Benutzt man den gfortran kann man diese Datei sogar mit einem einfachen Texteditor öffnen und sich ansehen. Man sieht in kodierter Form zu jede Funktion ihre Eigenschaften. Wieviel Parameter sie entgegen nimmt und vom welchen Type sie sind.
Mit einem Module kann der Compiler also sehr einfach unseren Fehler entdecken. Und genau das ist doch eine der Hauptaufgaben von Compiler. Neben der Umwandlung von Text in Binärcode muss er schauen, ob das alles Sinn macht was wir programmiert haben. Meistens stimmt es nicht...

Ich möchste dieses Beispiel benutzen um einen weiteren beliebten Fehler zu zeigen.
Angenommen der Benutzer unseres tollen Programms setzt nun CountDown von 5.5 Sekunden. Er übergibt also eine Gleitkommazahl vom Type Real einer Funktion, die einen Integer erwartet. Ohne Module sagt der Compiler nichts, bestensfalls ein Warning.
Beim Ausführen wird das Bitmuster der Real Zahl als Integer Zahl interpretiert. Dabei kann alles mögliche herauskommen. Sehr größe Zahlen die die Rakete erst nächstes Jahr starten oder negative Zahlen oder gar Null. "ROCKET STARTED, YOU ARE DEAD!" Schonwieder... Nicht so gut.

Also Module retten mal wieder die Welt. Hier die passende Fehlermeldung:

rocket.f03:71.28:

  call control%setCountDown(1.0)
                            1
Error: Type mismatch in argument 'value' at (1); passed REAL(4) to INTEGER(4)

Sehr schön. Der Compiler hat uns wieder gerettet.
Wir schreiben also dem Entwickler dass sein Programm scheisse ist, und es gefälligst auch Kommazahlen unterstützen soll. Der fleissige Entwickler schickt uns sofort die neue Version zu und...

Error: Type mismatch in argument 'value' at (1); passed REAL(8) to REAL(4)

Verdammt! Was ist denn nun schon wieder? In Fortran können 64Bit Zahlen nicht ohne weiteres in 32Bit Zahlen umgewandelt werden. Das ist auch richtig so. Man würde wertvolle Informationen bei der Umwandlung verlieren und gerade Zahlengenauigkeit ist in der Wissenschaft sehr wichtig. An dieser Stelle muss ich sagen: "Gut gemacht, Fortran Entwickler!" Genießt es, es kommt nicht noch einmal vor.
Wie löst man das Problem? Auf die richtige Art und Weise? Wir speichern in RocketControl 64Bit Zahlen und geben dem Benutzer die möglichkeit 64Bit oder 32Bit Zahlen einzugeben. Mit Function overloading geht das sehr elegant und die Umwandlung von 32Bit nach 64Bit geht verlustfrei.

Module können gut die Welt retten. War es das jetzt? Ha! Noch lange nicht. Was ist mit diesem Stück Code?


control%timeLeft = 1.0

Anders als bei Funktionsaufrufe wo man den Stack kaputt machen kann, ist hier die Umwandlung ohne Error Möglich. Man bekommt höchstens ein Warning

  control%timeLeft = 1.0
                     1
Warning: Possible change of value in conversion from REAL(4) to INTEGER(4) at (1)

Ja, was nutzen uns all die Funktionen im Module, wenn der Programmierer sie nicht benutzt und stattdessen direkt auf die Variable zugreift. Es sollte jedem einleuchten, dass die Welt wieder verloren ist.

Glücklicherweise wurde der Fortran Standard nachgerüstet und man kann type Variablen als private markieren.


  type, public :: RocketControl
    integer, private :: timeLeft
  end type

Per Default ist "public" vorgegeben, so dass die Variable gelesen und geschrieben werden kann. Gibt man "private" vor, kann die Variable nur noch von Procedure genutzt werden, die RocketControl enthält. Versucht man von ausserhalb auf die Variable lesend oder schreiben zuzugreifen, erhält man einen Compiler Fehler:

rocket.f03:71.18:

  control%timeLeft = 1
                  1
Error: Component 'timeleft' at (1) is a PRIVATE component of 'rocketcontrol'

Und die Welt ist wieder sicher. Geschützt vor un/absichtlichen Zugriffen von Programmierer die keine Ahnung haben was sie tun. Mein Superheld der Module-Compiler.

Wir zwingen den Benutzer also die setCountDown Funktion zu benutzen. Und es ist zu 100% sichergestellt, dass der Compiler mit Modulen die gezeigten Fehler erkennt. Jetzt können wir auch folgende Situation gut lösen:


call control%setCountDown(-1)

Negative Werte machen für einen Countdown natürlich keinen Sinn. Wir können in der Funktion den übergebenen Wert auf Gültigkeit überprüfen, eine Fehlermeldung ausgeben und den neue Wert nicht akzeptieren. Da der Benutzer die Funktion setCountDown() aufrufen MUSS, ist auch sichergestellt dass die Überprüfung stattfinden.

Und wir können uns entspannt zurück lehnen und sicher gehen, dass auch in den nächsten 10 Jahren niemand unser Programm falsch benutzen kann. Selbst wenn er es will. Compiler+Module+neuen Standard sei Dank.

Anmerkung:
Wie bereits erwähnt ist "public" default in Fortran. Bei C++ ist es "private" für Klassen. Fortran lebt also in dem Glauben, dass die Programmierer immer alles richtig machen und niemand un/absichtlich eine Variable falsch benutzt.
In C++ ist die Welt schwarz, jeder hat nur böses im Sinn, niemand kann man trauen, alles muss kontrolliert werden. Wenn wir mathematische Modulle implementieren, welche ein Teil der Umwelt abbilden, welcher Ansatz wäre da wohl realistischer? ;)

« Newer PostsOlder Posts »

Powered by WordPress