Ask HN: Learn C in 2023?

222 points by 0x008 ↗ HN
Hello,

I always have the itch to learn C, juts because it was such an influental language and when you are digging around a lot in GNU/linux you will have contact with it one way or another.

What is the way you would suggest learning C in 2023? I am already familiar with other languages (rust..) but would like to have an introduction to the C basics and program some hands-on projects in C, so I can learn the pecularities of the language.

219 comments

[ 5.7 ms ] story [ 248 ms ] thread
Depends on your style of learning, but most people learn best by implementing something in a new language. Here are a few examples: a Brainfuck interpreter, a raytracer, even command line tools like ls, cat, grep are good exercises. If you are into electronics C is great for embedded work - I would buy a Raspberry Pi Pico and write C programs for it.

I would also suggest to enable all warnings your compiler allow for and sanitizers if possible. You will also want to learn to use a debugger that works on your operating system.

maybe try some embedded development, like raspberry, light up some leds. Embedded development is fun, educational and one of the last bastions of programming where C makes sense.
ESP32 is quite nice. It is much lower level than Raspberry Pi or similar SBCs, and C really makes sense there.

It ships with pretty high-level libraries (connecting to a Wi-Fi network is ~200 lines of C code), and you'll be running on top of a small operating system (FreeRTOS). But you still have full access to hardware and all the fun of working directly with it. The dev boards are quite cheap at $3-4 per piece, and have lots of peripherals (including Wi-Fi and Bluetooth).

Have a look for ESP-WROOM-32D at aliexpress, or whatever the platform of choice in your country is. Some of the newer boards are built on RISC-V, if that's your thing.

Their SDK also has first-class Linux support (and the other two OSs, obviously).

https://docs.espressif.com/projects/esp-idf/en/latest/esp32/

I suspect they meant the Pico/RP2040.

Depending upon their goals, something like at ATmega may be a better choice. ESP32 and RP2040 development places a lot of emphasis on using libraries. That's fine, but it doesn't really highlight how C is different from most other languages. In contrast, there is a lot of information on low level programming for the ATmega. The RP2040 would be my second choice. I believe the libraries are entirely open source, which means you can read the datasheet and can reference the library source to see how something is implemented in C.

Granted, the ESP32 is a lot more fun if the intent is to build something.

Well, GNU/linux is more "C plus GCC" than pure C. There are a lot of fancy things that GCC gives you that you'd otherwise have to do explicitly.
"GNU C" has a number of features that make the language far more pleasant if you're willing to give up the portability and use them. A big one if you've been using modern languages is anonymous functions.
It’s worth noting that GNU C uses trampolines to implement their “nested functions” extension, which in turn requires an executable stack.

In other words, using that extension silently disables one of the most basic and effective security mitigations we have for C.

C has the unique distinction of being criticized on the one hand for the complexities of vendor-specific extensions and portability concerns... and on the other for being insufficiently like Rust or Go, which are defined entirely by their proprietary implementations.

I don't think I've ever seen this criticism leveled elsewhere. Do people criticize JavaScript programming because the ECMAScript standard doesn't give you everything you could want?

There is no meaningful sense in which either Rust or Go’s reference compilers is “proprietary.” If you’re trying to say that there is no ISO standard for the two, that’s an entirely different problem (and one shared by the overwhelming majority of programming languages).
> There is no meaningful sense in which either Rust or Go’s reference compilers is “proprietary.”

Nobody is talking about the compilers. This, right here — the fact that you are conflating a language and one implementation — is the whole point and is indicative of the tight control the respective organizations keep over their languages.

The direction of the languages is entirely up to their private owners, and that exclusivity is enforced via intellectual property law. They are exactly as proprietary as RHEL or Unix.

This is not a definition of "proprietary" used anywhere in the open source community. Private direction (which Rust notably does not have) does not somehow erase the fact that both are permissively licensed and have open codebases that you can use and contribute to to your heart's content.

I don't even know what you're referring to with IP law. The projects' names and logos?

> both are permissively licensed

No, the code of the implementation is permissively licensed.

Do I really have to explain, on HN of all places, the importance of distinguishing between implementation and interface?

This is incorrect on multiple levels: there is no organization that has "exclusive" control over Go, much less Rust. There's a company (Google) that made Go, but they have no control over the source code itself beyond the terms of the non-proprietary license they provide it under[1]. Even the things that Google does w/r/t Go are not even remotely controversial in the context of the definition of proprietary software, like CLAs. Linux has a CLA!

Comparing this to Borland or Microsoft is bizarre: Microsoft and Borland actually do have proprietary C compilers that they do not make publicly available in source code form. Those compilers in turn have all kinds of non-standard, proprietary extensions.

The one thing that sets C apart from Rust and Go is the presence of an official ISO standard. But this isn't material to the definition of "proprietary" in the open source world. And, well, that ISO standard is itself copyrighted.

[1]: https://github.com/golang/go/blob/master/LICENSE

Edit: You shouldn't delete and re-write your comments. Here's the original comment I responded to:

> That's a dubious claim, and irrelevant besides. They are proprietary, in the standard meaning of the word, because a single private organization has exclusive control over each, but whatever word you choose to describe that is up to you.

> Google owns Go in a way that has never been true of Microsoft (or Borland or GNU or Apple...) when it comes to C.

Edit 2: For the edited comment: you're setting up a distinction that does not matter for programming languages that do not have formal specifications. There is no "interface" to distinguish against, and the claim that Rust (or Go, or Python, or any other language without an ISO spec) is tacitly reserving all rights to the design of the language itself is completely unsubstantiated.

Your position that not having a standard is irrelevant for languages that don't have a standard is circular and bizarre.

The fact that you keep focusing on whether compilers are proprietary instead of whether Rust and Go are proprietary is starting to seem deliberately evasive. You're free to extol the virtues of open source software, and I agree with you, but it's utterly beside the point.

.rs is analogous to .doc. The "right" way to handle it is defined solely with respect to the reference implementation. I view that as a problem. You may disagree, and that's fine, but don't pretend this is a conversation about something else.

Where's the circularity? This is the claim: you can't claim that a nonexistent thing is proprietary. Rust and Go do not (to my knowledge) have any sort of standard other than the living standard of the reference implementation, which is itself not proprietary. Anybody is welcome to copy or modify that living standard, per the terms of the permissive license.

This is the overwhelmingly common state of affairs for programming languages: Python, PHP, Go, Rust, etc. do not have formal standards in the sense of C, but instead of a living standard in the form of their reference implementation. The reference implementation is permissively licensed, so the "standard," insofar as it can be said to exist, is also permissively licensed.

Edit: Again: this is just not what the definition of "proprietary" is, in the context of open source. There is a legitimate grievance that you're expressing about software standards and implementation diversity, but it has absolutely no bearing on whether the reference implementation is proprietary or not.

By analogy: I, an open source developer, create a new file format $FOO and release my reference implementation of a $FOO parser under a permissive software license. Am I somehow compelled to spend time writing a formal specification of $FOO to make it non-proprietary? It might be nice for me to do, and nothing stops anybody else from converting my living standard into a formal one, but I am absolutely not compelled to do it.

> The fact that you keep focusing on whether compilers are proprietary instead of whether Rust and Go are proprietary is starting to seem deliberately evasive.

"You keep using that word. It don't think you know what it means." -- Inigo Montoya

This is just bizarre. If MIT/Apache-licensed Rust is proprietary, then anything could be? AFAIK there is no POSIX definition for epoll/io_uring. I suppose epoll is proprietary too?

proprietary: "2: something that is used, produced, or marketed under exclusive legal right of the inventor or maker"

Can Rust be forked, modified, used/sold and distributed with your changes? If yes that hardly seems like an exclusive legal right. Your problem seems to be that people trust the Rust project, and don't trust you or a central committee (yet). But that doesn't make a project proprietary. It makes it implementation defined, which has its own problems, but those are not the same problems of as those of proprietary software.

> MIT/Apache-licensed Rust

Rust is not MIT/Apache-licensed, rustc is.

Rust is licensed under terms that require explicit permission for use outside of a few very narrowly-defined circumstances.

> Rust is licensed under terms that require explicit permission for use outside of a few very narrowly-defined circumstances.

I think you mean the Rust trademark, and sorry, but you don't get to use IBM or Google's mark willy-nilly either? I'm not sure it's a realistic expectation on your part to be able to market your changes under the other guy's mark.

All commits to the Go repository must be approved by two Google employees: https://groups.google.com/g/golang-dev/c/K7oGURi0wTM/m/3mg1O...
Yes. Again, Google can do whatever it likes with its repositories. This is entirely unrelated to whether or not the software is proprietary.

Compare Google's practices here to Qt's[1], CMake's[2], or any other large open source project that is primarily maintained by a single company.

To be abundantly clear: I don't think this is a good way to run an open source project, but it is a legitimate way to run one. So long as the source code and standards (insofar as they exist) are themselves permissive, then the community details are just that.

[1]: https://wiki.qt.io/Qt_Contribution_Guidelines

[2]: https://github.com/Kitware/CMake/blob/master/CONTRIBUTING.rs...

> I don't even know what you're referring to with IP law. The projects' names and logos?

I think it's the fact that if you took the Rust codebase and forked[1] it to accept new keywords, the resulting language that it accepts is most definitely not Rust.

If you did the same to a C compiler[2], the resulting compiler would still be called a C compiler by everyone.

[1] Assuming that the license allows it.

[2] One who's licence allowed this.

Yes, the license allows it. Rust's compiler is more permissively licensed than GCC, and is roughly as permissively licensed as LLVM.

This argument does not make sense: if you fork GCC or LLVM and change the language frontend for C, the thing you are parsing is no longer standard C. This is exactly the same as is true for Rust, except that the reference is the the living standard rather than an ISO standard.

By the original commenter's logic, the modified C compiler would effectively be proprietary: it no longer has a public, liberally licensed specification that it conforms to. But this isn't the way anybody in the open source community uses the word "proprietary," because it precludes any open-source work that doesn't have a corresponding formal specification.

> This argument does not make sense: if you fork GCC or LLVM and change the language frontend for C, the thing you are parsing is no longer standard C.

It doesn't make sense to you, certainly.

To the type of person who've used a dozen different C compilers, all of them containing extensions to the language, and all of them still advertised as "C compilers", the argument makes perfect sense.

I mean, the input to the GCC C compiler is, by default, C with extensions, and yet even you still called it a C frontend, not a "SomeOtherLanguage" frontend.

You may not agree that the argument is valid or sound, but you can't with a straight face say that it is an unreasonable position to take.

> You may not agree that the argument is valid or sound, but you can't with a straight face say that it is an unreasonable position to take.

That's not the argument I'm advancing: I'm saying it does not make sense to call that modified C compiler "proprietary."

I don't care if you call it "C" or not, because everybody who writes C understands that compiler-specific extensions are commonplace. But the existence of non-formally-specified extensions doesn't somehow override the permissively licensed nature of the compiler.

Similarly, going back to the original argument: it does not make sense to call Rust "proprietary" because it only has a living specification, rather than a formal one. It's permissively licensed, and you can modify it to your heart's content. The only reason people wouldn't call your changes "Rust" is because the community doesn't share the same expectations re: vendor extensions that C's community does.

> it does not make sense to call Rust "proprietary" because it only has a living specification,

It doesn't have a "living specification", it has a reference implementation. There's a difference. HTML is a living specification without a reference implementation. Rust (and many other languages) have a reference implementation without a living specification.

> The only reason people wouldn't call your changes "Rust" is because the community doesn't share the same expectations

That's not true; you cannot call it Rust because Rust, the programming language, is trademarked.

It is protected, you understand? There are legal barriers to calling your NewLanguage "Rust", as explained by the foundation themselves over here: https://foundation.rust-lang.org/policies/logo-policy-and-me...

Writing your own language and calling it Rust is legally forbidden.[1] You are, of course, free to use the permissive copyright but the name is trademarked.

Now, with C, OTOH, as an ISO standard it has no trademark so this argument does not even arise.

[1] As far as I can tell, it would be legally forbidden even if your new implementation of Rust behaves identical to the reference implementation. The best you can do is call it Rust compatible.

There is a difference between open software and open standards. The two are orthogonal. Open software can use closed standards (though must usually reverse-engineer them); closed software can use open standards. See, e.g., LibreOffice support for .doc files; Microsoft's TCP/IP stack. Please make an attempt to engage with intellectual honesty, and stop intentionally conflating the two.

The issue at bar is whether Rust the language is proprietary, especially as compared to C. Something is proprietary within the standard meaning of the term if it "is used, produced, or marketed under exclusive legal right of the inventor or maker." Merriam Webster, Online Edition, available at https://www.merriam-webster.com/dictionary/proprietary.

In this case, The Rust Foundation maintains and exercises exclusive legal rights over Rust that nobody exercises over C. If I make a C compiler with extensions, I can still call it C and I can still participate in C governance. C is an open standard. See https://www.open-std.org/. However, the same is not true for Rust, where governance happens entirely under the auspices of one vendor, who uses trademark law to ensure that no competitors can exist without its explicit approval. Specifically, any Rust variants other than those required for porting require explicit written permission. See https://foundation.rust-lang.org/policies/logo-policy-and-me.... Furthermore, even if I accepted my impotence in the governance process and even if I resolved to call my Rust variant something else (FeO?), competition in the market would still be chilled by The Rust Foundation's refusal to commit to fair, reasonable, and non-discriminatory (FRAND) licensing (something that is required as part of the ISO standardization process). If a competitor ever did arise, patenting the borrow checker mechanism would be all it would take to completely crush them.

I am glad that rustc is open source. I am glad that The Rust Foundation is a non-profit. I accept on faith that they want to do the right thing. However, they have deliberately created a monoculture that they enforce through IP law. Rust is proprietary, i.e., it is "something that is used, produced, or marketed under exclusive legal right of the inventor or maker." There are no open standards, there is no FRAND licensing obligation, there is no allowance for any Rust variants other than those explicitly permitted by the primary vendor of Rust compilers.

You brought up Python before, which is appropriate: Python explicitly is a "benevolent dictator for life" language. The difference with Rust is that the dictator is a legal personhood instead of a single living, breathing person. But dictatorships, even benevolent and well-intentioned ones, are ill-suited to the requirements of stability and predictability that are so vital to systems programming.

Imagine, God forbid, that The Rust Foundation goes defunct at some point in the next 100 years, and its IP bought up by the next generation's SCO or Novell or whatever, and they decide to patent the borrow checker and whatever other innovations are in Rust. Do you think that would be a bad thing?

Because it can only be bad if you agree that Rust is proprietary. If Rust were an open standard (including the borrow checker as part of the standardized compilation process and subject to mandatory FRAND licensing), then that outcome would be mitigated against.

This is a very large comment, filled with legitimate observations about how Rust (and most other programming language communities) choose to defend their name, logo, etc.

It has absolutely nothing to do with how "proprietary" is used in the open source community. To make it as simple as possible: if your use of the word "proprietary" includes Python and Rust but excludes Borland C, you've failed to use to word in a way that is substantive and useful to everyone else in this community.

The implied risk of Rust's trademark terms is entirely ahistorical: C did not win as a systems programming language because it satisfied FRAND or any other standard for openness. It won because Worse Is Better, in spite of onerous licensing restrictions by companies with extremely expensive and proprietary C compilers.

(comment deleted)
>It won because Worse Is Better

I don't think this is why. I think it "won" because it was the best "high-level" language of the time and exploded to fill a huge niche, historically. In other words, historical luck.

I think that's also how JavaScript won.

Since it's always luck, and since the luckiest thing is rarely the "best" thing, it just looks like it's a selection for ironically worse quality from the outside.

(comment deleted)
> If a competitor ever did arise, patenting the borrow checker mechanism would be all it would take to completely crush them.

Apache 2 includes a patent grant? So I think most of this comment is clearly erroneous.

(comment deleted)
> Imagine, God forbid, that The Rust Foundation goes defunct at some point in the next 100 years, and its IP bought up by the next generation's SCO or Novell or whatever, and they decide to patent the borrow checker and whatever other innovations are in Rust. Do you think that would be a bad thing?

1) I'm not certain the Rust Foundation actually owns the underlying patent IP for the borrow checker.

2) But granting it does, imagine, the foundation or its successors files for a patent years after including that IP in Rust and years after granting two extremely permissive licenses (MIT/Apache 2) for the use of that IP, and tries to enforce their patent rights against your fork, FeO.

3) What exactly is their hope of winning a patent infringement case after granting those extremely permissive licenses?

Don't both MIT and Apache 2 provide a very strong defense against infringement where one is using and modifying the Rust code base (you fork creating FeO)? If you don't like Apache 2's explicit patent grant, Rust of course offers you the option to use MIT's implied grant.

"Any language used by the owner of the patent, or any conduct on his part exhibited to another from which that other may properly infer that the owner consents to his use of the patent in making or using it, or selling it, upon which the other acts, constitutes a license and a defense to an action for a tort." De Forest Radio Telephone Co. v. United States, 273 US 236, 241 (1927).

Can you show me an instance where anything like what you describe has occurred? If not, you should describe precisely how your hypothetical could occur? Because, from where I sit, your theories seem like pure FUD.

> The direction of the languages is entirely up to their private owners, and that exclusivity is enforced via intellectual property law.

Can't you just fork the language, or, just re Rust, participate in the RFC process? Do we need more bureaucracy?

https://www.learn-c.org/

If you have lots of time: https://hal.inria.fr/hal-02383654

If you can't be bothered reading a whole book: https://matt.sh/howto-c

Exercises: https://www.codestepbystep.com/problem/list/c and https://exercism.org/tracks/c

Once you have syntax and basic algorithms down well, watch this, the only 2 hour YouTube video I'll ever recommend: https://m.youtube.com/watch?v=443UNeGrFoM

Both r/cprogramming and r/C_programming are active, also full of lazy students trying to get people to do their homework. If you come by, describe your problem well with code. Say you're learning for yourself, not for school.

Together C & C++ is a good Discord if you prefer live chat: https://discord.gg/tccpp

#c on libera is a genuinely helpful place full of neckbeards.
Sorry if this is silly question but what is libera?
A sibling comment already mentioned what it is so I'll not repeat that. Just some additional context if you're somewhat familiar with IRC. It's the spiritual successor of Freenode after it was taken over by a member of that community.
#c-jeopardy is great fun as well.
Any recommendations on static analyzers, unit tests, coverage for c? Can clang tidy give hints about modern c they way it does about modern c++?
One useful tool is running clang or gcc with —Werror -Wpedantic which will point out a lot of things which are useful to know about, even if occasionally a false positive.
Set warning level to max, use clang static analyzer or clang tidy (I'm not quite sure how those two relate to each other though), and especially ASAN (Address Sanitizer) and UBSAN (Undefined Behaviour Sanitizer) (and if you write threaded code: TSAN (ThreadSanitizer).

One thing that Apple did right in Xcode and where other IDEs lagged behind is easy access to those tools from the UI.

That sounds like all the things I do for c++. One thing I really like is that clang tidy can flag the "old way" to do stuff. Was hoping it had that for c.
Not exactly your question, but valgrind is an amazing tool. It does execution-time analysis of your code and provides an in-depth report on memory usage. It will find and report memory leaks, use of uninitialized variables, bad memory references, etc. I love programming in C, but there are a lot of ways to corrupt memory, get segfaults, etc. valgrind is a useful and powerful way to guard against many of these kinds of problems. C++ is no better. The way I see it, programming in C is like lying on a bed of nails. Doable if you are very careful. With C++, they put a sheet over the bed of nails.
I haven't used valgrind in years, more used to asan, but I've been told it can do things asan can't?

Edit: to stretch the metaphor, modern c++ is like a thick rubber pad on the bed of nails, but often on the job you are hot bunking and you don't know which c++ your predecessor used to make the bed... Or you are in a hurry and you aren't sure which one you grabbed.

Ideally you use both, as both can find some more obscure issues the other may not. At least in the past. Either in isolation also works well, though Valgrind is really a suite of many tools.
If you're used to ASAN, think of Valgrind like ASAN, TSAN, MSAN, and UBSAN all at once at the same time, and then has other facilities like profiling.

In any situation where I have control of how the code is built, I personally prefer to run those various SANs instead of Valgrind, usually three parallel builds: one with -fsanitize=address,integer,undefined, one -fsanitize=thread if more than one thread is involved in the project, and one -fsanitize=memory too if Linux is available. That gets you almost all the benefits of Valgrind faster than Valgrind.

But Valgrind is still an exceptionally powerful tool to learn and use. Its chief advantage to me is to be able to work with an unmodified binary. Its chief downside is it can be slow, and occasionally you may run into issues with support for more obscure instructions like vcvtps2ph.

MSAN is really the star of the sanitizer world, and is worth spinning up a Linux VM for. It tracks the initialized-ness of all your data and catches any use (really, branch) on anything uninitialized. I like to think of ASAN as "did I get my pointers right?" and MSAN as "did I get everything else right?".

If you're not familiar with integer sanitizer, it's like UBSAN except it checks for things that are often mistakes rather than things that are actually undefined. The most common situation I've found is code that's hashing data will often pop up a warning about unsigned integer overflow, which I choose to suppress as finely grained as possible with something like __attribute__((no_sanitize("unsigned-integer-overflow", "unsigned-shift-base"))). That way you can still find unintentional unsigned integer problems (e.g. with size_t).

Don't bother with this stuff (or with "modern C") if you're just beginning to learn the language. You don't need it for small programs. No one is going to yell at you if your first effort isn't perfect. And no one is going to yell at you if you read the "wrong" book, or try things out in the "wrong" order. Pick any learning resource and get started now. You'll probably get distracted and move on to something else. Come back to it later or not.
Well C was my second language. I learned it on an amiga 500. But I quickly transitioned to a 386 (yes i know, a downgrade) but my dad put turbo c++ on it and I never wrote much ansi c after that. A bit for odd embedded targets, but mostly c++ my whole career. So I can write old fashioned c, and I don't start new projects in c, but writing c abi shims for c++ libraries or reading modern c is something I do with some frequency. Learning it would be fun maybe as I've never really used c99 or newer for anything.
My comment is for the OP, not for you. Go nuts learning C tooling and have fun.
I’m not sure, it does help to even just know about whether your program is faulty, and C doesn’t like telling it you, or in unintuitive ways only.
Do bother with this stuff, it's super helpful and helps you understand your mistakes. Writing C without using sanitizers is like playing a game on hard mode, even for simple programs (especially when you're still learning).

You may leave static analyzers for later though, since they're more likely to bother you with unimportant stuff - but not much later.

What is your opinion on K&R?
It's great for what it is, but the language and style have come a long way since then. It would be better to learn "Modern C" (this is the title of a book but also at least learn C99 not K&R C).

E.g., people like the book by Jens Gustedt. I'm ambivalent about it, but it sure machine-guns the important points.

[1] https://hal.inria.fr/hal-02383654/file/ModernC.pdf

> not K&R C

K&R the 2nd edition of book teaches ANSI C. (It is still the best intro into the basics of C.)

It's much too awkward IMHO, C99 and onward almost feels like a whole new language (mainly because of struct literal and designated init support).
Not being forced to declare symbols in the beginning of the scope is the real game changer I say. K&R or C89 without e.g. GNU extensions is really annoying to use.
Idk about that. I think it's code with everything declared at the beginning of the scope is easier to read and understand. Every time I see code written in this style (Git, Linux) it's easier to build a mental model of what is happening.
Not the person you are replying to, but here are my thoughts. It sounds like OP is looking to learn C for historical and contextual reasons, and for that K&R is perfect. The history of C is very K&R-centric.

For modern, practical C programing it is missing a lot though. It is a short, simple, from the original authors introduction to the language itself. There is nothing about how to test your code, how to set it up for long term maintainability, how to mitigate some of the security downfalls or even any of the features added since C89.

It's a good technical book. A great one for it's time and subject matter. But that caveat is important, it needs to be taken in context of the time. It shouldn't be the first, and absolutely not the only, book you read if you are writing modern production C in 2023.

> or how to mitigate some of the security downfalls.

I think I'd go further, there are places where it advocates downright unsafe practices. Eg. It's been a long time, but I thought I remember it introducing gets() unironically, without any caveat. For those that do not know, there is no safe use of that interface possible.

If you're just beginning with C, it doesn't matter. If anything, it's better to be exposed to these unsafe practices and make mistakes with them in the very safe environment of your own home, where absolutely no one cares what you're doing.

K&R C is an excellent learning resource primarily because it's extremely strong pedagogically. Read K&R and then move onto something else later when you're done with it. If you eventually get a job doing something with C, then you'll learn on the job what's important to the job you're doing.

I kinda disagree. global hidden state under APIs is a really awful idea to put in someone's head.

I think it also presents the notion to neophytes that there is magic under there

Unfortunately, global hidden state is an unavoidable fact of programming. I don't see how one could be very good at programming if they were kept blissfully ignorant of it. Sometimes there is magic under there, and you just have to do your best to empirically figure out how that magic works.
libc has a few interfaces with implicit global storage. This leads to lack of thread safety and other issues. A good library will let the caller provide storage for these cases, or provide the caller a handle with which to pass it later/free it.

A trivial example would be strtok vs strtok_r. There are some, like, say, asctime(), that operate against a global string buffer.

Sometimes interfaces "look like" they're returning a global buffer, for example readdir(), but that structure can hang off the DIR* struct ... Nonetheless there is readdir_r()

I've taught C at university level, and used to give crash courses to new college grads and interns in industry.

The one thing I impart the most from the beginning is to develop good instincts for secure interfaces. You should be able to look at function signatures and see alarm bells for unsafe practices. For someone skilled at C this is second nature, you can see bad practices from a distance, often at the function signature level.

I think it would be unwise to neglect this.

It's unwise to neglect it in the long run. It's perfectly reasonable to ignore it initially. There are different valid pedagogical approaches here. Your approach may be suitable for the type of student you were interacting with, but the profile you're familiar with doesn't represent every person who might be interested in learning C.
I think a good approach is to have someone able to review code. So they can experiment and make all the common mistakes, then have feedback on it.
C is one of those things that unless you have proper testing, tooling and linting you don't know if you code is incorrect. It may have subtle memory bugs, rely on undefined behavior or crumple in exploitable ways under bad input. Having a feedback loop is really important in learning and improving. Undefined behavior helps subvert it.

For the exact example of above, how would a developer learn not to use gets? The best place would be a section on red flags in API design in the book. If you don't have that you need to find it through experience. Lints, fuzzing, sanitizers can help, but once again, none of this is covered in K&R. Some of it just didn't exist at the time or if it did, it was very primitive compared to what we have today.

Developers could pick up bad habits and be productive for years with them before they get bit by them. I know I did. I look back at some of the practices I picked up when first learning and cringe a bit. Especially since I think a fair amount of that code is still in production.

All of this is true, but if you are just starting with C, none of it matters.

If you continue using C, then you should eventually learn this stuff, but to overburden someone with a list of 100 things they must do right is the surest way to frustrate them to the point where they give up.

If OP eventually writes C code that goes into production for a company, it is the company's responsibility to have procedures and safeguards which ensure that OP writes reasonable code. Ideally, one such safeguard will be a senior colleague to introduce them to the thornier parts of C.

To expect a beginner to learn all of the finer points of a topic from the start is bad pedagogy.

On the other hand, K&R provides a set of exercises which are fun, graded, interesting, and challenging. By the time you finish them, you have a working knowledge of a large and useful subset of C. You can then proceed to reading another book (or consulting some other reference). Invariably, you will find that they disagree with K&R to some extent. Learning to spot these differences and begin to understand how C has evolved is itself an important lesson.

> If OP eventually writes C code that goes into production for a company, it is the company's responsibility to have procedures and safeguards which ensure that OP writes reasonable code. Ideally, one such safeguard will be a senior colleague to introduce them to the thornier parts of C.

You've mentioned something like this in reply to a few comments in here. This is a strange argument. It isn't too much to ask of a book to lay down more of a foundation so you don't need a 1 on 1 tutor later to correct misconceptions.

I get people like K&R because it is short and easy to read. Some subjects aren't best served by short and easy to read. The subject was well served by it when C was new and novel. We live in a very different world.

You don't actually seem to be addressing the quoted text... but I can reply to your arguments.

Sure, it would be ideal if there were a new book on C which did a better job of explaining some of the developments since K&R 2ed was written. If that book exists, I'm not aware of it. All of the books I've seen recommended are either too long, or poorly written. K&R's strong advantage isn't that it's short, it's that it's very strong pedagogically.

A "how do I learn C?" thread comes up on HN about once a month. In each of these threads, people will steer people clear of K&R, quoting something or another about how K&R isn't appropriate for this day and age (note: you do not explain why you think it isn't appropriate!). The tacit assumption here is that you can only read one book, or that if you read a book which isn't "perfect", you are somehow tainting yourself with corrupted knowledge. I think this is nonsense. An important meta-skill for learning is the ability to discern between good and bad information. The only way to build that skill is by consulting multiple sources.

Note that in my actual reply to OP, I don't specifically advocate for reading K&R, but instead to take a multipronged approach where you primarily draw from whatever learning medium you're most comfortable with. Most people I know that are in their early 20s or younger prefer learning from videos (I'm a math teacher and have lots of experience with people this age).

I think you've got a bit of a strawman in your head. Look back up at some of my comments you are replying to, namely the top one in this chain. A friend describes this as "violent agreement." I agree that it can be best to have a multipronged approach. That's why I said "It's a good technical book. A great one for it's time and subject matter. But that caveat is important, it needs to be taken in context of the time. It shouldn't be the first, and absolutely not the only, book you read if you are writing modern production C in 2023." I also address its short comings in that post.
I didn't read your other post. Glad we're in violent agreement!
It's wonderfully written. I'm a big fan of both Dennis Ritchie and Brian Kernighan personally.

However the book was last updated in 1988 and I prefer C99+ for almost every feature it adds. If I'm going to recommend just one book it will be something newer.

It's one of the best examples of technical writing in the field. A good introduction to the basics of the language. Read it for culture.

Read something else for safe C writing in a modern environment. Books like Modern C and Effective C are good starts.

+1 to https://hal.inria.fr/hal-02383654. It covers the language in extreme depth, and you likely won't remember it all on a first read but you'll walk away with a pretty good general understanding of the language. Plus, the author is an active contributor to the language.

I love C. As dangerous and as tedious it can be for large projects, I think it's my top choice for solo endeavors that don't require too many string manipulations. I don't think any language has come close to achieving the same balance of elegance and simplicity--maybe scheme? (zig comes close but has a substantial number of features that move it away from simplicity (e.g. payload capture))

> Once you have syntax and basic algorithms down well, watch this, the only 2 hour YouTube video I'll ever recommend: > https://m.youtube.com/watch?v=443UNeGrFoM

Hard +1 on this!

I think Eskil Steenberg work in C is really top notch - the video is heavily recommended, as is an overview of his work in general, he does some awesome stuff with C.

From the learn-c link:

    /* draws a point at 10, 5 */
    struct point p;
    p.x = 10;
    p.y = 5;
    draw(p);
I wish that would be a bit more 'modern', for instance:

    struct point p = {
        .x = 10,
        .y = 5
    };
    draw(p);
...or even:

    draw((struct point){ .x = 10, .y = 5 });
...main reason being that this avoids any accidents with unitialized data if the struct grows (the compound literal initialization makes sure that any struct members that are not mentioned are set to zero, while with the 'old school' approach you might end up with random junk in the struct).
I also love designated initialisers. C99 ftw.
I suggest downloading and playing with Raylib[1]. It's written in C, not C++, and is a library for video games, but it can be used to produce apps too. It has a ton of examples for you to review and modify as you learn, and it is cross platform. There are even bindings to other languages if you decide to leave C and use your favorite language. It's a one-click download and setup. Couldn't be easier and more fun!

[1] https://www.raylib.com/

SDL or Raylib are very good to quickly write interactive programs or simply play around with the samples.
I recommend C Programming: A Modern Approach by K.N. King http://knking.com/books/c2/index.html. My favorite part of this book is the exercises at the end of each chapter.
One of my favorite professors. His programming language concepts course was the most useful useful class I took in university.

I also recommend his book.

The C programming language book.
Just like C, there is a stark beauty to the original K&R C Programming Language book. And there's an admirable clarity in the writing that has set the standard for all language manuals that have followed. It is, indeed, a terrific read. As long as you're not trying to learn C.

It's really out of date. It doesn't cover subsequent C standards; and it doesn't cover any of the best practices that have subsequently evolved to protect us from the things in C that are deeply awful (in retrospect) in the original. Or any of the evolved practical lore for writing good, portable, safe C code.

> As long as you're not trying to learn C.

That's insane. K&R (2nd ed.) can easily be the only book you want to work through if you want to learn the basics of C.

Write a CHIP-8 emulator in C!

In my opinion it’s a perfect project because:

- it is a good size. You can be done in 10-20 hours, or longer if that’s your pace (have fun!)

- a lot of examples and community support.

- you have a whole project without any overwhelming complexities: you’re just taking inputs, reading some data, running it through a switch statement, writing data, writing to a screen, then repeating.

- you have lots of “test code” aka games.

- it mostly lacks tedium: you’re not repeating ideas too often

- it’s fun!

“can be done in a weekend”

RIP me who has spent 3 weeks on this

Great project for sure though!

haha I guess a “weekend” is a fuzzy unit of measure. I’ll amend.
If you've learned Rust then you've learned a lot about the importance of ownership and lifetimes, something C will never teach you directly. It will only punish you for failing to understand.
I'm a C# guy who learnt C over the past 2 years. It absolutely made me a better programmer. I learnt by going through K&R first and then Expert C programming by Peter van der Linden. I also went through OS books, books that talk about the machines like Understanding the machine. I program in many high level languages. The main difference I felt when learning C is, I felt like I needed to learn how the machine actually works (what happens when we run a program) to really appreciate C. I continue to pursue this learning, these days I'm looking into embedded stuff.

Also look at C programs. I study old, small unix programs like "more".

I must also say that I see people asking about learning C on sites like Stack Overflow and getting discouraged saying that it's outdated and all. These people are doing a great disservice.

As C# guy who had to work with C for a while - this language is minefield.

If you want to create your own language and want to experience what choices are subpar by modern standards - C is way to go.

If I had choice then I'd pick Rust anytime when messing with low level programming.

I don't understand fascination with C just because it's "tradition" "everywhere" or "gives you full control" - especially that, since it brought us countless security issues

You'll wish you have decent C experience whenever you need to call an OS API directly; or whenever you need to write your own bindings to a library written in C.

PInvoke requires a working knowledge of C's concepts; you need to know the basics about pointers and how data is represented in order to do it correctly.

This statement comes from a lot of personal experience: I wrote a (currently closed source) adapter to call into SWMM from C#. (https://www.openswmm.org/SWMM51007/swmm5-c). (Hopefully I can open-source it at some point.)

In https://www.syncplicity.com/en, I wrote code in C# to interface with a closed-source driver to create a user-mode virtual disk drive. I also integrated with a different Virtual disk drive product that was written in C++ via PInvoke. I used PInvoke to create bindings between C# and Objective C for the Mac version as well.

Maybe yes, but on the other hand I've never been paid for anything that used OS calls directly.

The only times I've used such APIs was creating simple keylogger for tests and very simple cheat for a video game (tested on non-official servers).

C# nowadays seems mostly like web backend development, tools, game scripting or automation, at least that's my perception.

Anyway even if WinAPI, then why not Rust?

> Anyway even if WinAPI, then why not Rust?

Because they said they already know rust and were asking about C…

> Anyway even if WinAPI, then why not Rust?

Plenty of reasons:

First of all, most operating systems (and other lower-level APIs) you want to work with expose their APIs as C. These don't all have bindings in Rust. Even if you want to do them in Rust, you need to know enough C to be able to understand how to consume the API.

But then, there's plenty of legacy code in C. We aren't going to rewrite everything in Rust overnight.

More importantly, I think the "jury's still out" if Rust is better than C for embedded programming; or programming that's primarily direct memory manipulation. (And, if your embedded environment only gives you an API in C, you need to know enough C to write the API bindings for Rust before you can do embedded programming in Rust.)

Personally, I don't have any experience with unsafe Rust code. (I only do hobby projects in Rust.) In C# you can do C-style direct memory manipulation if you need to; but I'm not sure if Rust's "unsafe" is equivalent.

I like firearms as an analogy here.

Rust is like a modern rifle, unloaded, with six safeties and three locked triggers in a gun safe. The ammunition will harmlessly self-destruct if you don’t focus properly before firing.

C is sort of like Rust except the rifle is on the couch, loaded, one in the chamber, all the safeties are off, the numerous open triggers are held by RNGs, and the business end is pointed right at your most prized possession.

It is fun, in a way. Use it in an environment that you don’t care too much about.

Meh. People love to hate on C but the invariants you have to keep track of are relatively simple (things like 0<=i<L.) Most languages replace these with multi-dimensional string based hashmaps that have much more complex invariants to keep track of and similar consequences for failing to do so.

Some languages like Rust and Haskell do a little better by having the compiler keep track of most of them (if you use it right) but the consequence of misunderstanding them here is accidentally coding yourself into a corner which is extremely unpleasant.

There are other things people like to talk about like automatic memory management and package management and again I'm not convinced these really improve much. The pain of allocating memory in C means people often find ways to avoid doing it altogether which results in much more predictable behavior. Same with pulling in third party libraries. It's rare using more than a couple is actually beneficial but when the package manager is there the temptation is way too much for most people and the result is both unpleasant and very vulnerable to supply chain attacks.

I'm still not convinced any of this is the improvement people think it is.

> the invariants you have to keep track of are relatively simple (things like 0<=i<L.)

If maintaining these invariants were so simple, why would the smartest people in the industry have so much trouble doing so? Humans cannot consistently write correct C, not even the ones who believe they can. We have decades of evidence.

> Most languages replace these with multi-dimensional string based hashmaps that have much more complex invariants to keep track of and similar consequences for failing to do so.

This take reflects a profound ignorance of the relative severity of different kinds of software defect.

When you get an array bound wrong in Java or Rust, you get an exception or a panic. Your program exits and restarts. You get a ticket for fixing the bug. Life goes on.

When you get an array bound wrong in C, you open the door for a nefarious actor to gain control over everything that your program can access.

These aren't similar consequences. They're not even on the same planet of consequences. C apologists are like apologists for asbestos and lead paint.

My point is that they can't consistently write correct software in any language. Javascript for example has similar issues where if you're not careful about which keys in a hashmap you write to you can create situations that allow for all kinds of things including arbitrary code execution.

Modern C compilers/runtimes also have bounds checking (although I've never used it) like java if you want to use it.

Sure there's some improvement but it's not nearly as extreme as it's made out to be.

It's true that humans will inevitably produce software with defects. There are differences in the impacts of these defects.

> Javascript for example has similar issues where if you're not careful about which keys in a hashmap you write to you can create situations that allow for all kinds of things including arbitrary code execution.

Not without eval() you can't.

> Modern C compilers/runtimes also have bounds checking

No, they don't. There are C-like languages (e.g. C++/CLI) that do, and you can use containers, but no, plain C cannot do bounds checking. There's no room in the ABI for it, at least not without CHERI or ASAN.

> although I've never used it

One of the most dangerous things in the world is giving powerful tools to people who don't know that they shouldn't use them.

> My point is that they can’t consistently write correct software in any language. Javascript for example

I don’t think Javascript is the counterexample you want to use as a good language. It is like C where safety is traded for flexibility.

If people can’t write correct software in any language, the language designers can help, which is exactly what the Rust designers did.

It’s not great for everyone because now all the scar tissue that great C devs have around what near-fatal errors they’ve made previously has lost some of its value.

> Humans cannot consistently write correct C,

So? Humans cannot consistently write correct code in any language.

> When you get an array bound wrong in C, you open the door for a nefarious actor to gain control over everything that your program can access.

What's really interesting to me is how Heartbleed (the largest example of getting an array bound wrong) was orders of magnitude less expensive to fix than Log4j, and yet it was Log4j that resulted in gauranteed RCE, not Heartbleed.

> C apologists are like apologists for asbestos and lead paint.

Let's not go spilling hyperbole all down our shirt.

I get the feeling that a lot of people comparing C to things that kill have never worked in life-critical situations, the systems of which ran C for decades and decades, and never appeared to have lost very many lives in the process.

Military, munitions, healthcare machines ... I can't remember any of them losing lives because of a misfeature in the C programming language, and yet there you are, proudly proclaiming to the word that C is like Asbestos, which has taken peoples lives.

Get a grip.

I mean I’ve worked with C for a long time. It’s love-hate. I think it’s hard to see the value from the other side of having internalized C, and Rust definitely encumbers you.

The number of things a beginner doesn’t have to know or worry about to code something as essential as a function that reads a file, searches for a string, and interacts in a serious way with the network in Rust vs C is substantial.

Probably I’ll get downvoted again but it’s fine.

But you do learn from such things (opening a file, reading from the network). All the stuff that can go wrong, and that it all has to be handled. Like writing a device driver.

It’s useful to write that C code, and then try to break it (e.g., to force a segfault rather than a proper error-then-exit).

Is knowing all that necessary? No, as you are kind of implying. ;-)

This analogy is really more like dogmatic Rustacean-ism than actual truth. Rust has it's merits. However, it's a tired language. It doesn't maintain very good stability due to it's ever-evolving compiler which makes it almost unusable in anything but a "move fast and break things" environment. It's too particular, making development a burden and interrupting flow. Most importantly, the community is absolutely exhausting to listen to and this post is a perfect example.

C can be dangerous, yes. No one is denying that. C is also extremely stable, fast, and a "known-known". With proper discipline that any experienced developer of ANY language will have C can be very safe. Most, if not all, dangerous errors can be found and fixed simply by understanding how to use valgrind. With the advent of modern CI/CD it's never been easier to remember to valgrind your code.

C is probably the first language I would use in an environment I cared a lot about. For example, if binary size, availability of libraries, competency of programmer, etc would be in my list of concerns I would use C. I take particular offense to the "RNG" part of your analogy. C errors are not random at all. They are always the developer's fault and are often very predictable (use-after-free, poor array hygiene, etc). Despite the ever-evolving condition of post-C religions (Go, Rust, etc) there are only a handful of languages that fit the bill for a so-called serious systems project. C, C++, Java, and C#. You simply will not find a better library ecosystem and community of highly tenured developers to draw from for at least another decade in other languages.

I don’t even prefer Rust to C personally, and I’m not a dogmatic sort. My analogy wasn’t particularly complimentary to either language.

If some people are too sensitive to take a little caricaturization of C in stride, maybe those people should examine those sensitivities.

Edit because I didn’t read your comment all the way through: uninitialized memory is RNG, functionally.

Ha.

Confession time: I came to this thread to Ctrl-F for "Rust" to find the Rust-C fisticuffs. And this answer is like what I would expect if I said "ChatGPT, write me a reddit-style Rust defense that would rustle some jimmies."

I'm about equally capable at both Rust and C - I could pass a 1st year undergrad CS course using either, but I'm no better than that.

I know C has its place - its marketshare of embedded devices and gamedev is huuuge - and Rust... may eventually reach a similar place of importance, but isn't there yet.

I also know the very best C and Rust devs are pretty much geniuses - Ph.D. level understanding of math, that kind of thing - and would squash me in any type of fair competition.

Maybe in the year 2050, an attitude of condescending acceptance towards C might be appropriate. And also... maybe not, because C might not slip. Who knows?

And really, beyond this pretty crude but accurate-ish description, I think we're getting into unnecessary hair-splitting, among people who don't work with either and wouldn't understand the tradeoffs anyway.

There is a lot of nuance in this discussion that someone inexperienced could misinterpret, you’re definitely right about that.
Modern C by Jens Gustedt is quite good. This video gives a good overview of some modern patterns in C:

https://www.youtube.com/watch?v=QpAhX-gsHMs

I think the best thing you can do is go find a unit testing library you like the look of, maybe some tools for address/leak sanitization, and spend a good amount of time learning CMake and creating a build/project workflow you're happy with. Coming from Rust you've got great tooling and good guardrails in place to keep you on the happy path, I think getting into that groove early in C where you can run experiments and break things in a sane environment is important.

Came also here to recommend this book. You can obtain it for free at the authors site: https://gustedt.gitlabpages.inria.fr/modern-c/

If you like it you could buy the print version.

There are some things he writes that are a bit controversial but generally I think it is very thorough. Sometimes it felt a bit dry to read to me.

The language is only part of it. In my opinion, you would learn even more about C by consuming well-known libraries written in C and trying to do something useful with it and complementing that with some books, etc rather than just learning the syntax and semantics of C.

There are tons of useful/fun things you could do with C. Write some desktop apps with GTK, a web server with Mongoose (https://mongoose.ws), etc.

Does anyone have a good recommendation on developing operating systems?
This depends on how deep you want to go. I honestly recommend starting small.

First, Code by Petzold to make sure you understand how a computer truly works. Then I recommend:

Understanding Microprocessors (Motorola)

8086/8088 Assembly Language Programming

Dissecting DOS

Practical Filesystem Design

FreeBSD Internals

The reason there are so many books is because it’s a big topic. The reason most are old is that things were once much simpler. Modern stuff builds on old, so if you get a good understanding of older stuff, you have an idea of what is happening. From there, I strongly recommend getting a good understanding of UEFI. UEFI will handle most of your system initialization no boot requirements, allowing you to focus on higher level stuff. Prior knowledge of systems will still be a requirement so that you can understand some of the assumptions and choices that UEFI makes.

OSDev is fun. I hope you have a good time.

Great recommendations; a couple i had not come across.

>Understanding Microprocessors (Motorola)

>Dissecting DOS

Can you share or point me to where i can get the above from?

K&R book. Nothing come close although it lacks on modern C.
Programming in C can turn you into a very disciplined programmer for the simple fact that you will need to track when you request memory and when you need to free it. Essentially you will become the garbage collector. I would recommend you to the read classic "The C Programming Language" (Kernighan & Ritchie) and "Expert C Programming Deep C Secrets" (Van Der Linden) which will get you far enough to write useful programs. I read half of Gustedt book and it has good advice, probably need to pick it up again. A good starting point is to implement your own data structures, like lists, double linked lists, dynamic arrays (had a lot of fun implementing this one). Also (this is personal preference) I would learn a bit of the autotools, they are painful to learn and feel kind of ugly, but once they are in place you basically will forget about a ton of things and your build will become a joy (there's a nice book by No Starch Press about it). I've managed to integrate Check (https://libcheck.github.io/check/) so now I have unit tests in C which is something nice.
I’m also a traditionalist and I think the classic K&R C Programming Language book is the best way to learn C—you also get an understanding why C and *nix-based systems seem to go so well.
I love books, even if I don't finish them, especially the technical ones. Today we're very used to recommend sites and youtube videos as primers for learning. In some situations that is ok but I wouldn't hesitate for even a second if I have to choose been something online or a book.
This. We called K&R as "The C bible" for a reason at Uni.
I don't recommend learning how to use Autotools unless you need to work on a project that uses it (and even then, try to avoid it).

Highly recommend learning CMake or Meson instead.

If you're just getting started, there's no need to tie yourself into knots learning "how to set up a project the right way". It's a waste of time. Your time would initially be better spent just calling the C compiler directly. This will help you learn a bit about the compilation and linking phase and will expose you to the C compiler's flags---useful skills for debugging automated builds later. Once you get to the point where you are working on a larger project, then you will be in a better position to decide what build tools are most useful for you.

> If you're just getting started, there's no need to tie ...

Yes, this is sane advice! Note that I'm not promoting autotools, I personally suffered it a lot before I got it for my builds, but is a tool that can be reliable across platforms. Yes I would leave the "how to set up a project" for the time when you really need it, no need to spend time on this if you're learning a language.

You probably don't need autotools as a beginner but if you intend to study those it makes no point starting with cmake. This is more of a C++ crowd thing with tiny mindshare among C programmers.
I disagree, I find autoconf and automake very pleasant in operation. Mainly due to the OOTB support for things like bindir, libdir, and so on, all properly documented via /path/to/configure --help.
I have a C project I picked up (and got working thanks to help from HN users) that was heavily dependent on Autotools. I wanted to be able to compile it under Windows and couldn't make heads nor tails of it. After guessing and tweaking a config.h file to replace the one it didn't create, I was finally able to get gcc to compile everything using a batch file.

Eventually I'll be purging Autotools, make and all build systems, replacing it with a single batch file that wipes the slate then compiles. It only takes a few seconds to run, and removes a huge set of dependencies.

I hope to port the code to run under the Cosmopolitan library so I can be cross-platform and more.

Deep C Secrets is one of the best programming books ever written.
+1 for "Deep C Secrets" - excellent book. I don't use C enough to judge if it has stood up to the test of time, but I loved reading it.
I’d also recommend reading The Linux Programming Interface [1] as an alternative. While it describes the Linux API, it does so through tons of examples in C, so you can see how things are actually done in C-land.

[1] https://man7.org/tlpi/

How in the world in this day and age of nearly infinite resources to learn ANYTHING on the internet is this a question that gets voted up to the top of HN?

Here's an idea: pick a project that interests you and start. For any question or issue you run into, there will be 1000 Youtube videos to choose from, and 10000 online tutorials. There will be an associated sub reddit to ask questions.

> How in the world in this day and age of nearly infinite resources to learn ANYTHING on the internet is this a question that gets voted up to the top of HN?

I'd guess it is because Sturgeon's Law[1] applies to most things on the internet. In fact there is so much crap when it specifically comes to instructional material on the internet that it reaches a point where we find that Sturgeon's Law was only a linear approximation to crappiness that is only valid with the relatively limited crap levels they were capable of producing 60 years ago when Sturgeon's Law was formulated.

The internet is capable of achieving crap harmonics, and we find that something like 90% of the stuff that doesn't seem to be crap is actually crap too.

The HN answers to this question are going to have a much lower crap level which will keep them well within the linear region of the Law. Many here recognize that having someplace that answers this question with only a 90% crap level is good and so upvote it.

[1] 90% of everything is crap

Arduino programming can be a pleasant intermediate step in learning C/C++. Specializing specially in C can be easier after that experience.
Since you are already familiar with other languages, I suggest just pick a tool that you use daily that is open source in C, and start hacking it.
The thing about C is that its library call API is generally how different languages interoperate: If you write a library in [insert language here] and you want it to be callable in any other language, you need to make bindings for C.

Likewise, if you're using any other language and you want to use a general library, you'll need to know enough C so that you can successfully encapsulate concepts like pointers; and copy your program's data into data structures that C can consume.

It's all much, much easier if you have some C experience.

In my case, I haven't done much C in 20 years. What really helped me "keep up" was occasionally doing Mac programming in Objective C. Because it's really easy to drop into C, it helped refresh me on a lot of the concepts. Even if you're doing Swift, you can mix in an class written in Objective C that's mostly straight C in the methods.

Are you me? I am learning C lately, been a few weeks. I am an SWE with 7 years of experience in JS/TS world.

Currently I am trying to learn how to test C correctly, seems there are lots of approaches.

It began when I decided to learn low level development a few months ago. I completed my Chip 8 emulator. Next I bought Arduino a month ago and I was playing around with it. Now I am reading AVR Programming book so I decided to learn C properly. On the side I already made a small keyboard with a microcontroller with QMK firmware. Soldering and stuffs.

Next what I’m trying to learn is some electronics knowledge just enough to make stuffs.

This is way more fun than my web development job!

Currently the resources I use: - Learn C The Hard Way - BeeJ’s guide to C - random video tutorials from the internet - Make: Arduino - Make: AVR programming