Notizen zum Intensivkurs C
- Rg-Text?
- 64-Bit-Architektur: 64-Bit pro Takt parallel
Geschichte: 4, 8, 16 und 32 Bit
+ Mehr Daten gleichzeitig, mehr Speicher adressierbar
– Oft kein Vorteil spürbar, braucht mehr Speicher/Cache, 32-Bit-Software nutzt Fähigkeiten nicht
C int bleibt meist 32, long/Zeiger wird 64: „LP64“
Kombinationen von Reihungen, Zeiger und Strukturen
Reihungen
Reihungen von Reihungen
Reihungen von Reihungen wurde schon im Rahmen der Behandlung zweidimensionaler Reihungen behandelt.
Reihungen von Zeigern
Reihungen von Zeigern wurden schon am Beispiel von »argv« (dem zweiten Parameter von »main«) behandelt.
Reihungen von Strukturen
struct complex { double real; double image; } z[ 10 ];
Strukturen
Reihungen in Strukturen
struct complex { double x[ 2 ]; };
Zeiger in Strukturen
Zeiger in Strukturen wurden schon bei den Listen behandelt.
Strukturen in Strukturen
struct dlist { struct dlist * prev; struct dlist * next; };
struct nlist { struct dlist header; int number; };
struct clist { struct dlist header; struct complex z; };
...
struct nlist * this;
...
... ( struct dlist * )this ...
Zeiger
Zeiger auf Reihungen
Zeiger auf Reihungen wurde auch schon bei den zweidimensionalen Reihungen behandelt. Sie sind aber selten, da Reihungen meistens durch Zeiger auf deren erste Komponente repräsentiert werden.
Zeiger auf Zeiger
Zeiger auf Zeiger wurden ebenfalls schon in Zusammenhang mit »argv« diskutiert, das ja als »char ** argv« definiert werden kann.
Zeiger auf Strukturen
Zeiger auf Strukturen wurden schon öfter behandelt, etwa in Zusammenhang mit verketteten Listen.
Fortsetzung
- Noch offene Fragen?
- ctags *.h *.c -> tags -> vi -t tag
- { int i = INT_MAX; printf( "%d\n", i + 1); printf( "a\n" ); }
{ unsigned int i = UINT_MAX; printf( "%u\n", i + 1); printf( "a\n" ); }
6.5: undefined, 6.2.5: unsigned - Hüllfunktionen, beispielsweise für malloc und free
- Übungsaufgaben aus der Lektion zu Bäumen (http://de.wikipedia.org/wiki/Bin%C3%A4rer_Suchbaum, Suchen in sortiertem Binärbaum, Einfügen in sortierten Binärbaum, Löschen aus sortiertem Binärbaum, Einige Einträge einfügen und wieder entfernen, Speicherprüfung mit valgrind oder durch eigenen Hüllfunktionen, Interpretieren von Kommandos (+/-/?) aus einer Textdatei )
- unions
Ältere Notizen
- Zu Zeigern
- Nach »int x;«: welchen Datentyp hat der Ausdruck »&x« und was bedeutet er? Was kann man zu »&&x« sagen? Was zu »&5«?
- Enthält »int * x; *x = 0;« einen Fehler?
- Enthält »int * x; scanf( "%d", x );« einen Fehler?
- Nach »int * x;«: welchen Datentyp hat der Ausdruck »*x« und was bedeutet er?
- Enthält »int a = 2; int * b = &a; *b = 2;« einen Fehler?
- Enthält »int a = 2; *&a = 2;« einen Fehler?
- Enthält »int a = 2; &*a = 2;« einen Fehler?
- Wie wird nach »int a = 5; int * b = &a; int ** c = &b;« der Wert in »a« (also »5«) angegeben, wenn dazu nur »c« verwendet werden darf? Oder ist dies gar nicht möglich?
- Nach »char const * x = "test";«: Was ist der kürzeste Ausdruck um das erste Zeichen des Textes anzugeben? Was ist der kürzeste Ausdruck um das zweite Zeichen des Textes anzugeben?
- Nach »char const * x = "test";«: Welchen Wert hat »*++x«?
- Nach »int x[2];«: Welchen Wert hat »( char * )( x + 1 )-( char * )x«?
- Nach »int x[2];«: Ist die Zuweisung »( char * )x = 2;« erlaubt?
- Was könnte »*( char * )100« bedeuten?
- offene Fragen:
- Systemaufrufe aus welchem Bereich (z.B. Dateizugriffe?)
- Welche Gebiete von Listen/Bäumen werden neben den Grundlagen noch benötigt?
- Bisher nur teilweise behandelte Themen/Punkte:
- Vorgehensweisen bei der Erstellung größerer Programme, wie strukturierte Programmierung
- Bisher noch offene Themen/Punkte:
- Linux -Systemaufrufe
Behandlung von Laufzeitfehlern/Ressourcen in C
- Muster 0: Mangelhaft, aber manchmal ausreichend: keine Prüfungen, keine Befreiungen
struct a *a = malloc( sizeof *a );
a->val = 1;
Zu Muster 0: Wenn undefiniertes Verhalten in bestimmten Fällen bewußt in Kauf genommen werden soll, dann kann auf meistens nicht benötigten Aufwand verzichtet werden. Man sagt auch das Programm sei “quick and dirty ” programmiert oder „mit heißer Nadel gestrickt“. Es ist nicht perfekt, aber erfüllt oft schon seinen Zweck.
- Muster 1: Programmabbruch, manchmal akzeptabel in Anwendungsprogramm je nach Aufgabenstellung
struct a *a = malloc( sizeof *a );
if( !a )abort();
a->val = 1;
/* später eventuell free(), je nach Aufgabenstellung */
Zu Muster 1: Etwas besser als das Muster 0 ist das Muster 1, da es undefiniertes Verhalten vermeidet. Ob ein Programmabbruch aber immer akzeptabel ist, das kann nicht allgemein beantwortet werden, sondern muß an Hand der spezifischen Anforderungen eines Software-Projekts ermittelt werden.
- Muster 2: „richtige“ Behandlung, nötig in Programmbibliotheken, statischer Fall
struct a *a = malloc( sizeof *a );
if( a )
{ a->val = 1;
/* ... hier a verwenden ... */
free( a ); }
Zu Muster 2: Wenn ein Programmteil zu einer Bibliothek gehören soll, dann ist es normalerweise unpassend, wenn er – wie bei Muster 0 – undefiniertes Verhalten zeigen oder – wie bei Muster 1 – das Programm abbrechen würde.
Die Entscheidung über ein Programmende oder einen Programmabbruch sollte von der Anwendung (dem Hauptprogramm) getroffen werden und nie von einem Teil einer Bibliothek.
Im Englischen sagt man dazu auch manchmal, daß eine Bibliothek “policy neutral ” sein müsse.
Daher ist es dann richtig, den Aufrufer über das Scheitern einer Anforderung zu informieren und diesen entscheiden zu lassen, wie darauf reagiert wird.
- Muster 3: „richtige“ Behandlung, nötig in Programmbibliotheken, dynamischer Fall
struct a *a = malloc( sizeof *a );
if( a )
{ a->val = 1;
/* ... hier a verwenden ... */ } ... if( a )free( a )
Zu Muster 3: Im statischen Fall wird eine Ressource zurückgegeben, wenn eine Anweisung (»free( a );« im Muster 2) ausgeführt wird, die nur nach dem Erfolg der entsprechenden Anforderung ausgeführt wird (»if( a )« im Muster 2). Beim Aufbau komplizierterer Datenstrukturen reicht diese Vorgehensweise nicht mehr aus. Dann muß die Information über den Erfolg einer früheren Anforderung den Datenstrukturen selber entnommen werden, wie dies mit dem – in Vergleich zum Muster 2 zusätzlichem – »if( a )« im Muster 3 geschieht.
- Verstecken hinter API: „richtige“ Behandlung hinter API versteckt
struct a *new_a()
{ struct a *a = malloc( sizeof *a );
if( a )a->val = 1;
return a; } void dispose_a( struct a *const a )
{ free( a ); } ...
Verstecken hinter API: Wenn Programmteile, die Ressourcen anfordern, hinter einer API versteckt werden, sind meist zwei Funktionen nötig: Eine zum Anlegen einer Einheit und eine weitere zum Freigeben. Diese beiden Funktionen bilden selber wieder eine neue Ressource: Die erste muß dem Aufrufer zurückmelden, ob sie erfolgreich war, und dieser muß in diesem später die zweite Aufrufe, um diese Ressource wieder freizugeben.
- Verschachtelte Anforderungen in Bibliothek: „richtige“ Behandlung mit einer „Transaktion“
struct a *new_a()
{ int error = 1;
struct a *a = malloc( sizeof *a );
if( a )
{ struct b *b = malloc( sizeof *b );
if( b )
{ a->b = b; error = 0;
/* ... eventuell weitere Ressourcen ... */
if( error )free( b ); }
if( error )free( a ); }
return a; } void dispose_a( struct a *const a )
{ /* eventuell weitere zur Freigabe nötige Aktionen */
free( a->b ); free( a ); }
Verschachtelte Anforderungen: Wenn mehrere Ressourcen benötigt werden, so ist besondere Sorgfalt nötig. Die erstellende Funktion wird ja entweder erfolgreich gewesen sein oder gescheitert sein. Es kann aber sein, daß sie bei mehreren nötigen Anforderungen einige schon erhalten hat, wenn dann eine Anforderung schließlich scheitert. In diesem Fall müssen genau die bisher erfolgreich angeforderten Ressourcen wieder zurückgegeben werden (oben: »if( error )free( a );«), dann der Aufrufer erwartet nach einem Scheitern, daß keine Ressourcen von ihm mehr freizugeben sind.
Das obige Programmbeispiel nimmt an, daß eine neue Struktur »a« selber noch einen Zeiger auf eine neue Struktur »b« benötigt. Es sind dann drei Fälle richtig zu behandeln:
- Schon das erste »malloc« ergibt 0.
- Erst das zweite »malloc« ergibt 0.
- Keines der beiden »malloc« ergibt 0. (Vollständiger Erfolg.)
Die Verwendung einer Hilfsvariablen hilft hierbei die Übersicht zu wahren: Diese (oben: »error«) wird erst auf 0 gesetzt, wenn alle Anforderungen erfolgreich waren. Ist sie am Ende eines Blocks nicht 0, so wird die Ressource wieder freigegeben, deren erfolgreiche Anforderung zuvor zur Ausführung dieses Blocks führte.
Diese Vorgehensweise ist jedoch nur in Bibliotheken nötig. In einem Hauptprogramm (einer Anwendung) kann der Rest des Programms, der die Ressourcen benötigt, dann direkt aufgerufen werden.
- Verschachtelte Anforderungen in Hauptprogramm:
int main( void )
{ int result = EXIT_FAILURE;
struct a *a = malloc( sizeof *a );
if( a )
{ struct b *b = malloc( sizeof *b );
if( b )
{ a->b = b; result = use( a, b ); /* Rest des Programms */
free( b ); }
free( a ); }
return result; }
Besitz (“ownership ”) Manchmal nennt man einen Programmteil, der für die Freigabe einer Ressource verantwortlich ist, den „Besitzer “ dieser Ressource. Besitz in diesem Sinne ist also Verpflichtung, eine Last. Wenn ein Ressource eine Referenz auf eine andere Ressource enthält, dann nennt man die erste Ressource den „Besitzer “ der zweiten Ressource, falls bei einer Freigabe der ersten Ressource auch die zweite freigegeben werden soll. (In anderen Zusammenhängen kann „Besitz“ auch etwas andere Bedeutungen haben, wie etwas den Besitz an Schreib- oder Leserechten für eine Ressource.)
Besitzerwechsel (“transfer of ownership ”) Wenn eine Referenz auf eine Ressource an einen anderen Programmteil übergeben wird oder in eine andere Ressource geschrieben wird, dann kann damit auch ein Besitzerwechsel vereinbart werden, demzufolge nun diese Ressource oder der andere Programmteil der Besitzer der Ressource wird. Wenn »malloc« einen Zeiger auf eine Objekt zurückgibt, dann übergibt es damit den Besitz an diesem Objekt beispielsweise an den Aufrufer.
Objektressourcen Bei Objekten (also Speicher) verkompliziert sich die Handhabung noch dadurch, daß es auch Objekte gibt, die nicht freizugeben ist, nämlich Objekte mit statischer oder automatischer Lebensdauer. Wenn also vereinbart wird, daß eine bestimmte Funktion den Besitz an einem Objekt erhält, wenn ihr ein Zeiger übergeben wird, dann darf dies im allgemeinen kein Zeiger auf ein Objekt mit statischer oder automatischer Lebensdauer sein (oder solche Objekte müssen gekennzeichnet werden).
Alleinbesitzer Im einfachsten Fall hat jede Ressource genau einen Besitzer. Andere Programmteile oder Objekte könnten auch eine Referenz auf diese Ressource verwenden oder enthalten, aber nur ein Programmteil ist für die Freigabe zuständig. Dies sollte wird natürlich so eingerichtet werden, daß die Freigabe durch den Besitzer erst dann erfolgt, wenn die Ressource auch von keinem anderen Programmteil mehr benötigt wird.
Dokumentation In der Dokumentation von Programmteilen oder Datenstrukturen sollte immer klargestellt werden, wer der Besitzer einer Ressource ist und wann Besitzerwechsel (etwa bei der Übergabe von Argumenten oder Rückgaben von Ergebnissen) stattfinden.
Übergabe an Aufrufer Bei der Übergabe des Besitzes an den Aufrufer gibt eine Funktion eine Referenz auf eine von ihr erzeugte Ressource und den Besitz an dieser Ressource an den Aufrufer zurück. Beispiel: »malloc( 10 )«, wenn es nicht 0 ist. Dies ist damit verträglich, daß die Kontrolle nach der Ausführung des Aufrufs im allgemeinen ja nicht mehr an die aufgerufene Funktion zurückkehrt, so daß diese auch gar nicht in der Lage wäre, die Ressource später wieder freizugeben.
Verbleib beim Aufrufer Bei Aufruferbesitz übergibt ein Aufrufer eine (Referenz auf )eine von ihm besessene Ressource an einen aufgerufenen Programmteil, aber bleibt weiterhin im Besitz dieser Ressource. Beispiel: »printf( "%p", pointer );«, wenn »pointer« auf eine Ressource zeigt. Dies ist damit verträglich, daß die Kontrolle nach der Ausführung des Aufrufs ja an den Aufrufer zurückgeht, so daß er dann auch die Möglichkeit hat, die Ressource freizugeben.
Übergabe an aufgerufene Funktion Der Besitz an einer Ressource kann aber auch an eine aufgerufene Funktion übergeben werden. Beispiel: »free( pointer );«, wenn »pointer« auf ein Objekt mit allozierter Lebensdauer zeigt. Dies ist für den Aufrufer eine Möglichkeit, den Besitz dauerhaft aufzugeben, sich also von seinem Besitz wieder zu entlasten.
Mehrere Besitzer, geteilte Ressourcen In machen Fällen kann es erwünscht sein, daß eine Ressource mehrere Besitzer hat (etwa, wenn zwei Bäume einen gemeinsamen Unterbaum teilen oder zwei Programmteile eine Datenbankverbindung nutzen). Dann muß sie erst dann freigegeben werden, wenn keiner dieser Besitzer ihrer nicht mehr bedarf. Hierzu kann ein „Referenzzähler“ in der Ressource verwendet werden (“reference counting ”), der bei jedem Erlangen eines Besitzes an dieser Ressource inkrementiert wird und dekrementiert wird, wenn ein bisheriger Besitzer den Besitz aufgeben will. Die Ressource wird genau dann freigegeben, wenn dieser Zähler den Wert 0 erreicht.
Automatische Ressourcenverwaltung Die Verwendung von Referenzzählung ist fehlerträchtig und oft nicht mehr möglich, wenn auch zirkuläre Referenzbezüge möglich sind, weil dann zwei Ressourcen, die sonst gar nicht mehr benötigt werden, es durch gegenseitige Verweise verhindern, daß ihre Referenzzähler jemals 0 werden. Eine automatische Ressourcenverwaltung ermitteln zu bestimmten Zeitpunkten, welche Ressourcen von einem Programm überhaupt noch erreichbar sind und gibt alle andere frei. Ein Beispiel für solche eine automatische Ressourcenverwaltung ist eine automatische Speicherverwaltung (“memory manager ”, “garbage collector ”).
Refaktor „Extraktion einer ‚inneren Funktion‘ “ (“Extract Method ”)
- Bei der strukturierten Ressourcenbehandlung sind die inhaltlich nahe zusammengehörigen Teile des Quellcodes zur Ressourcenverwaltung manchmal weit voneinander entfernt, wie in dem folgenden Programmbeispiel die Anforderung mit »malloc« und die Freigabe mit »free«. Dies ist besonders bei größeren Methoden und der Verschachtelung solcher Anweisungsstrukturen unübersichtlich und wird manchmal als Argument gegen die strukturierte Programmierung und zugunsten des Verlassens von Blöcken mit Sprunganweisungen vorgebracht.
- Monolithische strukturierte Ressourcenbehandlung
#include <stdio.h> /* printf */
#include <stdlib.h> /* EXIT_SUCCESS */ struct account{ const char * name; double balance; };
int main( void )
{ /* Verwaltung */
int result = EXIT_FAILURE;
struct account * const p = malloc( sizeof *p );
if( p )
{ /* Verwendung */
p->name = "Hans"; p->balance = 3.14;
printf( "%.32s %f\n", p->name, p->balance );
p->balance = 0;
printf( "%.32s %f\n", p->name, p->balance ); /* Verwaltung (Fortsetzung) */
free( p );
result = EXIT_SUCCESS; }
return result; }- Doch tatsächlich ist das Problem bei dieser Vorgehensweise gar nicht die strukturierte Programmierung, sondern die monolithische Programmierung. Die monolithische Funktion kann durch Herauslösen einer „inneren Funktion“ in zwei Funktionen zerlegt werden, von denen die eine sich nur mit der Verwaltung einer Ressource beschäftigt und die andere diese Ressource nur verwendet, ohne sie zu verwalten.
- Modulare strukturierte Ressourcenbehandlung
#include <stdio.h> /* printf */
#include <stdlib.h> /* EXIT_SUCCESS */ struct account{ const char * name; double balance; }; int use( struct account * const p )
{ p->name = "Hans"; p->balance = 3.14;
printf( "%.32s %f\n", p->name, p->balance );
p->balance = 0;
printf( "%.32s %f\n", p->name, p->balance );
return EXIT_SUCCESS; } int main( void )
{ int result = EXIT_FAILURE;
struct account * const p = malloc( sizeof *p );
if( p )
{ result = use( p );
free( p ); }
return result; }- Man sieht in dem voranstehenden Programm die Gleichheit der Deklaration des Parameters »p« mit dem Anfang der Deklaration der Variablen »p«. Um den Parameter richtig zu deklarieren, kann also einfach ein Teil der Variablendeklaration kopiert werden.
- Der Erfolgsstatus eines Programms kann vom Erfolgsstatus der „inneren Funktion“ abhängen, so daß dieser in der Methode »main« als Ergebnis festgelegt wird, obwohl er bei diesem Programm derzeit stets »EXIT_SUCCESS« ist.
Der extrahierte Teil wird hier „innere Funktion“ genannte, weil er hier aus dem Inneren einer Anweisungsstruktur stammt, in der es sowohl vor ihm als auch nach ihm noch etwas steht.
Notizen zum Lernen
Es folgen zwei Quellenangaben als Nachtrag zu entsprechenden früheren mündlichen Äußerungen.
Zur Lerngeschwindigkeit (Langzeitgedächtnis)
Zur Frage wieviele Bit pro Sekunde das Langzeitgedächtnis aufnehmen kann, findet man unterschiedliche Angaben. Beispielsweise
„Die Speicherung erfolgt sehr langsam mit 0,05 Bit/s “
http://www.medienwissenschaft.hu-berlin.de/vlz/Ged%E4chtnisNetz.pdf
Wenn diese eine gehirnphysiologische Gesetzmäßigkeit sein sollte, dann kann ein Kurs also nicht beliebig „intensiviert“ werden, um einen noch höheren Informationsfluß zu erreichen.
Zu Übungsaufgaben, die nicht richtig gelöst werden
Zum Lernen durch Fehlversuche:
“getting the wrong answer helps us remember the right one ”
http://scienceblogs.com/cortex/2009/10/learning_from_mistakes.php
Weitere, ältere Notizen
Funktionszeiger
- enumerate
Aufbau von Software-Projekten
- Strukturierte Programmierung
- Iterative Entwicklungszyklen
- Refaktorierung mit automatisierten Tests, Unit-Tests
- Leitsätze der Softwareentwicklung (getrennte Lektion)
- MVC
Zielsetzung der Lehrveranstaltung
In den obigen Notizen schon berücksichtigt
- Funktionen (Routinen)
- Makros
- Header-Dateien
- Umgang mit mehreren Quelldateien
- Reihungen (Arrays)
- Arithmetik von Zeigern (pointer arithmetic)
- dynamische Speicherverwaltung
- File I/O
- system calls
- strukturierte Datentypen
- Typdeklarationen
- Listen
- Bäume
In den obigen Notizen erst teilweise berücksichtigt
- Strukturierte Programmierung in größeren Paketen
- Das Testen von CPU-Zeit und RAM der geschriebenen Programme
In den obigen Notizen noch nicht berücksichtigt
- Einfluß von 32 sowie 64 bit Architektur auf Datentypen und Ressourcenverbrauch.
Wünsche zur Lehrveranstaltung
Nach den ersten beiden Terminen
- Einen Tick schneller
- Übungsaufgaben für die Zeit zwischen den Terminen
Motivation zu C
Obwohl die C -Programmierung manchmal schwierig und fehlerträchtig ist, ist es doch schwierig, eine bessere Alternative zu C zu finden.
“Here's the thing: C is everywhere. Recently Tim Bray made basically the same point; all the major operating systems, all the high-level language runtimes, all the databases, and all major productivity applications are written in C.”
http://girtby.net/archives/2008/08/23/in-defence-of-c/
Nach einer Statistik wird C heute deutlich häufiger eingesetzt als C++ , dabei hat C einen positiven und C++ einen negativen Trend. C könnte sogar Java bald (wieder) als die populärste Sprache ablösen.
http://www.tiobe.com/content/paperinfo/tpci/index.html
Auch für Perl 6 (Parrot) spielt C eine wichtige Rolle:
=head2 What language is Parrot written in?
C.
=head2 For the love of God, man, why?!?!?!?
Because it's the best we've got.
http://search.cpan.org/src/SFINK/parrot-0.0.11.2/docs/faq.pod
Web-Quellen zu C
- http://cprog.tomsweb.net/cintro.html
- http://www.vmunix.com/~gabor/c/draft.html
- The C201X draft
http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1425.pdf
http://www.open-std.org/JTC1/SC22/wg14/www/docs/n1336.pdf
Vorläufige Notizen zu weiteren Themen (Fragment)
Zeiger
- Funktionszeiger, Integration einer Funktion double f( double ) und strcall (Aufruf von Funktionen über Namen) mit strchr
Listen
- Listen zum terminierten Einlesen der Parteistimmen ohne vorherige Angabe der Anzahl der Parteien, Übung: Umkehren einer Liste
Bäume
- Rekursives Durchlaufen eines Verzeichnisbaums: Kopie im Speicher anlegen
Graphen
- Graphen zur Darstellung von Aussagemengen (Tripelmengen), Heraussuchen aller Aussagen über ein Subjekt, eine Relation oder ein Objekt.
Weitere Aspekte der Programmiersprache C
- vararg-Funktionen, allozierende printf-Variante
Weitere Aspekte des Programmierens
- Empfehlung zur Architektur und für Entwicklungsvorhaben
- allgemeine Heuristiken der Software-Entwicklung
- Anwendungsbeispiele: Simulation
Weitere Aspekte der Linux-Programmierung
- Systemprogrammierung:
— Rekursives Durchlaufen von Ordnern
— Definition und Ermittlung der Länge von Dateien
— Programme mit graphischer Benutzeroberfläche
Einige Quellen
C-FAQs
http://www.faqs.org/faqs/C-faq/abridged/
http://www.lysator.liu.se/c/c-faq/c-10.html
Auf den ersten Blick ganz gute Quellen:
http://www.mers.byu.edu/docs/standardC/index.html#Table%20of%20Contents
http://publications.gbdirect.co.uk/c_book/
Einige Werkzeuge
- Das klassische Programm zur automatischen Formatierung von C -Quellcode (“beautifier ”) heißt »indent«. Wo es installiert ist, kann unter »vim« der C -Quelltext beispielsweise mit »%!indent -st« formatiert werden.
- Beim Entwickeln und Testen von C -Programmen kann sonst auch noch »lint« (beispielsweise http://www.splint.org/, http://www.gimpel.com/) und »valgrind« (http://valgrind.org/) hilfreich sein.
- Viele Programmierer schwören auf Versionskontrollsysteme und halten eine Entwicklung ohne diese für fahrlässig: Etabliert war lange Zeit CVS, neuerdings beliebt sind subversion und git. (http://www.componentsoftware.com/csrcs/ soll weniger leistungsfähig, aber leichter zu erlernen sein).
- Zum Speichern oder Übermitteln von nicht-vertraulichem Quelltext: pastebin.
- cxref
- cflow
- ctags erzeugt tag -Dateien für Texteditor-Zugriffe
- gprof
- cstyle
- cdecl liest C Deklaration vor (»http://linux.about.com/cs/linux101/g/cdecl.htm«)
- cweb (cweave /ctangle ) literate programming mit C