Programmcode in eigene Bibliothek auslagern



  • Problemstellung: Ich schreibe ein C-Programm für einen Mikrocontroller und der Quellocde umfasst langsam hunderte Zeilen. Das macht es unübersichtlich und da dachte ich, macht es doch Sinn einige Funktionen in weitere Dateien auszulagern. Die große Frage ist nur: wie?
    Ich hab also folgende Dateien:
    - main.c
    - test_lib.c
    - test_lib.h
    (Quellcode unten)

    Die Umgebung: Spielt denke ich keine Rolle, da es eine grundlegende Frage ist, aber sollte es doch wichtig sein, die IDE ist Code Composer Studio v5.5, der Mikrocontroller (Piccolo F28035) ist einer von Texas Instruments.

    Mein Wissensstand: Ich würde behaupten, dass ich die grundlegenden Dinge von C, hatte es in der Schule, im Studium und sonst würde es wohl auch nicht mit dem Schreiben solcher Programme nicht klappen. Was mir fehlt ist das "unter der Haube". Das mit dem Auslagern von Programmcode gehört meiner Einschätzung nach auch zu den Grundlagen, wurde aber nie besprochen, fehlt daher. Und damit komme ich auch zum nächsten Punkt.

    Welche Quellen ich abgesucht habe und wie weit mich das gebracht hat: Habe sowohl im Internet danach gesucht (wohl einfach falscher Suchbegriff) und danach in der Hochschul-Bibliothek. Bin da auch etwas geschockt/genervt, dass so was in kaum einem Buch beschrieben steht. In einem Buch habe ich zumindest herausfinden können, dass ich die testlib.c nicht extra einbinden muss, weil die IDEs automatisch alle c-Dateien im Projektverzeichnis compilieren. Die test_lib.h muss ich sowohl in der test_lib.h, als auch in der main.c mit einbinden. Damit die Datei nicht doppelt eingebunden wird, wird das in der test_lib.h verhindert.
    Dann habe ich gelesen, dass man Variablen in der h-Datei deklarieren soll, dort aber kein Speicherplatz reserviert werden darf, was mit dem Schlüsselwort "extern" zu erreichen sei. Der Speicherplatz würde erst dann reserviert werden, wenn auf die Variable auch zugegriffen wird.
    Außerdem habe ich mir die Standard-Bibliotheken angeguckt, die mit der IDE mitgeliefert werden und das Gerüst habe ich übernommen, deckt sich auch mit der Info aus dem einen Buch.

    Der aktuelle Stand ist so:

    // main.c
    #include "DSP28x_Project.h"
    #include "test_lib.h"
    
    Uint32 durchlauf = 0;
    
    void main(void)
    {
    	// Initialisierung (habe ich weggelassen)
    	LED34 = 0;
    	while(1)
    	{
    		if(durchlauf)
    		{
    			LED34 = 1;
    			schalte_led();
    			durchlauf = 0;
    		}
    	}
    }
    
    // test_lib.c
    #include "DSP28x_Project.h"
    #include "test_lib.h"
    
    void schalte_led(void)
    {
    	GpioDataRegs.GPBDAT.bit.GPIO34 = LED34;
    }
    
    // test_lib.h
    #ifndef TEST_LIB
    #define TEST_LIB
    
    extern Uint32 LED34;
    
    void schalte_led(void);
    
    #endif
    

    Ich habe also eine Variable, die ich in der test_lib.h deklariere, auf die ich in der test_lib.c UND in der main.c zugreife.

    Das Programm ist recht sinnfrei, dient auch nur dazu das Problem ohne großen Overhead anzugehen. Über die grundlegende Struktur der Bibliothek (globale Variablen, der Umgang mit diesen) will ich an der Stelle bitte nicht diskutieren, da habe ich mich an den offiziellen Bibliotheken orientiert und das soll schon so sein, auch wenn das bei PC-Anwendungen unglücklich sein mag.

    Wenn ich das compilieren will, meckert der Compiler:

    symbol "_LED34" redefined: first defined in "./test_lib.obj"
    

    Verfrachte ich die Variablen-Deklaration in die test_lib.c und lass das "extern" weg, dann geht es, WENN ich in der main.c nicht auf die Variable zugreife. Andernfalls gibt es natürlich einen Fehler, weil ich nur die test_lib.h in die main.c einbinde.

    So, ich hoffe mir kann das jemand erklären, das ist langsam echt frustrierend, dass es scheinbar nirgendwo erklärt wird, nicht mal in 1000-Seiten-Wälzern.

    Edit: Habe nun das "extern Uint32 LED34;" in der test_lib.h gelassen und zusätzlich noch in "Uint32 LED34;" in die test_lib.c geschrieben. Nun wird das Programm ohne Beanstandung compiliert. Das kam aber nur durch Herumprobieren zustande. Das ist noch nicht ganz befriedigend, da ich das nicht 100%ig verstanden habe, zumal es in den Standard-Bibliotheken auch ohne geht. Wäre über eine Erklärung daher trotzdem dankbar.



  • Takeshi schrieb:

    Ich schreibe ein C-Programm für einen Mikrocontroller und der Quellocde umfasst langsam hunderte Zeilen. Das macht es unübersichtlich

    Und? Wenn es unübersichtlich ist, dann ist entweder eine Modularisierung im Hintern (sprich, deine Funktionen sind nicht logisch in .c-Dateien aufgeteilt), oder die Codequalität alleine ist mau. Hauptprogramme bei mir haben nicht selten an die 2K Zeilen Code, und dennoch würde ich den nicht in ein anderes Modul packen, denn:

    Takeshi schrieb:

    und da dachte ich, macht es doch Sinn einige Funktionen in
    weitere Dateien auszulagern. Die große Frage ist nur: wie?

    Nein, die Frage ist: Warum?
    Hast du vor, den Code auch in anderen Bibliotheken zu verwenden? Dann brauchst du ein anständiges API. Du hast ja ein anständiges API entwickelt, oder?

    Und wenn du das hast, dann musst du wissen, WELCHER Code denn jetzt in die Lib reinkommen soll. Das ist wichtig; eine Lib kann, wenn du das falsch aufziehst, halt noch zusätzliche Komplexität aufziehen. Und dann stellt sich die Frage, ob du dynamisch oder statisch linken willst.

    Dynamisch ist super, wenn du mehrere Programme hast, die - in deinem Beispiel jetzt - was mit dem Controller machen sollen. Die Lib ist an einer Stelle installiert, und solange sich das ABI nicht verändert, können beide Programme die bei Programmstart einladen. Und wenn sich was im Code (aber nicht an der ABI) geändert hat, kannst du einfach die Lib neukompilieren und installieren, und beide Programme laden die neue Lib ein und jeder ist zufrieden.

    Statisch kann man auch machen, aber eher dann, wenn einem die Geschwindigkeitsvorteile das Neukompilieren kompletter Anwendungen wert sind. Bei statischen Libs ist der Code wieder in der Anwendung und kann nicht (zumindest nicht mit einigem Aufwand) entfernt werden. Dafür muss nicht dynamisch nach Symbolen gesucht werden. Statisch linken hat auch seine Vorteile. Aber meistens eher doch nicht.

    Aber eventuell kannst du auch nur statisch linken, weil der Mikrocontroller dynamisch gar nicht kann? Zum dynamischen Linken gehört schon mehr als ein Verweis auf die Bibliothek, das muss vom ABI und von der Umgebung, auf welcher die Anwendung läuft, passen. Die Probleme haste beim statischen Linken nicht, weil die Lib ja schon im Binary ist.

    Takeshi schrieb:

    Die Umgebung: Spielt denke ich keine Rolle, da es eine grundlegende Frage ist, aber sollte es doch wichtig sein, die IDE ist Code Composer Studio v5.5, der Mikrocontroller (Piccolo F28035) ist einer von Texas Instruments.

    Kommt schon darauf an, nur halt dann nicht, wenn du eine IDE benutzt. Weil IDEs in der Regel eine Projekteinstellung haben, mit der man dem Linker sagen kann, dass er jetzt mal eine Bibliothek erstellen soll. Wenn er das denn kann. Ich kenne die IDE jetzt nicht, aber Mikrocontroller, das hört sich nach begrenzten Ressourcen an.

    Stichwort ist: "Projekteinstellungen". Wenn du Glück hast, findest du da die Linkereinstellung, was mit dem kompilierten Code gebaut werden soll - dann kannst du ein neues Projekt aufmachen, da den Code für die Kommunikation (oder whatever du da hast) reinpacken, als Lib kompilieren, und dann in den Linkereinstellungen in deinen Anwendungen nur noch auf das Kompilat verweisen, und fertig ist die Laube.
    Wenn du abnorm viel Glück hast, kann der für deine Umgebung sogar eine dynamische Lib erzeugen. Dann kannst du dem Linker sagen, dass die Symbole aus der dynamischen Lib geholt werden sollen, und dann bekommst du keinen Linkerfehler, obwohl die Funktionen nicht in der Anwendungen sind. Dafür startet die Anwendung nicht, wenn die Lib nicht bereitsteht (ist ja dynamisch, gell?).
    Wenn du Pech hast, gibt es dafür keine Option, und du musst deinen Plan begraben.

    Takeshi schrieb:

    Mein Wissensstand: Ich würde behaupten, dass ich die grundlegenden Dinge von C, hatte es in der Schule, im Studium und sonst würde es wohl auch nicht mit dem Schreiben solcher Programme nicht klappen.

    Sagen sie alle. 🙂

    Takeshi schrieb:

    Welche Quellen ich abgesucht habe und wie weit mich das gebracht hat: Habe sowohl im Internet danach gesucht (wohl einfach falscher Suchbegriff) und danach in der Hochschul-Bibliothek. Bin da auch etwas geschockt/genervt, dass so was in kaum einem Buch beschrieben steht.

    Ja, aber WONACH haste denn gesucht? "Linken C Programm"?

    Takeshi schrieb:

    In einem Buch habe ich zumindest herausfinden können, dass ich die testlib.c nicht extra einbinden muss, weil die IDEs automatisch alle c-Dateien im Projektverzeichnis compelieren.

    Nö.
    Ist übrigens keine Lösung zu deinem Problem, sondern soll nur zeigen, dass du auf bestimmten/vielen Plattformen die Kompilierung von bestimmten Dateien auch einfach unterlassen kannst. Für den Fall, dass Modul xyz grad nicht funzt, abe du keinen Bock hast, das zu fixen, und das Modul auch nicht kritisch ist.

    Takeshi schrieb:

    Die test_lib.h muss ich sowohl in der test_lib.h, als auch in der main.c mit einbinden. Damit die Datei nicht doppelt eingebunden wird, wird das in der test_lib.h verhindert.

    Eher die test_lib.c, hm? Und weißt du, was ich oben geschrieben habe? Das mit dem "In eigenes Projekt packen und hoffen, dass du ne entsprechende Option findest"?

    Takeshi schrieb:

    Dann habe ich gelesen, dass man Variablen in der h-Datei deklarieren soll, dort aber kein Speicherplatz reserviert werden darf, was mit dem Schlüsselwort "extern" zu erreichen sei. Der Speicherplatz würde erst dann reserviert werden, wenn auf die Variable auch zugegriffen wird.

    Nein, eigentlich nicht. Eigentlich packt man, WENN man sowas machen will, die Variable in die .c-Datei deiner Lib, und macht dann in main.c extern darauf, damit der Linker keine Krise bekommt. Noch intelligenter: du macht in deiner Header-Datei eine Zugriffsfunktion, die die Adresse dieser deiner Variable in der Lib-c zurückgibt. Wenn das denn nötig ist. Aber das ist es selten genug, glaube mir. extern hat in der Header-Datei fast NIE was zu suchen. Schlimm genug, dass du das über globale Variablen machen willst, anstatt eine ordentliche Statemachine (sprich: übergib doch die Adresse einer Variable auf dem Stack deiner main-Funktion als Parameter. Dann müssen spätere Programmierer (oder auch du), wenn sie das Programm debuggen müssen, dein früheres Ich nicht verfluchen, weil du dir aus dem Nirwana Speicher ziehst) zu vwenden.

    /*bla*/
    

    Takeshi schrieb:

    Ich habe also eine Variable, die ich in der test_lib.h deklariere, auf die ich in der test_lib.c UND in der main.c zugreife.

    Ja, und das ist falsch, siehe oben. Mit extern wird kein Speicherbereich reserviert. Das ist nur eine Mitteilung an den Compiler, dass er das Maul halten soll, weil in irgendeiner anderen Übersetzungseinheit dafür bestimmt eine richtige Definition steht. Hast du aber nicht. Und der Linker, der dir die Symbol-Fehlermeldung zurückgibt, ist in der Hinsicht wesentlich strenger als dein Compiler.

    Takeshi schrieb:

    Das Programm ist recht sinnfrei, dient auch nur dazu das Problem ohne großen Overhead anzugehen. Über die grundlegende Struktur der Bibliothek (globale Variablen, der Umgang mit diesen) will ich an der Stelle bitte nicht diskutieren, da habe ich mich an den offiziellen Bibliotheken orientiert und das soll schon so sein, auch wenn das bei PC-Anwendungen unglücklich sein mag.

    Nee, das ist Schwachsinn und sollte auch als Schwachsinn bezeichnet werden. Was der Compiler - und vor allem der Linker - am Ende daraus macht, ist uns allen egal, aber sowas ist einfach kaputter C-Code.

    Eigenes Projekt. Oder eigenes Makefile. Da packst du den Lib-Code rein. Kompilierst das als Lib. Wenn's geht. Wenn nicht, kannst du den Plan direkt in einen Glassarg legen und versenken. Und dann in deiner main.c einfach nur den Header einbinden. Wenn du auf Modul-Variablen zugreifen willst (was als böse gilt, weil du damit die Einheit des Moduls ohne kontrollierten Zugriff verletzt), dann mach das wenigstens über eine Funktion in deiner Lib.c, und die Deklaration packst du in die Lib.h.

    Takeshi schrieb:

    Verfrachte ich die Variablen-Deklaration in die test_lib.c und lass das "extern" weg, dann geht es, WENN ich in der main.c nicht auf die Variable zugreife. Andernfalls gibt es natürlich einen Fehler, weil ich nur die test_lib.h in die main.c einbinde.

    Ja. Weil die Variable in der Lib-Übersetzungseinheit eine Adresse hat, und der Linker das extern e Symbol auflösen kann. extern e Variablen bekommen perse keinen Speicherplatz. Brauchen sie aber, wenn du reinschreiben oder rauslesen willst. Darum kümmert sich der Linker - wenn in all den Libs, die noch in das Programm reingebracht werden, eine gleichnamige Variable drinsteckt, dann ist der Linker zufrieden und kann linken. Der Linker ist ja auch dein Freund, er will ja linken. Kann er aber nicht, wenn du ihm Symbole (d.h. Variablen, Funktionen) vorsetzt, mit denen er nichts anfangen kann.

    Takeshi schrieb:

    So, ich hoffe mir kann das jemand erklären, das ist langsam echt frustrierend, dass es scheinbar nirgendwo erklärt wird, nicht mal in 1000-Seiten-Wälzern.

    Glaube ich dir nicht. Ich weiß es ja auch, und das einzige 1000-Seiten-Buch, was ich gelesen habe, war der Orden des Phönix.

    EDIT: Noch ne Sache wegen den modulübergreifenden Variablen: errno ist genauso:

    *__errno_location ()
    

    Holt sich einfach nur die echte Adresse von errno und dereferenziert sie.


Anmelden zum Antworten