Most helpful critical review
146 of 153 people found the following review helpful
Essentially without competition, though far from perfect
on November 7, 2012
The C programming language is alive and well: it is used not only in vast amounts of legacy code but also in new development, often when efficiency is important and the learning curve imposed by C++ is deemed unacceptable. As for the literature on C, the standard high-quality text is the one by Kernighan & Ritchie (K&R), while the "second book" genre doesn't have too many representatives. Peter van der Linden's "Expert C programming: Deep C Secrets" and David Hanson's "C Interfaces and Implementations" are probably the best choices (perhaps supplemented by P. J. Plauger's "The Standard C Library"). However, given that all these books date from the 1990s or earlier, a book on modern C programming was sorely needed.
The Good: Ben Klemens' "21st Century C" is a text that assumes some knowledge of classic C and attempts to expand and update that knowledge. This has allowed the author to write a book that is not too long and can thus be read cover-to-cover. Even though this is not an elementary tutorial, the author has also included overviews of some basic concepts to make sure everyone's on board. The writing is for the most part pleasant enough and sometimes even humorous, e.g. "If you and a friend both have v1 [...] Or, if you have no friends". The code for the complete programs shown in the text has been packaged and is available on the publisher's website. Almost all of these programs are easy to compile and run -- this should go without saying, but unfortunately many books are rushed out too soon. One attractive aspect of this volume is that it contains a grab bag of tips (e.g. a function that takes in a pointer to void should immediately assign that to an appropriately typed pointer) as well as welcome summarizations of related topics, bringing to mind books like "Effective C++" and "Effective Java". For example, chapter 6 includes a useful table that goes over valuable aspects of static, automatic, and allocated storage duration (though Klemens doesn't use the standard terms "allocated" and "storage duration" but speaks instead of a "manual memory model"). A cute trick is given soon thereafter, in the form of a macro to initialize a static variable with a nonconstant value. More good macro material is included in later chapters, leading to convenient string-handling or helping us write functions that take in a variable number of named arguments. In the process of doing the latter, Klemens drives home a fun idiom: first define a struct, then define a variadic macro that puts its arguments inside a struct, and finally write a function that takes in a single struct as input. The main selling point of this text is that it's quite up-to-date, repeatedly mentioning C99 and C11. Importantly, the modern C features are not an afterthought but are used throughout the volume. Even so, chapters 9-12 would be the ones most useful to a reader unfamiliar with tools like strtok_s, designated initializers, the _Generic keyword, and anonymous unions. The last 2.5 chapters also attempt to inch toward programming in the large, with Klemens doing things like implementing a dictionary and using libraries (e.g. GSL and GLib) repeatedly. Speaking of libraries, Part I is dedicated to the modern ecosystem around C and is a charming departure from the language-centeredness of most programming books. Apart from giving basic usage info on debugging, Makefiles, and version control software, Klemens also introduces sleek tools like seq, pkg-config, and screen. One way to test an author's pedagogical skills is to evaluate a section containing material one is completely unfamiliar with. For me that applied to the sections on git rebase and on the variables appearing in Makefile.am; in both cases I felt the selection of topics as well as the explanations were nicely done.
The Bad: I begin by discussing structural problems and then go on to cover detailed issues. Klemens mentions in the Preface that "about a third of this book has almost no C code in it." First of all, Part I takes up 100 pages and Part II 150, so it's not really one third. Second, placing the non-C-specific material first leads to an excessive number of forward references. One example: p. 40 constructs the unit tests for a piece of code, p. 72 shows an Automake file that handles that testing, while the C program itself is introduced on p. 212. A related structural/misplacement issue has to do with Klemens' decision to introduce modern C features in chapter 10 (of 12). This wreaks havoc, given that he has (to his credit) opted to use new entities (like designated initializers and compound literals) throughout the text, even if he sometimes doesn't say he's doing so. Turning now to detailed issues, it's worth noting that the book contains many mistakes which are not just first-printing-typos, but flat-out factual errors. Here are a few examples, all drawn from a small fraction of the book: Klemens says that sources in distutils.core.Extension is "an array", even though it is a list of strings; he alleges that the stack size on Linux is limited to 2 or 3 megabytes -- stating this in apparent ignorance of the existence of ulimit and setrlimit; embarrassingly, the first use of pointers in the chapter on pointers contains at least two mistakes in one line (which is shown twice); he confusedly states that "the syntax for a function type is really a pointer to a function of a given type" -- this would mean that we could have array elements that are of function type, which is patently untrue. In other cases, the writing is slipshod: Klemens mentions "a local variable with static linkage" even though there is no such thing as static linkage in C (there's only external, internal, and none); he devotes an entire section to setting the array size at runtime but fails to note that variable-length arrays became optional in C11 -- later on, he can't make up his mind if such arrays can be initialized or not; he counterposes open to "the POSIX fopen" and refers to a "C-standard get_opt function"; he thinks he's uncovered a contradiction in K&R (just to be clear: he hasn't) but can't even be bothered to make the elementary distinction between declarations and definitions; the catalog of such issues is simply too long. In yet other cases, the text is characterized by plain old poor pedagogy, e.g. he defines list and then talks about &list as if he's already explained what that means (when he's only discussed list and &list). On a different note, we get a chapter on extending Python with C, but almost nothing on the impact of C/C++ feature differences. Bjarne Stroustrup has written an interesting article on C and C++'s "sibling rivalry" (which Klemens does not cite). Speaking of Stroustrup, the bibliography's only reference to "The C++ Programming language" is dated 1986 (presumably meaning the 1st edition). It's also worth noting that the bibliography includes two texts by Scott Meyers that are not cited in this book, but no reference to the volume by van der Linden that is the obvious competition in this niche.
In a nutshell, I liked the premise of the book much more than I enjoyed the execution. It was disappointing to see Part II start out by mentioning "especially complicated pointer setups like pointers to functions" only to repeatedly mishandle them near its end (both in the text and in the code). It should be noted that K&R managed to thoroughly discuss function pointers in one short section. Yes, expecting an author to match the quality of K&R is holding him to an impossible standard. But expecting him to write an authoritative and well-structured second book on C isn't as exacting. All in all, three stars.