{"id":2024,"date":"2014-12-20T22:03:11","date_gmt":"2014-12-20T21:03:11","guid":{"rendered":"http:\/\/roboblog.fatal-fury.de\/?p=2024"},"modified":"2014-12-23T12:35:41","modified_gmt":"2014-12-23T11:35:41","slug":"fortran-common-is-deadly","status":"publish","type":"post","link":"http:\/\/roboblog.fatal-fury.de\/?p=2024","title":{"rendered":"Fortran COMMON is deadly"},"content":{"rendered":"<p>In Fortran 77 gibt es den COMMON Block. Das sind sowas wie globale Variablen die man umbenennen kann. Gleich im n\u00e4chsten Standard (1990) hat man das wieder rausgenommen, weil es eine unheimliche Scheisse ist. (Die Erkenntnis hat ja nur 13 Jahre gedauert).<\/p>\n<p>Folgenden Beispiel zeigt warum.<\/p>\n<pre><code>\r\n      SUBROUTINE PREVENTSTART\r\n        COMMON \/ROCKETCONTROL\/ ISTARTCOUNTDOWN, ITIMELEFT\r\n\r\n        ! stop the countdown and reset time\r\n        ISTARTCOUNTDOWN = 0\r\n        ITIMELEFT = 1000\r\n      END SUBROUTINE\r\n\r\n      PROGRAM ROCKET\r\n\r\n        COMMON \/ROCKETCONTROL\/ ITIMELEFT, ISTARTCOUNTDOWN\r\n\r\n        ! initital values\r\n        ITIMELEFT = 1000\r\n        ISTARTCOUNTDOWN = 0\r\n\r\n        ! now we start the countdown\r\n        ISTARTCOUNTDOWN = 1\r\n\r\n        ! some countdown loop here\r\n        ! 999\r\n        ! 998\r\n        ! 997\r\n        ! ...\r\n        \r\n        ! Oh no! Stop the countdown\r\n        CALL PREVENTSTART\r\n\r\n        IF(ITIMELEFT == 0) THEN\r\n          WRITE(*,*) \"ROCKET STARTED, YOU ARE DEAD!\"\r\n        ELSE\r\n          WRITE(*,*) \"COUNTDOWN STOPPED!\"\r\n        ENDIF\r\n      END PROGRAM\r\n<\/code><\/pre>\n<pre>\r\n$ gfortran -Wall rocket.for -o rocket\r\n$ .\/rocket \r\n ROCKET STARTED, YOU ARE DEAD!\r\n<\/pre>\n<p>Shit ;) Ok die Erkenntnis hat gefruchtet. Man hat den Fortran Standard nicht umsonst schon 4 mal ge\u00e4ndert. Damit ihr auch versteht wie es richtig geht, hier ein Beispiel im Fortran 2003 Standard.<\/p>\n<pre><code>\r\nmodule RocketControlClass\r\n  implicit none\r\n  private\r\n\r\n  type, public :: RocketControl\r\n    integer, private :: timeLeft\r\n    integer, private :: countdownIsRunning\r\n\r\n    contains\r\n\r\n    procedure :: startCountdown\r\n    procedure :: preventStart\r\n    procedure :: getTimeLeft\r\n  end type\r\n\r\n  interface RocketControl\r\n    module procedure createRocketControl\r\n  end interface\r\n\r\n  contains\r\n\r\n  function createRocketControl() result(this)\r\n    implicit none\r\n    type(RocketControl) :: this\r\n\r\n    this%timeLeft = 1000\r\n    this%countdownIsRunning = 0\r\n  end function\r\n\r\n  subroutine startCountdown(this)\r\n    implicit none\r\n    class(RocketControl), intent(inout) :: this\r\n\r\n    this%countdownIsRunning = 1\r\n  end subroutine\r\n\r\n  subroutine preventStart(this)\r\n    implicit none\r\n    class(RocketControl), intent(inout) :: this\r\n\r\n    this%countdownIsRunning = 0\r\n  end subroutine\r\n\r\n  pure function getTimeLeft(this) result(timeLeft)\r\n    implicit none\r\n    class(RocketControl), intent(in) :: this\r\n    integer :: timeLeft\r\n\r\n    timeLeft = this%timeleft\r\n  end function\r\nend module\r\n\r\nprogram rocket\r\n  use RocketControlClass\r\n  implicit none\r\n\r\n  type(RocketControl) :: control\r\n\r\n  ! inititalise values\r\n  control = RocketControl()\r\n\r\n  ! now we start the countdown\r\n  call control%startCountdown\r\n\r\n  ! some countdown loop here\r\n  ! 999\r\n  ! 998\r\n  ! 997\r\n  ! ...\r\n\r\n  ! Oh no! Stop the countdown\r\n  call control%preventStart\r\n\r\n  if(control%getTimeLeft() == 0) then\r\n    write(*,*) \"rocket started, you are dead!\"\r\n  else\r\n    write(*,*) \"countdown stopped!\"\r\n  endif\r\nend\r\n<\/code><\/pre>\n<pre>\r\n$ gfortran -std=f2003 -Wall rocket.f03 -o rocket2003\r\n$ .\/rocket2003 \r\n countdown stopped!\r\n<\/pre>\n<p>Gott sei Dank. Diesmal hat es funktioniert, wir leben noch.<br \/>\nF\u00fcr 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\u00f6nnen. Scheisse, ich sag es ja.<\/p>\n<p>Kommen wir nun zum 2003er Code. Der ist erstmal deutlich l\u00e4nger. Aber das ist Absicht um einige Konzepte zu zeigen die man f\u00fcr ein ordentlichen Programm so braucht.<\/p>\n<p>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\u00fcrde schon langen um den 77er Fehler zu erkennen. <\/p>\n<p>Aber die Welt dreht sich weiter und seit 1995 gehts erst so richtig rund. <\/p>\n<p>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\u00e4lt, bekommt es stattdessen den Suffix \"Class\" in Anlehung an das class Keyword in C++. *uff* Fortran ist anstrengend.<\/p>\n<p>Okay zur\u00fcck 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 \u00dcbersicht erhalten und auch das Versionskontrollprogramm freut sich.<\/p>\n<p>Durch das Modul ist es m\u00f6glich den type RocketControl \u00fcberall im Programm zu nutzen, ohne die Definition mit allen Variabeln erneut hinschreiben zu m\u00fcssen. Damit verhindert man nicht nur etliche copy&paste Fehler. Wenn man den type RocketControl um einige Variablen erg\u00e4nzt, muss nur eine Stelle im Code ge\u00e4ndert werden. Mit COMMON m\u00fcssten alle Stellen im Programm ge\u00e4ndert werden, wo  RocketControl genutzt wird. Und jedesmal l\u00e4uft man Gefahr einen Fehler zu machen. Variablen vergessen oder zu vertauschen. Und wie gesagt, mit COMMON kann der Compiler uns nicht vor Fehler besch\u00fctzen. Werden types benutzt, kommt man erst garnicht in eine Situaton um die erw\u00e4hnten Fehler zu machen. Der Compiler erledigt den Rest f\u00fcr uns. Und er kann noch mehr!<\/p>\n<p>F\u00fcr alle die es nicht wissen: Mit dem use-keyword k\u00f6nnen Module benutzt werden. Z.B. \"use RocketControlModule\" <\/p>\n<p>Das Benutzen von types in Modulen bietet noch weitere Vorteile. Angenommen wird w\u00fcrden unseren type RocketControl um die Subroutine setCountDown(value) erweitern, um anzugeben wie lange der Countdown l\u00e4uft bis die Rakete startet. Die Subroutine nimmt genau ein Argument \"value\" entgegen der vom Type Integer sein soll.<\/p>\n<p>Wenn wir nun einen Fehler machen, und wir sind Menschen, wir machen etliche Fehler... Wenn wir also der Subroutine setCountDown zwei Variablen \u00fcbergeben, statt nur einer, was dann? Ohne Module sagt der Compiler garnichts. Im g\u00fcnstigsten Falle gibt es ein Warning.<br \/>\nDas Programm w\u00fcrde laufen, aber k\u00f6nnte auch Abst\u00fcrzen. <\/p>\n<p>Das ist doch sch\u00f6n. 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.<\/p>\n<p>Also? Genau, seine Funktionen in Module packen. Ich darf das mal demonstieren:<\/p>\n<pre>\r\nrocket.f03:71.32:\r\n\r\n  call control%setCountDown(1,2)\r\n                                1\r\nError: More actual than formal arguments in procedure call at (1)\r\n<\/pre>\n<p>Ja ist es denn Wahr, wir haben schon wieder die Welt gerettet.<br \/>\nZur Erkl\u00e4rung warum Module die Welt retten: Sieht der Compieler ein Modul, legt er eine .mod Datei an. In unserem Fall w\u00fcrde sie rocketcontrolclass.mod heissen. Benutzt man den gfortran kann man diese Datei sogar mit einem einfachen Texteditor \u00f6ffnen und sich ansehen. Man sieht in kodierter Form zu jede Funktion ihre Eigenschaften. Wieviel Parameter sie entgegen nimmt und vom welchen Type sie sind.<br \/>\nMit 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\u00e4rcode muss er schauen, ob das alles Sinn macht was wir programmiert haben. Meistens stimmt es nicht...<\/p>\n<p>Ich m\u00f6chste dieses Beispiel benutzen um einen weiteren beliebten Fehler zu zeigen.<br \/>\nAngenommen der Benutzer unseres tollen Programms setzt nun CountDown von 5.5 Sekunden. Er \u00fcbergibt also eine Gleitkommazahl vom Type Real einer Funktion, die einen Integer erwartet. Ohne Module sagt der Compiler nichts, bestensfalls ein Warning.<br \/>\nBeim Ausf\u00fchren wird das Bitmuster der Real Zahl als Integer Zahl interpretiert. Dabei kann alles m\u00f6gliche herauskommen. Sehr gr\u00f6\u00dfe Zahlen die die Rakete erst n\u00e4chstes Jahr starten oder negative Zahlen oder gar Null. \"ROCKET STARTED, YOU ARE DEAD!\" Schonwieder... Nicht so gut. <\/p>\n<p>Also Module retten mal wieder die Welt. Hier die passende Fehlermeldung:<\/p>\n<pre>\r\nrocket.f03:71.28:\r\n\r\n  call control%setCountDown(1.0)\r\n                            1\r\nError: Type mismatch in argument 'value' at (1); passed REAL(4) to INTEGER(4)\r\n<\/pre>\n<p>Sehr sch\u00f6n. Der Compiler hat uns wieder gerettet.<br \/>\nWir schreiben also dem Entwickler dass sein Programm scheisse ist, und es gef\u00e4lligst auch Kommazahlen unterst\u00fctzen soll. Der fleissige Entwickler schickt uns sofort die neue Version zu und...<\/p>\n<pre>Error: Type mismatch in argument 'value' at (1); passed REAL(8) to REAL(4)\r\n<\/pre>\n<p>Verdammt! Was ist denn nun schon wieder? In Fortran k\u00f6nnen 64Bit Zahlen nicht ohne weiteres in 32Bit Zahlen umgewandelt werden. Das ist auch richtig so. Man w\u00fcrde 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\u00dft es, es kommt nicht noch einmal vor.<br \/>\nWie l\u00f6st man das Problem? Auf die richtige Art und Weise? Wir speichern in RocketControl 64Bit Zahlen und geben dem Benutzer die m\u00f6glichkeit 64Bit oder 32Bit Zahlen einzugeben. Mit Function overloading geht das sehr elegant und die Umwandlung von 32Bit nach 64Bit geht verlustfrei. <\/p>\n<p>Module k\u00f6nnen gut die Welt retten. War es das jetzt? Ha! Noch lange nicht. Was ist mit diesem St\u00fcck Code?<\/p>\n<pre><code>\r\ncontrol%timeLeft = 1.0\r\n<\/code><\/pre>\n<p>Anders als bei Funktionsaufrufe wo man den Stack kaputt machen kann, ist hier die Umwandlung ohne Error M\u00f6glich. Man bekommt h\u00f6chstens ein Warning<\/p>\n<pre>  control%timeLeft = 1.0\r\n                     1\r\nWarning: Possible change of value in conversion from REAL(4) to INTEGER(4) at (1)<\/pre>\n<p>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.<\/p>\n<p>Gl\u00fccklicherweise wurde der Fortran Standard nachger\u00fcstet und man kann type Variablen als private markieren. <\/p>\n<pre><code>\r\n  type, public :: RocketControl\r\n    integer, <b>private<\/b> :: timeLeft\r\n  end type\r\n<\/pre>\n<p><\/code><\/p>\n<p>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\u00e4lt. Versucht man von ausserhalb auf die Variable lesend oder schreiben zuzugreifen, erh\u00e4lt man einen Compiler Fehler:<\/p>\n<pre>\r\nrocket.f03:71.18:\r\n\r\n  control%timeLeft = 1\r\n                  1\r\nError: Component 'timeleft' at (1) is a PRIVATE component of 'rocketcontrol'\r\n<\/pre>\n<p>Und die Welt ist wieder sicher. Gesch\u00fctzt vor un\/absichtlichen Zugriffen von Programmierer die keine Ahnung haben was sie tun. Mein Superheld der Module-Compiler.<\/p>\n<p>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\u00f6nnen wir auch folgende Situation gut l\u00f6sen:<\/p>\n<pre><code>\r\ncall control%setCountDown(-1)\r\n<\/code><\/pre>\n<p>Negative Werte machen f\u00fcr einen Countdown nat\u00fcrlich keinen Sinn. Wir k\u00f6nnen in der Funktion den \u00fcbergebenen Wert auf G\u00fcltigkeit \u00fcberpr\u00fcfen, eine Fehlermeldung ausgeben und den neue Wert nicht akzeptieren. Da der Benutzer die Funktion setCountDown() aufrufen MUSS, ist auch sichergestellt dass die \u00dcberpr\u00fcfung stattfinden. <\/p>\n<p>Und wir k\u00f6nnen uns entspannt zur\u00fcck lehnen und sicher gehen, dass auch in den n\u00e4chsten 10 Jahren niemand unser Programm falsch benutzen kann. Selbst wenn er es will. Compiler+Module+neuen Standard sei Dank. <\/p>\n<p>Anmerkung:<br \/>\nWie bereits erw\u00e4hnt ist \"public\" default in Fortran. Bei C++ ist es \"private\" f\u00fcr Klassen. Fortran lebt also in dem Glauben, dass die Programmierer immer alles richtig machen und niemand un\/absichtlich eine Variable falsch benutzt.<br \/>\nIn C++ ist die Welt schwarz, jeder hat nur b\u00f6ses im Sinn, niemand kann man trauen, alles muss kontrolliert werden. Wenn wir mathematische Modulle implementieren, welche ein Teil der Umwelt abbilden, welcher Ansatz w\u00e4re da wohl realistischer? ;)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In Fortran 77 gibt es den COMMON Block. Das sind sowas wie globale Variablen die man umbenennen kann. Gleich im n\u00e4chsten 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 [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[30],"class_list":["post-2024","post","type-post","status-publish","format-standard","hentry","category-allgemein","tag-fortran"],"_links":{"self":[{"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=\/wp\/v2\/posts\/2024","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2024"}],"version-history":[{"count":11,"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=\/wp\/v2\/posts\/2024\/revisions"}],"predecessor-version":[{"id":2046,"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=\/wp\/v2\/posts\/2024\/revisions\/2046"}],"wp:attachment":[{"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2024"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2024"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2024"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}