{"id":2468,"date":"2016-01-24T22:35:17","date_gmt":"2016-01-24T21:35:17","guid":{"rendered":"http:\/\/roboblog.fatal-fury.de\/?p=2468"},"modified":"2016-01-24T22:35:17","modified_gmt":"2016-01-24T21:35:17","slug":"warum-kurze-arrays-in-fortran-und-cc-nicht-immer-die-richtige-wahl-ist","status":"publish","type":"post","link":"http:\/\/roboblog.fatal-fury.de\/?p=2468","title":{"rendered":"Warum (kurze) Arrays in Fortran (und C\/C++) nicht immer die richtige Wahl ist"},"content":{"rendered":"<p>Der Name ist Programm.<br \/>\nHeute mal tex und pdf Version.<\/p>\n<p><a href=\"http:\/\/roboblog.fatal-fury.de\/wp-content\/uploads\/2016\/01\/wkfanidrwi.pdf\">wkfanidrwi.pdf<\/a><\/p>\n<pre>\r\n\\documentclass[]{article}\r\n\\usepackage{listings}\r\n\\usepackage[utf8]{inputenc}\r\n\\usepackage{ngerman}\r\n\r\n%opening\r\n\\title{Warum (kurze) Arrays in Fortran (und C\/C++) nicht immer die richtige Wahl ist}\r\n\\author{Thomas Huxhorn}\r\n\r\n\r\n\\begin{document}\r\n\r\n\\maketitle\r\n\r\n\\tableofcontents\r\n\r\n\\begin{abstract}\r\nIn diesem kleinen Artikel lege ich ein paar Gedanken nieder \u00fcber einen Fehler mit \r\n(kurzen) Arrays, der systematisch immer wieder Auftritt. Die Wahl der Programmiersprache\r\nist unabh\u00e4ngig f\u00fcr diese Art von Fehler.\r\n\\end{abstract}\r\n\r\n\\section{Ein Fehler}\r\nDer Fehler selbst l\u00e4sst sich in einem Satz beschreiben: Einer Funktion wurde nur ein Wert\r\n\u00fcbergeben, obwohl sie ein Array von zwei Werten erwartet. Das klingt erstmal recht banal, hat es aber in sich.\r\nHier der entsprechende Beispiel Code in Fortran:\r\n\r\n\\begin{lstlisting}[frame=single]\r\nsubroutine func(data)\r\n  integer, intent(in) :: data(2)\r\n  write(*,*) data(1), data(2)\r\nend subroutine\r\n\r\nprogram test\r\n  integer :: data(2)\r\n  data(1) = 1\r\n  data(2) = 2\r\n  call func(data(1))\r\nend program\r\n\\end{lstlisting}\r\n\r\nDas Programm compiliert ohne Warnings, ohne Laufzeitfehlermeldungen und keinerlei \u00dcberpr\u00fcfungen die \r\nder Compiler bereit stellt schlagen Alarm. Sogar die Ausgabe des Programms ist richtig. \r\nBenutzt wurde der neuste GNU Fortran Compiler in der Version 5.2.1\r\n\r\n\\begin{lstlisting}[frame=single]\r\n$ gfortran -Wall -Wextra -fcheck=all \r\n-fsanitize=address,undefined wkfanidrwi1.f95\r\n$ .\/a.out \r\n1           2\r\n\\end{lstlisting}\r\n\r\n\\section{Ein unsichtbarer Fehler}\r\nAuch der memory error detector valgrind findet keinerlei Fehler. Der Grund ist ganz einfach: Aus technischer\r\nSicht existiert in diesem Programm auch kein Fehler! Das ist auf den ersten Blick etwas verwirrend. \r\nDer Programmierer wollte, dass die Subroutine func() nur die erste Zahl im Array data verarbeitet. Die Subroutine \r\nhingegen, erwartet nicht nur zwei Zahlen, sie nimmt sich auch zwei Zahlen! Selbst, wenn man ihr nur eine gibt.\r\nWir haben hier also gleichzeitig zwei Arten von Fehler. Der Programmierer hat eine Vorstellung, was passieren soll.\r\nSeine Kenntnis \u00fcber die Subroutine func ist aber falsch, so dass er zu wenig Daten \u00fcbergibt. Das ist der erste Fehler.\r\nDer zweite Fehler ist, dass der Programmierer die Subroutine so erstellt hat, dass sie immer genau so viele Daten\r\nverarbeitet, wie sie braucht. Egal ob sie auch gen\u00fcgend Daten bekommt. Die beiden Fehler heben sich gegenseitig auf,\r\nund es kommt auch das richtige Ergebnis raus. Kein Programm kann den Fehler finden.\r\n\r\n\\section{Ein versteckter Fehler}\r\nHeutzutage sind Programme riesig. Sie bestehen aus einer Millionen Zeilen Code, sind \u00fcber Jahrzehnte gewachsen und kein\r\nMensch blickt mehr in allen Details durch. Diese Art Fehler zu erkennen ist also menschlich nicht m\u00f6glich. Ein \r\nComputer Programm k\u00f6nnte es aber. Der Compiler sieht beim Compilieren das gesamte Programm, den kompletten Quellcode.\r\nMan muss den Compiler aber auch seine Arbeit tun lassen, und nicht selbst die Arraygr\u00f6\u00dfe bestimmen.\r\n\r\n\\section{L\u00f6sungversuch}\r\nDie erste Idee ist also, das die Subroutine nicht ein Array fester Gr\u00f6\u00dfe, sondern eins variabler Gr\u00f6\u00dfe erwartet. So kann zur\r\nLaufzeit erkannt werden, ob zu viele oder zu wenige Daten \u00fcbergeben wurden. Hier eine Beispiel Implementierung.\r\n\r\n\\begin{lstlisting}[frame=single]\r\nmodule testmodule\r\ncontains\r\nsubroutine func(data)\r\ninteger, intent(in) :: data(:)\r\nwrite(*,*) data(1), data(2)\r\nend subroutine\r\nend module\r\n\r\nprogram test\r\nuse testmodule\r\ninteger :: data(2)\r\ndata(1) = 1\r\ndata(2) = 2\r\ncall func(data(1))\r\n! call func(data(1:1))\r\nend program\r\n\\end{lstlisting}\r\n\r\nSie Subroutine muss dazu in ein Modul gepackt werden, sonst k\u00f6nnen keine assumed-shape Arrays benutzt werden.\r\n\r\n\\begin{lstlisting}[frame=single]\r\n$ gfortran -Wall -Wextra -fcheck=all wkfanidrwi1.f95\r\nwkfanidrwi2.f95:14:12:\r\n\r\ncall func(data(1))\r\n1\r\nError: Rank mismatch in argument 'data' at (1) \r\n(rank-1 and scalar)\r\n\\end{lstlisting}\r\n\r\nSchon allein das Compilieren f\u00fchrt hier zu einem Fehler. Aber nur, weil ein Array erwartet, aber ein Skalar \u00fcbergeben wurde. Das kann man ganz leicht aus tricksen, in dem man ein Array der L\u00e4nge 1 \u00fcbergibt. Dieser Fall wird dann, wie\r\nerwartet, zur Laufzeit abgefangen.\r\n\r\n\\begin{lstlisting}[frame=single]\r\n$ .\/a.out \r\nAt line 5 of file wkfanidrwi2.f95\r\nFortran runtime error: Index '2' of dimension 1 of array\r\n'data' above upper bound of 1\r\n\\end{lstlisting}\r\n\r\nDer Fehler wird jetzt zur Laufzeit erkannt. Das ist schon einmal ein riesiger Fortschritt, als wenn der Fehler nie erkannt wird. Allerdings muss nun bei jedem Array Zugriff die L\u00e4nge des Arrays \u00fcberpr\u00fcft werden. Im Durchschnitt verdoppelt das die Laufzeit des Programms. Und der Fehler wird auch nur gemeldet, wenn entsprechende Checks im\r\nCompiler eingeschaltet wurden. \\\\\r\nK\u00f6nnen wir das nicht besser machen? Immerhin WISSEN wir ja, wie viele Daten die Subroutine brauch. Das muss sich\r\nausnutzen lassen.\r\n\r\n\\section{L\u00f6sung allgemein}\r\nDie allgemeine L\u00f6sung f\u00fcr diese Art von Fehler besteht darin, einfach keine Arrays f\u00fcr so wenige Daten zu nutzen.\r\nUm dennoch nicht jede einzelne Zahl als Parameter zu \u00fcbergeben, und damit den Code unn\u00f6tig gro\u00df zu machen,\r\nwerden die Daten in einem neuen Datentyp gruppiert. \\\\\r\nDa der Datentyp zwangsl\u00e4ufig einen Namen braucht, k\u00f6nnen wir den Variablen gleichzeitig eine Bedeutung geben. So ist\r\nein weiterer Fehler ausgeschlossen, dass man z.B. aus versehen data(1) statt data(2) benutzt.\r\nHier die Beispiel Implementierung:\r\n\r\n\\begin{lstlisting}[frame=single]\r\nmodule testmodule\r\ntype Uhrzeit_t\r\ninteger :: minute, stunde\r\nend type\r\ncontains\r\nsubroutine func(uhrzeit)\r\ntype(Uhrzeit_t), intent(in) :: uhrzeit\r\nwrite(*,*) uhrzeit%stunde, uhrzeit%minute\r\nend subroutine\r\nend module\r\n\r\nprogram test\r\nuse testmodule\r\ntype(Uhrzeit_t) :: uhrzeit\r\nuhrzeit%minute = 1\r\nuhrzeit%stunde = 2\r\ncall func(uhrzeit)\r\nend program\r\n\\end{lstlisting}\r\n\r\nDer urspr\u00fcngliche Fehler ist nun nicht mehr m\u00f6glich. Unn\u00f6tig Laufzeiteinverschlechterungen sind auch nicht mehr vorhanden. Der Compiler kann durch Typen\u00fcberpr\u00fcfungen zur Compilezeit feststellen, ob die Subroutine immer den\r\nrichtigen Datentype erh\u00e4lt. \\\\\r\n\r\n\\section{Ausblick}\r\nSo weit so gut. Mit der Implementierung dem Uhrzeit Modul haben wir aber viele weitere Fehlerquellen erschaffen.\r\nIn der Praxis reicht es nicht einfach nur Stunde und Minute auszugeben. Datum und Uhrzeit m\u00fcssen verglichen, umgerechnet, verrechnet und erstellt werden. F\u00fcr all das existieren schon fertige Module, teilweise standardisierte und alle getestet. \r\n\r\n\r\n\\section{L\u00f6sung Qt}\r\n\r\nQt bietet hierf\u00fcr die Klasse QTime an.\r\nHier ein Beispiel wie ein Zeitobjekt erstellt, einer Funktion\r\n\u00fcbergeben und ausgegeben wird.\r\n\r\n\\begin{lstlisting}[frame=single]\r\nvoid func(QTime time) {\r\n\tqDebug() << time.toString();\r\n}\r\n\r\nQTime time(1,2);\r\nfunc(time);\r\n\\end{lstlisting}\r\n\r\nQTime selbst ist eine Klasse welche nicht nur Stunden, Minuten, Sekunden\r\nund Millisekunden speichern kann. Sondern auch die Textausgabe im\r\njeweiligen Landesformat \u00fcbernimmt und mit der Klasse QDateTime werden\r\nauch Winter und Sommerzeit beachtet.\r\n\r\n\\section{Technische Details des Fehlers}\r\nWie am Anfang erw\u00e4hnt ist diese Fehlerart unabh\u00e4ngig der gew\u00e4hlten\r\nProgrammiersprache. Auch in C k\u00f6nnen Arrays als Funktions Parameter\r\neine feste L\u00e4nge zugewiesen werden. Und da C++ r\u00fcckw\u00e4rts kompatibel\r\nzu C ist, ist der Fehler auch in C++ machbar. Es h\u00e4ngt nicht von der \r\nSprache ab, sondern die Art wie man programmiert. \\\\\r\nIn der Subroutine wurde ein Array fester L\u00e4nge angegeben. Damit \r\nexistiert das Array f\u00fcr den Compiler! und er w\u00fcrde so nie einen\r\nFehler finden. Warum die ganzen Memory Check Programm auch keinen\r\nFehler finden, ist recht einfach. Es existiert im Programm ja auch\r\nein Array richtiger L\u00e4nge. Es wurde nur ein statt zwei Elemente \r\n\u00fcbergeben. Aber in dem Moment wo in der Subroutine auf das\r\nzweite Element zugegriffen wird, wird g\u00fcltiger Speicher ausgelesen.\r\n\r\n\\end{document}\r\n\r\n\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Der Name ist Programm. Heute mal tex und pdf Version. wkfanidrwi.pdf \\documentclass[]{article} \\usepackage{listings} \\usepackage[utf8]{inputenc} \\usepackage{ngerman} %opening \\title{Warum (kurze) Arrays in Fortran (und C\/C++) nicht immer die richtige Wahl ist} \\author{Thomas Huxhorn} \\begin{document} \\maketitle \\tableofcontents \\begin{abstract} In diesem kleinen Artikel lege ich ein paar Gedanken nieder \u00fcber einen Fehler mit (kurzen) Arrays, der systematisch immer wieder [&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":[17],"class_list":["post-2468","post","type-post","status-publish","format-standard","hentry","category-allgemein","tag-cpp"],"_links":{"self":[{"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=\/wp\/v2\/posts\/2468","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=2468"}],"version-history":[{"count":2,"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=\/wp\/v2\/posts\/2468\/revisions"}],"predecessor-version":[{"id":2471,"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=\/wp\/v2\/posts\/2468\/revisions\/2471"}],"wp:attachment":[{"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2468"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2468"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2468"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}