Buchrezension: 21st Century C. C Tips from the New School

Veröffentlicht von am 25. Februar 2015 in Buchrezensionen

Als ich in der Schule im HTML Unterricht nicht zu bremsen war und mit den Aufgaben in weniger als der Hälfte des Schuljahres fertig war, sagte mein Lehrer, ich solle doch Java lernen, wenn ich denn plane, den Informatik-Leistungskurs zu belegen. So kam ich zu meiner ersten Programmiersprache und mit den Eigenheiten, die ich schätzen und lieben gelernt habe, kamen dann natürlich auch die Probleme, die mit dem Lernen neuer Sprachen kommen, welche diese nicht haben. Ein besonderes Sprachmonstrum ist für mich C/C++. Memory-Management, Pointerarithmetik, das Fehlen einheitlicher Konventionen/Standards (bezogen auf C++), Fehlen essentieller Datentypen (Strings) oder unbequemes Handling derselben (Strings, Arrays), inklusive Standardfunktionen, die bekannte Sicherheitsrisiken darstellen (z.B. strcpy) und trotzdem in allen möglichen Büchern oft als die einzige Methode beschrieben werden. All das hat mir regelmäßig Kopfschmerzen bereitet; insofern würde ich nicht von mir behaupten, C zu können, obwohl ich schon einiges in C gemacht habe (inklusive Bachelorarbeit). Das führt bei mir manchmal immernoch zu Laufzeitfehlern, die ich mit keiner anderen Programmiersprache kenne; etwa Speicherlecks während der Laufzeit oder gar nur zufällig auftretende Abstürze, die nicht der Programmlogik geschuldet sind. Inkompatibilität zwischen den einzelnen C-Standards runden das Monster ab.

Daher steht auch schon seit längerem auf meiner ToDo-Liste, dass ich in C fitter werden möchte – und da kommt dieser Titel doch wie gerufen, oder nicht?

Wie gehabt werde ich zunächst die Rahmendaten darstellen, dann ein wenig detaillierter auf den Inhalt eingehen, damit sich jeder ein Bild davon machen kann, was genau er von diesem Buch erwarten kann, und lege abschließend meine Meinung zum Buch dar, bei welcher es hilfreich ist, auch den Erfahrungshintergrund des Rezensenten zu kennen, den ich in der Einleitung kurz angerissen habe. 😉

Rahmendaten

lrg
Wie auch bei meinen letzten Rezensionen handelt es sich bei diesem Titel um ein Buch, welches der O’Reilly Verlag uns netterweise für den Hackerspace zur Verfügung gestellt hat. Geschrieben ist es von Ben Klemens, der seine Brötchen vor allem durch das Schreiben von statistischen Analysetools für komplexe Berechnungsmodelle verdient; sein Kundenkreis reicht von Wirtschaftskunden über den medizinischen Bereich bis zum US Staat. Sein Buch ist 340 Seiten stark, zuzüglich Appendix und Glossar und liegt in der 2. Auflage vor, die einige Ergänzungen und Erweiterungen auf Grundlage des Feedbacks zur ersten Auflage aufweist.

Grob ist es in zwei Teile gegliedert, The Environment und The Language, wobei der erste Teil 5 Kapitel (120 Seiten) umfasst und der zweite Teil mit 8 Kapiteln (auf 220 Seiten) überwiegt.

Inhalt

Ungewöhnlich für eine Buchrezension möchte ich mit dem Epilog anfangen; der letzten Textseite 341. Hier nämlich erklärt Ben Klemens seine „Secret Agenda“:

As a C User, I want more people writing good libraries that I can use.

Und sein Versprechen:

If you’ve read this far, you know how to write modern code based on other libraries, how to write a suite of functions around a few simple objects, how to make the interface user-friendly, how to use a Git repository so that others can contribute, and how to package it for use by the general public using Autotools.

Das also ist die Vorstellung des Autors, zu was dieses Buch einen befähigen soll.

Appendix

Zunächst habe ich in das Appendix geschaut, denn hier liefert der Autor auf 20 Seiten eine Kompaktzusammenfassung aller C Sprachfeatures, die auch Menschen, welche noch nie mit C gearbeitet haben (aber grundlegende Programmierkonzepte verstehen), mit C zurecht kommen lassen sollten. Auf das Wesentliche beschränkt und dennoch recht umfassend, aber einfach genug, um den Buch folgen zu können; vorausgesetzt man hat schon mal in einer anderen (prozeduralen) Sprache programmiert.

Preface

Auch keine Lesezeitverschwendung, denn es wird für nützliche und interessante Hintergrundinformationen genutzt, etwa was der POSIX Standard ist, oder welche Kompiler welchen C-Standard unterstützen – wusstet ihr etwa, das Microsoft Visual Studio sich etwa weigert den ISO C99 Standard zu implementieren und daher größtenteils immernoch auf den mittlerweile 25 Jahre alten ANSI C89 Standard beruht? Und das zu Zeiten, wo von allen wichtigen Compilern sogar schon der ISO C11 vollständig unterstüzt wird. Für mich ein Augenöffner und weiterer Grund nie mehr zu diesem Betriebssystem zurückzukehren – aber keine Angst – Klemens betreibt hier kein OS-Bashing sondern zeigt wie man trotzdem eine moderne Entwicklungsumgebug auf Windows installiert bekommt und sogar wie man auch auf Windows POSIX Konformität erlangen kann. Die Einleitung ist daher ebenfalls sehr nützlich und wichtiger Bestandteil des Buches.

Kapitel 1: Set Yourself Up for Easy Compilation

Schon im Klappentext rechnet das Buch mit antiquierten Entwicklungsumgebungen ab und gleich im ersten Kapitel zeigt Klemens, wie man eine Umgebung einrichtet, in welcher man besser entwickeln kann. Dabei versteift sich Klemens aber nicht auf konkrete Software- oder Betriebssysteme, sondern stellt wichtige Tools vor und vergleicht diese, zeigt wie man Bibliotheken nachinstalliert und diese einfach und schnell wiederfindet und zum Kompilieren einbindet. Die Devise ist „so einfach und unkompliziert wie möglich“. Dabei beschränkt sich Klemens bewusst auf die Konsole und Tools wie gcc, icc oder llvm, da diese auch bei vielen grafischen IDEs als Backend-Tool eingesetzt werden.

Wo immer es Unterschiede zwischen dem GNU Compiler zu CLang oder dem Intel Compiler gibt, werden diese hervorgehoben, sodass die Beispiele im Buch auf unterschiedlichen Systemen und Präferenzen funktionieren.

Auch auf wichtige Compiler-Flags geht Klemens im ersten Kapitel neben den Standard-Tools ein und zeigt zur Kür kleine Skripte, die POSIX-Standards ausnutzen, um Code-Snippets direkt in die Shell einpassen und ausführen lassen zu können, ohne erst große Speicher- und Kompiliervorgänge manuell anstoßen zu müssen.

Kapitel 2: Debug, Test, Document

Diese drei Themen sind nach Klemens‘ Meinung eigentlich keine Tipps, denn er schreibt „who possibly woudln’t use a debugger/write tests/document?“. Die Erfahrung und das Feedback auf die erste Auflage haben ihn dieses Kapitel aber ausbauen lassen. Daher zeigt er zunächst, wie man mit gdb und lldb schritweise seinen Code durchlaufen, Breakpoints setzen, durch die Stackframes navigieren und den Ablauf durch direktes Manipulieren von Variableninhalten beeinflussen kann. Dies ist das „Logic checking“. Für das technische Überprüfen des Codes reißt er dann auch die Bedienung von Valgrind an, hält sich hier allerdings kurz.

Danach kommt er mit dem Thema „Test Harness“ zum Testen des Codes über die von der Bibliothek GLib bereit gestellten Funktionen.

Einen nicht gerade kurzen Teil verwendet Klemens dann darauf, über den Sinn von Error-Handling im Code zu philosophieren. Seiner Meinung nach wird hier in der Literatur und Praxis zu viel Zeit und Energie für etwas investiert, das wenig bewirkt – konkret wird er hier allerdings nicht.

Der letzte Teil dieses Kapitels wird noch mal in zwei Bereiche aufgeteilt – zum einen zur Dokumentation von Interfaces mithilfe von Doxygen, zum anderen zum Dokumentieren mit CWEB, einer Sprache, welche Dokumentation und C-Code in einem eigenen Dateiformat abspeichert, aus denen dann je nach Bedarf mit entsprechenden Tools der reine C-Code (ohne Kommentare) oder aber eine Dokumentation generiert werden kann.

Nur am Rande Erwähnung finden Techniken wie das Profiling zum Finden von Bottle-Necks oder das Coverage Testing.

Kapitel 3: Packaging Your Project

Auch wenn das Kapitel 3 dem Titel nach nur ein Thema hat, untergliedert Klemens es wieder in eine weitreichende Abhandlung. Zunächst nämlich gibt Klemens hier eine ausführliche Beschreibung der Fähigkeiten der Shell wieder, und zeigt, wie man Variablen setzt und ausliest, Befehle mittels Pipes und Backticks miteinander verknüpft, wie Schleifen und Arithmetik auf der Shell funktionieren, und wie man mit fc durch die History navigiert. Randbemerkungen bekommen die Tools TMUX, Screen und die ZSH als Alternative zur POSIX-Standardshell „Bash“.

Der nächst logische Schritt nach dem Automatisieren von Arbeitsschritten mithilfe der Shell ist für Klemens das Organisieren mithilfe von Makefiles. Auf den make-Befehl ist Klemens dabei schon im Kapitel 1 eingegangen – hier wird es nun sehr ausführlich; insbesondere auf den Aufbau der Makefiles und die Makevariablen wird eingegangen.

Abgeschlossen wird das Kapitel dann mit den Autotools, welches Klemens an einem aufwendigen Beispiel erläutert. Autotools – umgangssprachlich auch als „Linux-Dreisatz“: Configure, Make, Make Install – bekannt, sind für Makefiles das, was die Makefiles für die Shell-Funktionen sind – eine Methode zur automatischen Generierung.

Kapitel 4: Version Control

In einem Buch über moderne Programmierung darf natürlich dezentrale Versionskontrolle nicht fehlen! Und so nimmt sich auch Klemens dieses Themas an – allerdings erneut auf seine ganz besondere Art und Weise, indem er die prinzipielle Funktionsweise zunächst über manuelles Bedienen typischer POSIX-Tools darlegt, und Git als Automatisierung von Shellbefehlen wie diff und patch vorstellt.

Kapitel 5: Playing Nice with Others

Mit dem fünften Kapitel schließt Klemens den ersten Teil des Buches über die C Entwicklungsumgebung ab. „Others“ sind dabei andere Programmiersprachen, und am Beispiel der Sprache Python zeigt Klemens, wie in C ein Interface bereit gestellt werden kann, damit man auch von anderen Sprachen auf die in C bereitgestellten Funktionalitäten zugreifen kann. Auch dieses Beispiel wird bis hin zur Distribution per Autotools und dazugehöriger Konfiguration beschrieben.

Kapitel 6: Your Pal the Pointer

Auch wenn der Titel des Kapitels vermuten lässt, dass Klemens uns hier den Pointer als Datenstruktur der Wahl verkaufen möchte, ist das Gegenteil der Fall. Zwar wird auf die manuelle Speicherreservierung und die Pointer-Arithmetik eingegangen, Klemens warnt aber auch vor den Gefahren, und geht dabei soweit, eine Liste auszuarbeiten, die nur 3 Fälle (plus 2 absolute Raritäten) enthält, in denen es Klemens‘ Erfahrung nach keinen Weg an manueller Speicherreservierung (und damit Pointern) vorbei gibt. Für alle anderen Fälle rät er von der Pointerarithmetik und manuellen Speicherverwaltung ab (und geht damit übrigens auch mit vielen Industrie-Standards konform, wie etwa der Industriestandard Misra-C, welche auch vorschreiben auf malloc und Konsorten gänzlich zu verzichten).

Kapitel 7: Inessential C Syntax that Textbooks Spend a Lot of Time Covering

Hier rechnet Klemens dann mit anderen C-Büchern ab, und plädiert dafür, den Code so schlank wie möglich zu halten. Viele Sprachkonstrukte, die C bietet und die einem Bücher vorschreiben, sind aus Kompatibilitätsgründen zu alten Standards vorhanden – beispielsweise Di- und Trigraphen, welche er als plakatives Beispiel nennt (Digraphen wurden eingeführt, da einige Tastaturen in den 90gern noch keine geschweiften Klammern hatten – so konnte man diese durch die Digraphen <% und %> ersetzen). Von ähnlicher Qualität sind laut Klemens alle anderen Beispiele, die er aufführt, etwa das explizite return 0 in main(), vorangestellte Variabeldeklaration, feste Arrayweiten zur Kompilierzeit, Casting, Labels, Gotos, Switches und Dreaks, der Datentyp float, etc.

Auch wenn seine Thesen manchmal sehr gewagt klingen, so versichert Klemens, dass es sich bei diesen Techniken weder um welche handelt, die längere Laufzeiten noch größere Kompilate bedingen; allerdings sind sie veraltet und für moderne Computer nicht mehr angemessen. Damit wirbt Klemens indirekt auch ganz stark für die neuen Standards und das Loslassen von Kompatibilität zum C89-Standard.

Kapitel 8: Important C Syntax that Textbooks Often Do Not Cover

Das Gegenstück zum letzten Kapitel – hier geht Klemens auf seiner Meinung nach moderne und wichtige Neuerungen in der C-Sprache ein. Dies beschränkt sich jedoch vor allem auf den Preprozessor, der nach Klemens nur kurze Erwähnung in anderen Büchern findet – wenn überhaupt. Daher geht Klemens detaillierter auf das Erzeugen von Macros ein, und zeigt wie der Preprozessor mit diesen umgeht und was man mit diesen alles machen kann. Aber auch hier bleibt Klemens am Zahn der Zeit und seiner Devise „so einfach wie möglich“ treu. So rechnet er auch mit typischen Standards, wie etwa dem allseits beliebten „Header Guard“ zur Vermeidung von Doppelinklusionen in großen Softwareprojekten ab, und stellt der komplexen #ifndef#define#endif-Sequenz, das viel einfachere #pragma once entgegen, das zwar kein C-Standard ist, aber von allen wichtigen Compilern (gcc, clang, icc, bis hin zu Visual Studios C89-Compiler) verstanden wird.

Eher kurz fällt dann die Abhandlung zum Linkage ab, bei dem static und extern näher erläutert werden sollen, nachdem dies in anderen C-Büchern auch eher in Nebensätzen nur kurz erwähnt wird. Abgeschlossen wird das Kapitel mit den Besonderheiten des Keywords const.

Kapitel 9: Easier Text Handling

Das Problem, das C mit Strings hat, erklärt Klemens in einem Satz: Strings sind Arrays von Chars undefinierbarer Länge, und automatisch allozierte Arrays können nicht variabel lang sein. Die Lösung – frei von manueller Speicherallozierung – präsentiert Klemens mit asprintf und zeigt an einigen Beispielen, wie man unterschiedliche typische Aufgaben mit Strings mithilfe von asprintf erledigt.

Der zweite Teil des Kapitels widmet sich dann Unicode, denn, wie Klemens es so schön schreibt:

C was designed in the 1970s, before the invention of non-English languages.

Nach einer kurzen Beschreibung von Unicode zeigt Klemens, wie man mithilfe von Funktionen aus der GLib damit umgeht, wenn eine Programmeingabe plötzlich als Unicode in unser Programm kommt.

Kapitel 10: Better Structures

Das längste Kapitel dieses Buchs widmet Klemens den Neuerungen in C99: compound literals, variable-length macros und designated initializers. Klemens belässt es dabei nicht ausschließlich beim Aufzeigen dieser Mechanismen, sondern er untersucht in diesem Kapitel zu zeigen, wie sie, für sich genommen und in Kombination, genutzt werden können, um Funktionen zu schreiben, die flexible und strukturierte Ein- und Ausgaben erzeugen und damit eine verbesserte Schnittstelle zu Bibliotheken bereitstellen. Das knapp 50 Seiten starke Kapitel geht dabei sehr beispiellastig vor, stellt verschiedene Anwendungsszenarien vor und geht dabei auch auf Probleme und Fallen ein. Mit Tricks, wie das Befallen fehlender Eingabewerte über Macros zeigt Klemens sogar Wege auf, Mechanismen moderner Programmiersprachen (etwa Ruby) zu emulieren.

Kapitel 11: Object-Oriented Programming in C

Fast ähnlich viel Platz gönnt sich Klemens beim Thema „Objektorientierung“. Anders als andere Bücher, bei denen dann Bibliotheken vorgestellt werden, die eine Objektorientierung aufladen, bleibt Klemens seinem Schema treu und baut die Objektorientierung von Grund auf:
Ein Objekt kapselt Daten – ein Struct tut dies auch. Das Erzeugen, Kopieren, Löschen und Vergleichen von Objekten implementiert Klemens mithilfe von Dictionary-Datenstrukturen, die er ebenfalls aus Structs aufbaut, zu denen er einige Hilfsfunktionen definiert. Führt man nun noch Funktionen in die Structs ein, bekommt man Methoden, die zu den Daten gekapselt sind.

Whether these make your code more readable and improve the user interface is up to you.

,

schreibt Klemens, deutet aber darauf hin, wie einfach es wird, vorhandenen Code, der auf diese Weise geschrieben wurde, um Funktionalitäten zu erweitern.

Kapitel 12: Parallel Threads

Hier stellt Klemens zunächst OpenMP als High-Levle Framework für das parallele Rechnen vor und zeigt wie einfach es ist, bspw. Schleifen mithilfe einer einzigen Pragma-Direktive zu parallelisieren. Die Schwierigkeit des parallelen Rechnen ist das zurücksynchronisieren der Variablen zueinander, welches hier an einigen Beispielen dargelegt wird.
Dann geht Klemens auf die POSIX-Alternative namens Pthreads ein, die eine eher low-level API der Threaderzeugung und -synchronisierung darstellt.

Kapitel 13: Libraries

Das Buch wird im letzten Kapitel mit dem Vorstellen einiger Bibliotheken abgerundet: GLib, SQLite, GNU Scientific Library, libxml2 und libcURL sind Bibliotheken, die ein breites Spektrum abdecken, sodass für jeden Leser etwas dabei sein sollte. Von typischen Funktionen, die im C-Standard nicht dabei sind, hin zu Datenbankabfragen und wissenschaftlichen Rechnen mit mathematisch komplexen Funktionen, bis hin zum Handling von XML und Internet-Protokollen von HTTP, POP3, und SCP bis hin zu alten System wie Telnet und Gopher.

Kritik

Klemens hat sich für dieses Buch einiges vorgenommen und versucht auf verhältnismäßig wenigen Seiten mit Büchern über C mitzuhalten, die nicht selten als ziemlich dicke Schinken bezeichnet werden können. Alleine das ist schon ein sportliches Unterfangen, wie ich finde. Trotzdem beweist er ein ähnliches Händchen wie auch schon beimAppendix: Kurz und prägnant und dennoch recht umfassend legt der Autor die wichtigsten Punkte dar, zeigt die essentiellen Wege auf, und geht auf wichtige Hindernisse und Probleme ein, ohne sich dabei allzusehr in Details zu verlieren. Natürlich kann man dadurch nicht alle Themen gleichstark beleuchten, etwa das Profiling in Kapitel 2 wird nur erwähnt; auch zu Valgrind hätte ich gerne mehr gelesen. Dass aber Techniken genannt werden, sodass sich der Leser mit Stichworten dann selbst auf die Suche machen kann, finde ich besser, als es gleich unter den Tisch fallen zu lassen.

Die Thesen, die Klemens aufstellt sind teilweise sehr gewagt, etwa dass man nur Double verwenden sollte und Float komplett ignorieren soll. Hier muss man sich immer vor Augen führen, für wen oder was man programmiert: was bei der Anwendungsentwicklung sicherlich bei heutiger Leistung möglich ist, sieht schon wieder ganz anders aus, wenn man im Embedded-Bereich unterwegs ist – dafür ist dieses Buch dann sicherlich nicht geeignet – hier würde man aber, denke ich von vornherein zu ganz anderer Lektüre greifen. Gut finde ich, dass er alles genau begründet und man sich selbst überlegen kann, ob man dies dann genau so umsetzen möchte. Ich persönlich habe jedenfalls eine ganze Liste an Punkten herausziehen können, die ich in zukünftigen Projekten berücksichtigen möchte.

Schön fand ich auch, dass viele Beispiele gezeigt werden, anhand derer die Techniken gezeigt und die Theorie erläutert wird. Die Quellen dazu finden sich auch online auf einem Github Server, sage und schreibe 140 Beispielsdateien kommen dort zusammen und liefern einiges an Anschauungsmaterial. Was das Lesen ein wenig anstrengend gemacht hat, sind allerdings die Zwischenbezüge in den Kapiteln. So wird sich gleich in Kapitel 2 auf Code aus Kapitel 13 bezogen, sodass man hier oft hin und her blättern muss. Ich würde aber sagen, dass es insgesamt eher ein Schreibtisch-Buch ist, da man viele der Beispiele auch beim Lesen ausprobieren möchte und sollte, um den Lerneffekt zu unterstützen.

Ein weiterer Kritikpunkt war für mich das Kapitel über die Objektorientierung, da ich persönlich bei der Fülle an objektorientierten Programmiersprachen garkeinen Bedarf sehe, dieses in C auf umständliche Art zu erzwingen. Auch wenn das Kapitel an für sich nett zu lesen war, kommen für meinen Geschmack andere Kapitel dadurch zu kurz: Gerade Parallelisierung ist ein sehr komplexes Thema, zu dem man mehr machen könnte; auch die Vorstellung der vielen Libraries fällt verhältnismäßig sehr knapp aus.

Insgesamt aber war jedes Kapitel sehr lesenswert.

Fazit

Beim 21st Century C-Buch handelt es sich nicht um ein Einsteigerbuch; idealerweise hat man schon mal in C programmiert, oder einen sehr guten Programmierhintergrund in anderen (ähnlichen) Programmiersprachen. Auch glaube ich, dass der erfahrene C-Programmierer sicherlich den Großteil der Themen schon kennt. Wer aber, wie ich, wenig mit C programmiert und beim Schreiben von C oft nach einer Referenz greifen muss und gleichzeitig plant eine Applikation oder Bibliothek für den PC-Bereich zu schreiben, für den sollte dieses Buch wie geschaffen sein; alle essentiellen Tools werden vorgestellt, und eine moderne Arbeitsweise eingeführt. Alle relevanten und für C-Anfänger komplexen und schwierigen Themen werden erörtert und alles ganz ohne die Altlasten, die auch nach meiner Erfahrung nach wirklich viele C-Bücher mitbringen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert