40. Module

In Python konnten wir unsere Programme in Module gliedern und per import auf sie zugreifen. In C ist das etwas komplizierter. Wir wollen unsere Edelstrings zu einem Modul machen.

Vollzieht die Beispiele mit dem Code, der an die Folie “Dynamische Strings” angehängt ist, nach. Dieser Code hat keine Main-Funktion – er soll ja von anderen Programmen benutzt werden, und jedes Programm kann nur eine main-Funktion haben.

Dazu müssen wir zunächst eine Objektdatei erzeugen:

examples>  make edelstring.o
cc -Wall   -c -o edelstring.o edelstring.c

Der Knackpunkt an dem von make erzeugten Kommando ist dabei die Option -c, die make auf unsere Anforderung nach edelstring.o (anstelle von edelstring) hin eingefügt hat. Für den Compiler ist das das Zeichen, nicht den Linker aufzurufen. Da aber erst der Linker weiß, wie man ein ausführbares Programm macht und die Objektdatei jede Menge Referenzen in die C-Bibliothek enthält, die nicht aufgelöst sind, ist edelstring.o keine ausführbare Datei:

examples> chmod +x edelstring.o
examples> ./edelstring.o
./edelstring.o: Exec format error. Binary file not executable.
Exit 1

Der Name Objektdatei hat übrigens nur sehr wenig mit den Objekten mit Sinne von Python zu tun.

In edelstring.o befindet sich jetzt der Maschinencode für die Funktionen sowie eine Auflistung der Funktionen, die das Modul bietet, und derer, die es braucht (T bzw. U in der zweiten Spalte, vgl. man nm):

examples> nm edelstring.o
00000120 T eStr_add
0000019c T eStr_findChar
...
         U free
00000000 t gcc2_compiled.
         U malloc
...

Keine Prototypen und Typdefinitionen in der Objektdatei – zur Nutzung des Moduls sind sie aber nötig. C kann sie nicht selbst aus der Quelldatei extrahieren. Daher: Headerfile

/* edelstring.h */
typedef struct estr_s {
  int length;
  char *content;
  int cCompatible;
} eStr;

eStr *eStr_new(int len);
eStr *eStr_fromString(char *cString);
void eStr_free(eStr *eString);
eStr *eStr_add(eStr *op1, eStr *op2);
int eStr_findChar(eStr *eString, char c,
  int count);

Im Headerfile stehen also Typen, die von außen sichtbar sein müssen, sowie die Prototypen der Funktionen, die von außen benutzt werden sollen. In der Quelldatei oben muss jetzt die include-Direktive einkommentiert werden, der typedef dafür gelöscht.

Es ist eine sehr gute Idee, die Headerdatei eines Moduls in das Modul selbst einzubinden. Hauptgrund ist, dass dadurch Änderungen im Modul, die die durch die Headerdatei definierte Schnittstelle ändern, gleich auffallen. Die Konsistenz von Schnittstelle und Implementation wird nur durch die Einbindung der Headerdatei vermittelt, und ein Verlust dieser Konsistenz führt praktisch immer zu wilden Fehlern, die niemand findet.

Um das Modul zu verwenden, bindet man die Headerdatei in das Programm ein, wie wir das schon kennen – nur verwenden wir Anführungszeichen statt spitzer Klammern:

#include <stdio.h>
#include "edelstring.h"
int main(void)
{
  eStr *kuck=eStr_fromString("Kuckuck, ");
  eStr *dest1;

  dest1 = eStr_add(kuck, kuck);
  printf("%s %d\n", dest1->content,
    eStr_findChar(dest1, 'u', 3));
  return 0;
}

Hintergrund von spitzen Klammers vs. Anführungszeichen ist, dass auch stdio.h und Freunde nur Dateien sind. Sie stehen irgendwo im Dateisystem (üblicherweise in der Gegend von /usr/include), und die spitzen Klammern signalisieren dem Präprozessor, er möge sie dort suchen. Mit Anführungszeichen sagen wir dem Präprozessor, dass die Dateien zunächst mal im aktuellen Verzeichnis gesucht werden sollen – und so, wie wir unsere Quelltexte bisher verteilen, wird er bei von uns definierten Headern im Regelfall auch dort fündig werden.

Wenn wir dieses Programm kompilieren, passiert folgendes:

examples> make estr_test
cc -Wall    estr_test.c   -o estr_test
/tmp/ccGydNq3.o: In function ‘main':
/tmp/ccGydNq3.o(.text+0xf): undefined reference to ‘eStr_fromString'
/tmp/ccGydNq3.o(.text+0x27): undefined reference to ‘eStr_add'
/tmp/ccGydNq3.o(.text+0x42): undefined reference to ‘eStr_findChar'
collect2: ld returned 1 exit status
Exit 1

– der Linker wirft Fehlermeldungen des Inhalts, dass er die Funktionen in edelstring.o nicht gefunden hat. Der Linker sieht eben nicht in den C-Quelltext hinein und kann nicht ahnen, dass wir Funktionen aus edelstring.o verwendet haben. Wir müssen ihm das explizit sagen:

examples> cc -Wall estr_test.c edelstring.o -o estr_test
examples> ./estr_test
Kuckuck, Kuckuck,  10

Dass niemand solche Kommandozeilen eintippen möchte, ist klar. Abhilfe schafft hier make.


Markus Demleitner

Copyright Notice