Der Retter in der Not

Tag für Tag werden ganze Horden von fleißigen C-Compilern entfesselt, auf daß sie die ihnen zum Fraß vorgeworfenen kryptischen Zeichenfolgen wohlgefällig interpretieren und in ablauffähige Programme verwandeln möchten. Doch bevor sich ein solcher Compiler an das leckere Eingemachte seines Herrn und Meisters machen kann, muß ersieh zunächst meist durch das Bollwerk der in den Header-Files enthaltenen Strukturdefinitionen und Prototypen beißen, und das bei jedem Kompiliervorgang von neuem.

Um zeitraubende Harddisk- oder gar Diskettenzugriffe zu vermeiden, kopiert sich der in Eile befindliche Programmierer seinen INCLUDE-Ordner also in die RAM-Disk. Ist sein Tun und Trachten im Geschwindigkeitsrausch dahingehend gerichtet, auch noch die Libraries, den Objektcode und das fertige Programm dort unterzubringen, so kann es mitunter, insbesondere wenn sich die Megabytes noch an einem Finger abzählen lassen, im Arbeitsspeicher für den Compiler ungemütlich eng werden. Um ein solches Horrorszenario nicht dreist heraufzubeschwören, empfiehlt es sich also, frühzeitig Speicherfresser auszumachen und zu eliminieren.

Potentiell Schuldige sind schnell gefunden, nämlich die Header-Files selbst; sie belegen z.B. im Pure-C-Entwick-lungssystem etwa 105 KB. Bei näherer Betrachtung zeigt sich aber, daß diese Dateien zu gut einem Drittel aus Leerzeilen, Leerzeichen und Kommentaren bestehen. Da liegt es doch nahe, ein kleines Programm zur Ausmerzung jener subversiven Elemente zu ersinnen. Freundlicherweise habe ich Ihnen diese Arbeit bereits abgenommen; lesen Sie deshalb an dieser Stelle, was bei diesem Unterfangen herausgekommen ist und wo aus aus welchen Gründen Abstriche gemacht werden mußten:

Nach Start des RIDN erscheint das Hauptmenü. Um das Arbeiten zu beschleunigen, bietet der RIDN hier neben der Möglichkeit, eine Datei zu komprimieren, auch die Option, alle in einem Ordner zusammengefaßten Dateien zu bearbeiten.

Nach Ermittlung eines Dateinamens reserviert der RIDN zunächst einen Speicherbereich. In diesem findet nicht nur der Quelltext Platz, sondern darin wächst und gedeiht im Verlaufe der Komprimierung auch die Zieldatei, oder, anders betrachtet, der Quelltext schrumpft ein. Da der Zieltext niemals länger als der Quelltext werden kann, ist diese Vorgehensweise unbedenklich.

Das Einschrumpfen wird folgendermaßen bewerkstelligt: Kommentare werden überlesen, wobei auch geschachtelte Kommentare korrekt bearbeitet werden; Strings und String-Konstanten werden unverändert kopiert; Leerzeilen werden entfernt; ein weißes Leerzeichen (also ein Leerzeichen, ein Tabulator, ein Carriage Return oder ein Line Feed) wird nur dann entfernt, wenn es keine bedeutungsunterscheidende Wirkung hat, wenn es also nicht benötigt wird, um zwei Bezeichner voneinander zu trennen. [Das ist dann der Fall, wenn das Zeichen vor oder hinter einem weiteren Trennzei-chen (einem weißen Leerzeichen, einem Komma, einem Semikolon, einer Klammer, einem Operator...) steht.] Andere Zeichen werden kopiert.

Zusammenfassend läßt sich also feststellen: Ein eindeutig als subversives Element oder als zu einem solchen gehörig erkanntes Zeichen wird übergangen, also vergessen. Ein gültiges Zeichen wird hinter das zuletzt gefundene gültige Zeichen kopiert. Komplikationen können dabei keine auftre-ten, da das Zeichen des Quelltextes, das dabei verlorengeht, zu diesem Zeitpunkt schon bearbeitet worden ist.

Einschränkend ist anzumerken, daß Line Feeds nur dann überlesen werden, wenn die entsprechende Zeile leer ist, andernfalls könnten Zeilen die für manche Programme magische Länge von 255 Zeichen überschreiten.

Soweit, so gut. Doch infamerweise lassen sich Präprozessor-Kommandos mit der oben beschriebenen Methode zur Bewertung von Zeichen nicht korrekt bearbeiten, und zwar aufgrund ihrer etwas anderen Syntax. Schwierigkeiten würde insbesondere die define-Direktive bereiten, mit der man den Präprozessor anweisen kann, jede beliebige Zeichenkette durch eine andere beliebige Zeichenkette zu ersetzen, wobei die beiden Zeichenketten durch ein Leerzeichen voneinander getrennt angegeben werden müssen. Probleme würden nun auftreten, wenn die erste Zeichenkette mit einem Trennzeichen enden und/oder die zweite Zeichenkette mit einem solchen beginnen würde. Dann würde nämlich das Leerzeichen entfernt werden, und bei der Kompilierung könnte der Präprozessor die Definition nicht mehr korrekt erkennen. Ein Beispiel aus STDIO.H: Aus #define stdout (&_StdOutF) würde #define stdout(&_StdOutF). Deshalb kennt der RIDN intern zwei Bearbeitungsmodi: Ist der Präprozessor-Kommando-Modus aktiv, wird ein weißes Leerzeichen nur dann entfernt, wenn vor oder hinter diesem ein weiteres weißes Leerzeichen steht. Das bedeutet praktisch, daß mindestens ein weißes Leerzeichen stehenbleibt.

Läßt man den RIDN nun z.B. an den INCLUDE-Ordner des Pure-C-Systems, komprimiert er dessen Inhalt von 105 auf 63 KB! Diese Datenreduktion bringt nicht nur ein Mehr an Arbeitsspeicher im RAM-Disk-Betrieb, sondern macht sich auch in einer deutlichen Beschleunigung des Compilers bemerkbar.

C-Freaks wissen natürlich, daß damit das Komprimierungspotential noch lange nicht ausgeschöpft ist. So könnten z.B. auf mehrere Zeilen aufgeteilte Definitionen von Variablen gleichen Typs zusammengezogen werden; aus den Prototypen könnten die Namen der zu übergebenden Variablen entfernt werden. Doch dafür brauchte man bereits einen intelligenten Parser mit integriertem Präprozessor. Es wäre jedoch Unsinn, einen entsprechend leistungsfähigen Algorithmus zu implementieren, da es einen solchen ja in jedem Compiler gibt. Hier sind also - Cache hin, Cache her -die Compiler-Bauer gefordert.

/*
RIDN
"Retter in der Not"
Programm zum Kürzen von Header-Files auf das Wesentliche
by Michael Marte (c) 1992 MAXON Computer 
*/

#include <stddef.h> 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ext.h>

/* Typendefinitionen */

/*
Folgende Typen stehen in meiner STDDEF.H :

typedef unsigned char byte; 
typedef unsigned int word; 
typedef unsigned long lword;

typedef enum {failed=0,false=0,ok=1,true=1} bool; 
typedef bool succ;
*/

/* Variablendefinitionen */

struct ffblk fspec;
/* Dateinformationen */

char sdir[65];
/* Quellverzeichnis */

char ddir[65];
/* Zielverzeichnis */

char sfile[81];
/* Quelldatei (kompletter Pfad) */ 

char dfile[81];
/* Zieldatei (kompletter Pfad) */

char *white_blanks=" \t\n\r";
/* Weiße Leerzeichen */

char *separators=" \t\n\r,;=+-!?:*%&|/\<>()[]{}"; 
/* Trennzeichen */

/* Prototypen */

succ ridn(char *sfile,char *dfile,lword size); 
void show_err(char *msg);

main(){
	int choice;

	do{
		do{
			printf("\x1b\x45"
				"RIDN - \"Retter in der Not\"\n" 
				"Programm zum Kürzen von Header-" 
				"Files auf das Wesentliche\n" 
				"v1.02 vom 12.12.1991\n"
				"(c)1990 by Michael Marte\n\n"
				"1. Datei bearbeiten\n"
				"2. Alle Dateien eines Ordners " 
				"bearbeiten\n"
				"3. Ende\n\n"
				"Bitte wählen Sie : "); 
			scanf("%d",&choice);
		}while(choice<1 || choice>3); 
		printf("\x1b\x45");
		switch(choice){ 
			case 1:
				printf("Quelldatei : ");
				scanf("%80s",sfile);
				printf("Zieldatei : ");
				scanf("%80s",dfile);
				if(findfirst(sfile,&fspec,0)==0)
					(void)ridn(sfile,dfile, fspec.ff_fsize); 
				break; 
			case 2:
				printf("Quellordner : "); 
				scanf("%64s",sdir); 
				printf("Zielordner : "); 
				scanf("%64s",ddir);
				(void)strcpy(sfile,sdir);
				(void)strcat(sfile,"\\*.H"); 
				if(findfirst(sfile,&fspec,0)==0) 
					do{
						(void)strcpy(sfile,sdir);
						(void)strcat(sfile, "\\");
						(void)strcat(sfile, fspec.ff_name);
						(void)strcpy(dfile,ddir);
						(void)strcat(dfile,"\\");
						(void)strcat(dfile, fspec.ff_name); if(!ridn(sfile,dfile, fspec.ff_fsize)) 
							break;
						(void)strcpy(sfile,sdir);
						(void)strcat(sfile,"\\*. H"); 
					}while(findnext(&fspec)==0);
		}

	}while(choice!=3); 
	
	return 0;
}

succ ridn(char *sfile,char *dfile,lword size){ 

	char *sptr;
	/* Zeiger in den Quelltext */ 

	char *dptr;
	/* Zeiger hinter das "Eingemachte" */ 

	char *Start;
	/* Zeiger auf den Beginn der Quelltextes */ 

	char *end;
	/* Zeiger auf dessen Ende */

	bool ppc=false;
	/*
	Bearbeitungsmodus : 
	ppc=true =>
		ein Präprozessorkommando wird bearbeitet
	*/

	int sfid,dfid;
	/* Handles der Quell- und Zieldatei */

	printf("Quelle : %s Ziel : %s\n",sfile,dfile);

	/* Quelldatei öffnen */ 
	if((sfid=open(sfile,O_RDONLY))==-1){ 
		show_err("Quelldatei öffnen "); 
		return failed;
	}

	/* Speicher reservieren */ 
	if((start=(char *)malloc(size))==NULL){ 
		show_err("Speicher reservieren ");
		(void)close(sfid); 
		return failed;
	}

	end=start+size-1;

	/* Quelldatei einlesen */ 
	if(read(sfid,Start,size)<size){ 
		show_err("Quelldatei lesen "); 
		free(Start);
		(void)close(sfid); 
		return failed;
	}
	(void)close(sfid);

	/* Quelldatei komprimieren */

	sptr=dptr=start;

	while(sptr<=end){

		switch(*dptr=*sptr++){	j
			case '#':
				/* Präprozessor-Kommando */ 
				ppc=true; 
				dptr++; 
				break; 
			case '\r':
				/* Carriage Return */ 
				if(ppc)
					/*
					Wenn am Ende der vorherigen Zeile 
					kein Backslash steht, wenn die 
					Zeile also zu Ende ist, muß der 
					ppc-Bearbeitungsmodus 
					ausgeschaltet werden.
					*/
					if(dptr>start && *(dptr-1)!='\\') ppc=false;
				/*
				Wenn das letzte Zeichen des 
				Eingemachten kein Line Feed ist, 
				dann ist die aktuelle Zeile keine 
				Leerzeile und das Line Feed 
				muß kopiert werden.
				*/
				if(dptr>start && *(dptr-1)!='\n') dptr++; 
				break; 
			case '\n':
				/* Line Feed*/

				/*
				Sinngemäßer Kommentar s. bei 'Carriage Return'.
				*/
				if(dptr>start && *(dptr-1)!='\n') dptr++; 
				break; 
			case '/':
				/* eventuell Kommentar */ 
				if(*sptr!='*') dptr++; 
				else {
					int rem_count=l;
					/*
					Zähler zur Bearbeitung verschachtelter Kommentare 
					*/

					sptr++;
					while(rem_count>0 && sptr<end){ 
						switch(*sptr++){
							case '/':
								/* eventuell noch ein Kommentar */ 
								if(*sptr=='*'){ 
									sptr++; 
									rem_count++;
								}
								break; 
							case '*':
								/* eventuell Ende eines Kommentars */ 
								if(*sptr=='/'){ 
									sptr++; 
									rem_count--;
								}
						}
					}
				}
				break; 
			case '\"':
				/* Stringkonstante */
				{
					bool eos=false;
					/* Stringende noch nicht erreicht */
					dptr++;
					/* String kopieren */ 
					while(!eos && sptr<=end){ 
						switch(*dptr++=*sptr++){ 
							case '\\':
								/* Escapesequenz kopieren */
								*dptr++=*sptr++; 
								break; 
							case '\'':
								/* Stringende erreicht */ 
								eos=true;
						}
					}
				}
				break; 
			case '\"':
				/*
				String :
				Sinngemäßer Kommentar s. bei 'Stringkonstante'
				*/
				{
					bool eos=false; 
					
					dptr++;
					while(!eos && sptr<=end){ 
						switch(*dptr++=*sptr++){ 
							case '\\':
								*dptr++=*sptr++; 
								break; 
							case '\"': 
								eos=true;
						}
					}
				}
				break; 
			case '\t':
				/* Tabulator */ 
			case ' ':
				/* Space */
				/*
				Wenn das Zeichen unbedingt zur 
				Trennung zweier Bezeichner benötigt 
				wird, dann muß es kopiert werden.
				*/
				if(ppc){
					if(strchr(white_blanks,*sptr)== NULL &&
						strchr(white_blanks, *(dptr-1)) ==NULL) 
						dptr++;
				}else
					if(dptr>start && strchr(separators,*sptr)==NULL && strchr(separators,*(dptr-1))==NULL)
						dptr++;
				break; 
			default:
				/* sonstiges Zeichen */ 
				dptr++;
		}
	}

	/* Zieldatei öffnen */
	if((dfid=creat(dfile))==-1){
		show_err("Zieldatei öffnen/anlegen "); 
		return failed;
	}

	/* Eingemachtes schreiben */

	if(write(dfid,start,size=dptr-start)<size){ 
		show_err("Zieldatei schreiben ");
		(void)close(dfid); 
		return failed;
	}

	/* Zieldatei schließen */ 
	if(close(dfid)==-1){
		show_err("Zieldatei schließen "); 
		return failed;
	}

	return ok;

}

void show_err(char *msg){ 
	perror(msg); 
	printf("\n<Taste>\n");
	(void)getch();
}

Michael Marte
Aus: ST-Computer 10 / 1992, Seite 92

Links

Copyright-Bestimmungen: siehe Über diese Seite