Entwicklerdokumentation

Inhaltsverzeichnis

  1. Einleitende Worte
  2. Kompilieren der Anwendung
    1. Windows
    2. Mac OS
    3. Linux
  3. Das grundlegende Design
  4. Der Assembler-Code
    1. Der Stackframe
    2. Laden der Parameter
    3. Erläuterung der Pointer-Arithmetik
    4. Die Auswertung der Formel
    5. Globale Variablen: Anpassen von εr1 und εr2
  5. Der C-Code
    1. Behandlung der Kommandozeile: parse_params und usage
    2. Debug-Funktionen: debug_print_input und debug_print_results
    3. Lesen und Schreiben von CSV-Dateien: parse_csv und write_csv
    4. Die main-Funktion
  6. Anhang: Sourcecode
    1. calc.s
    2. calc.c

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:

  1. Dem Initialisieren des Stackframes (Source)
  2. Dem Laden der Parameter in die Register (Source)
  3. Einer kleinen Anpassung der eben geladenen Pointer (Source)
  4. Der eigentlichen Berechnung (Source)
  5. 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:

AddresseInhalt
[EBP-16]Der gesicherte Wert von EDI
[EBP-12]Der gesicherte Wert von ESI
[EBP-8]Lokale Variable: 2*π*ε0r2
[EBP-4]Lokale Variable: 2*π*ε0r1
[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:

RegisterInhalt
EAXPointer auf Längen-Array
EBXPointer auf Array mit inneren Radien
ECXAnzahl der zu bearbeitenden Tupel
EDXPointer auf Array mit äußeren Radien
ESIPointer auf εr1-Ausgabe-Array
EDIPointer 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ückgabewertBedeutung
0Die Berechnung wurde korrekt durchgeführt
1Das Parsen der Parameter ist fehlgeschlagen
2Eine Memory-Allocation ist fehlgeschlagen
3Es 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(&params, 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]); } }