Tips und Tricks für C-Programmierer

PC-relative Sprünge

Sobald das Textsegment die magische Grenze von 32 KByte überschreitet, meldet sich früher oder später der Linker mit der Fehlermeldung »PC-relativ overflow«. Ist das Programm in mehrere Module aufgeteilt, läßt sich durch eine geschickte Anordnung der Module das Programm noch vergrößern, ohne beim Linken mit besagter Fehlermeldung abzubrechen. Irgendwann ist aber auch damit Schluß, und die Compiler-Option »use absolute calls« ist für alle Module zu setzen. Das bedeutet jedoch, daß das Programm nun alle Funktionen, also auch eine direkt nachfolgende Funktion »absolut« anspringt. Durch die Trennung der Aufgaben zwischen Compiler und Linker kennt der Compiler den Abstand zweier Funktionen nicht und muß so immer vom schlimmsten Fall ausgehen, es sei denn die Funktionen befinden sich im gleichen Modul. In der Praxis sieht es dann so aus, daß Pure C zu dem Zeitpunkt der Übersetzung alle vorherigen Funktionen aus dem gleichen Modul kennt, und so trotz der gesetzten Option »use absolute call« einen PC-relativen Sprung verwendet, wenn dies vom Abstand her reicht. Ordnet man also die Funktionen in einem Modul so an, daß die Unterfunktionen oberhalb der aufrufenden stehen, profitiert man auch bei großen Projekten von den PC-relativen Sprüngen.

Versionen via Makro

Da hat man nichts anderes gemacht, als eine neue Routine in sein Programm eingebaut und neu compiliert. Wenn man das Ergebnis dann startet, geht auf einmal überhaupt nichts mehr, obwohl doch die neue Funktion nur irgendwo im hintersten Eck des Menüs angesprochen wird, also bestimmt nicht der Übeltäter sein kann. Ohne jetzt alle Details der langwierigen Fehlersuche aufzuführen, steht fest, daß man hier beliebig lange auf dem Holzweg herumirren kann, bevor es wie ein Blitz einschlägt: Durch die neue Funktion hat sich das Format der Info-Datei geändert, so daß das neue Programm immer noch die bisherigen Daten, natürlich falsch, interpretiert. Um solche Probleme erst gar nicht entstehen zu lassen, tut sich eine Versionsnummer am Anfang jeder Konfigurationsdatei gut. Beim Lesen vergleicht man dann diese auf ihre Gültigkeit. Die zwei vordefinierten Makros »DATE« und »TIME« enthalten dabei das Datum und die Uhrzeit im Moment des Compilierens.

#define XVERSION "Version 1.xx vom "__DATE__" "__TIME__"
#define FALSE    0
#define TRUE    (!0)

int Schreiben (FILE *fp)
{
    return (fprintf (fp, XVERSION ) ! = EOF );
}

int Lesen (FILE * fp)
{
    char temp(sizeof (XVERSION) + 1L);
    if (fread (temp, sizeof (XVERSION) + 1L, 1L, fp ) == 1L && !strcmp (temp, XVERSION))
        return TRUE
    else
        return FALSE
}

Inline Assembling in Turbo/Pure C

Was bei vielen C-Compilern selbstverständlich, aber von den Pure/Turbo-C-Entwicklern verpönt ist, nämlich das Inline-Assemblieren, funktioniert über eine Hintertüre, zumindest eingeschränkt, auch bei Pure/Turbo C. Dazu muß man den Opcode eines Assemblerbefehls selbst berechnen. Mit der Funktionsdefinition

long Swap ( long swp ) 0x4840;

läßt sich dann der Assemblerbefehl »swap d0« an jeder Stelle im Programm einbauen. Die Parameterübergabe ist dabei die gleiche wie bei einem »normalen« Funktionsaufruf: D0 - D2 für Daten und A0/A1 für Adressen (genaueres im Handbuch zu Turbo/Pure C). Diese Register rettet Pure C vor jedem Aufruf, wodurch sie sich zweckentfremdet als lokale Variablen im Assemblerteil benutzen lassen. Um auch das letzte Argument der Inline-Assembler-Gegner zu nehmen, definieren wir die Swap-Funktion so:

#ifdef __TURBOC__
    long Swap ( long swp ) 0x4840;
#else
    #define Swap (swp) ((long) ((unsigned long) spw >> 16) + ((long) swp << 16))
#endif

Eine Tücke beim Umgang mit den Opcode-Funktionen ist dennoch zu berücksichtigen: Die obige Zeile entspricht natürlich keiner C-Konvention, was sich spätestens daran zeigt, daß es sich hier weder um eine Funktionsdefinition noch um einen Funktionsprototypen handelt. Würde man dem Compiler nur den Prototypen bekannt machen, erzeugt dieser den Code für den Aufruf einer externen Funktion. Deshalb ist der Ausdruck am besten in einer Header-Datei aufgehoben und wie ein Makro zu behandeln.

BIOS-, XBIOS- und GEMDOS-Funktionen mit Turbo/Pure C

Die Parameterübergabekonvention der Betriebssystemfunktionen (BIOS, XMOS und GEMDOS) ähnelt der der C-Parameterübergabe doch sehr. Zusammen mit der variablen Parameterübergabe »va_arg« und der Inline-Assembler-Instruktionen von Turbo/Pure C reichen drei Funktionsdefinitionen, um diese Bibliotheken, ebenfalls »inline«, in C nachzubilden.

long cdecl Bios   (int num, ... ) 0x4e4d;
long cdecl Xbios  (int num, ... ) 0x4e4e;
long cdecl Gemdos (int num, ... ) 0x4e41;

Den Rest erledigen dann entsprechende Macros:

#define Bconstat(dev)  ((int)    Bios  (1,  (int)dev))
#define Iorec(devno)   ((IOREC*) Xbios (14, (int)devno))
#define Fopen (name,mode) ((int)Gemdos (61, (char *) name, (int) mode))

oder aber direkt:

long cdecl _Bconstat (int num, int dev)   0x4e4d;
long cdecl _Iorec    (int num, int devno) 0x4e4e;
long cdecl _Fopen    (int num, char *name, int mode) 0x4e41;

#define Bconstat (dev)  ((int) _Bconstat (1, dev))
#define Iorec (devno)   ((IOREC *) _Iorec (14, devno))
#define Fopen (name,mode) ((int) Fopen (61, name, mode ))

Da bei der direkten Methode keine »type castings« notwendig sind, um sicherzustellen, daß alle Parameter im richtigen Format auf den Stack gelangen, erhält man hier sogar Compiler-Warnungen bei falscher Parameterübergabe. Zu Berücksichtigen ist noch, daß beim Compilieren die Funktionsprototypen alleine nicht reichen, da der Compiler annehmen würde, es handle sich um ganz normale externe Funktionen, und dementsprechend einen »jump subroutine« einbaut, was ja genau zu vermeiden war.

Meisterhaftes Makro

Bei den Compiler-Optionen der integrierten Entwicklungsumgebung von Pure/Turbo C läßt sich ein Makro definieren, ohne den Quelltext zu modifizieren. Typische Makros hierfür sind zum Beispiel »DEBUG« oder »DEMO«. Beide Makros gleichzeitig zu definieren, geht aber nur mit einem kleinen Trick: Define macro :DEBUG -DDEMO So erzeugt der Compiler eine Debug-Demo-Version. Da die Shell der integrierten Entwicklungsumgebung von Pure C nichts anderes macht, als sich eine Kommandozeile zusammenzubauen, um damit den internen Compiler genauso wie den kommandozeilenorientierten Compiler aufzurufen, braucht man nur dort nachschauen, wie die verschiedenen Optionen einzuleiten sind. Für »define macro« ist das zum Beispiel »-Dxxxx«. Auf diese Weise lassen sich dann auch Optionen einstellen, die in der Shell nicht direkt zur Verfügung stehen (z.B. »undefine macro«). Gleiches gilt beim Setzen der Optionen in der Projekt-Datei. So ist es oft einfacher, zwei Projekt-Dateien zu verwalten, um mit nur einer Funktion (hier: »select Project«) komplexe Optionen für verschiedene Module auf einmal zu ändern.


Links

Copyright-Bestimmungen: siehe Über diese Seite
Classic Computer Magazines
[ Join Now | Ring Hub | Random | << Prev | Next >> ]