I thought 21st Century C was good, i've still kept my copy. I'd happily recommend it.
I like the K&R book too - it feels reeeeeeally old but it's really short.
There's a few others that have helped me in various ways but these are a little older -
Love C by Tim Love (free online, my copy is something i just printed out, it's not that long).
Programming from the Ground Up (x86 assembly) - this is available freely online but i bought the book and that helped me a lot with C even though it's a book with only assembly... (to be fair, it does go through calling conventions).
Finally there's another book i love, Advanced Programming in the Unix Environment by Stevens, i have the 6th edition updated by Rago after Stevens' passing. Fascinating book - but huge.
I tried to refresh my memory from previous threads, and I think the general trend has been to recommend (as seen in sibling comments):
https://modernc.gforge.inria.fr/
https://nostarch.com/Effective_C
I've also seen the older:
https://www.oreilly.com/library/view/21st-century-c/97814493...
Mentioned.
There's also Architecture of Open Source Applications, which can help with starting to read some larger code bases, some of which are in C: http://aosabook.org/en/index.html
And there's a general recommendation to go read the source code of the Redis cache/db.
Finally i came across a mention of this short article on gdb (nb: mention of TUI text ui should probably have been in the top, not a foot note):
https://www.recurse.com/blog/5-learning-c-with-gdb
I feel I'm missing a book that has come up often, but can't think of which one.
I did like zed Shaws learn c the hard way, but I'm afraid it's getting a bit long in the tooth: https://learncodethehardway.org/c/
It's like telling someone that SICP is the best way to learn Scheme. Maybe, but lots of people learn differently.
Unfortunately I don't have good suggestions myself, but at least they won't feel bad if K&R isn't their cup of tea.
Besides I was in the same boat as you. I come from the world of JS/Python/Go. I even wrote https://github.com/Himujjal/ekon in pure C recently. The reason I thought C would be good is performance and portability. But I found it to be better to invest time in Rust rather than in C. C is a fantastic language but cross platform dependency management is difficult. Unit Testing is also difficult. There are solutions but not as efficient as Rust's ecosystem.
BTW I wish there could be a Cargo for every language.
Odd...I find that a very strange sentiment.
I thoroughly enjoy C, and have even written a compiler for it, but I don't think it's a well-designed language by any account.
But the next step, which I think is far more important, is to look at the source code of tools that you actually use in real life. Things like cp, or wc, or head. You've probably used them for years without thinking about it, but they're all written in C. Don't look at the modern GNU versions just yet, since they can be packed full of complex functionality, start with micro implementations like Busybox or Toybox. Then look at some OSes that are known for super clean code like NetBSD or OpenBSD. In those OSes you have the benefit of being able to go back to the version that existed 25+ years ago, so you can read through the diffs to see how they adopted new features and found new ways to address C's biggest flaw/risk - memory exploits.
If you're interested in kernel programming, check the Linux and BSD sources, and lurk on the mailing lists for a while to see how people talk about the code. The review process tends to be a bit more brusque than you might be used to on Github, but it's often detailed and results in code that is of a relatively high quality, or at least a consistent standard. It's a great place to learn.
The first three are just useful for getting the basics. The next two are for starting to learn low level stuff. You don't really understand C until you understand how it relates to lower level code.
I don't like 21st Century C, it may have changed since then but it has a chapter titled "Object-Oriented Programming in C" which is confusing object with Abstract data type.
struct Object;
void Object_init(struct Object *o);
That's ADT.