Ask HN: Best book to learn C in 2022?
Any recommendations?
Any recommendations?
Peter A. Darnell and Philip E. Margolis C - A Software Engineering Approach (3rd ed.) https://www.amazon.com/Software-Engineering-Approach-Peter-D...
David Hanson C Interfaces and Implementations: Techniques for Creating Reusable Software (!st ed.) https://www.amazon.com/Interfaces-Implementations-Techniques...
The first is clearly written and focuses on ANSI C (lexis, syntax and semantics), with a healthy dose of software engineering education along the way, which you may already have. (It does not yet cover the latest ISO changes.)
The second book is a true gem that teaches even seasoned programmers how to implement correct, clean and portable components as libraries in ISO C, with plenty of source code to show how the true masters of the language craft the finest code you can in C. Most books how fragments of linked list implementations, but only this book shows complete a whole range of important ADTs you cannot live without (e.g. List, Ring, Atom) in C portably, with strong typing, bullet-proof error handling etc. (Hanson is also the author of lcc, a portable C compiler.)
I'm actually surprised that these are both are so little known, and that the second one hasn't seen more editions.
Hanson's style is a bit idiosyncratic, with his somewhat Modula-3-ish naming style and literate programming use, but there are a lot of usable data structures in there, and I especially like that the focus is on the module level, not on the algorithms themselves.
Now, having said that, if you're in a particularly weird sub-domain of C, books from there might supersede or at least color a lot of what you're doing on a day to day basis. In the days of yore, if you wanted to do Win16/Win32 programming, getting to work with your Petzold early is a good idea, and I would assume the same to be true if you do glib/gobject based Gtk programming.
Two free resources that looked good to me and that I forwarded in the past were "An Introduction to the C Programming Language and Software Design"[1] by Tim Baily and "Beej's Guide to C Programming"[2]. Those are a bit more concise than your average intro to C book, but for some paths (prior experience, quick headway to C++ etc), they might be sufficient.
[1]: https://www-personal.acfr.usyd.edu.au/tbailey/ctext/ctext.pd...
Will it help me understand how to build reusable, language agnostic, code?
So it does basic error handling & modularization. You get that "for free" in a lot of other languages, or sent in totally different directions anyway (good modularization in OO and/or FP looks somewhat different)
Then there's a lot of practical data structures and algorithms, with good references to other versions and sources, so both something you could use immediately but also serving as jumping off points to advanced study.
So I appreciate it a lot more in combination with bare bones C than with other languages or even C environments with more standard library functions.
For style & technical guidelines, I think Code Complete is still a great book, or the Pragmatic Programmer. For a trad-C book, maybe even Pike's Practice of Programming. Avoid anything avuncular.
Of recent years (and not including OO stuff in general), Ousterhout's "A Philosophy of Software Design" packs a lot in a few pages.
However, i am not clear what you mean by "language agnostic"? Obviously Code cannot be language independent but if you can read C then you should be able to translate the data structures/algorithms given in this book to a language of your choice; the difficulty might vary (eg. how would you represent pointers in your language to implement say recursive tree structures?) but it can be done. The data structures/algorithms given in Hanson's book are "industrial strength" with no missing parts (i.e. no exercise left to the reader). So you get complete code all written in the "Literate Programming" style which takes a little getting used to but is eminently worth it.
However i interpreted the original "language agnostic" question as maybe wanting to implement the C code in terms of some pseudo-code and hence my question. The phrase also seems to have another meaning: https://en.wikipedia.org/wiki/Language-agnostic
The style doesn't match any C code you're likely to see. It basically presenting an STL-like set of data structures and standard library in C, not C++.
That style of programming isn't idiomatic in C. One thing that took me a long time to realize is that C is NOT a modular language! The best way to use C is to roll your own (or copy and paste) data structures for your problem, not try to use canned ones!
If you want the latter approach, C++ is better. It has better support for type safety, abstraction, and modularity (at great cost).
i.e. if you compare code of commonly used codebases like Lua, CPython, sqlite, Redis, BSD kernels, Linux, etc. you will see patterns, and they don't look anything like Hanson's book
I'd say it's still easy to get the wrong idea -- the title is "C Interfaces and Implementations: Techniques for Creating Reusable Software"
So the claim is that focusing on reuse is missing what C is good for. It's going against the grain; if you want that style, use C++ or Python
Those were less viable choices when the book was written, which is another reason I wouldn't recommend this old book, especially to beginners.
Also the techniques for real long-term reuse involve C ABIs and not C APIs, which are not covered in almost any book, including this one.
On the other hand, you do hint that you think C can be used reusably, provided that the ABI is the focus. I agree with this.
In my opinion, the techniques discussed in the book are in fact useful techniques for accomplishing exactly this end. If you want reusability the ABI way, what you want is a small surface area which changes extremely slowly. Ideally, you want each ABI to be a subset of the last to avoid breaking changes. OK: accomplishing data hiding via opaque pointers in tandem with a simple, high-level interface is one way to accomplish this, and is the main lesson to be learned by reading this book. (I would argue you really only need to read a chapter or two to learn it, but that's a different story---I would argue that this book is in fact not very good for this reason, but whatever.)
If you look at most reusable C libraries, they take this approach. Obvious this book takes a bit of an extreme approach where as much of the details of a data type are hidden as possible, but there is no real reason to do this provided that the details of a type are stable. Then it's OK to expose them as part of the interface.
I suppose I take your point that in reading this book, one might get the idea that it's a good idea to develop an STL-style library to use with C and run with that. On the other hand, someone who is learning C might just as well read many books about it (or read lots of code), and be able to contextualize this book well enough to understand that it should be taken with a grain of salt.
I think it's also important to point out that there are exceptions to these "rules". GLib is a generic STL-style library written in C which seems to be pretty widely used. At the other end of the spectrum, you have more people (I think mostly in the games community) developing "header only C libraries". YMMV.
https://news.ycombinator.com/item?id=33142349
So basically, use EITHER idiomatic C, which means long functions / few internal interfaces, reuse with ABIs not APIs, bespoke data structures, etc.
OR use C++ and ADTs (type safety, abstraction, polymorphism). Or use Rust if you don't need compatibility.
So books like CII are of very limited use, especially not for beginners. They will be fighting with the language and not understanding what it's about.
----
To sum it up, C is better for coarse-grained reuse (deep interfaces a la Ousterhout, Unix file system) than fine-grained reuse like CII (hash table, array, set)
Related long article I wrote about API (compile time) vs. ABI (protocol, runtime) and related issues: https://www.oilshell.org/blog/2022/03/backlog-arch.html
----
I'll take your point that if you want ABI stability, then hiding layout behind a pointer and not exposing structs is a good idea!
Although that pointer's type doesn't necessarily have to be void* as it is in CII -- it could just be an application-specific type
Also agree that there are several different ways of using C and C++.
The whole point of having a small surface-area for your ABI with plenty of detail hiding is that you are free to do what you like beneath it. If the code uses a struct named "hash_map" that takes a generic hash function and stores void pointers, but runs fast for my purposes, what do I care? These techniques give you a controlled way to build black boxes.
C is a very flexible language, and it is possible to build up quite a variety of patterns in it. Your job as an engineer is to look in your toolbox and select the best tool for the job. Adhering to a "correct idiom" gets in the way of this.
I committed atrocities until I realized this. C is not suitable for generic programming.
And I just looked it up -- C was invented BEFORE abstract data types were! This explains a lot.
Programmers think "hash table", stack, queue, set, etc. are "the way you program", because that's how it's taught in CS 101. It's also asked a lot in interview questions.
But that line of thinking was invented AFTER C. I recall that Liskov did foundational work on ADTs, and Wikipedia agrees
Programming with abstract data types (Liskov and Zilles, 1974)
https://dl.acm.org/doi/10.1145/942572.807045
i.e. the origin of abstract data types is in this work on the CLU language.
Her 2008 Turing Award cites this work: https://amturing.acm.org/award_winners/liskov_1108679.cfm
At MIT she led the design and implementation of the CLU programming language, which emphasized the notions of modular programming, data abstraction, and polymorphism
-----
Meanwhile C was developed around 1972-73 to make the Unix kernel portable. It's a minimal layer meant to generate machine different machine instructions that will work on machines; it doesn't feature strong abstraction.
So what Hanson was trying to do is to add abstraction to C, but you need a language more like CLU or C++ to do that. Arguably Bjarne was "doing it right" -- actually changing the language.
----
HOWEVER I also had a recent experience that vividly shows the folly of ADTs even in C++, a language designed for them.
In C++, unordered_set<void star>::insert() is shockingly slow -- slower than malloc(1) !!! And it is slow BY THE SPEC. Because of the abstract operations that the C++ standard requires (e.g. iterators and invalidation), you have to use a slow closed-addressing/linked list implementation that allocates for EVERY element !!!
I hit this when working on the garbage collector for https://www.oilshell.org/ -- we're making a fork() friendly collector (no intrusive mark bits) that works with arbitrary addresses returned by malloc.
It made some of our benchmarks 10x slower in TOTAL runtime!
Best comment I found that explains it:
https://old.reddit.com/r/programming/comments/5pwgtn/hash_ma...
Chrome message from 2017:
https://groups.google.com/a/chromium.org/g/chromium-dev/c/rd...
So basically the hash table in C++ is a shockingly bad default, and it has to with a bad ADT design.
Doing ADTs in C is even worse. Hanson's book is more like a thought experiment in merging two distinct lines of thinking -- not something you should actually use and base your code on!!
----
This would be a good blog post -- I would title it something like C Was Invented Before ADTs, and C++ Isn't Great Either
The fact these are abstract types doesn't seem relevant, if you explicitly implemented this feature set for, say, the long type and named it "chubots_long_set" it would still suck, but it's not abstract, the abstraction played no part.
I'm just saying that using a canned set with "many useful" operations is suboptimal, design by committee is suboptimal, etc.
As opposed to analyzing the ops your app needs and creating custom data structures. i.e. there are many different ADTs for "hash table" or "set"
That might seem obvious, but it's not how software development is being done today. There's a lot of code reuse that leads to suboptimal software; it would actually be better to copy and paste and refine more.
> copy and paste and refine more.
That doesn't get you better algorithms, which is what you need. If you start with the C++ standard library's unordered_set implementation in a source file and you "refine" that you won't get from there to absl::flat_hash_set except in the same sense you could start with the footage from "Grease" and end up making "Bugsy Malone". Start over with a different design.
There are apps where absl:flat_hash_set isn't optimal either
We're talking about abstract data types
Common Lisp yes, and its research predecessors, but not Lisp in 1972
CLU itself was based on Algol, and influenced C++
DEFINE STRUCTURE + type functions + function/procedures is all one needs in PL/I to achieve similar workflow,
https://www.ibm.com/docs/en/epfz/5.1 (No earlier online version found)
And if you want to read PL/I documentation that uses the term "abstract data types",
https://www.ibm.com/docs/en/z-netview/5.4.0?topic=SSZJDU_5.4...
Interlisp initially created in 1970, besides the lists everyone knows, introduced atoms, string, arrays and compound data types, which alongside macros provided the necessary foundation to create abstract data types
https://www.softwarepreservation.org/projects/LISP/interlisp...
And going back to CLU,
> The language which most closely resembles, in form, the language presented here is SIMULA 67. 8 SlMULA class definitions have many similarities with cluster definitions.
"Programming with Abstract Data Types (1974)"
https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.136...
Either way, it doesn't really matter, as it's clear that C does not include any of those ideas, and the CII style isn't idiomatic
Especially when it was created, but even today!
And it's trivial to upgrade to from C
So basically, use EITHER idiomatic C, which means long functions / few internal interfaces, reuse with ABIs not APIs, bespoke data structures, etc.
OR use C++ and ADTs (type safety, abstraction, polymorphism). Or use Rust if you don't need compatibility.
So books like CII are of very limited use, especially not for beginners. They will be fighting with the language and not understanding what it's about.
Anyway, I agree with the sentiment.
i.e. it's about code generation, not abstraction, not type checking, etc.
"C Programming: A Modern Approach, 2nd Edition" - https://www.amazon.com/C-Programming-Modern-Approach-2nd/dp/...
Note the above book is good for its quality as a reference, but not enterily considered as "modern". For a more updated approach I would also recommend:
"Modern C" - https://www.manning.com/books/modern-c
and
"Effective C" - https://nostarch.com/Effective_C
The first book on the language:
"C Programming Language, 2nd Edition" - https://www.amazon.com/Programming-Language-2nd-Brian-Kernig...
Is interesting from a historical perspective, but considered as not a recommended resource for learning C.
Edit: If you are learning C there is something quite important you need to know... C and C++ are two completely different languages, and that is how you should approach your learning. In other words, don't go learning the C in C++ :-)
1) An old classic to understand low-level C "System" interface: The C Companion by Allen Holub.
2) Followup with the more modern Computer Systems: A Programmer's Perspective by Randal Bryant and David O'Hallaron.
In college I'd programmed Fortran on punched cards. APL was like dropping acid, and I worked one summer as a commercial APL programmer, but it wasn't appropriate for what I needed. Pascal didn't quite fit either. C was in that context mind-blowing. The future had arrived! I had full access to the machine!
If one is learning to become a plumber, one might not be so interested in how plumbing worked in 1840. If however, one is a herding dog, and needs serious work to avoid getting bored and destructive, reading K&R would put C in historical context.
Later, as I depended on C, I bought each edition of C: A Reference Manual by Harbison and Steele, and read it as if I were studying to be a lawyer.
I now program in Haskell, and wish I was equally versed in Clojure and Rust. If I were returning to C, I'd instead choose Rust. For my work, I have a free choice.
This made me laugh. I always described it as feeling like a collector/completist, but lawyer works too.
Visitors to my home office (back in the dark ages) would often comment on the growing set of editions of "UNIX System Administration Handbook", lined up side by side on my shelf.
Honestly at some point in editions 3 through 6, I was just buying the new ones for the shelf art.
RIP Evi Nemeth.
C has few concepts. You can learn to write C in an hour with a couple of web pages. Then you have to learn how to implement the most useful data structures but that’s not difficult either. What’s hard is writing safe code in a maintainable way and K&R does very little to teach you that properly.
There are probably some sharp corners I’m not thinking of around type conversions but that’s the kind of thing you expect from a strongly typed language.
Most of the caveats are in how compilers treat C rather than how close it is to the hardware to be honest.
I've read a lot of books about programming over the many decades I've been doing this, and the K&R book still stands out as one of the best examples of how to do a good job of documenting the tool you've just built. The K&R book gave me the heuristic of "read the book by the folks who wrote the language". That heuristic turned out to be mostly wrong. But the book has stood the test of time.
To appreciate C properly, I think at some point you need to become a bit of a historian. Look at BCPL. Look at what assembly listings looked like or old Fortran. Read some of the very early papers from C and UNIX history. Check out Lions' commentary on UNIX. When you see a little of the problem C was invented to solve, it becomes a lot easier to understand why it is what it is. There's a lot of angst about why C allows Bad-Thing-X or doesn't have New-Fancy-Thing-Y. Nobody seems to remember what a change it brought to writing systems code.
Then you can compare it to things written in modern C (C99+) and see that yes it has evolved quite nicely and can, if used correctly, be a nice application language too.
You can read it for free here:
https://gustedt.gitlabpages.inria.fr/modern-c/
And if you like it, I encourage you to purchase a copy here:
printf("element␣%zu␣is␣%g,␣\tits␣square␣is␣%g\n"
I have a feeling the PDF file is messed up...Or is he showing you a literal space?It seems very rare these days.
https://www.manning.com/books/modern-c
The C language can be quite tricky to navigate (at least to me) and this book helped me a lot to understand writing idiomatic code.
Edit: I just saw your post history - if you don't mind me asking what approach/resources did you take with self-teaching yourself?
For learning Elixir I bought "Elixir in Action" and then supplemented with my own side projects.
King’s is amazing, IMO. Very didactic and covers C in depth.
K&R 2e, 272 pages
King, 832 pages
There's a recent book out I came across called "Bare Metal C" (No Starch Press, Oualline, 2022) which unpacks embedded programming in a very readable manner. I imagine a lot of, if not most, C programming these days is done in the low-level embedded world, and this book clears up a lot of the mysteries.
https://nostarch.com/bare-metal-c
Also it never hurts to look at a good open-source codebase written in C, for example the SQLite code is worth looking at (if a bit overwhelming):
I learnt from this book. It's still good for the basics.
I never used to need to study this way when I was younger, but things change now that I'm older and I've adapted =)
http://www.davisr.me/dropbox/rms-c-20221008/c.pdf
Source in Savannah.
The standards for C99, C11 are open. Look up what you don't learn in them and grab one of the other more modern suggestions from here for that, but K&R is a must have for any C programmer.
You probably won't be able to put the book down if you start reading it.
You can find it here: https://cs.yale.edu/homes/aspnes/classes/223/notes.html
[1] Advanced Programming in the UNIX Environment https://www.amazon.com/Advanced-Programming-UNIX-Environment...
[2] C Programming Language https://www.amazon.com/Programming-Language-2nd-Brian-Kernig...
In combination with other classes (and books) on networking, operation systems, data structures we covered a big variance of use cases with C. My question is: How do I take this to the next level? For example I feel I never missed a concept from those classes but when I see C code posted on a thread here it's probably something completely unreadable and complex. Can anyone provide resources to advance a little? What should I study if my goal is to make good and useful modern C software?
I feel like my typing is a bit abstract but I will be happy to clarify.
PS Yes, I've heard of C++ and Rust ;P
https://github.com/redis/redis/tree/unstable/src
Entry point here at line 6816: https://github.com/redis/redis/blob/unstable/src/server.c
Also "Code Reading" will be useful. - https://www.amazon.com/Code-Reading-Open-Source-Perspective/...
"Code Reading" tends to be criticized, but I think mostly unfairly.
Secure C from Oreilly and Modern C from Manning are more relevant in 2022.
Then have a look into Frama-C and SAL security frameworks, MISRA C, and CERT C coding standards.
Edit: If you are critizing K&R for not teaching how to write safest code then, well, you are right. It is simply not meant to do that
You're right on the security part, and that C authors hardly care about it is visible on the language itself, naturally the book wouldn't be any better.
Also, reading through some codebases gave me good (and horrible) ideas and habits. However, C projects are usually messy and I am not sure whether recommending it would be helpful or just confusing. But I would say TenDRA, BearSSL, pkgconf, s6, kivaloo, apk-tools, libsodium, musl, OpenSSH and cosmopolitan libc are worth checking out.
Finally increasing my tiny understanding of architectures, memory models and decades of safety issues was significantly helpful as well.
K&R is a bit more intermediate. Come back to it when you're more experienced.
I've written a few such wrappers that work, but I have no idea what I'm doing WRT const void * et al. and would love to understand it a little better.
Are there any of these resources that would help me understand how to write better FFI code in C, while maybe skimping on details that are less important if I don't plan to write much code that is "C first"?
Thanks for any suggestions. Apologies in advance for wrong terminology and generally being a noob.
Something like this?
Rust/Swift -> <FFI> - > C -> C++ libraries
If so, you first need to understand ABIs and the C ABI in particular. Start here - https://stackoverflow.com/questions/2171177/what-is-an-appli...
Providing a C interface to C++ is done using "extern C" - https://stackoverflow.com/questions/1041866/what-is-the-effe...
So you should now have a binary C++ library but which exposes its public interfaces using C language conventions and linkage i.e. C ABI.
You can now call into the above using your Rust/Swift FFI. I am not familiar with these so you will have to look at their FFI docs. Here is one for Swift - https://stackoverflow.com/questions/24004732/how-to-call-c-f...
Be very careful about strictly following the rules for FFI since you are bridging two different languages with their own semantics and runtimes which could be very different from each other. See - https://en.wikipedia.org/wiki/Foreign_function_interface
The key is to understand what a ABI is and how the C ABI has become sort of de-facto standard for FFIs.
> are you trying to call into C++ libraries from Rust/Swift using their FFI to C?
Yes. Examples (just POC, getting things to link and compile): <https://github.com/n8henrie/brokencppmap> <https://github.com/n8henrie/swiftcpp>
Thanks for the links -- I've been putting things together here and there from a combination of SO and the Rust user forum, which is why I was wondering about a more formal reference focused on this use case.
That said there is one other crucial point to keep in mind when calling C++ from C. Using "extern C" for function declarations informs the C++ compiler to emit code to C ABI conventions so it can be called from C code. But what about data structures which you might pass and use between C++-land and C-land? They have to follow strict "POD" criteria for compatible memory layouts. See for example - https://stackoverflow.com/questions/49407290/memory-layout-d...
I knew a girl in college who was really smart and on a CS major track. At the time everybody used Borland C++, and some people's first exposure to C++ -- or programming in general -- was through this IDE. She asked me if her C++ classroom exercises would compile on a laptop. I said it should be fine. Then she said, "But isn't there like a chip or something in my desktop that compiles the programs?" I was a bit flabbergasted that anyone would think that, but simply replied that the compiler was just another piece of software that will run on any PC.
People come in to programming and they don't understand what's going on. A lot of details are hidden with development today. And it's great that we have the IDEs and so forth to help us not be bogged down in these details -- but a greater perspective is useful to any programmer, and that's why I and Charles Petzold and some other programmers recommend that your first, simple programs be done "the old fashioned way".
(Come to think of it though, maybe if there were a compilation chip -- an FPGA or something -- the compilation times of C++ and Rust could be mitigated...)
Or just build in the terminal with:
cc hello.c -o hello
After those first steps I would recommend installing the MS CMake Tools extensions (https://github.com/microsoft/vscode-cmake-tools/blob/main/do...), this automatically discovers CMakeLists.txt files in the project and then uses those to setup build- and debugging targets and configure the C/C++ language server / Intellisense.I prefer this setup because it is more 'transparent' than a 'fat IDE' like Visual Studio, Xcode or CLion, but YMMV of course :)
Also, by following many of the exploits covered in the book, you get a real grasp of what is happening at the machine level - which is one of the major reasons you'd choose C over something more modern.
(e.g. shameless plug: https://floooh.github.io/2019/09/27/modern-c-for-cpp-peeps.h...)
K&R 2nd Edition is still a very good read of course, but mainly for all the little interesting details that are not directly related to the C syntax, but there really should have been a K&R 3rd Edition when C99 came around.
Top reply: "Why do you want to do x?"
Thought I was on StackOverflow for a second.
Seems as if they just wanted some insight into my decision making process and why I chose to do x over y so they can better make the decision themselves.
I do feel that if I were to write more c now it would be better for me knowing rust and having a feel for designs that pass the borrow checker.
Unhelpfully the only c book I've read was the k&r book. It seemed fine. Perhaps the question would be easier to answer if the reason for wanting to learn c was given (eg to contribute to existing c based project or to write embedded code)
I find just learning language features in isolation doesn’t give a complete picture.
Just skip the tutes and books, and read drafts of the ISO C standard. C99 is the most widely used. For newcomers, C11 might make sense.
Here: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
Absorb that and then you can play the Game of Spot The Error in C books and tutorials. Not to mention git repos of C programs.
Just ignore suggestions (parroting really) for K&R. It’s probably the worst introduction to C when it comes to good habits, undefined behavior and writing secure code, since it doesn’t even attempt to cover any of these but presents a total facade regarding what programming in C is like.
K&R has created generations of people who call themselves C programmers but are essentially clueless when it comes to deep understanding of the language. Anyone that recommends K&R today is someone you want to avoid taking any advice on programming from.
I spent time on my own learning C from K&R since the CS program at my school didn't expose anyone to C (or C++) during the first year. Later, in e.g. the computer architecture course or the data structures course when C was being used liberally (and sloppily), and as I was running across lots of C in the wild, I was continually frustrated by the frequency I saw people relying on common-but-undefined behavior that's explicitly identified as unsafe/unreliable in K&R.
Here's my advice to the original poster:
If you come across unsubstantiated comments telling you to ignore K&R for all the bad things it contains, then ignore those. Or you can ask for evidence of the bad things they say about it. It's become a meme in the last few years to write meatless swipes about how bad K&R is, but in all the times I've asked for evidence of the things it's purportedly rife with, no one has ever produced anything. The most egregious thing I know of is that it uses `gets` and `puts` in some places—while taking extra effort to tell you it's wrong to use them. This is arguably exactly how do-not-do-this wisdom should be imparted.
There is one criticism that holds true about K&R, if you're being generous (to the naysayers, that is), which is that it really does just teach you C the programming language (with some extra stuff about the UNIX system interface at the end). It's not a general software engineering book. You don't learn about build tools or how to structure large, maintainable programs. It exposes you to the fact that C has various nooks and crannies, and it teaches you them, and that's all it really tries to be.
A gross misrepresentation that, taken at face value, can only lead to a perpetuation of disasters committed by the sort of C programmers that K&R has produced in droves.
Just because there are a few mentions of undefined behavior in K&R, doesn't mean the concept is addressed or even identified as an issue. There is not a single mention of the "C abstract machine" (what is that, the K&R C programmer wonders), pointer provenance, aliasing, or pretty much any aspect of the language that has emerged in the last 20 years as of supreme importance to correctness and security.
As someone else wrote in this thread, the entire book is hopelessly obsolete and should be retired to the annals -if not the garbage bin- of history.
But what could a book last updated in the 1980s have to say about this subject? The phrase falls out of DR260 (Defect Report #260 in the ISO C standard) which wasn't even raised until this century.
Here's the mention of "provenance" from what WG14 (the committee for the C standard) wrote for DR260 back in 2001:
"the C Standard does not prohibit an implementation from tracking the provenance of the bit-pattern representing a value"
Vague right? And it's technically true, the C standard says nothing whatsoever about this subject. Of course it also doesn't have anything to say about which Pony is Best [Princess Celestia is Best Pony, don't @ me]
You might assume after DR260 the C standard was fixed. Presumably C11 and so C++ 11 and modern languages in this family all spell out how provenance works exactly right?
Nope.
The status quo is that the standard appears to say pointers are basically just addresses, but they clearly aren't in practice in your C or C++ compiler. If you raise bug reports about this, your compiler vendor will say it's provenance and, if you're persistent and they don't just stop answering you, eventually point to DR260, the unresolved defect from 2001.
There are folks trying to produce a consistent model which is somewhere in the ballpark of what programmers expect to work versus what compiler vendors actually deliver, so that both will be happy or, if not happy, at least both will agree on what the situation is. This model is some variation of what's called "Provenance Not Via Integers" but it must have exceptions because clearly sometimes provenance is transmitted via integers, and how that works must be explained too. WG14 did not take this work for C23, but it will become a Technical Specification in the C23 era. If compilers implement this TS, and if programmers prefer having at least the guarantees from the TS over a shrug emoji then perhaps some day it becomes part of the standard itself.
K&R mentions that you can't go around pointing to things of the wrong type (but C programmers do it anyway, and then doubtless you'll say K&R didn't warn them) and that you can't use pointer arithmetic to get at objects which are out of bounds (again, C programmers do it anyway).
The language is unsafe, and it's poorly designed in various ways by modern standards, but the OP didn't ask "What language should I learn?" they have apparently set their heart on learning C and so it's very strange to imagine the goal is to learn a good modern language which C isn't.
I'm guessing based on the comments to my posts that some people are upset due to K&R being their first introduction to C (or their first introduction to technical writing) which leads to emotional responses that miss the point.
So let me re-iterate that point. K&R is a terrible C book in 2022, and should not be recommended as an introduction to the language. It wraps C in a veneer of simplicity-hiding-untold-complexity which leads readers thinking that the language is much safer and much easier to deploy than reality dictates. This has proven to be disastrous and something that we should point out and drive newcomers away from.
That's a shamelessly self-serving assumption.
When confronted with a claim/argument that is not favorable to your position, you have (at least) two options: one involves employing humility and intellectual honesty in an earnest attempt to resolve the conflict, and another involves inventing a convenient narrative in order to avoid the conflict. An example of the latter would be a stand-up comic who consistently bombs on stage and chooses to cope with it by telling themselves that the audiences are just too sensitive for the comic's edgy-but-otherwise-brilliant material. An example of the former would be for the comic to at least entertain the idea that their sets always bomb because the jokes are shit. Your response (which amounts to "u just mad") indicates that you seem to have taken this approach.
You have made a fact claim. You said that K&R "doesn't even attempt" to cover undefined behavior. That claim has been disputed. The next step for you to do is not to change the subject by questioning the motives and emotional maturity of those who have disputed the claim, but rather to substantiate it.
(For the sake of completeness: K&R was neither my first introduction to C nor my first introduction to technical writing—not that it matters.)
I'm not familiar with John Regehr - At least recently, Regehr's blog appears to be extremely low level (relative to C at least). Reading about the IR in LLVM or the ARM machine code which makes a spinlock go isn't a "solid grounding in C". It's fascinating - but it's not about C. Perhaps he used to write more about C?
Robert Seacord though, I just don't see it. I think you've recommended Seacord because you're thinking about what you'd need to write good software in C in 2022. But if we wanted to write good software in 2022 we shouldn't use C for that task in the first place. That's my point here. "Secure Coding in C" goes on the shelf next to "Agile Development using the Waterfall Model" and "Version Control using Visual SourceSafe". In each case these should be one paragraph inside, "Better not I think".
My assumption is that the OP is curious about the language, to the extent maybe they'll solve Advent of Code in it or something, not that they're about to write or maintain serious C software.
K&R is so obsolete it's now wrong about some things. It's a pretty good read sometime down the road - when you can recognize its shortcomings - but it's not a book from which to start learning C.
It also corrected my oversimplified view that C is a simple and small language simply because it is an old language, but it was already a simple and small language compared to other languages in the late 60's and early 70's 'programming language zoo'.
All the code examples are deeply rooted in the 80's programmer mindset though, and are not very relevant for "modern C".
To the OP:
K&R Ansi C is one of the best book to learn "Fluent C". The code is terse, no-frills and to-the-point. It is not a "Software Engineering" book so you have look elsewhere for that. Also without an understanding of the language, its runtime services and system interfaces (eg. ABI) it is pointless to look at Security/UB and hence don't bother about it in the beginning; You can get to it later.
In summary, read K&R C along with any other books that you like.