Completely agreed. While the sample may be a great way of illustrating the authors ability to write prose it does nothing for illustrating his ability to provide clear technical explanations, nor does it give a good indication for the skill level of the intended audience, or the authors knowledge in the subject for that matter.

I work primarily in C, and I'm always up for a good informative book on the subject to improve my knowledge, but there are a lot of crap books out there on C, and even more that while well written are shallow enough to not really provide any value to someone who is already a competent C programmer.

Thank you for updating the sample - this helped significantly in evaluating the book and its anticipated content. I would also suggest listing a full table of contents (so prospective readers can see which patterns are described, if not included in the sample).

Many useful OO concepts can be represented easily in C. E.g. FILE* and the I/O operations you can perform on it are a (very useful) form of encapsulation. (Unlike in most OO languages where you have access control, in C you could peek inside the struct a FILE* points to and mess with it, but in 99.99% of cases, doing so is idiotic, so people don't.)

Another example: OSes often interface with device drivers using a table of function pointers (one for read(), one for write(), etc.), which behaves like an instance of a derived class.

Unlike in most OO languages where you have access control, in C you could peek inside the struct a FILE* points to and mess with it

Using an opaque pointer actually gives stronger encapsulation than a C++ class. With an opaque pointer all that's provided to the client is that there is a type named FILE and that this is a pointer to one of those. With a C++ class you have to provide the definition for the whole class, including private members. This is why people use the pimpl idiom in C++, to get the stronger level of encapsulation that an opaque pointer provides.

By "opaque pointer", I take it you mean a pointer to an incomplete type? I.e. the header file might forward-declare

struct FILE;

which enables calling code to work with struct FILE* without actually seeing what's inside. Only in the translation unit containing the implementation of fopen() etc. would the full struct FILE definition appear.

Interesting wrinkle: In C (but not C++), in order to be able to say simply FILE* instead of struct FILE*, you need to use a dummy name for the struct and then write a typedef that uses it. Are you allowed to typedef an incomplete type? I.e. is the following example legal C:

C doesn't need to know the implementation of the struct when you're just dealing with a pointer, since pointers are all the same size on a given architecture. It just needs to "know" that certain symbols will exist at link time. So this is valid, compilable, runable C:

Interesting. Does anyone ever create a struct of function pointers? The functions wouldn't have access to the other members of the struct, so I guess there wouldn't be much point, other than maybe serving as a virtual class type of construct.

Yes, the Linux kernel uses this technique extensively to provide virtual functions in C. For instance, each filesystem driver implements a specific set of functions for operating on its specific type of filesystems and then creates a struct with pointers to those functions (this is the equivalent of a vtable in C++) and then each filesystem struct contains a pointer to the struct of function pointers for its type (this is the equivalent of an object's vtable pointer in C++). This lets the kernel operate on filesystems with a common set of operations without worrying about what kind of filesystem it is.

That would be the "table of function pointers" I was talking about. As Gotebe said, if a function needs access to the entire struct, e.g. in order to invoked other "methods", its address can be passed as a parameter. (That's literally what happens under the hood in C++ with this. It pays to remember that the first ever "implementation" of C++ simply translated C++ source to C source.)

While the implementations in the GoF book are object-oriented, the intent of many patterns suggest use in other paradigms too.
As an example, consider the Strategy pattern. There's nothing object-oriented about it. In fact, it's a trivial pattern in languages with first-class functions.

How is GoF applicable to programs that weren't designed after having a first read through GoF?

My point is that patterns, as developed in that book, are rarely justified by anything except the use of that pattern. From then on everything happens in terms of that pattern, rather than in terms of the nuts and bolts of the tools at hand. This is an exceptionally poor fit for a language such as C.

I'd have expected this book to cover topics such as "using the preprocessor to add structure-specific for_each() constructs" and "applications of offsetof(), function pointers, and inlining to gain type and allocator independence without compromising performance". Not state machines: everyone knows at least three ways to do a state machine.