Entwicklerdokumentation
Einleitende Worte
In dieser Dokumentation findet sich eine ausführliche Erklärung des Aufbaus und der Umsetzung des calc
-Programms, welches die Kapazität mehrerer Zykinderkondensatoren mit zwei verschiedenen Dielektrika anhand der Formel (2*pi*e0*e1)/(ln(r1/r2))
berechnet. Dieses Dokument stellt keine Anleitung zur Benutzung des Programms dar. Es wird nicht darauf eingegangen, wie man das Programm konkret einsetzt. Sollte an diesen Informationen Interesse bestehen, sei auf die Benutzerdokumentation verwiesen.
Kompilieren der Anwendung
Zum Kompilieren des Programms muss in der Konsole in das Verzeichnis des Projekts gewechselt werden. Anschließend wird abhängig vom Betriebssystem folgendes eingegeben:
Windows:nasm -o calc.obj -f win --prefix _ calc.s
gcc calc.c calc.obj -o calc.exe
Mac OS:nasm -o calc.o -f macho calc.s --prefix _
gcc calc.c calc.o -o calc -m32
Linux:nasm -o calc.o -f elf32 calc.s
gcc calc.c calc.o -o calc –m32
Zur ersten Zeile:
NASM ist ein Assembler, der den Assemblercode des Unterprogramms in Maschinensprache übersetzt. –o
gibt an, wie die Ausgabedatei heißen soll. –f
gibt an, in welchem Format die Datei sein soll, dies variiert je nach Betriebssystem. --prefix _
stellt beim Assemblieren allen Definitionen, die mit global markiert sind, einen Unterstrich _
voran. Das ist notwendig für Mac OS und Windows, weil diese Systeme beim Aufruf einer Funktion erwarten, dass deren Namen mit einem Unterstrich beginnt. Als letztes wird der Name der Eingabedatei angegeben (hier: calc.s
).
Zur zweiten Zeile:
GCC ist der Compiler für C. Hier werden das C-Programm (calc.c
) und das bereits in Maschinensprache übersetzte Assemblerprogramm (calc.o
) übergeben. Hinter –o wird angegeben, wie die Datei, die nun das fertig kompilierte Programm enthält, heißen soll. Unter Mac OS und Linux-Systemen, die auf einer 64-Bit-Architektur basieren ist des Weiteren der Switch -m 32
notwendig, um dem Compiler mitzuteilen, dass ein 32-Bit-Programm gelinkt werden soll.
Das grundlegende Design
Das Programm ist, wie auch aus dem Kompiliervorgang hervorgeht, zweigeteilt. Es besteht zum einen Teil aus Assemblercode, der andere Teil wurde in C geschrieben. Der Assemblercode implementiert die Berechnung der Kapazitäten von beliebig vielen Zylinderkondensatoren. Dazu greift das Assemblerprogramm auf drei verschiedene Arrays zu, die die Länge des Kondensators sowie den inneren und äußeren Radius enthalten. Die Werte, die in diesen drei Arrays in der selben Zeile stehen, gehören zusammen. Das Programm berechnet dann zu den Wertetripeln jeweils die Kapazität für zwei verschiedene Dielektrika und speichert die Ergebnisse wiederum in zwei Arrays ab.
Das C-Programm übernimmt im Gegenzug sämtliche Logistik: Es validiert die Parameter, die bei der Ausführung des Programms übergeben werden, parst und speichert die Wertetripel, die im CSV-Format (getrennt durch Tabulatorstopps) anzugeben sind in Arrays und übernimmt letztendlich auch die Kommunikation mit dem Benutzer beim Auftreten von Fehlern sowie die Ausgabe der Rechenergebnisse. Die eigentliche Berechnung der Werte wird vom C-Programm an die Assembler-Routine calc
delegiert. Das C-Programm schafft insgesamt also den nötigen Rahmen für die Ausführung der oben genannten, in Assembler implementierten Funktion.
Der Assembler-Code
Der Assembler-Quellcode findet sich in der calc.s
-Datei. Sein wesentlicher Bestandteil ist die calc
-Methode, welche aus 5 Abschnitten besteht:
- Dem Initialisieren des Stackframes (Source)
- Dem Laden der Parameter in die Register (Source)
- Einer kleinen Anpassung der eben geladenen Pointer (Source)
- Der eigentlichen Berechnung (Source)
- Dem Zerstören des Stackframes (Source)
Des Weiteren befinden sich in dieser Datei die Deklarationen einiger globaler Variablen, welche es ermöglichen, der calc
-Methode modifizierte Werte für εr1 und εr2 mitzugeben.
In diesem Teil der Dokumentation soll auf die einzelnen Abschnitte weiter eingegangen werden. Hierbei werden der erste und letzte Punkt zusammengefasst behandelt.
Der Stackframe
Der Stack eines Programms wird, wie in jeder modernen Sprache, so auch in C, in Frames unterteilt. Damit die calc
-Routine aus C heraus ohne Komplikationen aufrufbar ist, sollte man sich an diese Konvention halten.
Für jede aufgerufene Funktion muss zu Beginn im sogenannten Funktionsepilog ein Stackframe erzeugt werden. Der Stackframe beinhaltet, neben allgemeinen Steuerdaten wie der Rücksprungadresse und dem gesicherten Inhalt von EBP
auch die lokalen Variablen der Funktion. Nachdem das EBP
-Register auf dem Stack abgelegt wurde, wird in ihm der aktuelle Stackpointer zwischengespeichert. EBP
dient im Rumpf der Funktion der Adressierung der übergebenen Parameter sowie der lokalen Variablen. Die calc
-Funktion benötigt zwei Fließkommazahlen zu je 4 Byte und somit insgesamt 8 Byte an lokalen Variablen. Dieser Platz wird mit der Anweisung sub esp,8 auf dem Stack alloziert. Darüberhinaus werden die beiden Register ESI
und EDI
auf dem Stack gesichert.
Nach der Ausführung des Funktionsepilogs ergibt sich folgende Stackstruktur:
Addresse | Inhalt |
---|---|
[EBP-16] | Der gesicherte Wert von EDI |
[EBP-12] | Der gesicherte Wert von ESI |
[EBP-8] | Lokale Variable: 2*π*ε0*εr2 |
[EBP-4] | Lokale Variable: 2*π*ε0*εr1 |
[EBP] | Der alte Wert von EBP |
[EBP+4] | Die Rücksprung-Adresse |
[EBP+8] | 1. Parameter (int ): Anzahl der zu bearbeitenden Wertetripel |
[EBP+12] | 2. Paramter (int* ): Pointer auf Array, welches die Längen der einzelnen Kondensatoren enthält |
[EBP+16] | 3. Paramter (float* ): Pointer auf Array, welches die inneren Radien der Kondensatoren enthält |
[EBP+20] | 4. Paramter (float* ): Pointer auf Array, welches die äußeren Radien der Kondensatoren enthält |
[EBP+24] | 5. Paramter (float* ): Pointer auf Array, in welches die Ergebnisse mit εr1 gespeichert werden |
[EBP+28] | 6. Paramter (float* ): Pointer auf Array, in welches die Ergebnisse mit εr2 gespeichert werden |
Bevor die Funktion die Kontrolle wieder an das C-Programm zurückgibt, muss der erstellte Stackframe wieder freigegeben werden. Dies geschieht im Funktionsepilog. Zu diesem Zweck werden als erstes die Register ESI
und EDI
wieder vom Stack geladen. Anschließend wird der Stackpointer auf den zu Beginn in EBP
gespeicherten Wert zurückgesetzt und es wird das EBP
-Register der aufrufenden Funktion wiederhergestellt. Nachdem alle für die aufrufende Funktion wichtigen Register wieder in ihren Ausgangszustand gebracht wurden, wird mittels ret
die Kontrolle wieder an diese übertragen.
Laden der Parameter
Im nächsten Codeblock werden die übergebenen Pointer in Register geladen. Die Anzahl der Tripel wird in einem späteren Codeblock geladen. Hierbei ergibt sich folgende Belegung:
Register | Inhalt |
---|---|
EAX | Pointer auf Längen-Array |
EBX | Pointer auf Array mit inneren Radien |
ECX | Anzahl der zu bearbeitenden Tupel |
EDX | Pointer auf Array mit äußeren Radien |
ESI | Pointer auf εr1-Ausgabe-Array |
EDI | Pointer auf εr1-Ausgabe-Array |
Erläuterung der Pointer-Arithmetik
Zwischen dem Laden der Pointer und dem Laden von ECX
findet sich im Code noch ein kleiner Einschub. In ihm werden die eben geladenen Pointer allesamt um 4 verringert, um ihre Verwendung im weiteren Programmablauf zu vereinfachen.
Bei der Berechnung der Ergebnisse gemäß der Kapazitäts-Formel wird auf die Eingabewerte jeweils nach dem Schema [EBX+4*ECX]
zugegriffen. Hierbei wird ECX
mit jedem Schleifendurchlauf dekrementiert. ECX
beginnt im ersten Schleifendurchlauf mit der Anzahl der zu berechnenden Wertetripel als Wert und zählt von dort runter bis zur 1 (inklusive). ECX
ist somit eigentlich um eins zu groß, um als Arrayindex zu dienen. Man könnte den Zugriff [EBX+4*ECX]
natürlich in [EBX+4*ECX-4]
abändern, hierdurch würde die Adressberechnung jedoch komplexer werden. Stattdessen wird dieser konstante Offset gleich zu Beginn in die Pointer mit einberechnet.
Die Auswertung der Formel
Die eigentliche Auswertung der Formel wird aus Performancegründen in zwei getrennten Schritten ausgeführt. Im ersten Schritt wird der Teil der Formel, welcher nicht von der Länge des Kondensators oder den Radien abhängt außerhalb der eigentlichen Hauptschleife berechnet. Hierzu werden der Wert von εr1 bzw. εr1 jeweils mit ε0 multipliziert. Die Ergebnisse werden in die beim Erstellen des Stackframes resevierten lokalen Variablen zwischengespeichert.
Innerhalb der Hauptschleife wird dann der zweite, von Länge und Radien abhängige, Teil errechnet. Hierbei werden die oben vorberechneten Faktoren verwendet um das Zwischenergebniss auf die beiden Endergebnisse zu skalieren. Die Ergebnisse werden bereits in der Schleife an die richtigen Stellen in den Ausgabe-Arrays geschrieben.
Globale Variablen: Anpassen von εr1 und εr2
Am Ende des Codes finden sich die Deklarationen von 3 globalen Variablen. Aufgrund der Tatsache, dass die beiden ersten Variablen εr1 und εr2, mit denen die calc
-Methode ihre Ergebnisse berechnet global verfügbar sind, können deren Werte ohne weiteres überschrieben werden. Es wird dann einfach mit anderen Werten für εr1 bzw. εr2 gerechnet. Diesen Weg verwendet auch das C-Programm um die vom Nutzer gewünschten Dielektrizitätskonstaten an die Assemlerroutine weiterzureichen.
Auch wenn die beiden Variablen überschrieben werden können, so haben sie dennoch bereits zu Beginn sinnvolle Startwerte, nämlich die Dielektrizitätskonstaten von Paraffin und Ölpapier. Sollten also keine speziellen Werte für εr2 gewünscht sein, so kann man die globalen Variablen getrost ignorieren.
Die letzte der drei globalen Variablen sollte auf keinen Fall überschrieben werden. Sie ist nur als global deklariert, um aus den Testcases heraus bequem auf ihren Wert zugreifen zu können.
Der C-Code
Der C-Code kümmert sich darum, dem Benutzer ein aufrufbares Rahmenprogramm zur Verfügung zu stellen. Sämtliche Steuer-Ausgaben dieses Programms werden nach stderr
geschrieben. Konkret bedeutet das, dass einzig die eigentlichen Berechnungsergebnisse auf stdout
geschrieben werden. Das C-Programm verwendet folgende Rückgabewerte:
Rückgabewert | Bedeutung |
---|---|
0 | Die Berechnung wurde korrekt durchgeführt |
1 | Das Parsen der Parameter ist fehlgeschlagen |
2 | Eine Memory-Allocation ist fehlgeschlagen |
3 | Es kam zu einem I/O-Fehler |
Behandlung der Kommandozeile: parse_params und usage
Um die übergebenen Parameter zu interpretieren, wird zu Beginn der main-Funktion eine Instanz der Struktur Parameters erzeugt. In ihr werden sogleich die Standardwerte gespeichert, welche verwendet werden sollen, wenn der Benutzer zu einem Parameter keine Angaben macht. Die Adresse dieser Struktur sowie die Anzahl der Parameter (argc
) und der Inhalt der Parameter (argv
) wird dann an die Funktion parse_params übergeben. Diese Funktion iteriert über sämtliche Parameter und versucht die übergebenen Parameter zu parsen und zu validieren. Die Ergebnisse werden in die übergebene Parameters
-Struktur gespeichert.
Sollte die Validierung fehlschlagen, so wird die Funktion usage aufgerufen und die Ausführung des Programms anschließend abgebrochen. usage
schreibt eine Übersicht der verfügbaren Parameter und Hinweise zur korrekten Nutzung des Programms nach stderr
.
Debug-Funktionen: debug_print_input und debug_print_results
Diese beiden Funktionen spielen nur dann eine Rolle, wenn der Parameter -v
mit einem Wert größer oder gleich 2 angegeben wurde. Ist dies der Fall, dann gibt die Funktion debug_print_input nocheinmal sämtliche eingelesene Wertetripel auf stderr
aus. debug_print_results funktioniert analog und gibt sämtliche errechneten Wertepaare aus.
Lesen und Schreiben von CSV-Dateien: parse_csv und write_csv
Die Funktion parse_csv übernimmt das Parsen der Eingabedaten. Diese Eingabedaten haben im CSV-Format vorzuliegen, wobei die einzelnen Werte einer Zeile jeweils durch einen Tabstop getrennt sind. Eine while-Schleife iteriert über alle verfügbaren Zeilen. Bei Bedarf werden in dieser Schleife die Zielarrays vergrößert und neu allokiert. Die Array-Größe wird hierbei immer verdoppelt, wodurch sich eine logarithmische anstatt einer quadratischen Laufzeitkomplexität erreichen lässt. Überflüssiger Speicher wird vor Verlassen der Funktion wieder freigegeben. Mithilfe der Funktion fscanf
werden die Daten der jeweiligen Zeile geparst und im jeweiligen Zielarray gespeichert. Fehleingaben führen zum vorzeitigen Abbruch der while-Schleife, die Ausführung des Programms wird aber dennoch fortgesetzt.
Die Funktion write_csv stellt das Gegenstück zur Funktion parse_csv
dar: Sie iteriert über die Ergebnis-Wertepaare, formatiert diese mithilfe von fprintf
und gibt sie dabei an die als Funktionsargument übergebene Ausgabe weiter.
Die main-Funktion
Aufgabe der main-Funktion ist im Grunde nur noch Variablen anzulegen und die beschriebenen Funktionen in der richtigen Reihenfolge aufzurufen. Nachdem die Parameter geparst wurden, werden Pointer auf die Arrays für die Eingabedaten deklariert. Pointer auf diese werden gemeinsam (Pointer auf Pointer auf Werte) mit einem Pointer auf einen Integer, welcher die Anzahl der gelesenen Wertetripel speichern wird, an die Funktion parse_csv übergeben. Ist die Funktion fertig, werden die zwei Arrays, die später die Ergebnisse der Berechnungen enthalten sollen, deklariert und allooziert und die Werte der externen Variablen epsilon_r1 und epsilon r2 durch die als Parameter übergebenen ersetzt. Dann wird die Ausführung der calc-Funktion, die in Assembler implementiert wurde, angestoßen. Abschließend werden die Ergebnisse mit write_csv ausgegeben, der von den Arrays belegte Speicher wieder freigegeben. Während all dem sorgen eingeschobene if-Blöcke dafür, dass dem Verbosity-Level entsprechend Meldungen über den internen Zustand gemacht werden.
Anhang: Sourcecode
calc.s
section .text
global calc
; signature: void calc(int arraysize, int* in1, float* in2, float* in3, float* out1, float* out2);
calc:
; function prolog
push ebp ; Here we set up the stack frame, we save the current base pointer to the stack
mov ebp, esp ; and save the stack pointer as our new base pointer
sub esp, 8 ; We need 2 additional floats on the stack
push esi ; and we want to save the ESI and the EDI registers as well
push edi
finit ; We need to initialize the FPU since we are going to perform floating point operations
; calculate the two floats for scaling the results with 2*pi*e_0*e_r
fld dword [epsilon0pi] ; load the constant 2*pi*epsilon_0 to the FPU stack
fmul dword [epsilon_r1] ; multiply the previously loaded constant with epsilon_r1
fstp dword [ebp-4] ; storing the result on the stack to address ebp-4
fld dword [epsilon0pi] ; load the constant 2*pi*epsilon_0 to the FPU stack
fmul dword [epsilon_r2] ; multiply the previously loaded constant with epsilon_r2
fstp dword [ebp-8] ; store the result on the stack to address ebp-8
; load some pointers
mov eax, [ebp+12] ;in1 - this is our second parameter!
mov ebx, [ebp+16] ;in2
mov edx, [ebp+20] ;in3
mov edi, [ebp+24] ;out1
mov esi, [ebp+28] ;out2
; adjust pointers, go one value back, ecx is used as offset later and ecx will be off by one all the time!
sub eax, 4
sub ebx, 4
sub edx, 4
sub edi, 4
sub esi, 4
; setup loop counter
mov ecx, [ebp+8]
test ecx, ecx
jz .end
; the main loop
.loop:
;dividend
fild dword [eax+ecx*4] ;load in1 (remember - it contains the length of the condensator, not any radius)
;divisor
fldln2 ;load ln(2)
fld dword [ebx+ecx*4] ;load in2 (this is the inner radius)
fdiv dword [edx+ecx*4] ;divide by in3 (this is the outer radius)
fyl2x ;calculating the ln of in2/in3 => in1 in st0, ln in st1, remaining stack empty
;divide them
fdivp st1, st0 ;dividing in1 (the length) by the previously calculated logarithm
;duplicate the result
fld st0
;scale for first result, store and pop it
fmul dword [ebp-4]
fstp dword [edi+ecx*4]
;scale to second result, store and pop it
fmul dword [ebp-8]
fstp dword [esi+ecx*4]
;loop(counter in cx)
loop .loop
.end:
; wait for the fpu before returning
fwait
; function epilog
pop edi ; restoring EDI
pop esi ; restoring ESI
mov esp, ebp ; changing the stack pointer to the adress of our base pointer
pop ebp ; we just restore the previous base pointer, and we are right at the return adress
ret ; so we can easliy return to our C-program
section .data
global epsilon_r1
global epsilon_r2
global epsilon0pi ; declared global just for testing purposes
epsilon_r1 dd 2.2 ;paraffin
epsilon_r2 dd 4.0 ;oilpaper
epsilon0pi dd 5.56325027987829e-11 ;2*pi*epsilon_0
calc.c
/* compilation under windows:
* cd <path>
* nasm -o calc.obj -f win --prefix _ calc.s
* gcc calc.c calc.obj -o calc.exe
* compilation under linux (32 bit binary):
* cd <path>
* nasm -o calc.o -f elf32 calc.s
* gcc calc.c calc.o -o calc
* for compiling a 32bit binary under 64bit linux the additional "-m32" for gcc switch is required
*/
#include <stdio.h>
#include <stdlib.h>
/*
* The following two global variables are used to set epsilon_r1 and epsilon_r2 for the calculation.
* They are declared in calc.s
*/
extern float epsilon_r1, epsilon_r2;
/**
* This external function calculates the capacity of condensators to the given values
*
* @param int arrSize - how many sets of values have to be calculated
* @param int* length - the pointer to an array of integers containing the length of the specific capacitor in mm
* @param float* outer - the pointer to an array of floats containing the radius of the outer cope
* @param float* inner - the pointer to an array of floats containing the radius of the inner cope
* @param float* out1 - the pointer to an array of floats containing the results to the dielectricum 1
* @param float* out2 - the pointer to an array of floats containing the results to the dielectricum 2
*/
extern void calc(int arrSize, int* lenghts, float* outer, float* inner, float* out1, float* out2);
///structure used as container for commandline arguments
struct Parameters {
float er1, er2;
FILE* in;
FILE* out;
int verbosity;
};
///prints a short help message
void usage(char *prgname);
///parses commandline arguments. results will be stored in the Parameters structure referenced by params.
void parse_params(struct Parameters* params, int argc, char**argv);
///a debug function used to print the input data to stderr
void debug_print_input(int size, int* in1, float* in2, float* in3);
///a debug function used to print the results to stderr
void debug_print_results(int size, float* out1, float* out2);
/**
* Reads the input data from inFile.
* This function tries to interpret the inFile as a tab-separated CSV file. If an error occurs, it will abort parsing
* and print a warning to stderr. Everything read until this error, is stored correctly in the arrays.
* This function is able to read an arbitrary amount of input data. It automaticly allocates memory. Therefore the pointers
* referenced by in1, in2 and in3 will be overwritten. Make sure to free them after use. The number of data sets read will be
* stored into size.
*/
void parse_csv(int* size, int** in1, float** in2, float** in3, FILE* inFile);
///writes the results as tab-separated CSV into the file referenced by out.
void write_csv(int size, float* out1, float* out2, FILE* out);
/**
* main function.
* return values:
* * 0: success
* * 1: parameter parsing failure
* * 2: memory allocation failure
* * 3: I/O error
*/
int main(int argc, char** argv)
{
//create Parameters structure and set default values
struct Parameters params;
params.er1 = 2.2f; //paraffin
params.er2 = 4.0f; //oilpaper
params.in = stdin; //by default we read from stdin
params.out = stdout; //and write to stdout
params.verbosity = 0;
//parse the command line
parse_params(¶ms, argc, argv);
//read input file
int inputSize;
int *in1;
float *in2, *in3;
if(params.verbosity >= 1) fputs(" * Parsing input file\n", stderr);
parse_csv(&inputSize, &in1, &in2, &in3, params.in);
//print the input data
if(params.verbosity >= 2) debug_print_input(inputSize,in1,in2,in3);
//allocate memory for results
float* out1 = (float *)malloc(inputSize * sizeof(float));
float* out2 = (float *)malloc(inputSize * sizeof(float));
if(!out1 || !out2) {
fputs("error: memory allocation failed\n", stderr);
exit(2);
}
//set ers
if(params.verbosity >= 1) fputs(" * Calculating...\n", stderr);
if(params.verbosity >= 1) fprintf(stderr, " -> er1: %f; er2: %f\n", params.er1, params.er2);
epsilon_r1 = params.er1;
epsilon_r2 = params.er2;
//calculate the values
calc(inputSize, in1, in3, in2, out1, out2);
//print the results
if(params.verbosity >= 2) debug_print_results(inputSize,out1,out2);
if(params.verbosity >= 1) fputs(" * Writing results to file\n", stderr);
//write to CSV
write_csv(inputSize, out1, out2, params.out);
if(params.verbosity >= 1) fputs(" * Cleaning up\n", stderr);
//close the input file
if(params.in != stdin) fclose(params.in);
//and the output file
if(params.out != stdout) fclose(params.out);
//free the memory
free(in1);
free(in2);
free(in3);
free(out1);
free(out2);
return 0;
}
/*
* For a description of the following functions see above.
*/
void usage(char *prgname)
{
fprintf(stderr,"usage: %s [options] [input file] \n",prgname);
fputs("The following options are avaible:\n", stderr);
fputs(" -h\tprints this help\n", stderr);
fputs(" -v <level>\tsets the verbosity to a level between 0 and 2; default:0\n", stderr);
fputs(" -o <filename>\tsets the input file [default: stdin]\n", stderr);
fputs(" \tWARNING: The current contents of this file will be overwritten\n", stderr);
fputs(" -er1 <value>\tsets the value for epsilon_r 1\n", stderr);
fputs(" -er2 <value>\tsets the value for epsilon_r 2\n", stderr);
fputs("\n", stderr);
}
void parse_params(struct Parameters* params, int argc, char**argv) {
//the argument currently handled
int currArg = 1;
//some booleans to make sure, that every parameter will be specified only once
char outSpecified = 0, inSpecified = 0, vSpecified = 0, er1Specified = 0, er2Specified = 0;
//handle all passed arguments
while(currArg < argc) {
//compare them with "-<char>" switches
if(strcmp(argv[currArg], "-h") == 0) {
//print usage information and exit
usage(argv[0]);
exit(0);
} else if(strcmp(argv[currArg], "-o") == 0) {
//=>output file => next argument: filename
currArg++;
//some sanity checks
if(currArg >= argc) {
fputs("error: filename expected after -o, but nothing found\n", stderr);
usage(argv[0]);
exit(1);
}
if(outSpecified) {
fputs("error: output file already set\n", stderr);
exit(1);
}
//open it
params->out = fopen(argv[currArg],"w");
if(!params->out) {
fprintf(stderr, "error: unable to open output file: \"%s\"", argv[currArg]);
usage(argv[0]);
exit(3);
}
outSpecified = 1;
} else if(strcmp(argv[currArg], "-v") == 0) {
//verbosity => next argument: verbosity level
currArg++;
//check sanity
if(currArg >= argc) {
fputs("error: value expected after -v, but nothing found\n", stderr);
usage(argv[0]);
exit(1);
}
if(vSpecified) {
fputs("error: verbosity already set\n", stderr);
exit(1);
}
//parse the verbosity level (in a pedantic way)
char* next_char;
params->verbosity = strtod(argv[currArg], &next_char);
if(*next_char != '\0') {
fputs("error: invalid value for er1 specified", stderr);
exit(1);
}
//range checking
if(params->verbosity < 0 || params->verbosity > 2) {
fputs("error: invalid verbosity level. allowed range: [0-2]\n", stderr);
usage(argv[0]);
exit(1);
}
vSpecified = 1;
} else if(strcmp(argv[currArg], "-er1") == 0) {
//er1 => next argument: value for epsilon_r1
currArg++;
//the usual checks... once again
if(currArg >= argc) {
fputs("error: value expected after -er1, but nothing found\n", stderr);
usage(argv[0]);
exit(1);
}
if(er1Specified) {
fputs("error: er1 already set\n", stderr);
exit(1);
}
//parse the value (and make sure that the whole string was parsed!)
char* next_char;
params->er1 = strtod(argv[currArg], &next_char);
if(*next_char != '\0') {
fputs("error: invalid value for er1 specified", stderr);
exit(1);
}
er1Specified = 1;
} else if(strcmp(argv[currArg], "-er2") == 0) {
//er2 => see above
currArg++;
if(currArg >= argc) {
fputs("error: value expected after -er2, but nothing found\n", stderr);
usage(argv[0]);
exit(1);
}
if(er2Specified) {
fputs("error: er2 already set\n", stderr);
exit(1);
}
char* next_char;
params->er2 = strtod(argv[currArg], &next_char);
if(*next_char != '\0') {
fputs("error: invalid value for er2 specified", stderr);
exit(1);
}
er2Specified = 1;
} else {
//not a switch? => input file
if(inSpecified) {
fputs("error: input file already set\n", stderr);
exit(1);
}
//(try to) open it
params->in = fopen(argv[currArg],"r");
if(!params->in) {
fprintf(stderr, "error: unable to open input file: \"%s\"", argv[currArg]);
usage(argv[0]);
exit(3);
}
inSpecified = 1;
}
//NEXT!
currArg++;
}
}
void debug_print_input(int size, int* in1, float* in2, float* in3) {
fputs("input data:\n", stderr);
int i; for(i = 0; i < size; i++) {
fprintf(stderr, "[%i] =>\t%i\t%e\t%e\n", i, in1[i], (double) in2[i], (double) in3[i]);
}
}
void debug_print_results(int size, float* out1, float* out2) {
fputs("results:\n", stderr);
int i; for(i = 0; i < size; i++) {
fprintf(stderr, "[%i] =>\t%e\t%e\n", i, (double) out1[i], (double) out2[i]);
}
}
void parse_csv(int* size, int** in1, float** in2, float** in3, FILE* inFile) {
//reserve some initial memory
*size = 50;
*in1 = (int *)malloc(*size * sizeof(int));
*in2 = (float *)malloc(*size * sizeof(float));
*in3 = (float *)malloc(*size * sizeof(float));
if(!*in1 || !*in2 || !*in3){
fputs("error: memory allocation failed", stderr);
exit(2);
}
//Parse the input file
int linesread = 0;
while(!feof(inFile)) {
//buffer full?
if(linesread>*size-1)
{
//double the size.
//why not just add 50? => GAD. By doubling the size every time we get O(n) = n.
//Otherwise we would get O(n) = n*n.
*size *= 2;
//the buffer contents will be copied by realloc automatically
*in1 = (int *) realloc(*in1, *size * sizeof(int));
*in2 = (float *) realloc(*in2, *size * sizeof(float));
*in3 = (float *) realloc(*in3, *size * sizeof(float));
if(!*in1 || !*in2 || !*in3) {
fputs("error: memory allocation failed\n", stderr);
exit(2);
}
}
if(fscanf(inFile,"%d\t%f\t%f\n",
&(*in1)[linesread], &(*in2)[linesread], &(*in3)[linesread]) != 3){
fprintf(stderr, "Parsing input file: Error in line %i\n",linesread);
fprintf(stderr, "Aborted.\n");
break;
}
linesread++;
}
//tailor the buffers.
*size = linesread;
*in1 = (int *) realloc(*in1, *size * sizeof(int));
*in2 = (float *) realloc(*in2, *size * sizeof(float));
*in3 = (float *) realloc(*in3, *size * sizeof(float));
if(!*in1 || !*in2 || !*in3) {
fputs("error: memory allocation failed", stderr);
exit(2);
}
}
void write_csv(int size, float* out1, float* out2, FILE* out) {
int i; for(i = 0; i < size; i++) {
fprintf(out, "%e\t%e\n",out1[i], out2[i]);
}
}