Numerische Methoden

Aus Physik
Wechseln zu: Navigation, Suche

Allgemeines

WICHTIG - Über diese Wiki-Seite

Eine der Grundideen eines Wikis ist es, dass sich jeder einbringen kann. Deshalb wird darum gebeten nicht davor zurückzuschrecken Erweiterungen, Fragen, Ideen, usw. hier einzutragen. Damit profitieren alle Besucher der Lehrveranstaltung, und zukünftige Teilnehmern steht noch mehr Information zur Verfügung.

Ein paar Anmerkungen zum Thema "Objektorientierte Programmierung (OOP)"

Objektorientierte Programmierung ist bedeutend mehr als die Verwendung von Klassen!

Erst wenn man die grundlegenden Ideen wie Kapselung, Vererbung, Polymorphismus, ... verstanden hat und einsetzen kann, ist man in der Lage die Vorteile der OOP vollständig und effizient zu nutzen. Das heißt aber nicht, dass diese Vorteile einem nicht auch schon vorher das Leben leichter machen können.

Was sind aber nun die Vorteile (kein Anspruch auf Vollständigkeit):

  • Mit Hilfe der Kapselung können klare Zuständigkeiten geschaffen, und damit Seiteneffekte vermieden werden.
  • Einzelne Module können leichter wiederverwendet werden
  • Der Code wird leichter wartbar und erweiterbar
  • Durch die gegebene Strukturierung fällt es leichter den Code zu verstehen (auch für den Programmierer, wenn er seinen Code nach einer gewissen Zeit wieder anschaut) und sich in den Code einzuarbeiten
  • Bessere Kommunizierbarkeit. Einerseits ist auch hierbei die Strukur hilfreich, andererseits gibt es viele Tools zur Planung und Visualisierung des Designs.

Ist OOP nötig?
Zweifelsohne sind die (was den Codeumfang betrifft) kleinen Beispiele, die in der Numerische Methoden UE zu schreiben sind, problemlos mit "herkömmlicher" strukturierter Programmierung programmierbar. Natürlich kann man leicht grobe (Design-)Fehler einbauen, wie z.B. globale Variablen "unpassend" zu verändern, jedoch schützt auch OOP nicht vor falschem Vorgehen, Verdenkern und Fehldesign.
Zwar lassen sich die Vorteile der OOP schon bei solch kleinen Projekten nutzen, bei größeren und/oder wachsenden Systemen ist jedoch die Verwendung objektorienterter Konzepte beihahe unumgänglich. Mit "Strukturierter Programmierung" erstellter Code wird mit zunehmender Größe rapide unlesbarer und unwartbarer. Insbesondere globale Variablen und globale Sichtbarkeit der Funktionen stellen große Probleme dar. Am schlimmsten zeigen sich Designmängel wenn der Code refactored/geändert oder ein Teil daraus wiederverwendet werden soll.
Sollte also jemand vorhaben sich in Zukunft etwas mehr mit Anwendungsprogrammierung zu beschäftigen, so kommt man um das Thema OOP nicht herum. Z.B. ist jede bekanntere Library für die Erstellung von GUIs (QT, WxWidgets, MFC) objektorientiert.

Ist OOP die "höchste Stufe der Programmierkunst"?
Nein. Das Verständnis für Objektorientierung ist erst die Grundlage für viele weiterführende, "darüberliegende" Konzepte wie (Design) Patterns. Weiters gibt es parallel dazu auch ganz andere Programmieransätze wie z.B. Funktionale Programmierung. Geht man noch weiter, so entdeckt man, dass der Softwareentwicklungsprozess an sich eine eigene Wissenschaft ist. Hier möge als Schlagwort eine jüngere Entwicklung, nämlich Extreme Programming (XP), genannt werden.


Gegenüberstellung verschiedener Programmiersprachen

Matlab

Matlab ist sehr vielseitig anwendbar. Man kann es sowohl als konsolenbasierten Taschenrechner als auch für die Erstellung von Simulationsprogrammen mit graphischer Benutzeroberfläche und wunderschön bunten Ausgaben benutzen. - Und natürlich für eine Unmenge an Aufgaben die irgendwo dazwischen liegen.

Das Besondere an Matlab ist sicherlich, dass es speziell für Berechnungen mit Vektoren und Matritzen zugeschnitten ist. So bestehen z.B. viele Berechnungen, für die man in den anderen Programmiersprachen mindestens eine Schleife braucht, aus einem einzigen simplen Befehl. Selbstverständlich kann man dies jedoch nur dann nutzen, wenn man in der Matlab-Programmierweise denkt.

Weiters bringt Matlab eine Unmenge an Funktionen und Toolboxes, die man sich in anderen Programmiersprachen oftmals erst mit vielen Libraries selbst zusammensuchen muss, mit.

Ein weiterer Vorteil ist, dass man sehr schnell und einfach aussagekräftige Diagramme erstellen kann. Diese können auch gleich in diversen Formaten gespeichert und so leicht für Berichte u.Ä. verwendet werden.

Matlab ist jedoch nicht das allmächtige Wunderwerkzeug für den Physiker. Das sicherlich größte Problem ist, dass es wesentlich langsamer als compilierte Programme ist. Wenn man dann auch noch Schleifen programmiert, wo es möglich wäre Vektorrechnungen direkt zu benutzen, dann wird es nochmals wesentlich langsamer.

Matlab-Programme werden (üblicherweise) interpretiert, was heißt, dass man Matlab installiert haben muss, um ein Programm laufen lassen zu können. Zwar gibt es Matlab-Cmpiler, jedoch sind diese bei den mehr oder weniger offiziellen Studentenversionen meist nicht enthalten. Ein daraus resultierender Vorteil ist, dass Matlab-Scripts plattformunabhängig sind.

C

C ist eine imperative, eher alte (entwickelt in den 70ern), nicht allzu umfangreiche und standardisierte (ANSI-C) Sprache. Ein compiliertes (man kann C-Code nicht interpretieren) C-Programm läuft jedoch nur auf einer Plattform.

C ist nicht sehr abstrahiert, wodurch man sie relativ leicht erlernen kann, allerdings bringt sie wenig Komfort mit sich. Der Programmierer kann sehr viel machen, und damit auch viele Fehler, was insbesondere beim Thema "Pointer" oft zu unerklärlichen Abstürzen und langen Fehlersuchen führt.

Für C sind viele Bibliotheken, darunter die stets vorhandene Standard C Library, erhältlich, welche einem die wichtigsten Hilfsmittel für das Programmieren zur Verfügung stellen.

Eines der Hauptanwendungsgebiete ist die Systementwicklung. Einerseits kann man mit C sehr schnelle und hardwarenahe Programme entwickeln, andererseits gibt es für fast jeden Prozessor (vom Mikrocontroller bis zum High-Tech-Großrechner) einen Compiler. Obwohl die Verwaltung großer Projekte mit C eher schwierig ist, ist beinahe jeder Betriebssystemkern in C geschrieben, insbesondere Unix ist eng mit C verknüpft (C wurde für Unix entwickelt).

Eine ganz nette Kurzübersicht über C findet man im Wikipedia.

C++

C++ basiert auf C, was man insbesondere an der gleichen Syntax merkt, ist aber mehr als eine Bloße Erweiterung, da (insbesondere) mit Templates und Objektorientierung vollkommen neue, grundlegende Konzepte hinzugekommen sind. Weiters wurde die Standard-C-Library zur wesentlich umfangreicheren C++ Standardbibliothek mit der STL (Standard Template Library) ausgebaut, welche OO und Templates exzessiv verwendet.

C++ ist zwar eine objektorientierte Programmiersprache, man kann jedoch auch imperativ programmieren und einfach das sehr umfangreiche Angebot an (beinahe immer objektorientierten) C++ Bibliotheken ausnutzen, was aber natürlich nicht gerade "schön" ist. Dafür ist es jedoch auch mit C++ möglich, recht hardwarenahe zu programmieren.

Die meisten Vorteile gegenüber C resultieren aus der OO, hauptsächlich deshalb weil man so große Libraries und Programme übersichtlich gestalten, leicht warten und gut erweitern kann.

Häufige Anwendungen von C++ sind die Entwicklung von Anwendungssoftware mit grafischer Oberfläche, Grafik- und Spieleprogrammierung.

Auch hier bietet Wikipedia eine gute Übersicht (auch über einige Compiler).

Java

Java ist eine beinahe vollständig objektorientierte Sprache, deren Syntax sehr stark an C++ angelehnt ist. Oftmals wirkt es wie eine einfacher zu programmierende Version von C++, so ist z.B. das oftmals verwirrende und fehlerverursachende Konzept der Pointer in Java nicht vorhanden. Der Programmierer hat nicht so viel Macht wie in C oder C++, kann somit aber auch weniger Fehler produzieren.

Wenn man ein Java-Programm schreibt, so werden daraus *.class - Files generiert, welche dann von der Java Virtual Machine "interpretiert" werden. Dies hat den Vorteil, dass ein Class-File auf verschiedensten Betriebssystemen ausgeführt werden kann. Doch genau dies verursachte der größte Nachteil von Java: es war zu langsam. Jedoch ist es seit einiger Zeit möglich Java-Code auch mit einem Native Compiler zu compilieren, welcher ausführbaren Maschinencode, der allerdings nicht mehr plattformunabhängig ist, erzeugt.

Sehr beliebt ist Java für die Erstellung von Anwendungssoftware mit schönen Oberflächen und sog. Applets. Dies sind Anwendungen, die in eine Webseite eingebunden werden. Ein Vorteil von Java ist, dass man Klassen für sehr viele Funktionalitäten "mitkriegt", insbesondere neben GUI-Klassen auch welche für die verschiedensten neuen Technologien wie Datenbankanbindungen, Netzwerkdieste, ...

Hinweis: Die Java Version von Microsoft erfüllt nicht den Java-Standard (von Sun)

Übrigens sind Java und viele Java-Entwicklungsumgebungen frei erhältlich.

Java von Sun (Firma die Java entwickelt hat): SUN-Java

Ressourcen

C/C++ - Spezifisches

Wenn von c-Files oder *.c Files die Rede ist, so sind damit auch meist die C++ Varianten gemeint.

Die Rolle von c- und h-Files

Funktionsdefinitionen und -deklarationen

Eine Funktionsdefinition beinhaltet den gesamten Code, z.B.:

void sayHello(char* name)
{
    printf("hello %s", name);
}

In einer Funktionsdeklaration hingegen steht nur der "Name" der Funktion mit Ein- und Ausgabeparametern (Signatur).

void sayHello(char* name);

Wichtig ist dabei der ; am Ende. Dies macht die Funktion an dieser Stelle nur bekannt, sagt aber nicht, was sie macht. Beim Linken muss dann irgendwo eine Definition der Funktion gefunden werden.

c-Files

beinhalten den eigentlichen Code, genauer gesagt Funktionsdefinitionen. Im einfachsten Falle besteht ein Projekt nur aus einem einzigen c-File, welches die main-Methode beinhaltet.

h-Files

werden benötigt, wenn z.B. Funktionen in mehreren c-Files verwendet werden. In diesem Falle darf der Code (Funktionsdefinition) nur an einer Stelle stehen, meist ein eigenes c-File. Um diese Funktion in anderen Files bekannt zu machen kann nun ein h-File, welches die Funktionsdeklaration beinhaltet, verwendet werden. Dieses wird nun überall eingebunden, wo die Funktion verwendet wird.

Die Include-Anweisung

fügt den Inhalt des inkludierten Files an der Stelle der Anweisung ein. So könnte man es sich sparen ein h-File für nur eine Funktionsdeklaration zu schreiben, wenn man die gleich die Deklaration anstelle der Include-Anweisung einfügt.

Allozierung von Speicher

Statisches Anlegen

Wenn man zum Zeitpunkt der Programmierung weiß wie viel Speicher man benötigt, so kann man diesen mit dem [] Operator reservieren. Dieser hat dann die angegebene, fixe Größe und den selben Gültigkeitsbereich wie eine an dieser Stelle angelegte Variable.

Ein Beispiel:

 int int_array[100];

Auf den Inhalt des Arrays kann man mit int_array[0] bis int_array[99] zugreifen.

Die "C - Methode"

C beinhaltet keine automatische Verwaltung von dynamischem Speicher. Für diesen Zweck gibt es verschiedene Funktionen:

void* malloc(size_t size)

Damit wird Speicher der Größe size_t reserviert und ein Pointer auf diesen Speicherbereich zurückgegeben. Da die Funktion jedoch nicht weiß, für welchen Variablentyp dieser Speicher gedacht ist, erhält man "nur" einen void*, welchen man erst auf den passenden Typ casten wird/muss (Pflicht ist dies nur für C++ Compiler).

Ein typischer Aufruf sieht so aus:

 int* int_array = (int *)malloc(100*sizeof(int)); 

void* calloc(size_t num_elements, size_t element_size)

Offensichtlich verwendet dieser Befehl eine etwas andere Syntax (man gibt an wie viele Elemente man im Array haben will und dann die Größe eines Elements), weiters wird der reservierte Speicher mit 0 initialisiert.

Unser Beispiel:

 int* int_array = (int *)calloc(100, sizeof(int)); 

void* realloc(void *ptr, size_t new_size)

Hin und wieder will man die Größe des reservierten Speichers ändern. Dies ist mit realloc möglich. Man darf allerdings nicht übersehen, dass man einen neuen Pointer zurückbekommt!

Wollten wir z.B. den oben reservierten Speicher verdoppeln:

 int_array = (int *)realloc(int_array, 200*sizeof(int)); 

void free(void *ptr)

Dynamisch angelegten Speicher muss man auch wieder freigeben, wofür free zuständig ist. Oftmals werden Pointer auf gerade freigegebenen Speicher sofort auf NULL gesetzt, damit man diesen nicht versehentlich weiter benutzt.

Freigeben unseres Arrays:

 free(int_array);
 int_array = NULL;

Die "C++ - Methode"

In C++ sind die Befehle new und delete für die Speicherverwaltung zuständig. Und zwar für Objekte und Arrays von primitiven Typen (int, long, int*, ...). Dabei wird new der Typ des anzulegenden Speichers übergeben, und man erhält bereits einen passenden Pointer.

Hier ein paar Anwendungen:

 int* int_pointer      = new int;
 int* int_array        = new int[100];
 TestClass* test_class = new TestClass();
 
 delete int_pointer;
 delete[] int_array;
 delete test_class;

Benutzung von nrutil

Dies ist eine Bibliothek die Funktionen zum anlegen von Arrays und Matrizen zur Verfügung stellt. Das Angenehme dabei ist, dass man den Indexbereich für den Zugriff selbst bestimmen kann. Dies ist insbesondere dann praktisch, wenn man nicht von beispielsweise 0-99 sondern 1-100 wie in Matlab indizieren will.

Ein paar Beispiele:

 float *float_array   =  vector(1, 100);
 int   *int_array     = ivector(1, 100);
 float **float_matrix = matrix(1, 100, 1, 100);

Freigegeben wird der Speicher folgendermaßen:

 free_vector(float_array, 1, 100); 

usw.

Eine nähere Beschreibung findet sich im File c_hilfe.txt, Kapitel 6

Ein Nachteil ist, dass dies nicht die eleganteste Variante ist, da ein von einer dieser Funktionen gelieferter Pointer nicht auf den Beginn des Speichers zeigt. Dies weiß der Programmierer beim Schreiben des Codes natürlich, allerdings kann es zu Problemen kommen, wenn der betreffende Code für eine spätere Wiederverwendung, eventuell durch andere Programmierer, bestimmt ist.

Wenn hier schon das Thema Eleganz angesprochen wird, so sei an dieser Stelle auch erwähnt, dass es Datei:Nrutil.zip eine Version der nrutil.c gibt, welche aus einem .c und einem .h File besteht.

Visualisierung der Daten

Es gibt mehrere Möglichkeiten die erstellten Daten hübsch auszugeben. Einige der für diese LV relevanten werden in Plotten von Daten aus C beschrieben.

Eine umfangreichere Beschreibung des Gnuplot-C-Interfaces findet man unter Verwendung von gnuplot i


Lesen von Daten aus Files

Eine "Mini-Library" zum Einlesen von Daten aus Files wie sie in der Übung vorkommen kann hier heruntergeladen werden:
Datei:Data2.zip

Dies ist eine Klasse, die folgendermaßen benutzt werden kann:

 Data2* exp_data = new Data2();
 exp_data->readDataFromFile("daten_EXP");
 int num_data = exp_data->getNumLines();
 double* t = exp_data->getXData();
 double* s = exp_data->getYData();
 ...
 delete exp_data;

Möchte man diese Klasse modifizieren um Daten, die in einer etwas anderen Form gespeichert sind, einzulesen, so kann man in Zeile 42 im File data2.cpp den Lesebefehl

   sscanf(line, "%lf%lf", &x_temp, &y_temp);

anpassen.

Natürlich muss der Header im Hauptprogramm inkludiert werden:

#include "data2.h"

Einige Eigenschaften:

  • Es wird davon ausgegangen, dass keine Kopfzeile vorhanden ist
  • Die Anzahl der Datensätze braucht nicht bekannt zu sein
  • Die Indizes für die Daten in den Arrays (die man mit get?Data() bekommt) beginnen mit 1 wie in Matlab üblich, nicht mit 0!

Eine gleich zu benutzende Klasse, welche auf von <tt>experiment.exe</tt> generierte Files spezialisiert ist, ist auch vorhanden:
Datei:Data2 experiment exe.zip

Vorsicht!
Die Daten werden in der Instanz der Klasse gespeichert, mit exp_data->getXData() holt man sich nur einen Pointer auf diese. Verwendet man diesen Pointer NACHDEM man die Instanz der Data2 Klasse gelöscht hat => CRASH! (muss heimtückischerweise aber nicht sein, wie bei Speicherfehlern so üblich)
Bitte trotzdem nicht auf's Löschen der Data2-Instanz vergessen ;-)

Compilierung und Projektaufbau

Besteht das gesamte Projekt aus einem einzigen C-File, so ist auch das Compilieren und Linken sehr einfach:

 $ g++  <name_quellcode_file> -o <name_executable>  

Programm ausführen mit:

 $ ./<name_executable>

Jedoch wird man mit wachsendem Code diesen auf mehrere Files aufteilen. Auch wenn Libraries verwendet hat man automatisch mit weiteren Dateien zu tun. In diesem Fall gibt es mehrere Möglichkeiten alles Nötige zusammenzufügen.

Die schnellste (Vorschlaghammer-) Methode

Hat man mehrere Files, so ist die schnellste Variante aus all diesen eines zu machen. Dies kann man, indem man im zentralen File (das mit der main Funktion) alle weiteren mit der Präprozessoranweisung #include einfügt.

 #include "nrutil.c"

Das so (vom Präprozessor) zusammengefügte File kann dann wieder einfach zu einer ausführbaren Datei weiterverarbeitet werden.

Diese Methode ist nicht die hübscheste, lässt sich aber bei kleinen Projekten einfach anwenden. Kniffliger wird es, wenn z.B. Funktionen in file_1.c Funktionen, die in file_2.c stehen benötigen. In diesem Falle muss zuerst file_2.c vor file_1.c eingebunden werden. Benötigt eine Funktion in file_2.c etwas in file_1.c so hat man ein Problem. Dies kann man einfach lösen, indem man die kritischen Funktionen oder Elemente vor den Inkludierungen deklariert:

 void criticalFunc(int dummy);
 int  critical_global_var = 0;
 #include "file_1.c"
 #include "file_2.c"


Der elegantere, umständliche Weg

Wächst das Projekt, verstrickt man sich auf die oben beschriebenen Art relativ schnell in unübersichtlichen Abhängigkeiten. Deshalb, und um Zusammengehöriges in leicht wiederverwendbare Module zu verpacken, ist eine Aufteilung in Source Code Files (*.c) und Header Files (*.h) üblich. In den Headern stehen Deklarationen und Includes, in den c-Files der eigentliche Code. Damit brauchen nur die Header inkludiert werden.

Allerdings müssen nun alle c-Files zu Object Files compiliert werden, und diese, zusammen mit bereits vorcompilierten Libraries, gemeinsam zur ausführbaren Datei gelinkt werden. Ändert man ein c-File, so muss dieses und alle davon abhängigen Files neu compiliert und die Executable neu gelinkt werden.


Makefiles (elegant und schnell, aber man muss mehr lernen)

Man kann sich denken, dass auch die letzte Variante in größeren Projekten für sehr viel Arbeit sorgt, auch wenn sie noch so elegant ist. Schön wäre es, wenn es ein Werkzeug gäbe, welches nach dem Ändern einer Datei alles was notwendig ist compiliert und danach wieder alles zusammenlinkt.

Genau dieses Tool gibt es und es hört auf den Namen make.

Das Prinzip ist folgendes: Man schreibt ein so genanntes Makefile, in welchem alle zu diesem Projekt gehörigen Files und Einstellungen angeführt sind. Weiters muss man angeben, was wovon abhängig ist. Statt den Compileraufrufen geht man einfach in das Verzeichnis, in welchem das Makefile liegt, und ruft

 $ make -f <makefilename>

auf. Wenn das Makefile exakt makefile oder Makefile heißt, dann genügt der Befehl

 $ make

Ein einfaches Makefile könnte so aussehen:

 project1: data.o main.o io.o
         gcc data.o main.o io.o -o project1
 data.o: data.c data.h
         gcc -c data.c
 main.o: data.h io.h main.c
         gcc -c main.c
 io.o: io.h io.c
         gcc -c io.c

Es werden verschiedene "Targets" (z.B. project1) definiert, wobei gleich aufgelistet wird, wovon diese abhängen. Darunter kommt dann der Befehl, mit welchem dieses Target erzeugt werden kann.

Ruft man make auf, so wird das erste Target project1 erzeugt. Dafür werden allerdings data.o, main.o und io.o benötigt. Ist eines von diesen Files nicht vorhanden oder nicht aktuell, wo wird zusätzlich das entsprechende Target erzeugt.

Wunderschöne Einführung mit Tutorial Make für C

Ein kurzes Make-Tutorial


qmake (die einfache Makefile-Variante)

Das Tool qmake übernimmt die Erstellung von Makefiles, was den Arbeitsaufwand auf beinahe null sinken lässt.

Wenn das zu compilierende Projekt z.B. im Verzeichnis uebung1 liegt, sieht die einfachste Verwendung von qmake folgend aus:

 $ qmake -project
 $ qmake uebung1.pro
 $ make
 $ ./uebung1

Dabei erzeugt der erste Befehl das Project-File uebung1.pro. Mit dessen Hilfe wird beim zweiten Befehl das Makefile generiert. Die restlichen Kommandos sind schon bekannt.
Natürlich muss das Makefile nur dann wieder erzeugt werden, wenn neue zu kompilierende Files dazugekommen sind.

Einen Nachteil teilt sich dieses Tool mit allen Automatiken: sie sind praktisch aber irgendwann zu dumm. Hier werden alle c-Files, die in dem aktuellen Ordner liegen, in das Project- und Makefile aufgenommen, was zu Problemen führt wenn Files vorhanden sind, die eigentlich nicht ins Projekt gehören. Dies lässt sich allerdings leicht beheben, indem man das sich selbst erklärende Project-File von Hand editiert.

Möchte man mit Debug-Informationen kompilieren, so kann man der Überarbeitung des Makefiles entgehen, indem man folgendes ins Project-File aufnimmt:

CONFIG += debug 

Bei Verwendung von QT wird qmake sehr empfohlen. Jeder der versucht hat QT von Hand einbinden weiß warum.


Zusatz: Filetypes

Die Compiler g++ und gcc "wissen" anhand der File-Extension was mit einem übergebenen File zu tun ist. Dabei verhalten sich beide Compiler gleich, so wie auch die meisten Compiler-Optionen für beide gleich sind. Im Folgenden werden die wichtigsten Filetypes so wie in den Manpages beschrieben angeführt.

*.c C source code which must be preprocessed.
*.i C source code which should not be preprocessed.
*.h C header file (not to be compiled or linked).
*.cc
*.cp
*.cxx
*.cpp
*.c++
*.C
C++ source code which must be preprocessed. Note that in .cxx, the last two letters must both be literally x. Likewise, .C refers to a literal capital C.


Compiler und IDEs

Ressourcen

FAQ / Troubleshooting

FEHLER: Programm compiliert, kann aber nicht gelinkt werden

Dies kann viele Ursachen haben, aber insbesondere falls ein Programm, welches einmal hier oder wo anders funktioniert hat, plötzlich nicht mehr mag, könnte folgendes Problem aufgetreten sein:

Die auftretenden Fehlermeldungen könnten ungefähr so aussehen:

[osiris@fubphpc11 Uebung1_2]$ make
g++ -o  uebung_1_2.exe nrutil.o gauint_code.o gauint_test.o func.o gnuplot_i.o
gauint_test.o(.text+0x7bd): In function `main':
: undefined reference to `cin'
gauint_test.o(.text+0x7c2): In function `main':
: undefined reference to `istream::operator>>(char &)'
gauint_test.o(.text+0x7fa): In function `main':
: undefined reference to `cin'
gauint_test.o(.text+0x7ff): In function `main':
: undefined reference to `istream::operator>>(double &)'
collect2: ld returned 1 exit status
make: *** [uebung_1] Error 1

Der am ITP installierte g++ ist so konfiguriert, dass, wenn man C++ Funktionalitäten wie cout benutzt, auch den Namespace angeben MUSS. Also muss man entweder std::cout verwenden, oder am Anfang des Files den Namespace std global deklarieren:

 using namespace std;

Segmentation Fault, Speicherzugriffsfehler, Speicherleck, ... - was ist das und vor allem: HILFE

Dies sind Probleme die auftreten, wenn man auf Speicher zugreift, den man eigentlich nicht reserviert hat.
Ein kleines Beispiel:

int* mein_feld = new int[10];
for (int counter = 1; counter <= 10; counter++)
{
    mein_feld[counter] = 0;
}

Hier wird zum Schluss auf mein_feld[10] zugegriffen, aber der dafür vorgesehene Indexbereich ist von 0-9! Das kann zu einem Speicherzugriffsfehler führen.

Das heimtückische an diesen Fehlern ist jedoch, dass ein Speicherzugriffsfehler nur dann auftritt, wenn auf Speicher zugegriffen werden soll, der nicht für das gesamte Programm reserviert ist. Das heißt, wenn die Speicherstelle, wo mein_feld[10] liegen würde, für eine andere Variable in diesem Programm reserviert ist, so tritt kein Fehler auf, aber der Speicher wird geändert und die andere Variable ändert plötzlich wie von Geisterhand ihren Wert. Wäre im obigen Beispiel diese Variable zufällig counter, so würde aus der hübschen for - Schleife eine Endlosschleife!

Es kann jedoch auch passieren, dass ein derart fehlerhaftes Programm lange Zeit ohne Probleme läuft, und dann hin und wieder abstürzt, je nachdem wie der Speicher grad organisiert wird - viel Spaß bei der Fehlersuche ist garantiert. Auch sehr fies ist es, wenn man ein Programm entwickelt und währenddessen immer wieder testweise ausführt. Wenn nun ein Fehler auftritt, so sucht man naheliegenderweise in den Änderungen seit dem letzten erfolgreichen Test. Ein sich bis dorthin gut tarnender Speicherfehler kann jedoch bereits sehr viel vorher programmiert worden sein.

Und noch eine Gemeinheit gibt es: Man kann ja sehr gut eingrenzen, wo im Code ein Fehler (als Folge eines falschen Speicherzugriffs) auftritt, jedoch liegt die Ursache (eben der falsche Zugriff) praktisch immer ganz wo anders - in irgendeinem Eck zu dem es keine logische Verbindung gibt.

FEHLER: Variablenwerte ändern sich "von Geisterhand"

Siehe vorheriger Abschnitt!

Matlab - Spezifisches

Workspace und Variablen

Speichern und Laden von Variablen

Wenn man rechenaufwändige Programme entwickelt (oder anderes Zeitfressendes unternimmt) kann es praktisch sein, einen "Zwischenstand" der Variablen zu speichern. Von diesem aus kann dann das Programm weiterentwickelt werden, ohne jedes Mal all jene Operationen, die zu diesem Zwischenstand geführt haben, ausführen zu müssen.

Mit

 save('filename')

werden alle Variablen im gesamten Workspace in das angegebene File geschrieben, wobei das current directory als Ausgangspunkt verwendet wird. Sollen nur bestimmte Variablen gesichert werden, so kann man auch

 save('filename', 'varname1', 'varname2', ...)

benutzen.

Diese üblicherweise mit der Extension .mat versehenen Files werden mit

 load('filename')

eingelesen. Dieser Befehl liest alle in dem angegebenen File enthaltenen Variablen in den Workspace ein.

Dies ist mit allen Variablen, egal ob einfache Zahl, Matrix oder mit Frames gefüllter Vektor, möglich.


Ressourcen


FAQ / Troubleshooting

quad oder quadl hat Probleme mit meinen Übergabeparametern

Es passiert recht gerne, dass man den Parameter trace (von quad/quadl) vergisst, wenn über quad/quadl weitere Parameter an die zu integrierende Funktion übergeben werden sollen.

Beispiel: Die zu integrierende Funktion ist:

function erg = integr(x, param)

Über x wird integriert, param ist ein weiterer Parameter.

Dann sieht der korrekte Aufruf von quad so aus:

intErg = QUAD(@integr, grenzeUnten, grenzeOben, 1e-8, [], paramVar)

wobei für trace "nichts" in Form eines leeren Vektors [] übergeben wurde.

Matlab öffnet Emacs als Editor, ich hätte gerne den Matlab-Editor

Dies kann im Preferences Dialog (Menü File -> Preferences) unter dem Hauptpunkt "Editor / Debugger" umgestellt werden.