FAQ:Verschiedenes

[ FAQ in de.comp.lang.c ]


Diese FAQ bezieht sich in ihrer Gänze auf den inzwischen nicht mehr aktuellen ISO-C Standard 9899:1990, vielfach auch als C90 bezeichnet. Der seit Dezember 1999 existierende neue ISO 9899:1999 Standard (oder auch C99) wird nicht berücksichtigt.


[ Inhalt ][ Index ][ ZurÜck ][ Weiter ]


Frage 17.1: Welche Annahmen über die automatische Initialisierung von Variablen sind erlaubt? Reicht eine Initialisierung von Null-Zeigern und Gleitkommavariablen mit 0 aus?

Antwort: Variablen der Speicherklasse static (Variablen die außerhalb einer Funktion deklariert werden und solche, die als static deklariert werden) werden garantiert mit 0 initialisiert, und zwar genau einmal beim Start des Programms, als wenn der Programmierer bei der Deklaration "= 0" geschrieben hätte. Diese Variablen werden daher mit Null-Zeigern (mit korrektem Typ - siehe Abschnitt 1), wenn es sich um Zeiger handelt, und 0.0, wenn es sich um Gleitkomma Variablen handelt, initialisiert.

Variablen der Speicherklasse automatic (lokale Variable ohne statische Speicherklasse) werden beim Programmstart nicht initialisiert, es sei denn, der Programmierer tut dies explizit. Über den Inhalt dieser Variablen darf ansonsten keine Annahme gemacht werden.

Der Inhalt von mittels malloc und realloc dynamisch belegtem Speicher sollte ebenso als nicht initialisiert betrachtet werden. Er muß durch das aufrufende Programm entsprechend initialisiert werden. Speicherbereiche die mit calloc belegt werden, werden bitweise mit 0 initialisiert. Dies muß nicht notwendigerweise einem Null-Zeiger oder einer Gleitkomma 0 entsprechen (siehe auch 3.13 und Abschnitt 1).


Frage 17.2: Dieses Programm, direkt aus einem Buch, läßt sich nicht kompilieren

	f()
	{
	char a[] = "Hello, world!";
	}

Dies könnte darn liegen, dass es sich um einen alten, nicht ANSI-kompatiblen Compiler handelt und dieser die Initialisierung von "automatic aggregates" (d.h. nicht statische lokale Arrays und Strukturen) nicht erlaubt. Als Ausweg kann man das Array global oder statisch deklarieren und mittels strcpy beim beim Aufruf von f initialisieren. (Man kann lokale char * Variablen immer mit String Konstanten initialisieren, siehe auch 17.20). Siehe auch 5.16 und 5.17.


Frage 17.3: Wie kann man Daten so in Dateien schreiben, dass sie auch auf fremden Maschinen mit anderen Wortlängen, Byteorder oder anderem Gleitkommaformat gelesen werden können?

Die beste Lösung ist es, Textdateien zu verwenden (i.d.R. ASCII), die mit fprintf geschrieben und mit fscanf gelesen werden (ähnliches gilt auch bei Netzwerkprotokollen). Die Argumente, Textdateien seien zu groß und Lesen und Schreiben von Textdateien sei zu langsam, sind nicht sehr stichhaltig. Normalerweise ist ihre Effizienz in der Praxis akzeptabel. Darüberhinaus kann der Vorteil, dass man die Daten mit Standardwerkzeugen bearbeiten kann, eventuelle Nachteile meist mehr als wettmachen.

Soll dennoch ein Binärformat verwendet werden, kann die Portabilität durch Verwenden von standardisierten Formaten, wie SUN XDR (RFC 1014), OSI ASN.1, CCITT X.409, oder ISO 8825 "Basic Encoding Rules" erhöht werden. Für diese Formate gibt es u.U. auch bereits fertige E/A Bibliotheken. Siehe auch 9.11.


Frage 17.4: Wie kann man in der Mitte einer Datei eine Zeile (oder einen Record) löschen oder einfügen?

Antwort: Ausser durch ein Neuschreiben der Datei ist dies wahrscheinlich nicht möglich. Siehe auch 16.9.


Frage 17.5: Wie kann man mehrere Werte aus einer Funktion zurückgeben?

Antwort: Entweder wird der Funktion ein Zeiger auf Speicherstellen, die die Funktion beschreiben kann, übergeben, oder die Funktion muß eine struct zurückgeben, die die gewünschten Werte enthält. Als Notlösung kommen auch globale Variable in Betracht. Siehe auch 2.17, 3.4 und 9.2.


Frage 17.6: Wenn man eine char * Variable hat, die auf den Namen einer Funktion als String zeigt, wie kann man diese Funktion aufrufen?

Die offensichtlichste Lösung ist es, eine Tabelle mit den Namen der Funktionen und den entsprechenden Zeigern anzulegen:

	int function1(), function2();

	struct {char *name; int (*funcptr)(); } symtab[] =
		{
		"function1",    function1,
		"function2",    function2,
		};

Nun braucht man nur die Tabelle zu durchsuchen und kann über den entsprechenden Zeiger die Funktion aufrufen. Siehe auch 9.9 und 16.11.


Frage 17.7: Auf meinem System scheint die Headerdatei <sgtty.h> nicht vorhanden zu sein. Kann mir jemand eine Kopie zuschicken?

Standard Headerdateien existieren unter anderem deswegen, damit für den jeweiligen Compiler, das Betriebssystem und den Prozessor entsprechende Definitionen zur Verfügung gestellt werden können. Es ist nicht möglich, einfach eine Kopie einer fremden Header Datei zu übernehmen und dann zu erwarten, diese würde funktionieren, es sei denn, die Header Datei wurde von jemandem mit der exakt gleichen Umgebung zur Verfügung gestellt. Hier ist eine Nachfrage beim Compilerhersteller/Vertrieb notwendig, warum die entsprechende Datei nicht zur Verfügung gestellt wurde.


Frage 17.8: Wie kann man FORTRAN (C++, BASIC, Pascal, Ada, LISP) Funktionen aus C aufrufen (und umgekehrt)?

Die Antwort hängt von der verwendeten Hardware und den Aufrufkonventionen der verschiedenen beteiligten Compiler ab, und es kann sogar unmöglich sein. Man sollte in einem solchen Fall die Dokumentation des Compilers besonders genau durcharbeiten. Manchmal gibt es Kapitel über "mixed language" Programmierung. Aber selbst hier werden wichtige Themen wie die Übergabe von Argumenten und die Laufzeitinitialisierung oft nur oberflächlich behandelt. Weitere (englischsprachige) Informationen hierzu sind in Glenn Geers FORT.gz erhältlich, das man u.a. per ftp von suphys.physics.su.oz.au im src Verzeichnis erhält.

cfortran.h, eine C Header Datei und vereinfacht die C/FORTRAN Schnittstelle auf vielen gängigen Maschinen. Erhältlich ist sie per ftp von zebra.desy.de (131.169.2.244).

In C++ verwendet man den "C" Modifier in einer external Funktionsdeklaration um dem Compiler zu sagen, dass die Funktion nach der C Konvention aufgerufen werden soll.

Bei Ada ist in der entsprechenden Norm (ISO/IEC 8652:1995) im Abschnitt B.3 eine Beschreibung zu finden, die das Vorgehen in allen Details erläutert. Alle gängigen Ada-Compiler unterstützen diese Schnittstelle.


Frage 17.9: Kennt jemand ein Programm das Pascal oder Fortran (oder LISP, Ada, awk, "altes C" (K&R), ...) nach C übersetzt?

Antwort: Es sind verschiedene Public Domain Programme erhältlich:

p2c Ein Pascal/C Konverter von Dave Gillespie, veröffentlicht in comp.os.sources im März 1990; ftp: csvax.cs.caltech.edu, file pub/p2c-1.20.tar.Z.

ptoc Ein weiteres Pascal/C Konvertierprogramm, dieses ist in Pascal geschrieben. (comp.sources.unix)

f2c Ein Fortran/C Konverter, gemeinsam von Angehörigen von Bell Labs, Bellcore und Carnegie Mellon Universität entwickelt. Um mehr Informationen über f2c zu erhalten, braucht man nur eine e-mail mit dem Inhalt "send index from f2c" an netlib@research.att.com oder research!netlib zu schicken (außerdem ftp://netlib.att.com im Verzeichnis netlib/f2c).

Beim Autor der englischen comp.lang.c FAQ ist außerdem eine Liste weiterer kommerzieller Übersetzungsprodukte sowie einiger Konverter für weniger verbreitete Sprachen erhältlich.

Siehe auch 5.3.


Frage 17.10: Ist C++ eine Obermenge von C? Kann ich einen C++ Compiler benutzen um C Programme zu kompilieren?

Antwort: C++ wurde von C abgeleitet und basiert größtenteils auf C, aber es gibt zulässige C Programme, die in C++ nicht zulässig sind. Außerdem gibt es Programme, die in C und C++ eine unterschiedliche Semantik haben.

Hinweis von Jochen:
Das folgende Programm erkennt zum Beispiel, ob es mit einem C90-Compiler oder einem C++-Compiler übersetzt wurde:

	#include <stdio.h>

	int main(void)
	{
	  int result = 1 //* */ 2
	  ;
	  printf("Dies ist %s C-Compiler\n",(result?"kein":"ein"));

	  return 0;
	}
	

In der Praxis werden sich viele C-Programme auch mit einem C++-Compiler anstandslos übersetzen lassen.


Frage 17.11: Ich brauche einen Crossreference-Generator (Beautifier, Pretty Printer) für C.

Antwort:

	Ich brauche:                     Dafür sind folgende Programme
					 erhältlich (siehe auch 17.12):

	---------------------------------------------------------------

	Einen C Cross-Reference-         cflow, calls, cscope
	Generator

	Einen C Beautifier/              cb, indent
	Pretty-Printer


Frage 17.12: Wo sind all die erwähnten Public-Domain Programme erhältlich?

Antwort: Im Usenet erscheinen regelmässig in den Newsgruppen comp.sources.unix und comp.sources.misc Postings, die recht genau beschreiben, wie man an bestimmte Programmarchive gelangen kann. Normalerweise benutzt man ftp und/oder uucp, um die Archive von einem ftp-Server zu bekommen, wie z.b. uunet (ftp.uu.net) An dieser Stelle kann aber keine vollständige Liste der möglichen Server und wie man auf sie zugreift, aufgeführt werden.

Ajay Shah führt eine Liste mit kostenloser numerischer Software. Sie wird regelmäßig gepostet und ist mit dem FAQ zu comp.lang.c auf rtfm.mit.edu in /pub/usenet-by-group/comp.lang.c erhältlich. in der Newsgroup comp.archives werden darüberhinaus zahlreiche Ankündigungen über verschiedenerlei Programme und auf welchen Servern sie erhältlich sind veröffentlicht. Mit "archie" kann man herausfinden, auf welchem ftp Server welche Programmpakete archiviert werden; für weitere Informationen kann man eine e-mail mit dem Subject "help" an archie@th-darmstadt.de schicken. Grundsätzlich ist auch comp.sources.wanted ein guter Platz, um nach Programmen und Programmpaketen zu fragen. Aber auch hier gilt: Lieber erst die entsprechende FAQ lesen!


Frage 17.13: Wann findet der nächste "International Obfuscated C Contest" (IOCC) statt? Wie bekommt man eine Kopie früherer und gegenwärtiger Sieger?

Antwort: Es gibt inzwischen einen eigenen Webserver für den Wettbewerb. Unter http://www.ioccc.org/ findet man alle Informationen zu vergangenen, aktuellen und zukünftigen Wettbewerben.


Frage 17.14: Warum gibt es in C keine verschachtelten Kommentare? Wie kann ich Programmtext, der Kommentare enthält, auskommentieren? Darf ich Kommentare in Stringkonstanten einfügen?

Antwort: Verschachtelte Kommentare würden mehr schaden als nutzen, weil die Gefahr groß ist, versehentlich Kommentare nicht zu schließen, wenn die Zeichen "/*" in ihnen enthalten sind. Deshalb ist es normalerweise besser, größere Programmteile, die auch Kommentare enthalten, mit #ifdef oder #if 0 "auszukommentieren" (siehe hierzu auch 5.11).

Die Zeichenfolgen /* und */ sind innerhalb von Stringliteralen keine Sonderzeichen! Daher leiten sie auch keine Kommentare ein, da es ja möglich wäre, dass ein Programm diese Zeichenfolge ausgeben will (besonders, wenn es C Code erzeugt).

Referenz: ANSI Appendix E p. 198, Rationale Sec. 3.1.9 p. 33.


Frage 17.15: Wie kann man den ASCII Wert eines Zeichens und umgekehrt das passende Zeichen zu einem ASCII Wert rausfinden?

Antwort: In C werden Zeichen (char) generell durch Integer repräsentiert, deren Wert direkt dem Wert des Zeichens im Zeichensatz der jeweiligen Maschine entspricht. Man braucht deshalb keine Konvertierungsroutine, wenn man ein Zeichen hat, hat man seinen Wert!


Frage 17.16: Wie programmiert man Bit-Sets und/oder Bit-Arrays?

Antwort: Man benutzt char oder int Arrays, mit ein paar Makros um ein bestimmtes Bit an einer bestimmten Stelle zu manipulieren. (sollte <limits.h> nicht implementiert sein, kann man es mit CHAR_BIT 8 probieren)

	    #include <limits.h>             /* for CHAR_BIT */

	    #define BITMASK(bit) (1 << ((bit) % CHAR_BIT))
	    #define BITSLOT(bit) ((bit) / CHAR_BIT)
	    #define BITSET(ary, bit) ((ary)[BITSLOT(bit)] |= BITMASK(bit))
	    #define BITTEST(ary, bit) ((ary)[BITSLOT(bit)] & BITMASK(bit))


Frage 17.17: Wie kann man am effizientesten die Zahl der gesetzten Bits in einer Variablen ermitteln?

Antwort: Diese und viele andere Bit-Manipulationsprobleme können häufig durch Tabellen beschleunigt werden.


Frage 17.18: Wie kann man dieses Programm effizienter machen?

Antwort: Obwohl vielfach in de.comp.lang.c über Effizienz diskutiert wird, ist sie nicht annähernd so häufig ein entscheidender Faktor, wie viele denken. Der größte Teil eines Programm ist nicht zeitkritisch. Ist aber ein Programmteil nicht zeitkritisch, so ist es wesentlich wichtiger, dass dieser verständlich und portabel geschrieben wird, als ihn auf Effizienz zu optimieren (schliesslich sind Computer trotz allem ziemlich schnell, so dass auch ineffiziente Programmteile ohne Verzögerung laufen können).

Es ist sehr schwierig vorherzusagen, welche Teile eines Programms besonders laufzeitkritisch sind. Wenn das Laufzeitverhalten von entscheidender Bedeutung ist, sollte man einen Profiler benutzen, um diejenigen Programmteile zu finden, die besonderer Aufmerksamkeit in Bezug auf ihre Optimierung bedürfen. Häufig ist zu beobachten, dass E/A Operationen sich auf das Laufzeitverhalten stärker auswirken als die eigentlichen Berechnungen. Dies kann durch Cache Techniken beschleunigt werden.

Um den kleinen Anteil des wirklich zeitkritischen Codes in einem Programm zu optimieren, ist es besonders wichtig, dass man einen guten Algorithmus auswählt. Die "Mikrooptimierung" von Programmteilen ist weniger entscheidend. Die meisten Tricks zur Effizienz-Steigerung werden selbst von einfachen Compilern automatisch durchgeführt (wie z.B. Multiplikationen mit Zweierpotenzen durch Shiftoperationen zu ersetzen). Ungeschickte Optimierungen können ein Programm so umständlich machen, dass das Laufzeitverhalten darunter leidet.

Weitere Hinweise zu diesem Thema finden sich im Kapitel 7 von Kernighan, Plauger "The Elements of Programming Style" sowie in Jon Bentley "Writing Efficient Programs".


Frage 17.19: Sind Zeiger wirklich schneller als Arrays? Wie sehr verlangsamen Funktionsaufrufe die Programmausführung? Ist ++i schneller als i= i + 1?

Antwort: Die Antworten hierauf hängen natürlich von dem verwendeten Compiler und der Hardware ab. Die sicherste Antwort hierauf erhält man, indem man es einfach selbst austestet (meistens werden die Unterschiede so klein sein, dass man mehrere hunderttausend Iterationen braucht, um irgendeinen Unterschied zu sehen. Wenn möglich sollte man sich das Assemblerlisting anschauen, um zu sehen, ob die beiden Alternativen nicht den gleichen Assemblercode liefern).

Es ist "gewöhnlich" schneller, durch grosse Arrays mit Zeigern als mit dem Arrayindex zu "wandern". Allerdings ist dies bei einigen Prozessoren auch umgekehrt.

Funktionsaufrufe sind zwar unwesentlich langsamer als inline Code, sie tragen aber so stark zur Modularität und Lesbarkeit eines Programms bei, dass es selten einen vernünftgen Grund gibt, auf sie zu verzichten.

Bevor man Ausdrücke wie i = i + 1 umschreibt, sollte man bedenken, dass man mit einem C Compiler umgeht, nicht mit einem Taschenrechner! Jeder halbwegs gute Compiler wird identischen Code für ++i, i+=1 und i = i + 1 erzeugen! Es ist nur eine Frage des Stils (bzw. des Zusammenhangs) ob man i++, i+=1 oder i = i + 1 wählen sollte.


Frage 17.20: Warum stürzt dieser Code ab:

	char *p = "Hello, world!";
	p[0] = tolower(p[0]);

Antwort: String Konstanten sind nicht notwendigerweise modifizierbar, außer wenn sie zum Initialisieren von Arrays verwendet werden. Folgendes Beispiel sollte funktionieren:

	char a[] = "Hello, world!";

(Um ältere Programme zu kompilieren haben einige Compiler eine Option, um zu steuern, ob Strings beschreibbar sind oder nicht). Siehe auch 2.1, 2.2, 2.8 und 17.2

Referenz: ANSI Sec. 3.1.4 .


Frage 17.21: Mein Programm stürzt ab, bevor es überhaupt die erste Zeile ausführt (Wenn man es mit einem Debugger untersucht bricht das Programm vor der ersten Zeile von main ab).

Antwort: In dem Programm gibt es wahrscheinlich einen oder mehrere sehr grosse lokale Arrays. Der Stack hat auf vielen Systemen eine feste Größe. Systeme, die den Stack dynamisch anlegen können, können durcheinander kommen, wenn der Stack plötzlich um ein sehr grosses Stück wächst.

Oft ist es besser, große Arrays als static zu deklarieren (außer natürlich, wenn bei jedem neuen Aufruf ein neues Array benötigt wird).

(Siehe auch 9.4.)


Frage 17.22: Was bedeuten "Segmentation Violation" und "Bus Error"?

Antwort: Das Programm hat versucht, auf Speicher zuzugreifen, auf den es nicht so zugreifen durfte, oft im Zusammenhang mit falscher Benutzung von Zeigern, wobei diese meist nicht initialisiert oder falsch belegt werden (siehe 3.1 und 3.2), oder von malloc (siehe 17.23) oder von scanf (siehe 11.3).


Frage 17.23: Hat jemand ein Programmpaket um den Compiler zu überprüfen?

Antwort: Ein kommerzielles Produkt ist von Plum Hall erhältlich. Die GNU C Distribution der FSF (gcc) enthält c-torture-test.tar.Z, das eine Reihe von gängigen Compilerproblemen überprüft. Der paranoia Test von Kahan, erhältlich in netlib/paranoia auf netlib.att.com testet ausführlich die Gleitkomma Fähigkeiten der jeweiligen Implementation.


Frage 17.24: Wo bekommt man eine YACC Grammatik für C her?

Antwort: Die "definitive" Grammatik ist diejenige im ANSI Standard. Eine weitere Grammatik von Jim Roskind ist in pub/*grammar* auf ics.uci.edu erhältlich. Ein funktionierendes Beispiel der ANSI Grammatik (verfasst von Jeff Lee) ist im uunet (siehe 17.12) in usenet/net.sources/anis.c.grammar.Z (inklusive eines lexers) erhältlich. Der GNU C Compiler der FSF enthält ebenso eine Grammatik wie der Anhang von K&R II.

Referenz: ANSI Sec. A.2 .


Frage 17.25: Ich benötige Code um mathematische Ausdrücke zu parsen und auszuwerten.

Antwort: Zwei Pakete sind: "defunc", erhältlich auf sunsite.unc.edu in pub/packages/development/libraries/defunc-1.3.tar.Z; sowie "parse" auf lamont.ldgo.columbia.edu.


Frage 17.26: Wo bekommt man eine Routine für den "ungefähren" Vergleich von 2 Strings her, die ähnliche, aber nicht unbedingt exakt gleiche Strings erkennt.

Antwort: Gewöhnlich verwendet man hierfür den Soundex Algorithmus, welcher ähnlich klingenden Worten identische Zahlenwerte zuordnet. Dieser Algorithmus wird in Donald Knuth's "The Art of Computer Programming" im Band "Searching and Sorting" beschrieben.


Frage 17.27: Wie kann man den Wochentag aus einem gegebenen Datum herausfinden?

Antwort: Mittels mktime (Siehe 12.6 und 12.7) oder Zeller's congruence oder im FAQ zu sci.math, oder mit folgendem Programm:

	dayofweek(y, m, d)      /* 0 = Sonntag */
	int y, m, d;            /* 1 <= m <= 12,  y > 1752 oder so */
	{
	    static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
	    y -= m < 3;
	    return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7;
	}


Frage 17.28: Ist das Jahr 2000 ein Schaltjahr gewesen? Ist (year % 4 == 0) ein zuverlässiger Test für ein Schaltjahr?

Antwort: Ja und Nein (in dieser Reihenfolge). Für den Gregorianischen Kalender lautet der vollständige Test:

	year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)

In jedem guten astronomischen Führer gibt es zu diesem Thema weitere Informationen.

[ Inhalt ][ Index ][ ZurÜck ][ Weiter ]


[ FAQ Logo ]   © 1997-2004 Jochen Schoof (joscho@bigfoot.de)
Diese Version wurde am 14. März 2004 erzeugt. Sie wird zukünftig nicht weiter gepflegt.